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

knowledgepixels / nanodash / 27811003513

19 Jun 2026 07:06AM UTC coverage: 26.612% (-0.4%) from 26.963%
27811003513

Pull #484

github

web-flow
Merge 2b4ad3339 into 0f6281554
Pull Request #484: Space-ref disambiguation: conflict notice, claimants overview, ref-pinned pages

1552 of 6853 branches covered (22.65%)

Branch coverage included in aggregate %.

3420 of 11830 relevant lines covered (28.91%)

4.26 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/AboutSpacePanel.java
1
package com.knowledgepixels.nanodash.component;
2

3
import com.google.common.collect.ArrayListMultimap;
4
import com.google.common.collect.Multimap;
5
import com.knowledgepixels.nanodash.QueryApiAccess;
6
import com.knowledgepixels.nanodash.View;
7
import com.knowledgepixels.nanodash.ViewDisplay;
8
import com.knowledgepixels.nanodash.domain.Space;
9
import org.apache.wicket.markup.html.panel.Panel;
10
import org.nanopub.extra.services.QueryRef;
11

12
/**
13
 * The "About" tab body for a space: its structure (assigned presets, roles, and
14
 * configured view displays; issue #302), its users (members and observers), and
15
 * its sub-units (sub-spaces and maintained resources). Rendered as views
16
 * (query result tables) rather than the live view content.
17
 */
