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

knowledgepixels / nanodash / 25678638590

11 May 2026 03:08PM UTC coverage: 20.512% (+2.2%) from 18.296%
25678638590

push

github

web-flow
Merge pull request #455 from knowledgepixels/fix-repetition-resets-edited-values

fix: don't reseed shared model from URL param on repetition (#271)

1013 of 6236 branches covered (16.24%)

Branch coverage included in aggregate %.

2592 of 11339 relevant lines covered (22.86%)

3.28 hits per line

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

15.13
src/main/java/com/knowledgepixels/nanodash/component/AgentChoiceItem.java
1
package com.knowledgepixels.nanodash.component;
2

3
import com.knowledgepixels.nanodash.LocalUri;
4
import com.knowledgepixels.nanodash.NanodashSession;
5
import com.knowledgepixels.nanodash.Utils;
6
import com.knowledgepixels.nanodash.component.IriTextfieldItem.Validator;
7
import com.knowledgepixels.nanodash.domain.IndividualAgent;
8
import com.knowledgepixels.nanodash.domain.User;
9
import com.knowledgepixels.nanodash.page.ProfilePage;
10
import com.knowledgepixels.nanodash.template.Template;
11
import com.knowledgepixels.nanodash.template.TemplateContext;
12
import com.knowledgepixels.nanodash.template.UnificationException;
13
import org.apache.wicket.Component;
14
import org.apache.wicket.ajax.AjaxRequestTarget;
15
import org.apache.wicket.ajax.form.OnChangeAjaxBehavior;
16
import org.apache.wicket.behavior.AttributeAppender;
17
import org.apache.wicket.markup.ComponentTag;
18
import org.apache.wicket.markup.html.WebMarkupContainer;
19
import org.apache.wicket.markup.html.basic.Label;
20
import org.apache.wicket.markup.html.link.ExternalLink;
21
import org.apache.wicket.model.IModel;
22
import org.apache.wicket.model.Model;
23
import org.apache.wicket.request.resource.ContextRelativeResourceReference;
24
import org.apache.wicket.validation.Validatable;
25
import org.eclipse.rdf4j.model.IRI;
26
import org.eclipse.rdf4j.model.Value;
27
import org.nanopub.vocabulary.NTEMPLATE;
28
import org.slf4j.Logger;
29
import org.slf4j.LoggerFactory;
30
import org.wicketstuff.select2.ChoiceProvider;
31
import org.wicketstuff.select2.Response;
32
import org.wicketstuff.select2.Select2Choice;
33

34
import java.util.*;
35

36
/**
37
 * A component that allows users to select an agent (user) from a list or enter an ORCID or URL.
38
 */
