• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

knowledgepixels / nanodash / 28444877373

30 Jun 2026 12:38PM UTC coverage: 28.035% (-0.01%) from 28.046%
28444877373

push

github

web-flow
Merge pull request #522 from knowledgepixels/feat/truncate-entity-link-labels

feat: truncate over-long entity labels in links, buttons, and breadcrumbs

1723 of 7007 branches covered (24.59%)

Branch coverage included in aggregate %.

3607 of 12005 relevant lines covered (30.05%)

4.45 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

43.59
src/main/java/com/knowledgepixels/nanodash/component/NanodashLink.java
1
package com.knowledgepixels.nanodash.component;
2

3
import com.knowledgepixels.nanodash.domain.IndividualAgent;
4
import com.knowledgepixels.nanodash.domain.MaintainedResource;
5
import com.knowledgepixels.nanodash.domain.Space;
6
import com.knowledgepixels.nanodash.domain.User;
7
import com.knowledgepixels.nanodash.Utils;
8
import com.knowledgepixels.nanodash.connector.ios.DsConfig;
9
import com.knowledgepixels.nanodash.connector.ios.DsNanopubPage;
10
import com.knowledgepixels.nanodash.connector.pensoft.BdjConfig;
11
import com.knowledgepixels.nanodash.connector.pensoft.BdjNanopubPage;
12
import com.knowledgepixels.nanodash.connector.pensoft.RioConfig;
13
import com.knowledgepixels.nanodash.connector.pensoft.RioNanopubPage;
14
import com.knowledgepixels.nanodash.page.ExplorePage;
15
import com.knowledgepixels.nanodash.page.MaintainedResourcePage;
16
import com.knowledgepixels.nanodash.page.ResourcePartPage;
17
import com.knowledgepixels.nanodash.page.SpacePage;
18
import com.knowledgepixels.nanodash.page.UserPage;
19
import com.knowledgepixels.nanodash.repository.MaintainedResourceRepository;
20
import com.knowledgepixels.nanodash.repository.SpaceRepository;
21
import com.knowledgepixels.nanodash.template.Template;
22
import com.knowledgepixels.nanodash.template.TemplateData;
23
import net.trustyuri.TrustyUriUtils;
24
import org.apache.commons.codec.Charsets;
25
import org.apache.wicket.Component;
26
import org.apache.wicket.behavior.AttributeAppender;
27
import org.apache.wicket.markup.html.basic.Label;
28
import org.apache.wicket.markup.html.link.BookmarkablePageLink;
29
import org.apache.wicket.markup.html.link.ExternalLink;
30
import org.apache.wicket.markup.html.panel.Panel;
31
import org.apache.wicket.model.Model;
32
import org.apache.wicket.request.mapper.parameter.PageParameters;
33
import org.eclipse.rdf4j.model.IRI;
34
import org.eclipse.rdf4j.model.Statement;
35
import org.eclipse.rdf4j.model.ValueFactory;
36
import org.eclipse.rdf4j.model.impl.SimpleValueFactory;
37
import org.eclipse.rdf4j.model.vocabulary.RDFS;
38
import org.nanopub.Nanopub;
39
import org.nanopub.vocabulary.NP;
40
import org.nanopub.vocabulary.NTEMPLATE;
41

42
import java.net.URLEncoder;
43
import java.util.*;
44

45
/**
46
 * A Wicket component that creates a link to a nanopublication or an IRI.
47
 */
