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

knowledgepixels / nanodash / 27622310436

16 Jun 2026 01:49PM UTC coverage: 26.963% (+6.3%) from 20.697%
27622310436

Pull #483

github

web-flow
Merge dbba567c9 into 663f14f46
Pull Request #483: Space/resource About pages, ref-aware spaces, and magic query params

1542 of 6717 branches covered (22.96%)

Branch coverage included in aggregate %.

3407 of 11638 relevant lines covered (29.27%)

4.31 hits per line

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

0.0
src/main/java/com/knowledgepixels/nanodash/component/ProfileAccountPanel.java
1
package com.knowledgepixels.nanodash.component;
2

3
import com.knowledgepixels.nanodash.NanodashPreferences;
4
import com.knowledgepixels.nanodash.NanodashSession;
5
import com.knowledgepixels.nanodash.Utils;
6
import com.knowledgepixels.nanodash.domain.User;
7
import com.knowledgepixels.nanodash.page.ExplorePage;
8
import com.knowledgepixels.nanodash.page.ProfilePage;
9
import com.knowledgepixels.nanodash.page.PublishPage;
10
import com.knowledgepixels.nanodash.page.UserPage;
11
import org.apache.wicket.RestartResponseException;
12
import org.apache.wicket.markup.html.WebMarkupContainer;
13
import org.apache.wicket.markup.html.basic.Label;
14
import org.apache.wicket.markup.html.form.Form;
15
import org.apache.wicket.markup.html.form.TextField;
16
import org.apache.wicket.markup.html.link.ExternalLink;
17
import org.apache.wicket.markup.html.link.Link;
18
import org.apache.wicket.markup.html.list.ListItem;
19
import org.apache.wicket.markup.html.list.ListView;
20
import org.apache.wicket.markup.html.panel.FeedbackPanel;
21
import org.apache.wicket.markup.html.panel.Panel;
22
import org.apache.wicket.model.Model;
23
import org.apache.wicket.request.mapper.parameter.PageParameters;
24
import org.apache.wicket.util.string.Strings;
25
import org.apache.wicket.validation.validator.PatternValidator;
26
import org.nanopub.extra.setting.IntroNanopub;
27

28
import java.util.ArrayList;
29
import java.util.List;
30

31
/**
32
 * Account/identity controls for the current user's own About tab: a logout
33
 * button, and — only in local mode (running without ORCID authentication) — a
34
 * form to set or change the ORCID identifier. There is deliberately no login
35
 * button here (that belongs to the logged-out state); in ORCID-login mode the
36
 * identifier comes from authentication and the form is hidden.
37
 */