39
public class AgentChoiceItem extends AbstractContextComponent {
40

41
    private Select2Choice<String> textfield;
42
    private ExternalLink tooltipLink;
43
    private Label tooltipDescription;
44
    private IRI iri;
45
    private IModel<String> model;
46
    private static final Logger logger = LoggerFactory.getLogger(AgentChoiceItem.class);
12✔
47

48
    private String getChoiceLabel(String choiceId) {
49
        IRI iri = vf.createIRI(choiceId);
×
50
        String name = User.getName(iri);
×
51
        if (name != null) return name;
×
52
        return choiceId;
×
53
    }
54

55
    /**
56
     * Constructor for AgentChoiceItem.
57
     *
58
     * @param id       the component ID
59
     * @param parentId the parent component ID
60
     * @param iriP     the IRI of the agent choice item
61
     * @param optional whether the choice is optional
62
     * @param context  the template context
63
     */
64
    public AgentChoiceItem(String id, String parentId, final IRI iriP, boolean optional, final TemplateContext context) {
65
        super(id, context);
12✔
66
        this.iri = iriP;
9✔
67
        final Template template = context.getTemplate();
9✔
68
        model = (IModel<String>) context.getComponentModels().get(iri);
24✔
69
        boolean modelIsNew = false;
6✔
70
        if (model == null) {
9!
71
            model = Model.of("");
×
72
            context.getComponentModels().put(iri, model);
×
73
            modelIsNew = true;
×
74
        }
75
        String postfix = Utils.getUriPostfix(iri);
12✔
76
        if (modelIsNew && context.hasParam(postfix)) {
6!
77
            model.setObject(context.getParam(postfix));
×
78
        }
79
        final List<String> possibleValues = new ArrayList<>();
12✔
80
        for (Value v : template.getPossibleValues(iri)) {
27!
81
            possibleValues.add(v.toString());
×
82
        }
×
83

84
        ChoiceProvider<String> choiceProvider = new ChoiceProvider<String>() {
45✔
85

86
            @Override
87
            public String getDisplayValue(String choiceId) {
88
                if (choiceId == null || choiceId.isEmpty()) return "";
×
89
                String label = getChoiceLabel(choiceId);
×
90
                if (label == null || label.isBlank()) {
×
91
                    return choiceId;
×
92
                }
93
                return label + " (" + choiceId + ")";
×
94
            }
95

96
            @Override
97
            public String getIdValue(String object) {
98
                return object;
×
99
            }
100

101
            // Getting strange errors with Tomcat if this method is not overridden:
102
            @Override
103
            public void detach() {
104
            }
×
105

106
            @Override
107
            public void query(String term, int page, Response<String> response) {
108
                if (term == null) {
×
109
                    if (possibleValues.isEmpty()) {
×
110
                        if (NanodashSession.get().getUserIri() != null) {
×
111
                            response.add(NanodashSession.get().getUserIri().stringValue());
×
112
                        }
113
                    } else {
114
                        response.addAll(possibleValues);
×
115
                    }
116
                    return;
×
117
                }
118
                if (term.startsWith("https://") || term.startsWith("http://")) {
×
119
                    response.add(term);
×
120
                } else if (term.matches(ProfilePage.ORCID_PATTERN)) {
×
121
                    response.add("https://orcid.org/" + term);
×
122
                }
123
                Map<String, Boolean> alreadyAddedMap = new HashMap<>();
×
124
                term = term.toLowerCase();
×
125
                for (String s : possibleValues) {
×
126
                    if (s.toLowerCase().contains(term) || getDisplayValue(s).toLowerCase().contains(term)) {
×
127
                        response.add(s);
×
128
                        alreadyAddedMap.put(s, true);
×
129
                    }
130
                }
×
131

132
                // TODO: We'll need some indexing to perform this more efficiently at some point:
133
                for (IRI iri : User.getUsers(true)) {
×
134
                    // Collect approved users
135
                    if (response.size() > 9) break;
×
136
                    if (response.getResults().contains(iri.stringValue())) continue;
×
137
                    String name = User.getName(iri);
×
138
                    if (iri.stringValue().contains(term)) {
×
139
                        response.add(iri.stringValue());
×
140
                    } else if (name != null && name.toLowerCase().contains(term)) {
×
141
                        response.add(iri.stringValue());
×
142
                    }
143
                }
×
144
                for (IRI iri : User.getUsers(false)) {
×
145
                    // Collect non-approved users
146
                    if (response.size() > 9) break;
×
147
                    if (response.getResults().contains(iri.stringValue())) continue;
×
148
                    String name = User.getName(iri);
×
149
                    if (iri.stringValue().contains(term)) {
×
150
                        response.add(iri.stringValue());
×
151
                    } else if (name != null && name.toLowerCase().contains(term)) {
×
152
                        response.add(iri.stringValue());
×
153
                    }
154
                }
×
155
            }
×
156

157
            @Override
158
            public Collection<String> toChoices(Collection<String> ids) {
159
                return ids;
×
160
            }
161

162
        };
163
        textfield = new Select2Choice<String>("textfield", model, choiceProvider);
27✔
164
        textfield.getSettings().getAjax(true).setDelay(500);
21✔
165
        textfield.getSettings().setCloseOnSelect(true);
18✔
166
        String placeholder = template.getLabel(iri);
15✔
167
        if (placeholder == null) placeholder = "select user or paste ORCID/URL";
12!
168
        textfield.getSettings().setPlaceholder(placeholder);
18✔
169
        Utils.setSelect2ChoiceMinimalEscapeMarkup(textfield);
9✔
170
        textfield.getSettings().setAllowClear(true);
18✔
171

172
        if (!optional) textfield.setRequired(true);
21!
173
        textfield.add(new AttributeAppender("class", " wide"));
42✔
174
        textfield.add(new Validator(iri, template, "", context));
36✔
175
        context.getComponents().add(textfield);
18✔
176

177
        tooltipDescription = new Label("description", new IModel<String>() {
48✔
178

179
            @Override
180
            public String getObject() {
181
                String obj = AgentChoiceItem.this.getModel().getObject();
×
182
                if (obj == null || obj.isEmpty()) return "choose a value";
×
183
                String label = getChoiceLabel(AgentChoiceItem.this.getModel().getObject());
×
184
                if (label == null || !label.contains(" - ")) return "";
×
185
                return label.substring(label.indexOf(" - ") + 3);
×
186
            }
187

188
        });
189
        tooltipDescription.setOutputMarkupId(true);
15✔
190
        add(tooltipDescription);
30✔
191

192
        tooltipLink = Utils.getUriLink("uri", model);
18✔
193
        tooltipLink.setOutputMarkupId(true);
15✔
194
        add(tooltipLink);
30✔
195

196
        final WebMarkupContainer userIcon = new WebMarkupContainer("user-icon") {
39✔
197
            @Override
198
            protected void onComponentTag(ComponentTag tag) {
199
                super.onComponentTag(tag);
×
200
                String selectedValue = model.getObject();
×
201
                IRI selectedIri;
202
                if (selectedValue != null && !selectedValue.isEmpty()) {
×
203
                    try {
204
                        selectedIri = vf.createIRI(selectedValue);
×
205
                    } catch (IllegalArgumentException e) {
×
206
                        selectedIri = NanodashSession.get().getUserIri();
×
207
                    }
×
208
                } else {
209
                    selectedIri = NanodashSession.get().getUserIri();
×
210
                }
211
                IRI profilePicIri = (selectedIri != null) ? User.getProfilePicture(selectedIri) : null;
×
212
                if (profilePicIri != null) {
×
213
                    tag.put("src", profilePicIri.stringValue());
×
214
                } else if (selectedIri != null && IndividualAgent.isSoftware(selectedIri)) {
×
215
                    tag.put("src", urlFor(new ContextRelativeResourceReference("images/bot-icon.svg", false), null).toString());
×
216
                } else {
217
                    tag.put("src", urlFor(new ContextRelativeResourceReference("images/user-icon.svg", false), null).toString());
×
218
                }
219
            }
×
220
        };
221
        userIcon.setOutputMarkupId(true);
12✔
222
        add(userIcon);
27✔
223

224
        textfield.add(new OnChangeAjaxBehavior() {
81✔
225

226
            @Override
227
            protected void onUpdate(AjaxRequestTarget target) {
228
                for (Component c : context.getComponents()) {
×
229
                    if (c == textfield) continue;
×
230
                    if (c.getDefaultModel() == textfield.getModel()) {
×
231
                        c.modelChanged();
×
232
                        target.add(c);
×
233
                    }
234
                }
×
235
                target.add(tooltipLink);
×
236
                target.add(tooltipDescription);
×
237
                target.add(userIcon);
×
238
            }
×
239

240
        });
241
        add(textfield);
30✔
242
    }
3✔
243

244
    /**
245
     * Returns the IRI of the agent choice item.
246
     *
247
     * @return the IRI of the agent choice item
248
     */
249
    public IModel<String> getModel() {
250
        return model;
×
251
    }
252

253
    /**
254
     * {@inheritDoc}
255
     */
256
    @Override
257
    public void removeFromContext() {
258
        context.getComponents().remove(textfield);
×
259
    }
×
260

261
    /**
262
     * {@inheritDoc}
263
     */
264
    @Override
265
    public boolean isUnifiableWith(Value v) {
266
        if (v == null) return true;
×
267
        if (v instanceof IRI) {
×
268
            String vs = v.stringValue();
×
269
            if (Utils.isLocalURI(vs)) vs = vs.replaceFirst("^" + LocalUri.PREFIX, "");
×
270
            Validatable<String> validatable = new Validatable<>(vs);
×
271
            if (context.getTemplate().isLocalResource(iri) && !Utils.isUriPostfix(vs)) {
×
272
                vs = Utils.getUriPostfix(vs);
×
273
            }
274
            new Validator(iri, context.getTemplate(), "", context).validate(validatable);
×
275
            if (!validatable.isValid()) {
×
276
                return false;
×
277
            }
278
            if (textfield.getModelObject() == null || textfield.getModelObject().isEmpty()) {
×
279
                return true;
×
280
            }
281
            return vs.equals(textfield.getModelObject());
×
282
        }
283
        return false;
×
284
    }
285

286
    /**
287
     * {@inheritDoc}
288
     */
289
    @Override
290
    public void unifyWith(Value v) throws UnificationException {
291
        if (v == null) return;
×
292
        if (!isUnifiableWith(v)) throw new UnificationException(v.stringValue());
×
293
        String vs = v.stringValue();
×
294
        if (Utils.isLocalURI(vs)) {
×
295
            vs = vs.replaceFirst("^" + LocalUri.PREFIX, "");
×
296
        }
297
        textfield.setModelObject(vs);
×
298
    }
×
299

300
    /**
301
     * {@inheritDoc}
302
     */
303
    @Override
304
    public void fillFinished() {
305
    }
×
306

307
    /**
308
     * {@inheritDoc}
309
     */
310
    @Override
311
    public void finalizeValues() {
312
        Value defaultValue = context.getTemplate().getDefault(iri);
×
313
        if (NTEMPLATE.CREATOR_PLACEHOLDER.equals(defaultValue)) {
×
314
            defaultValue = NanodashSession.get().getUserIri();
×
315
        }
316
        if (isUnifiableWith(defaultValue)) {
×
317
            try {
318
                unifyWith(defaultValue);
×
319
            } catch (UnificationException ex) {
×
320
                logger.error("Could not unify default value: {}", defaultValue, ex);
×
321
            }
×
322
        }
323
    }
×
324

325
    /**
326
     * <p>toString.</p>
327
     *
328
     * @return a {@link java.lang.String} object
329
     */
330
    public String toString() {
331
        return "[Agent choiced item: " + iri + "]";
×
332
    }
333

334
}
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