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

knowledgepixels / nanodash / 28880882078

07 Jul 2026 04:09PM UTC coverage: 28.157% (-0.3%) from 28.465%
28880882078

Pull #543

github

web-flow
Merge fb5390948 into f1a7efda7
Pull Request #543: feat: query-form views (gen:QueryFormView) with view-results page

1813 of 7301 branches covered (24.83%)

Branch coverage included in aggregate %.

3717 of 12339 relevant lines covered (30.12%)

4.48 hits per line

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

27.78
src/main/java/com/knowledgepixels/nanodash/component/ViewActionMappings.java
1
package com.knowledgepixels.nanodash.component;
2

3
import com.knowledgepixels.nanodash.QueryResult;
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.request.mapper.parameter.PageParameters;
14
import org.eclipse.rdf4j.model.IRI;
15
import org.nanopub.extra.services.ApiResponseEntry;
16
import org.nanopub.extra.services.QueryRef;
17

18
/**
19
 * Applies a view entry action's per-row query mappings and decides whether the
20
 * action's button should render for that row. See docs/magic-query-params.md
21
 * (phase 2: empty-into-required visibility + multiple mappings / non-{@code param_}
22
 * targets).
23
 */
24
class ViewActionMappings {
25

26
    private ViewActionMappings() {
27
    }
28

29
    /**
30
     * Writes the action's mapped values into the link parameters and returns whether
31
     * the button should be shown for this row.
32
     *
33
     * <p>Each mapping is {@code "col:target"}: the row's value for result column
34
     * {@code col} is written to URL parameter {@code param_target}, or — when
35
     * {@code target} starts with {@code @} — to the raw URL key {@code target}
36
     * (fill-mode keys such as {@code @derive-a} / {@code @supersede}). The button is
37
     * <b>hidden</b> (returns false) if any <i>required</i> mapped value is empty: a
38
     * raw key is always required; a {@code param_} target is required unless its
39
     * template placeholder is optional. Empty values for optional placeholders are
40
     * simply skipped (button kept).</p>
41
     *
42
     * @param view      the view declaring the action
43
     * @param actionIri the action node IRI
44
     * @param row       the result row
45
     * @param params    the link parameters to populate
46
     * @return true if the action button should be rendered for this row
47
     */
48
    /**
49
     * Adds a button to the result component for each result action declared by the view,
50
     * linking to the action's template on the publish page. Resource-context parameters
51
     * (the target field, the context, the part, and the part field) are only set when the
52
     * corresponding id/contextId is available, so this also works on resource-less pages
53
     * such as the general Spaces page or the standalone view-results page.
54
     *
55
     * @param result              the result component to add the action buttons to
56
     * @param viewDisplay         the view display whose view declares the actions
57
     * @param queryRef            the query reference backing the component (used for refresh and query mapping)
58
     * @param id                  the resource id, or null if there is no specific resource in context
59
     * @param contextId           the context id, or null if there is no context
60
     * @param resourceWithProfile the resource whose page the component is on, or null
61
     * @param refRoot             the pinned ref's root nanopub, or null
62
     */
63
    static void addResultActions(QueryResult result, ViewDisplay viewDisplay, QueryRef queryRef, String id, String contextId, AbstractResourceWithProfile resourceWithProfile, String refRoot) {
64
        View view = viewDisplay.getView();
×
65
        if (view == null) return;
×
66
        for (IRI actionIri : view.getViewResultActionList()) {
×
67
            // Per-action role gating (docs/role-specific-views.md): skip an action
68
            // whose gen:isVisibleTo the current viewer does not satisfy. Additive —
69
            // actions without gen:isVisibleTo are unaffected. Gated against the pinned
70
            // ref's authority on a ?root=-pinned page. See docs/space-ref-identity.md.
71
            if (!SpaceMemberRole.isViewerEntitled(view.getActionVisibleTo(actionIri), resourceWithProfile, refRoot)) continue;
×
72
            Template t = view.getTemplateForAction(actionIri);
×
73
            if (t == null) continue;
×
74
            String targetField = view.getTemplateTargetFieldForAction(actionIri);
×
75
            if (targetField == null) targetField = "resource";
×
76
            String label = view.getLabelForAction(actionIri);
×
77
            if (label == null) label = "action...";
×
78
            if (!label.endsWith("...")) label += "...";
×
79
            PageParameters params = new PageParameters().set("template", t.getId())
×
80
                    .set("template-version", "latest");
×
81
            if (id != null) params.set("param_" + targetField, id);
×
82
            if (contextId != null) params.set("context", contextId);
×
83
            if (id != null && contextId != null && !id.equals(contextId)) {
×
84
                params.set("part", id);
×
85
            }
86
            String partField = view.getTemplatePartFieldForAction(actionIri);
×
87
            if (partField != null && contextId != null) {
×
88
                // The part field pre-fills a namespaced child IRI (the user fills the suffix).
89
                // TODO Find a better way to pass the MaintainedResource object to this method:
90
                MaintainedResource r = MaintainedResourceRepository.get().findById(contextId);
×
91
                String namespace = null;
×
92
                if (r != null) {
×
93
                    namespace = r.getNamespace();
×
94
                } else if (resourceWithProfile instanceof Space) {
×
95
                    // The Space-creation templates' `space` placeholder has a fixed
96
                    // `https://w3id.org/spaces/` prefix, so the pre-fill is relative to it.
97
                    // Nesting the new space's IRI under this space's path makes it a
98
                    // sub-space via the prefix match.
99
                    namespace = contextId.replaceFirst("https://w3id.org/spaces/", "") + "/";
×
100
                }
101
                if (namespace != null) {
×
102
                    params.set("param_" + partField, namespace + "<SET-SUFFIX>");
×
103
                }
104
            }
105
            String queryMapping = view.getTemplateQueryMapping(actionIri);
×
106
            if (queryMapping != null && queryMapping.contains(":")) {
×
107
                params.set("values-from-query", queryRef.getAsUrlString());
×
108
                params.set("values-from-query-mapping", queryMapping);
×
109
            }
110
            params.set("refresh-upon-publish", queryRef.getAsUrlString());
×
111
            if (result.getPostPublishTab() != null) params.set("postpub-tab", result.getPostPublishTab());
×
112
            result.addButton(label, PublishPage.class, params);
×
113
        }
×
114
    }
×
115

116
    static boolean applyEntryMappings(View view, IRI actionIri, ApiResponseEntry row, PageParameters params) {
117
        Template template = view.getTemplateForAction(actionIri);
12✔
118
        for (String mapping : view.getTemplateQueryMappings(actionIri)) {
36✔
119
            int sep = mapping.indexOf(':');
12✔
120
            if (sep < 0) continue;
6!
121
            String col = mapping.substring(0, sep);
15✔
122
            String target = mapping.substring(sep + 1);
18✔
123
            boolean rawKey = target.startsWith("@");
12✔
124
            String key = rawKey ? target.substring(1) : target;
24✔
125
            String value = row.get(col);
12✔
126
            if (value == null || value.isBlank()) {
15✔
127
                // Empty: hide the action only if the target is required.
128
                if (rawKey || template == null || template.isRequiredField(key)) return false;
30!
129
                continue;
130
            }
131
            params.set(rawKey ? key : "param_" + key, value);
30✔
132
        }
3✔
133
        return true;
6✔
134
    }
135

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