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

knowledgepixels / nanodash / 25791109883

13 May 2026 09:40AM UTC coverage: 20.815% (+0.2%) from 20.6%
25791109883

push

github

web-flow
Merge pull request #462 from knowledgepixels/speed-up-statusline-verification

Auto-update StatusLine until newly-published nanopub is verified

1035 of 6272 branches covered (16.5%)

Branch coverage included in aggregate %.

2649 of 11427 relevant lines covered (23.18%)

3.32 hits per line

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

82.42
src/main/java/com/knowledgepixels/nanodash/component/StatusLine.java
1
package com.knowledgepixels.nanodash.component;
2

3
import com.knowledgepixels.nanodash.ApiCache;
4
import com.knowledgepixels.nanodash.QueryApiAccess;
5
import com.knowledgepixels.nanodash.page.ExplorePage;
6
import net.trustyuri.TrustyUriUtils;
7
import org.apache.wicket.Component;
8
import org.apache.wicket.ajax.AjaxRequestTarget;
9
import org.apache.wicket.ajax.AjaxSelfUpdatingTimerBehavior;
10
import org.apache.wicket.markup.html.basic.Label;
11
import org.apache.wicket.markup.html.link.BookmarkablePageLink;
12
import org.apache.wicket.markup.html.list.ListItem;
13
import org.apache.wicket.markup.html.list.ListView;
14
import org.apache.wicket.markup.html.panel.Panel;
15
import org.apache.wicket.model.IModel;
16
import org.apache.wicket.request.mapper.parameter.PageParameters;
17
import org.nanopub.extra.services.ApiResponse;
18
import org.nanopub.extra.services.ApiResponseEntry;
19
import org.nanopub.extra.services.QueryRef;
20

21
import java.time.Duration;
22
import java.util.ArrayList;
23
import java.util.List;
24

25
/**
26
 * A component that displays the status of a nanopublication.
27
 * <p>
28
 * If the registry has not yet indexed the nanopublication at first render, the
29
 * panel polls the cache every few seconds and updates itself in place once
30
 * verification succeeds — so users don't have to manually reload the page
31
 * after publishing.
32
 */
33
public class StatusLine extends Panel {
34

35
    private static final Duration POLL_INTERVAL = Duration.ofSeconds(3);
12✔
36
    private static final long POLL_TIMEOUT_MS = 2 * 60 * 1000L;
37

38
    /**
39
     * Creates a new StatusLine component.
40
     *
41
     * @param markupId the Wicket markup ID for this component
42
     * @param npId     the nanopublication ID to check for newer versions
43
     * @return a new StatusLine component
44
     */
45
    public static Component createComponent(String markupId, String npId) {
46
        ApiResultComponent c = new ApiResultComponent(markupId, new QueryRef(QueryApiAccess.GET_NEWER_VERSIONS_OF_NP, "np", npId)) {
60✔
47

48
            @Override
49
            public Component getApiResultComponent(String markupId, ApiResponse response) {
50
                return new StatusLine(markupId, npId, response);
24✔
51
            }
52

53
        };
54
        c.setWaitComponentHtml("<p><strong>Status:</strong> " + ApiResultComponent.getWaitIconHtml() + "</p>");
12✔
55
        return c;
6✔
56
    }
57

58
    private final String npId;
59
    private final long pollStart = System.currentTimeMillis();
9✔
60
    private String statusText;
61
    private List<String> links = List.of();
9✔
62
    private boolean verified = false;
9✔
63

64
    /**
65
     * Constructs a StatusLine component with the given markup ID, nanopublication ID, and API response.
66
     *
67
     * @param markupId the Wicket markup ID for this component
68
     * @param npId     the nanopublication ID to check for newer versions
69
     * @param response the API response containing data about newer versions or retractions
70
     */
71
    StatusLine(String markupId, String npId, ApiResponse response) {
72
        super(markupId);
9✔
73
        this.npId = npId;
9✔
74
        setOutputMarkupId(true);
12✔
75
        applyResponse(response);
9✔
76

77
        add(new Label("statusText", (IModel<String>) () -> statusText).setEscapeModelStrings(false));
57✔
78
        add(new ListView<String>("linkList", (IModel<List<String>>) () -> links) {
78✔
79
            @Override
80
            protected void populateItem(ListItem<String> item) {
81
                String id = item.getModelObject();
12✔
82
                String shortLabel = TrustyUriUtils.getArtifactCode(id).substring(0, 10);
18✔
83
                BookmarkablePageLink<Void> link = new BookmarkablePageLink<>("npLink",
27✔
84
                        ExplorePage.class,
85
                        new PageParameters().add("id", id));
9✔
86
                link.add(new Label("npLabel", shortLabel));
39✔
87
                item.add(link);
27✔
88
            }
3✔
89
        });
90

91
        if (!verified) {
9✔
92
            final QueryRef queryRef = new QueryRef(QueryApiAccess.GET_NEWER_VERSIONS_OF_NP, "np", npId);
21✔
93
            add(new AjaxSelfUpdatingTimerBehavior(POLL_INTERVAL) {
72✔
94
                @Override
95
                protected void onPostProcessTarget(AjaxRequestTarget target) {
96
                    ApiCache.clearCache(queryRef, 0);
×
97
                    ApiResponse fresh = ApiCache.retrieveResponseAsync(queryRef);
×
98
                    if (fresh != null) {
×
99
                        applyResponse(fresh);
×
100
                    }
101
                    if (verified || System.currentTimeMillis() - pollStart > POLL_TIMEOUT_MS) {
×
102
                        stop(target);
×
103
                    }
104
                }
×
105
            });
106
        }
107
    }
3✔
108

109
    private void applyResponse(ApiResponse response) {
110
        List<String> latest = new ArrayList<>();
12✔
111
        List<String> retractions = new ArrayList<>();
12✔
112
        for (ApiResponseEntry e : response.getData()) {
33✔
113
            String newerVersion = e.get("newerVersion");
12✔
114
            String retractedBy = e.get("retractedBy");
12✔
115
            String supersededBy = e.get("supersededBy");
12✔
116
            if (retractedBy.isEmpty() && supersededBy.isEmpty()) {
18!
117
                latest.add(newerVersion);
15✔
118
            } else if (!retractedBy.isEmpty() && supersededBy.isEmpty()) {
18!
119
                retractions.add(retractedBy);
12✔
120
            }
121
        }
3✔
122

123
        if (latest.isEmpty() && retractions.isEmpty()) {
18✔
124
            statusText = "<em>This nanopublication doesn't seem to be properly published (yet). This can take a minute or two for new nanopublications.</em>";
9✔
125
            links = List.of();
9✔
126
            verified = false;
12✔
127
        } else if (!latest.isEmpty()) {
9✔
128
            if (latest.size() == 1 && latest.getFirst().equals(npId)) {
33✔
129
                statusText = "This is the latest version.";
9✔
130
                links = List.of();
12✔
131
            } else if (latest.size() == 1) {
12✔
132
                statusText = "This nanopublication has a <strong>newer version</strong>:";
9✔
133
                links = latest;
12✔
134
            } else {
135
                statusText = "This nanopublication has <strong>newer versions</strong>:";
9✔
136
                links = latest;
9✔
137
            }
138
            verified = true;
12✔
139
        } else {
140
            statusText = "This nanopublication has been <strong>retracted</strong>:";
9✔
141
            links = retractions;
9✔
142
            verified = true;
9✔
143
        }
144
    }
3✔
145

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