18
public class AboutSpacePanel extends Panel {
19

20
    /**
21
     * The "ℹ️ Info" view: key-value facts about the space (type, alternative IDs,
22
     * dates, latest and root definition). Also shown on the Content tab; surfaced
23
     * here at the top of the About tab. Its query needs both the space IRI
24
     * ({@code space}) and the space's nanopub ({@code spaceNp}) so it can scope to
25
     * a single space-ref.
26
     */
27
    public static final String SPACE_INFO_VIEW = "https://w3id.org/np/RAIh3Cq4K99abRiL2xZphMYTjByvZYATK-d--dI3DD05g/space-info-view-kind";
28

29
    /**
30
     * View that lists all assigned view displays of a resource (built on the
31
     * get-view-displays query Nanodash uses internally). Shown on About tabs
32
     * instead of rendering the assigned views themselves.
33
     */
34
    public static final String VIEW_DISPLAYS_VIEW = "https://w3id.org/np/RA2Wxm80NAzOrXCjIZK9oVJ2vzbycuQtT3wLSGTX5vDVw/view-displays-view";
35

36
    /**
37
     * View listing the presets assigned to a resource (issue #302).
38
     */
39
    public static final String PRESET_ASSIGNMENTS_VIEW = "https://w3id.org/np/RA1kCQYXscKPY_qDvQ-WAKhoVomrTQLSt91JSD4Y03CKI/preset-assignments-view";
40

41
    /**
42
     * View listing a space's assigned roles as a table (role, schema:name, and a
43
     * count of how many of the space's users hold each role), built on the
44
     * list-space-roles query. The built-in Admin role is always the first row.
45
     */
46
    public static final String SPACE_ROLES_VIEW = "https://w3id.org/np/RActRjB7sOPegsSWdlJPNXvEfqEuomlO9HvjmBuXMqfQw/space-roles-view";
47

48
    /**
49
     * View listing a space's members (admins, maintainers, members) with their
50
     * highest role tier, built on the list-space-members query. Observer-tier
51
     * members are excluded.
52
     */
53
    public static final String MEMBERS_VIEW = "https://w3id.org/np/RAFmrcEqniX7mqrZQ8c4OCqjU-3wwKjLhE2glXAZKFNT0/space-members-view";
54

55
    /**
56
     * View listing a space's non-approved role claims (agents holding an
57
     * admin/maintainer/member-tier role instantiation that is not in the
58
     * validated state — a self-assigned or otherwise ungranted claim awaiting
59
     * approval), built on the list-space-non-approved query. Carries a per-row
60
     * "approve" action (visible to members and above) that re-asserts the same
61
     * role triple, signed by the approver.
62
     */
63
    public static final String NON_APPROVED_VIEW = "https://w3id.org/np/RAk5nU4XXK1-CzrE2mcSLcRmJXnANVWgkQ2dNUQzDVR64/pending-members-view";
64

65
    /**
66
     * View listing a space's observers (members whose highest tier is observer,
67
     * i.e. holding no admin/maintainer/member role), built on the
68
     * list-space-observers query.
69
     */
70
    public static final String OBSERVERS_VIEW = "https://w3id.org/np/RA7uYe4WmsJCk3NaMTE8p0YofcMcRfjLyM8PGLaADkH18/space-observers-view";
71

72
    /**
73
     * View listing a space's direct sub-spaces with their types, built on the
74
     * list-sub-spaces query.
75
     */
76
    public static final String SUB_SPACES_VIEW = "https://w3id.org/np/RAoO4uYnSJXCHr0F5uVC5sYpkD4HOh-lsHQj4k3epqeH0/sub-spaces-view";
77

78
    /**
79
     * View listing the resources maintained by a space, built on the
80
     * list-maintained-resources query.
81
     */
82
    public static final String MAINTAINED_RESOURCES_VIEW = "https://w3id.org/np/RA4mk84QDZ4njO5N1sryJ5_wbyG7bAisL4BIAsNoISt-Y/maintained-resources-view";
83

84
    /**
85
     * @param id    the Wicket markup id
86
     * @param space the space whose About listings to render
87
     */
88
    public AboutSpacePanel(String id, Space space) {
89
        this(id, space, null);
×
90
    }
×
91

92
    /**
93
     * @param id            the Wicket markup id
94
     * @param space         the space whose About listings to render
95
     * @param effectiveRoot the root nanopub of the specific ref to scope the ref-aware views
96
     *                      (Info, Observers) to, or null to use the representative ref. See
97
     *                      docs/space-ref-identity.md.
98
     */
99
    public AboutSpacePanel(String id, Space space, String effectiveRoot) {
100
        super(id);
×
101

102
        // The ref (root definition) every ref-scoped listing on this page is keyed to: the
103
        // pinned claimant when viewing one (?root=), otherwise this space's representative ref.
104
        // Falls back to null when no ref root is known (pre-v3 data), in which case each table
105
        // below uses its IRI-keyed query. See docs/space-ref-identity.md.
106
        final String refRoot = effectiveRoot != null ? effectiveRoot : space.getRefRootId();
×
107

108
        // "Structure" section: key-value info, presets, assigned roles, view displays.
109

110
        // The info view leads the section (to the left of the presets). Its query is
111
        // scoped to a single space-ref, so it needs both the space IRI and the ref's
112
        // nanopub — bind them as separate params. When viewing a specific claimant the
113
        // effectiveRoot pins it to that ref's root definition.
114
        View infoView = View.get(SPACE_INFO_VIEW);
×
115
        Multimap<String, String> infoParams = ArrayListMultimap.create();
×
116
        infoParams.put("space", space.getId());
×
117
        infoParams.put("spaceNp", effectiveRoot != null ? effectiveRoot : space.getNanopubId());
×
118
        add(QueryResultTableBuilder.create("info", new QueryRef(infoView.getQuery().getQueryId(), infoParams), new ViewDisplay(infoView)).resourceWithProfile(space).id(space.getId()).contextId(space.getId()).refRoot(refRoot).build());
×
119

120
        View presetsView = View.get(PRESET_ASSIGNMENTS_VIEW);
×
121
        add(QueryResultTableBuilder.create("presets", new QueryRef(presetsView.getQuery().getQueryId(), "resource", space.getId()), new ViewDisplay(presetsView)).resourceWithProfile(space).id(space.getId()).contextId(space.getId()).refRoot(refRoot).build());
×
122

123
        View rolesView = View.get(SPACE_ROLES_VIEW);
×
124
        // Drive the roles table from the ref-scoped query (scoped by npa:forSpaceRef) so a
125
        // ?root=-pinned page shows only that ref's roles, falling back to the view's IRI-keyed
126
        // query when the ref root is unknown. The view nanopub is left untouched. Pass the space
127
        // as resource/context so the roles view's per-entry action button (publish a role
128
        // assignment) renders with param_space prefilled, mirroring the "+" button on the content
129
        // tab's role list. postPublishTab keeps the user on the About tab after publishing a
130
        // role/assignment, so they see the updated roles list (the presets/view-display views
131
        // intentionally fall through to the Content tab, where their effect shows).
132
        QueryRef rolesQuery = (refRoot != null && !refRoot.isEmpty())
×
133
                ? new QueryRef(QueryApiAccess.LIST_SPACE_ROLES_REF, "root_np", refRoot)
×
134
                : new QueryRef(rolesView.getQuery().getQueryId(), "space", space.getId());
×
135
        add(QueryResultTableBuilder.create("roles", rolesQuery, new ViewDisplay(rolesView)).resourceWithProfile(space).id(space.getId()).contextId(space.getId()).postPublishTab("about").refRoot(refRoot).build());
×
136

137
        // View displays aren't materialised into the spaces repo, so the ref-scoped variant is a
138
        // federated join taking two concrete params — the space IRI and the ref's root nanopub —
139
        // and gating the authorised signers on the ref's admins/maintainers (npa:forSpaceRef). Falls
140
        // back to the IRI-keyed query (admins merged across refs) when the ref root is unknown. The
141
        // view nanopub is left untouched.
142
        View vdView = View.get(VIEW_DISPLAYS_VIEW);
×
143
        QueryRef vdQuery;
144
        if (refRoot != null && !refRoot.isEmpty()) {
×
145
            Multimap<String, String> vdParams = ArrayListMultimap.create();
×
146
            vdParams.put("resource", space.getId());
×
147
            vdParams.put("root_np", refRoot);
×
148
            vdQuery = new QueryRef(QueryApiAccess.LIST_VIEW_DISPLAYS_REF, vdParams);
×
149
        } else {
×
150
            vdQuery = new QueryRef(vdView.getQuery().getQueryId(), "resource", space.getId());
×
151
        }
152
        add(QueryResultTableBuilder.create("viewdisplays", vdQuery, new ViewDisplay(vdView)).resourceWithProfile(space).id(space.getId()).contextId(space.getId()).refRoot(refRoot).build());
×
153

154
        // "Users" section: admins/maintainers/members, then observers.
155

156
        // Drive the members table from the ref-scoped query (scoped by npa:forSpaceRef) so a
157
        // ?root=-pinned page shows only that ref's members, falling back to the view's IRI-keyed
158
        // query when the ref root is unknown. The view nanopub is left untouched.
159
        View membersView = View.get(MEMBERS_VIEW);
×
160
        QueryRef membersQuery = (refRoot != null && !refRoot.isEmpty())
×
161
                ? new QueryRef(QueryApiAccess.LIST_SPACE_MEMBERS_REF, "root_np", refRoot)
×
162
                : new QueryRef(membersView.getQuery().getQueryId(), "space", space.getId());
×
163
        add(QueryResultTableBuilder.create("members", membersQuery, new ViewDisplay(membersView)).build());
×
164

165
        // Non-approved (pending) higher-tier role claims, between members and observers.
166
        // Ref-scoped (root_np), like the observers table below; there is no IRI-keyed
167
        // fallback query, so when the ref root is unknown (pre-v3 data) the table is driven
168
        // by the param-less query, which yields no rows. The space is passed as
169
        // resource/context so the per-row "approve" action pre-fills param_space; the agent
170
        // and role template come from the row via the view's query mappings. postPublishTab
171
        // returns the approver to the About tab, where the approved member now shows.
172
        View nonApprovedView = View.get(NON_APPROVED_VIEW);
×
173
        QueryRef nonApprovedQuery = (refRoot != null && !refRoot.isEmpty())
×
174
                ? new QueryRef(QueryApiAccess.LIST_SPACE_NON_APPROVED_REF, "root_np", refRoot)
×
175
                : new QueryRef(QueryApiAccess.LIST_SPACE_NON_APPROVED_REF);
×
176
        add(QueryResultTableBuilder.create("pendingmembers", nonApprovedQuery, new ViewDisplay(nonApprovedView)).resourceWithProfile(space).id(space.getId()).contextId(space.getId()).postPublishTab("about").refRoot(refRoot).build());
×
177

178
        View observersView = View.get(OBSERVERS_VIEW);
×
179
        // Drive the Observers table from the ref-scoped query that also includes un-introduced
180
        // self-declared observers (flagged via the headerless ⚠️ column), instead of the view's
181
        // own validated-only query. The view nanopub is left untouched. Falls back to the view's
182
        // IRI-keyed query when the ref root is unknown (pre-v3 data). See docs/space-ref-identity.md.
183
        QueryRef observersQuery = (refRoot != null && !refRoot.isEmpty())
×
184
                ? new QueryRef(QueryApiAccess.LIST_SPACE_OBSERVERS_REF, "root_np", refRoot)
×
185
                : new QueryRef(observersView.getQuery().getQueryId(), "space", space.getId());
×
186
        add(QueryResultTableBuilder.create("observers", observersQuery, new ViewDisplay(observersView)).build());
×
187

188
        // "Sub-units" section: sub-spaces and maintained resources, side by side
189
        // (both views declare 6/12 width). resourceWithProfile/id/contextId let the
190
        // views' "add" actions pre-fill the new sub-space's IRI under this space's
191
        // namespace and this space as the maintainer, respectively; postPublishTab
192
        // returns the user here, where the new entry shows up.
193

194
        // Both sub-unit tables are ref-scoped via the ref-level npa:hasSubSpace /
195
        // npa:hasMaintainedResource edges (subject = the ref), falling back to their IRI-keyed
196
        // queries when the ref root is unknown. The view nanopubs are left untouched.
197
        View subSpacesView = View.get(SUB_SPACES_VIEW);
×
198
        QueryRef subSpacesQuery = (refRoot != null && !refRoot.isEmpty())
×
199
                ? new QueryRef(QueryApiAccess.LIST_SUB_SPACES_REF, "root_np", refRoot)
×
200
                : new QueryRef(subSpacesView.getQuery().getQueryId(), "space", space.getId());
×
201
        add(QueryResultListBuilder.create("subspaces", subSpacesQuery, new ViewDisplay(subSpacesView)).resourceWithProfile(space).id(space.getId()).contextId(space.getId()).postPublishTab("about").refRoot(refRoot).build());
×
202

203
        View maintainedResourcesView = View.get(MAINTAINED_RESOURCES_VIEW);
×
204
        QueryRef maintainedResourcesQuery = (refRoot != null && !refRoot.isEmpty())
×
205
                ? new QueryRef(QueryApiAccess.LIST_MAINTAINED_RESOURCES_REF, "root_np", refRoot)
×
206
                : new QueryRef(maintainedResourcesView.getQuery().getQueryId(), "space", space.getId());
×
207
        add(QueryResultListBuilder.create("maintainedresources", maintainedResourcesQuery, new ViewDisplay(maintainedResourcesView)).resourceWithProfile(space).id(space.getId()).contextId(space.getId()).postPublishTab("about").refRoot(refRoot).build());
×
208
    }
×
209

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