38
public class ProfileAccountPanel extends Panel {
39

40
    /**
41
     * @param id            the Wicket markup id
42
     * @param userIriString the IRI of the user whose (own) About page this is
43
     */
44
    public ProfileAccountPanel(String id, String userIriString) {
45
        super(id);
×
46
        final NanodashSession session = NanodashSession.get();
×
47
        session.loadProfileInfo();
×
48
        final NanodashPreferences prefs = NanodashPreferences.get();
×
49
        final boolean loginMode = prefs.isOrcidLoginMode();
×
50

51
        // Prominent onboarding CTA: a "Create Introduction" button shown only when the
52
        // user has a local key but no introduction from this site yet. Mirrors the
53
        // "Create Introduction" action of the 👉 Recommended-actions view (same intro
54
        // template + params), but surfaced as a primary button so it can't be missed.
55
        boolean canCreateIntro = session.getKeyPair() != null && session.getLocalIntroCount() == 0;
×
56
        String createIntroUrl = "";
×
57
        if (canCreateIntro) {
×
58
            String shortKey = Utils.getShortPubkeyName(session.getPubkeyString());
×
59
            String aboutUrl = UserPage.MOUNT_PATH + "?id=" + Utils.urlEncode(userIriString) + "&tab=about";
×
60
            createIntroUrl = PublishPage.MOUNT_PATH
×
61
                    + "?template=" + Utils.urlEncode("https://w3id.org/np/RAT8ayO62s4SFqDY1qjv24Iw0xarpbpc6zH68n7hRsAsA")
×
62
                    + "&template-version=latest"
63
                    + "&param_user=" + Utils.urlEncode(userIriString)
×
64
                    + "&param_public-key=" + Utils.urlEncode(session.getPubkeyString())
×
65
                    + "&param_key-declaration=" + Utils.urlEncode(shortKey)
×
66
                    + "&param_key-declaration-ref=" + Utils.urlEncode(shortKey)
×
67
                    + "&param_key-location=" + Utils.urlEncode(prefs.getWebsiteUrl())
×
68
                    + "&postpub-redirect-url=" + Utils.urlEncode(aboutUrl)
×
69
                    + "&link-message=" + Utils.urlEncode("Check the checkbox at the end of this page and press 'Publish' to "
×
70
                            + "publish this introduction linking your ORCID identifier to the local key used on this site.");
71
        }
72
        ExternalLink createIntro = new ExternalLink("createIntro", createIntroUrl, "Create Introduction");
×
73
        createIntro.setVisible(canCreateIntro);
×
74
        add(createIntro);
×
75

76
        // Logout only makes sense with an ORCID-login session; in local mode
77
        // there is no login to end.
78
        Link<Void> logout = new Link<Void>("logout") {
×
79
            @Override
80
            public void onClick() {
81
                session.logout();
×
82
                throw new RestartResponseException(UserPage.class, new PageParameters().set("id", userIriString));
×
83
            }
84
        };
85
        logout.setVisible(loginMode);
×
86
        add(logout);
×
87

88
        Model<String> model = Model.of("");
×
89
        if (session.getUserIri() != null) {
×
90
            model.setObject(session.getUserIri().stringValue().replaceFirst("^https://orcid.org/", ""));
×
91
        }
92
        final TextField<String> orcidField = new TextField<>("orcidfield", model);
×
93
        orcidField.add(new PatternValidator(ProfilePage.ORCID_PATTERN));
×
94
        Form<Void> form = new Form<Void>("form") {
×
95
            @Override
96
            protected void onSubmit() {
97
                if (loginMode) return;
×
98
                session.setOrcid(orcidField.getModelObject());
×
99
                String newUserIri = "https://orcid.org/" + orcidField.getModelObject();
×
100
                session.invalidateNow();
×
101
                throw new RestartResponseException(UserPage.class,
×
102
                        new PageParameters().set("id", newUserIri).set("tab", "about"));
×
103
            }
104
        };
105
        form.add(orcidField);
×
106
        // Setting/changing the ORCID is only available in local mode (no ORCID
107
        // authentication); in ORCID-login mode the identifier comes from auth.
108
        form.setVisible(!loginMode);
×
109
        add(form);
×
110

111
        add(new FeedbackPanel("feedback"));
×
112

113
        // Signing key: public key + (local mode) the local key-file path.
114
        add(new ProfileSigItem("sigpart"));
×
115

116
        // Recommended actions — hard-coded here (formerly the 👉 Recommended-actions
117
        // view/query). One bullet per applicable case, computed from the session's
118
        // introduction/approval state. The "create" case is the Create Introduction
119
        // button above, so it has no bullet of its own.
120
        // All recommendations concern the site's local key, so only apply when there is one.
121
        List<String> recs = new ArrayList<>();
×
122
        if (session.getKeyPair() != null) {
×
123
            int localCount = session.getLocalIntroCount();
×
124
            boolean approved = session.isPubkeyApproved();
×
125
            // create: no introduction with the local key yet (the action is the Create
126
            // Introduction button above, but it gets a bullet too for context).
127
            if (localCount == 0) {
×
128
                recs.add("The local key from this site is not part of an introduction yet. Use the "
×
129
                        + "<em>'Create Introduction'</em> button above to link it to your identity.");
130
            }
131
            // get-approval: exactly one local introduction, not approved yet
132
            if (localCount == 1 && !approved) {
×
133
                String t = "Your introduction with the local key is not approved yet. Share it so an already "
×
134
                        + "approved user can approve it";
135
                IntroNanopub localIntro = session.getLocalIntro();
×
136
                if (localIntro != null) {
×
137
                    String introUri = localIntro.getNanopub().getUri().stringValue();
×
138
                    t += ": <a href=\"" + ExplorePage.MOUNT_PATH + "?id=" + Utils.urlEncode(introUri) + "\">"
×
139
                            + Strings.escapeMarkup(introUri) + "</a>.";
×
140
                } else {
×
141
                    t += ".";
×
142
                }
143
                recs.add(t);
×
144
            }
145
            // derive: no local introduction, but the user has introductions elsewhere.
146
            // This always co-occurs with the create bullet above (same localCount==0),
147
            // so it is phrased as the alternative to creating a fresh introduction.
148
            if (localCount == 0 && !session.getUserIntroNanopubs().isEmpty()) {
×
149
                recs.add("As you have introductions elsewhere, you can alternatively use "
×
150
                        + "<em>'derive new introduction'</em> from the row menu in the Introductions table below to "
151
                        + "declare those keys alongside the local key.");
152
            }
153
            // retract: more than one local introduction
154
            if (localCount > 1) {
×
155
                recs.add("You have multiple introductions from this site. Use <em>'retract'</em> from the row menu in "
×
156
                        + "the Introductions table below to remove the redundant ones.");
157
            }
158
            // update-approved: local key not approved, but the user has another approved key
159
            String localHash = session.getPubkeyhash();
×
160
            boolean hasAnotherApprovedKey = localHash != null && session.getUserIri() != null
×
161
                    && User.getPubkeyhashes(session.getUserIri(), true).stream().anyMatch(h -> !h.equals(localHash));
×
162
            if (localCount > 0 && !approved && hasAnotherApprovedKey) {
×
163
                recs.add("Your local key is not approved, but you have an approved introduction elsewhere. Add this "
×
164
                        + "site's local key to that approved introduction, at the site where you created it.");
165
            }
166
        }
167
        WebMarkupContainer recommendations = new WebMarkupContainer("recommendations");
×
168
        recommendations.setVisible(!recs.isEmpty());
×
169
        recommendations.add(new ListView<String>("recItems", recs) {
×
170
            @Override
171
            protected void populateItem(ListItem<String> item) {
172
                item.add(new Label("recText", item.getModelObject()).setEscapeModelStrings(false));
×
173
            }
×
174
        });
175
        add(recommendations);
×
176
    }
×
177

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