48
public class NanodashLink extends Panel {
49

50
    public NanodashLink(String id, String uri, Nanopub np, IRI templateClass, String label) {
51
        this(id, uri, np, templateClass, label, null);
24✔
52
    }
3✔
53

54
    /**
55
     * Creates a link to a nanopublication or an IRI.
56
     *
57
     * @param id            the Wicket component ID
58
     * @param uri           the URI of the nanopublication or IRI
59
     * @param np            the nanopublication, or null if the link is not to a nanopublication
60
     * @param templateClass the template class of the nanopublication, or null if the link is not to a nanopublication
61
     * @param label         the label to display for the link, or null to derive it from the nanopublication or IRI
62
     */
63
    public NanodashLink(String id, String uri, Nanopub np, IRI templateClass, String label, String contextId) {
64
        super(id);
9✔
65

66
        final List<Template> templates = new ArrayList<>();
12✔
67
        final Map<IRI, String> labels = new HashMap<>();
12✔
68
        if (np != null) {
6!
69
            for (Statement st : np.getPubinfo()) {
33✔
70
                if (st.getPredicate().equals(NTEMPLATE.HAS_LABEL_FROM_API) || st.getPredicate().equals(RDFS.LABEL)) {
30!
71
                    labels.put((IRI) st.getSubject(), st.getObject().stringValue());
×
72
                }
73
            }
3✔
74
        }
75

76
        final TemplateData td = TemplateData.get();
6✔
77

78
        if (NTEMPLATE.ASSERTION_TEMPLATE.equals(templateClass)) {
12!
79
            IRI templateId = td.getTemplateId(np);
×
80
            if (templateId != null) {
×
81
                templates.add(td.getTemplate(templateId.stringValue()));
×
82
            }
83
        } else if (NTEMPLATE.PROVENANCE_TEMPLATE.equals(templateClass)) {
12!
84
            IRI templateId = td.getProvenanceTemplateId(np);
×
85
            if (templateId != null) {
×
86
                templates.add(td.getTemplate(templateId.stringValue()));
×
87
            }
88
        } else if (NTEMPLATE.PUBINFO_TEMPLATE.equals(templateClass)) {
12!
89
            Set<IRI> templateIds = td.getPubinfoTemplateIds(np);
×
90
            for (IRI templateId : templateIds) {
×
91
                templates.add(td.getTemplate(templateId.stringValue()));
×
92
            }
×
93
        }
94

95
        final IRI iriObj = vf.createIRI(uri);
12✔
96
        if (np != null && uri.equals(np.getUri().stringValue())) {
24!
97
            ExternalLink link = new ExternalLink("link", ExplorePage.MOUNT_PATH + "?id=" + URLEncoder.encode(uri, Charsets.UTF_8), "this");
×
98
            add(link);
×
99
            add(new Label("description", "this specific nanopublication"));
×
100
        } else if (np != null && uri.equals(np.getAssertionUri().stringValue())) {
24!
101
            ExternalLink link = new ExternalLink("link", ExplorePage.MOUNT_PATH + "?id=" + URLEncoder.encode(uri, Charsets.UTF_8), "this assertion");
×
102
            link.add(new AttributeAppender("class", " nanopub-assertion "));
×
103
            add(link);
×
104
            add(new Label("description", "the assertion of this specific nanopublication"));
×
105
        } else if (uri.equals(NP.HAS_ASSERTION.stringValue())) {
15!
106
            ExternalLink link = new ExternalLink("link", ExplorePage.MOUNT_PATH + "?id=" + URLEncoder.encode(uri, Charsets.UTF_8), "assertion");
×
107
            link.add(new AttributeAppender("class", " nanopub-assertion "));
×
108
            add(link);
×
109
            add(new Label("description", "links a nanopublication to its assertion"));
×
110
        } else if (uri.equals(NP.HAS_PROVENANCE.stringValue())) {
15!
111
            ExternalLink link = new ExternalLink("link", ExplorePage.MOUNT_PATH + "?id=" + URLEncoder.encode(uri, Charsets.UTF_8), "provenance");
×
112
            link.add(new AttributeAppender("class", " nanopub-provenance "));
×
113
            add(link);
×
114
            add(new Label("description", "links a nanopublication to its provenance"));
×
115
        } else if (uri.equals(NP.HAS_PUBINFO.stringValue())) {
15!
116
            ExternalLink link = new ExternalLink("link", ExplorePage.MOUNT_PATH + "?id=" + URLEncoder.encode(uri, Charsets.UTF_8), "pubinfo");
×
117
            link.add(new AttributeAppender("class", " nanopub-pubinfo "));
×
118
            add(link);
×
119
            add(new Label("description", "links a nanopublication to its pubinfo"));
×
120
        } else {
×
121
            if (label == null || label.isBlank()) {
6!
122
                label = Utils.getShortNameFromURI(uri);
9✔
123
                if (iriObj.equals(User.getSignatureOwnerIri(np))) {
15!
124
                    // TODO We might want to introduce a "(you)" flag here at some point
125
                    label = User.getShortDisplayName(iriObj);
×
126
                } else if (User.getName(iriObj) != null) {
9!
127
                    label = User.getShortDisplayName(iriObj);
×
128
                } else if (SpaceRepository.get().findById(uri) != null) {
12!
129
                    label = SpaceRepository.get().findById(uri).getLabel();
×
130
                } else if (SpaceRepository.get().findByAltId(uri) != null) {
12!
131
                    label = SpaceRepository.get().findByAltId(uri).getLabel();
×
132
                } else if (MaintainedResourceRepository.get().findById(uri) != null) {
12!
133
                    label = MaintainedResourceRepository.get().findById(uri).getLabel();
×
134
                } else {
135
                    for (Template template : templates) {
18!
136
                        // TODO For pubinfo templates, we don't consider which triple came from which template (which is non-trivial):
137
                        String l = template.getLabel(iriObj);
×
138
                        if (l != null) {
×
139
                            label = l;
×
140
                            break;
×
141
                        }
142
                        l = labels.get(iriObj);
×
143
                        if (l != null) {
×
144
                            label = l;
×
145
                            break;
×
146
                        }
147
                    }
×
148
                }
149
            }
150
            String shortLabel = label.replaceFirst(" - [\\s\\S]*$", "");
15✔
151
            add(createLink("link", uri, shortLabel, contextId));
39✔
152
            String description = "";
6✔
153
            if (np != null && uri.startsWith(np.getUri().stringValue())) {
24!
154
                description = "This is a local identifier that was minted when the nanopublication was created.";
×
155
            }
156
            if (label.contains(" - ")) description = label.replaceFirst("^.* - ", "");
12!
157
            add(new Label("description", description));
39✔
158
        }
159
        add(Utils.getUriLink("uri", uri));
33✔
160
    }
3✔
161

162
    /**
163
     * <p>createLink.</p>
164
     *
165
     * @param markupId a {@link java.lang.String} object
166
     * @param uri      a {@link java.lang.String} object
167
     * @param label    a {@link java.lang.String} object
168
     * @return a {@link org.apache.wicket.Component} object
169
     */
170
    public static Component createLink(String markupId, String uri, String label, String contextId) {
171
        boolean isNp = TrustyUriUtils.isPotentialTrustyUri(uri);
9✔
172
        PageParameters params = new PageParameters().set("id", uri);
21✔
173
        if (contextId != null) params.set("context", contextId);
6!
174
        // TODO Improve this
175
        if (isNp && uri.startsWith(DsConfig.get().getTargetNamespace())) {
6!
176
            return new BookmarkablePageLink<Void>(markupId, DsNanopubPage.class, params.set("mode", "final")).setBody(Model.of(Utils.truncateLinkLabel(label)));
×
177
        } else if (isNp && uri.startsWith(BdjConfig.get().getTargetNamespace())) {
6!
178
            return new BookmarkablePageLink<Void>(markupId, BdjNanopubPage.class, params.set("mode", "final")).setBody(Model.of(Utils.truncateLinkLabel(label)));
×
179
        } else if (isNp && uri.startsWith(RioConfig.get().getTargetNamespace())) {
6!
180
            return new BookmarkablePageLink<Void>(markupId, RioNanopubPage.class, params.set("mode", "final")).setBody(Model.of(Utils.truncateLinkLabel(label)));
×
181
        } else if (IndividualAgent.isUser(uri)) {
9!
182
            label = User.getShortDisplayName(vf.createIRI(uri));
×
183
            return new BookmarkablePageLink<Void>(markupId, UserPage.class, params).setBody(Model.of(Utils.truncateLinkLabel(label)));
×
184
        } else if (SpaceRepository.get().findById(uri) != null) {
12!
185
            label = SpaceRepository.get().findById(uri).getLabel();
×
186
            return new BookmarkablePageLink<Void>(markupId, SpacePage.class, params).setBody(Model.of(Utils.truncateLinkLabel(label)));
×
187
        } else if (SpaceRepository.get().findByAltId(uri) != null) {
12!
188
            Space space = SpaceRepository.get().findByAltId(uri);
×
189
            label = space.getLabel();
×
190
            params.set("id", space.getId());
×
191
            return new BookmarkablePageLink<Void>(markupId, SpacePage.class, params).setBody(Model.of(Utils.truncateLinkLabel(label)));
×
192
        } else if (MaintainedResourceRepository.get().findById(uri) != null) {
12!
193
            label = MaintainedResourceRepository.get().findById(uri).getLabel();
×
194
            return new BookmarkablePageLink<Void>(markupId, MaintainedResourcePage.class, params).setBody(Model.of(Utils.truncateLinkLabel(label)));
×
195
        } else {
196
            boolean isCaret = "^".equals(label);
12✔
197
            if (!isCaret) params.set("label", label);
21!
198
            BookmarkablePageLink<Void> link;
199
            if (isPartOfResource(uri, contextId)) {
12!
200
                link = new BookmarkablePageLink<Void>(markupId, ResourcePartPage.class, params);
×
201
            } else {
202
                link = new BookmarkablePageLink<Void>(markupId, ExplorePage.class, params.set("forward-to-part", "true"));
30✔
203
            }
204
            // Truncate only the displayed label; the full label stays in the page
205
            // params above so the destination page can still show it as its title.
206
            link.setBody(Model.of(Utils.truncateLinkLabel(label)));
18✔
207
            // The "^" source-nanopub caret is styled like the admin lists (subtle, with
208
            // hover-darken), reusing SourceNanopub's `a.source` styling.
209
            if (isCaret) link.add(new AttributeAppender("class", "source"));
6!
210
            return link;
6✔
211
        }
212
    }
213

214
    /**
215
     * Returns the best page URL for a given URI, routing to user/space/resource pages when applicable,
216
     * falling back to the Explore page otherwise.
217
     */
218
    public static String getPageUrl(String uri) {
219
        if (IndividualAgent.isUser(uri)) {
9!
220
            return UserPage.MOUNT_PATH + "?id=" + URLEncoder.encode(uri, java.nio.charset.StandardCharsets.UTF_8);
×
221
        } else if (SpaceRepository.get().findById(uri) != null) {
12!
222
            return SpacePage.MOUNT_PATH + "?id=" + URLEncoder.encode(uri, java.nio.charset.StandardCharsets.UTF_8);
×
223
        } else if (SpaceRepository.get().findByAltId(uri) != null) {
12!
224
            String spaceId = SpaceRepository.get().findByAltId(uri).getId();
×
225
            return SpacePage.MOUNT_PATH + "?id=" + URLEncoder.encode(spaceId, java.nio.charset.StandardCharsets.UTF_8);
×
226
        } else if (MaintainedResourceRepository.get().findById(uri) != null) {
12!
227
            return MaintainedResourcePage.MOUNT_PATH + "?id=" + URLEncoder.encode(uri, java.nio.charset.StandardCharsets.UTF_8);
×
228
        } else {
229
            return ExplorePage.MOUNT_PATH + "?id=" + URLEncoder.encode(uri, java.nio.charset.StandardCharsets.UTF_8);
15✔
230
        }
231
    }
232

233
    private static boolean isPartOfResource(String uri, String contextId) {
234
        if (contextId == null) return false;
12!
235
        String uriNamespace = MaintainedResource.getNamespace(uri);
×
236
        MaintainedResource resource = MaintainedResourceRepository.get().findByNamespace(uriNamespace);
×
237
        if (resource == null) return false;
×
238
        return resource.getId().equals(contextId);
×
239
    }
240

241
    /**
242
     * Creates a link to a nanopublication or an IRI.
243
     *
244
     * @param id            the Wicket component ID
245
     * @param uri           the URI of the nanopublication or IRI
246
     * @param np            the nanopublication, or null if the link is not to a nanopublication
247
     * @param templateClass the template class of the nanopublication, or null if the link is not to a nanopublication
248
     */
249
    public NanodashLink(String id, String uri, Nanopub np, IRI templateClass) {
250
        this(id, uri, np, templateClass, null);
21✔
251
    }
3✔
252

253
    /**
254
     * Creates a link to a nanopublication or an IRI.
255
     *
256
     * @param id  the Wicket component ID
257
     * @param uri the URI of the nanopublication or IRI
258
     * @param np  the nanopublication, or null if the link is not to a nanopublication
259
     */
260
    public NanodashLink(String id, String uri, Nanopub np) {
261
        this(id, uri, np, null);
×
262
    }
×
263

264
    /**
265
     * Creates a link to a nanopublication or an IRI.
266
     *
267
     * @param id  the Wicket component ID
268
     * @param uri the URI of the nanopublication or IRI
269
     */
270
    public NanodashLink(String id, String uri) {
271
        this(id, uri, null, null);
×
272
    }
×
273

274
    private static ValueFactory vf = SimpleValueFactory.getInstance();
9✔
275

276
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc