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

3
import com.knowledgepixels.nanodash.ApiCache;
4
import com.knowledgepixels.nanodash.SpaceMemberRole;
5
import com.knowledgepixels.nanodash.View;
6
import com.knowledgepixels.nanodash.ViewDisplay;
7
import com.knowledgepixels.nanodash.domain.AbstractResourceWithProfile;
8
import com.knowledgepixels.nanodash.domain.MaintainedResource;
9
import com.knowledgepixels.nanodash.domain.Space;
10
import com.knowledgepixels.nanodash.page.PublishPage;
11
import com.knowledgepixels.nanodash.repository.MaintainedResourceRepository;
12
import com.knowledgepixels.nanodash.template.Template;
13
import org.apache.wicket.Component;
14
import org.apache.wicket.behavior.AttributeAppender;
15
import org.apache.wicket.request.mapper.parameter.PageParameters;
16
import org.eclipse.rdf4j.model.IRI;
17
import org.nanopub.extra.services.ApiResponse;
18
import org.nanopub.extra.services.QueryRef;
19

20
import java.io.Serializable;
21

22
/**
23
 * Builder class for creating QueryResultTable components with various configurations.
24
 */
25
public class QueryResultTableBuilder implements Serializable {
26

27
    private String markupId;
28
    private boolean plain = false;
×
29
    private ViewDisplay viewDisplay;
30
    private String contextId = null;
×
31
    private QueryRef queryRef;
32
    private AbstractResourceWithProfile resourceWithProfile = null;
×
33
    private String id = null;
×
34
    private String postPublishTab = null;
×
35
    private String refRoot = null;
×
36

37
    private QueryResultTableBuilder(String markupId, QueryRef queryRef, ViewDisplay viewDisplay) {
×
38
        this.markupId = markupId;
×
39
        // Bind session-derived "magic" query parameters here on the request thread
40
        // (ApiCache fetches on background threads where the session is absent).
41
        this.queryRef = com.knowledgepixels.nanodash.MagicQueryParams.augment(queryRef);
×
42
        this.viewDisplay = viewDisplay;
×
43
    }
×
44

45
    /**
46
     * Creates a new QueryResultTableBuilder instance.
47
     *
48
     * @param markupId    the markup ID for the component
49
     * @param queryRef    the query reference
50
     * @param viewDisplay the view display object
51
     * @return a new QueryResultTableBuilder instance
52
     */
53
    public static QueryResultTableBuilder create(String markupId, QueryRef queryRef, ViewDisplay viewDisplay) {
54
        return new QueryResultTableBuilder(markupId, queryRef, viewDisplay);
×
55
    }
56

57
    /**
58
     * Sets the resource with profile for the QueryResultTable.
59
     *
60
     * @param resourceWithProfile the ResourceWithProfile object
61
     * @return the current QueryResultTableBuilder instance
62
     */
63
    public QueryResultTableBuilder resourceWithProfile(AbstractResourceWithProfile resourceWithProfile) {
64
        this.resourceWithProfile = resourceWithProfile;
×
65
        return this;
×
66
    }
67

68
    /**
69
     * Sets the ID for the QueryResultTable.
70
     *
71
     * @param id the ID string
72
     * @return the current QueryResultTableBuilder instance
73
     */
74
    public QueryResultTableBuilder id(String id) {
75
        this.id = id;
×
76
        return this;
×
77
    }
78

79
    /**
80
     * Sets whether the table should be plain.
81
     *
82
     * @param plain true for plain table, false otherwise
83
     * @return the current QueryResultTableBuilder instance
84
     */
85
    public QueryResultTableBuilder plain(boolean plain) {
86
        this.plain = plain;
×
87
        return this;
×
88
    }
89

90
    /**
91
     * Sets the context ID for the QueryResultTable.
92
     *
93
     * @param contextId the context ID string
94
     * @return the current QueryResultTableBuilder instance
95
     */
96
    public QueryResultTableBuilder contextId(String contextId) {
97
        this.contextId = contextId;
×
98
        return this;
×
99
    }
100

101
    /**
102
     * Sets the tab to return to after publishing via one of the table's action
103
     * buttons (so the user stays on, e.g., the About tab).
104
     *
105
     * @param postPublishTab the tab name, or null for the default
106
     * @return the current QueryResultTableBuilder instance
107
     */
108
    public QueryResultTableBuilder postPublishTab(String postPublishTab) {
109
        this.postPublishTab = postPublishTab;
×
110
        return this;
×
111
    }
112

113
    /**
114
     * Pins this table to a specific ref (root definition), so action visibility is gated
115
     * against that claimant's authority rather than the resource's representative ref. Used
116
     * on {@code ?root=}-pinned pages. Null leaves it on the representative ref.
117
     *
118
     * @param refRoot the ref's root nanopub, or null
119
     * @return the current QueryResultTableBuilder instance
120
     */
121
    public QueryResultTableBuilder refRoot(String refRoot) {
122
        this.refRoot = refRoot;
×
123
        return this;
×
124
    }
125

126
    /**
127
     * Builds the QueryResultTable component based on the configured parameters.
128
     *
129
     * @return the constructed Component
130
     */
131
    public Component build() {
132
        ApiResponse response = ApiCache.retrieveResponseAsync(queryRef);
×
133
        String colClass = " col-" + viewDisplay.getDisplayWidth();
×
134
        if (resourceWithProfile != null) {
×
135
            if (response != null) {
×
136
                QueryResultTable table = new QueryResultTable(markupId, queryRef, response, viewDisplay, false);
×
137
                table.setContextId(contextId);
×
138
                table.setPostPublishTab(postPublishTab);
×
139
                table.setRefRoot(refRoot);
×
140
                if (id != null && contextId != null && !id.equals(contextId)) {
×
141
                    table.setPartId(id);
×
142
                }
143
                table.setResourceWithProfile(resourceWithProfile);
×
144
                table.setPageResource(resourceWithProfile);
×
145
                addViewActions(table, viewDisplay, queryRef, id, contextId, resourceWithProfile, refRoot);
×
146
                table.add(new AttributeAppender("class", colClass));
×
147
                return table;
×
148
            } else {
149
                ApiResultComponent comp = new ApiResultComponent(markupId, queryRef) {
×
150
                    @Override
151
                    public Component getApiResultComponent(String markupId, ApiResponse response) {
152
                        QueryResultTable table = new QueryResultTable(markupId, queryRef, response, viewDisplay, false);
×
153
                        table.setContextId(contextId);
×
154
                        table.setPostPublishTab(postPublishTab);
×
155
                        table.setRefRoot(refRoot);
×
156
                        if (id != null && contextId != null && !id.equals(contextId)) {
×
157
                            table.setPartId(id);
×
158
                        }
159
                        table.setResourceWithProfile(resourceWithProfile);
×
160
                        table.setPageResource(resourceWithProfile);
×
161
                        addViewActions(table, viewDisplay, queryRef, id, contextId, resourceWithProfile, refRoot);
×
162
                        return table;
×
163
                    }
164
                };
165
                comp.add(new AttributeAppender("class", colClass));
×
166
                return comp;
×
167
            }
168
        } else {
169
            if (response != null) {
×
170
                QueryResultTable table = new QueryResultTable(markupId, queryRef, response, viewDisplay, plain);
×
171
                table.setContextId(contextId);
×
172
                table.setPostPublishTab(postPublishTab);
×
173
                table.setRefRoot(refRoot);
×
174
                addViewActions(table, viewDisplay, queryRef, id, contextId, resourceWithProfile, refRoot);
×
175
                table.add(new AttributeAppender("class", colClass));
×
176
                return table;
×
177
            } else {
178
                ApiResultComponent comp = new ApiResultComponent(markupId, queryRef) {
×
179
                    @Override
180
                    public Component getApiResultComponent(String markupId, ApiResponse response) {
181
                        QueryResultTable table = new QueryResultTable(markupId, queryRef, response, viewDisplay, plain);
×
182
                        table.setContextId(contextId);
×
183
                        table.setPostPublishTab(postPublishTab);
×
184
                        table.setRefRoot(refRoot);
×
185
                        addViewActions(table, viewDisplay, queryRef, id, contextId, resourceWithProfile, refRoot);
×
186
                        return table;
×
187
                    }
188
                };
189
                comp.add(new AttributeAppender("class", colClass));
×
190
                return comp;
×
191
            }
192
        }
193
    }
194

195
    /**
196
     * Adds a button to the table for each result action declared by the view, linking to the
197
     * action's template on the publish page. Resource-context parameters (the target field, the
198
     * context, the part, and the part field) are only set when the corresponding id/contextId is
199
     * available, so this also works on resource-less listings such as the general Spaces page.
200
     *
201
     * @param table       the table to add the action buttons to
202
     * @param viewDisplay the view display whose view declares the actions
203
     * @param queryRef    the query reference backing the table (used for refresh and query mapping)
204
     * @param id          the resource id, or null if there is no specific resource in context
205
     * @param contextId   the context id, or null if there is no context
206
     */
207
    private static void addViewActions(QueryResultTable table, ViewDisplay viewDisplay, QueryRef queryRef, String id, String contextId, AbstractResourceWithProfile resourceWithProfile, String refRoot) {
208
        View view = viewDisplay.getView();
×
209
        if (view == null) return;
×
210
        for (IRI actionIri : view.getViewResultActionList()) {
×
211
            // Per-action role gating (docs/role-specific-views.md): skip an action
212
            // whose gen:isVisibleTo the current viewer does not satisfy. Additive —
213
            // actions without gen:isVisibleTo are unaffected. Gated against the pinned
214
            // ref's authority on a ?root=-pinned page. See docs/space-ref-identity.md.
215
            if (!SpaceMemberRole.isViewerEntitled(view.getActionVisibleTo(actionIri), resourceWithProfile, refRoot)) continue;
×
216
            Template t = view.getTemplateForAction(actionIri);
×
217
            if (t == null) continue;
×
218
            String targetField = view.getTemplateTargetFieldForAction(actionIri);
×
219
            if (targetField == null) targetField = "resource";
×
220
            String label = view.getLabelForAction(actionIri);
×
221
            if (label == null) label = "action...";
×
222
            if (!label.endsWith("...")) label += "...";
×
223
            PageParameters params = new PageParameters().set("template", t.getId())
×
224
                    .set("template-version", "latest");
×
225
            if (id != null) params.set("param_" + targetField, id);
×
226
            if (contextId != null) params.set("context", contextId);
×
227
            if (id != null && contextId != null && !id.equals(contextId)) {
×
228
                params.set("part", id);
×
229
            }
230
            String partField = view.getTemplatePartFieldForAction(actionIri);
×
231
            if (partField != null && contextId != null) {
×
232
                // The part field pre-fills a namespaced child IRI (the user fills the suffix).
233
                // TODO Find a better way to pass the MaintainedResource object to this method:
234
                MaintainedResource r = MaintainedResourceRepository.get().findById(contextId);
×
235
                String namespace = null;
×
236
                if (r != null) {
×
237
                    namespace = r.getNamespace();
×
238
                } else if (resourceWithProfile instanceof Space) {
×
239
                    // The Space-creation templates' `space` placeholder has a fixed
240
                    // `https://w3id.org/spaces/` prefix, so the pre-fill is relative to it.
241
                    // Nesting the new space's IRI under this space's path makes it a
242
                    // sub-space via the prefix match.
243
                    namespace = contextId.replaceFirst("https://w3id.org/spaces/", "") + "/";
×
244
                }
245
                if (namespace != null) {
×
246
                    params.set("param_" + partField, namespace + "<SET-SUFFIX>");
×
247
                }
248
            }
249
            String queryMapping = view.getTemplateQueryMapping(actionIri);
×
250
            if (queryMapping != null && queryMapping.contains(":")) {
×
251
                params.set("values-from-query", queryRef.getAsUrlString());
×
252
                params.set("values-from-query-mapping", queryMapping);
×
253
            }
254
            params.set("refresh-upon-publish", queryRef.getAsUrlString());
×
255
            if (table.getPostPublishTab() != null) params.set("postpub-tab", table.getPostPublishTab());
×
256
            table.addButton(label, PublishPage.class, params);
×
257
        }
×
258
    }
×
259

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