Skip to content

Commit a8ffed9

Browse files
authored
Add support for experimental Run UI (#2150)
1 parent aea9219 commit a8ffed9

File tree

21 files changed

+542
-152
lines changed

21 files changed

+542
-152
lines changed

plugin/src/main/java/io/jenkins/plugins/analysis/core/model/IssuesDetail.java

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,8 @@
2020
import java.util.NoSuchElementException;
2121
import java.util.function.Function;
2222

23+
import org.kohsuke.accmod.Restricted;
24+
import org.kohsuke.accmod.restrictions.NoExternalUse;
2325
import org.kohsuke.stapler.StaplerRequest2;
2426
import org.kohsuke.stapler.StaplerResponse2;
2527
import org.kohsuke.stapler.bind.JavaScriptMethod;
@@ -80,6 +82,24 @@ public class IssuesDetail extends DefaultAsyncTableContentProvider implements Mo
8082

8183
private final HealthDescriptor healthDescriptor;
8284

85+
/**
86+
* RunSubpage calls `getObject` to retrieve the current run.
87+
* @return the current run.
88+
*/
89+
@Restricted(NoExternalUse.class) // for Jelly
90+
public Run<?, ?> getObject() {
91+
return getOwner();
92+
}
93+
94+
/**
95+
* We need access to RunTab to provide the sidepanel items.
96+
* @return the RunTab.
97+
*/
98+
@Restricted(NoExternalUse.class) // for Jelly
99+
public RunTab getTab() {
100+
return new RunTab(getObject());
101+
}
102+
83103
/**
84104
* Creates a new detail model with the corresponding view {@code IssuesDetail/index.jelly}.
85105
*

plugin/src/main/java/io/jenkins/plugins/analysis/core/model/ResultAction.java

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
package io.jenkins.plugins.analysis.core.model;
22

3+
import jenkins.management.Badge;
34
import org.apache.commons.lang3.Strings;
45
import org.apache.commons.lang3.StringUtils;
56

@@ -16,6 +17,8 @@
1617
import java.util.Collection;
1718
import java.util.Set;
1819

20+
import org.kohsuke.accmod.Restricted;
21+
import org.kohsuke.accmod.restrictions.DoNotUse;
1922
import org.kohsuke.stapler.StaplerProxy;
2023
import org.kohsuke.stapler.bind.JavaScriptMethod;
2124
import org.jenkinsci.plugins.scriptsecurity.sandbox.whitelists.Whitelisted;
@@ -351,6 +354,25 @@ public String resetReference() {
351354
return "{}";
352355
}
353356

357+
/**
358+
* Displays a badge if there are issues.
359+
*
360+
* <p>
361+
* Only for use in Jelly.
362+
*
363+
* @return the badge or {@code null} if there are no issues
364+
*/
365+
@Restricted(DoNotUse.class)
366+
public Badge getBadge() {
367+
var warningActionsCount = getResult().getTotalSize();
368+
369+
if (warningActionsCount == 0) {
370+
return null;
371+
}
372+
373+
return new Badge(String.valueOf(warningActionsCount), Messages.ResultAction_Badge(warningActionsCount), Badge.Severity.WARNING);
374+
}
375+
354376
private static class CustomIconLabelProvider extends StaticAnalysisLabelProvider {
355377
@Override
356378
public DetailsTableModel getIssuesModel(final Run<?, ?> build, final String url, final Report report) {
Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
1+
package io.jenkins.plugins.analysis.core.model;
2+
3+
import hudson.model.Actionable;
4+
import jenkins.management.Badge;
5+
import jenkins.model.Tab;
6+
import jenkins.model.experimentalflags.BooleanUserExperimentalFlag;
7+
import org.kohsuke.accmod.Restricted;
8+
import org.kohsuke.accmod.restrictions.NoExternalUse;
9+
import org.kohsuke.stapler.HttpRedirect;
10+
import org.kohsuke.stapler.StaplerResponse2;
11+
12+
import java.io.IOException;
13+
import java.util.Comparator;
14+
import java.util.List;
15+
import java.util.NoSuchElementException;
16+
17+
/**
18+
* Defines the Warnings tab for a run.
19+
*/
20+
public class RunTab extends Tab {
21+
/**
22+
* Constructs a new {@link RunTab}.
23+
* @param object the run to construct the tab for
24+
*/
25+
public RunTab(final Actionable object) {
26+
super(object);
27+
}
28+
29+
@Override
30+
public String getIconFileName() {
31+
if (getObject().getActions(ResultAction.class).isEmpty()) {
32+
return null;
33+
}
34+
35+
return "symbol-warning-outline plugin-ionicons-api";
36+
}
37+
38+
@Override
39+
public String getDisplayName() {
40+
return "Warnings";
41+
}
42+
43+
@Override
44+
public String getUrlName() {
45+
return "warnings";
46+
}
47+
48+
@Override
49+
public Badge getBadge() {
50+
var warningActionsCount = getWarningActions().stream().map(e -> e.getResult().getTotalSize()).reduce(0, Integer::sum);
51+
52+
if (warningActionsCount == 0) {
53+
return null;
54+
}
55+
56+
return new Badge(String.valueOf(warningActionsCount), Messages.ResultAction_Badge(warningActionsCount), Badge.Severity.WARNING);
57+
}
58+
59+
/**
60+
* The list of warning actions belonging to a run.
61+
* @return the list of ordered warning actions, sorted by warning count, then alphabetized.
62+
*/
63+
@Restricted(NoExternalUse.class)
64+
public List<ResultAction> getWarningActions() {
65+
return getObject()
66+
.getActions(ResultAction.class)
67+
.stream()
68+
.sorted(Comparator
69+
.comparingInt((ResultAction a) -> a.getResult().getTotalSize())
70+
.reversed()
71+
.thenComparing(ResultAction::getDisplayName, String.CASE_INSENSITIVE_ORDER)
72+
)
73+
.toList();
74+
}
75+
76+
/**
77+
* Redirects to first warning action if new UI enabled.
78+
* @param rsp the response to use for forwarding if the new UI is disabled.
79+
* @return the highest priority warning action.
80+
* @throws IOException If an input or output exception occurs.
81+
*/
82+
public HttpRedirect doIndex(final StaplerResponse2 rsp) throws IOException {
83+
Boolean newUiEnabled = BooleanUserExperimentalFlag.
84+
getFlagValueForCurrentUser("jenkins.model.experimentalflags.NewBuildPageUserExperimentalFlag");
85+
86+
if (Boolean.TRUE.equals(newUiEnabled)) {
87+
return new HttpRedirect(getWarningActions().get(0).getUrlName());
88+
}
89+
90+
rsp.sendError(404, "This page requires the new build page UI to be enabled");
91+
return null;
92+
}
93+
94+
/**
95+
* Renders a dynamic warning action of the Warnings tab.
96+
* @param name the name of the warning action to render.
97+
* @return the warning action.
98+
*/
99+
public ResultAction getDynamic(final String name) {
100+
for (ResultAction ui : getWarningActions()) {
101+
String urlName = ui.getUrlName();
102+
if (name.equals(urlName)) {
103+
return ui;
104+
}
105+
}
106+
107+
throw new NoSuchElementException("No warnings found for " + getObject());
108+
}
109+
}
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
package io.jenkins.plugins.analysis.core.model;
2+
3+
import edu.umd.cs.findbugs.annotations.NonNull;
4+
import hudson.Extension;
5+
import hudson.model.Run;
6+
import jenkins.model.Tab;
7+
import jenkins.model.TransientActionFactory;
8+
9+
import java.util.Collection;
10+
import java.util.List;
11+
12+
/**
13+
* Adds a Warnings tab to the run page.
14+
*/
15+
@Extension
16+
public class RunTabFactory extends TransientActionFactory<Run> {
17+
@Override
18+
public Class<Run> type() {
19+
return Run.class;
20+
}
21+
22+
@NonNull
23+
@Override
24+
public Collection<? extends Tab> createFor(@NonNull final Run target) {
25+
return List.of(new RunTab(target));
26+
}
27+
}

plugin/src/main/java/io/jenkins/plugins/analysis/core/model/StaticAnalysisLabelProvider.java

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
package io.jenkins.plugins.analysis.core.model;
22

3+
import jenkins.model.experimentalflags.UserExperimentalFlag;
34
import org.apache.commons.lang3.StringUtils;
45

56
import edu.hm.hafner.analysis.Issue;
@@ -208,6 +209,13 @@ public String toString() {
208209
* @return the name of the side panel link
209210
*/
210211
public String getLinkName() {
212+
if (Boolean.TRUE.equals(UserExperimentalFlag.getFlagValueForCurrentUser("jenkins.model.experimentalflags.NewBuildPageUserExperimentalFlag"))) {
213+
if (StringUtils.isNotBlank(name)) {
214+
return name;
215+
}
216+
return getDefaultName();
217+
}
218+
211219
if (StringUtils.isNotBlank(name)) {
212220
return Messages.Tool_Link_Name(name);
213221
}

plugin/src/main/java/io/jenkins/plugins/analysis/warnings/SpotBugs.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,12 @@
22

33
import java.io.Serial;
44

5+
import io.jenkins.plugins.analysis.core.model.SymbolIconLabelProvider;
56
import org.kohsuke.stapler.DataBoundConstructor;
67
import org.jenkinsci.Symbol;
78
import hudson.Extension;
89

910
import io.jenkins.plugins.analysis.core.model.StaticAnalysisLabelProvider;
10-
import io.jenkins.plugins.analysis.core.model.SvgIconLabelProvider;
1111

1212
/**
1313
* Provides a parser and customized messages for FindBugs.
@@ -37,7 +37,7 @@ public Descriptor() {
3737

3838
@Override
3939
public StaticAnalysisLabelProvider getLabelProvider() {
40-
return new SvgIconLabelProvider(getId(), getName(), getDescriptionProvider());
40+
return new SymbolIconLabelProvider(getId(), getName(), getDescriptionProvider(), "symbol-spotbugs plugin-warnings-ng");
4141
}
4242
}
4343
}

plugin/src/main/java/io/jenkins/plugins/analysis/warnings/tasks/OpenTasks.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -190,7 +190,7 @@ public OpenTasks() {
190190
private static class LabelProvider extends SymbolIconLabelProvider {
191191
LabelProvider() {
192192
super(ID, Messages.Warnings_OpenTasks_Name(), i -> StringUtils.EMPTY,
193-
"symbol-solid/clipboard-check plugin-font-awesome-api");
193+
"symbol-clipboard plugin-ionicons-api");
194194
}
195195

196196
@Override
Lines changed: 26 additions & 0 deletions
Loading
Lines changed: 48 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -1,33 +1,54 @@
11
<?jelly escape-by-default='true'?>
2-
<j:jelly xmlns:j="jelly:core" xmlns:st="jelly:stapler" xmlns:bs="/bootstrap5">
3-
4-
<st:header name="Content-Type" value="text/html;charset=UTF-8"/>
5-
6-
<bs:page it="${it}">
7-
8-
<st:adjunct includes="io.jenkins.plugins.echarts"/>
9-
<link rel="stylesheet" href="${resURL}/plugin/warnings-ng/css/custom-style.css"/>
10-
11-
<j:set var="i" value="${it.issues}"/>
12-
13-
<j:choose>
14-
<j:when test="${i.isNotEmpty()}">
15-
16-
<st:include page="filled.jelly"/>
17-
2+
<j:jelly xmlns:j="jelly:core" xmlns:st="jelly:stapler" xmlns:l="/lib/layout" xmlns:d="jelly:define">
3+
<l:run-subpage>
4+
<st:adjunct includes="io.jenkins.plugins.analysis.core.model.RunTab.styles"/>
5+
6+
<l:userExperimentalFlag var="newBuildPage" flagClassName="jenkins.model.experimentalflags.NewBuildPageUserExperimentalFlag" />
7+
8+
<d:taglib uri="local">
9+
<d:tag name="contents">
10+
<st:adjunct includes="io.jenkins.plugins.echarts"/>
11+
<link rel="stylesheet" href="${resURL}/plugin/warnings-ng/css/custom-style.css"/>
12+
13+
<j:set var="i" value="${it.issues}"/>
14+
15+
<j:choose>
16+
<j:when test="${i.isNotEmpty()}">
17+
<st:include page="filled.jelly"/>
18+
</j:when>
19+
<j:otherwise>
20+
<st:include page="empty.jelly"/>
21+
</j:otherwise>
22+
</j:choose>
23+
24+
<st:bind var="proxy" value="${it}" />
25+
<st:adjunct includes="io.jenkins.plugins.analysis.core.model.IssuesDetail.issues-detail"/>
26+
</d:tag>
27+
</d:taglib>
28+
29+
<j:choose xmlns:local="local">
30+
<j:when test="${newBuildPage}">
31+
<l:app-bar title="${it.tab.displayName}" />
32+
33+
<div class="wng-body">
34+
<div id="side-panel" class="app-page-body__sidebar wng-sidebar">
35+
<l:tasks>
36+
<j:forEach var="item" items="${it.tab.warningActions}" indexVar="index">
37+
<l:task title="${item.displayName}"
38+
icon="${item.iconFileName}"
39+
href="${url + '/' + it.tab.urlName + '/' + item.urlName}"
40+
badge="${item.badge}" />
41+
</j:forEach>
42+
</l:tasks>
43+
</div>
44+
<div>
45+
<local:contents />
46+
</div>
47+
</div>
1848
</j:when>
1949
<j:otherwise>
20-
21-
<st:include page="empty.jelly"/>
22-
50+
<local:contents />
2351
</j:otherwise>
24-
2552
</j:choose>
26-
27-
28-
<st:bind var="proxy" value="${it}" />
29-
<st:adjunct includes="io.jenkins.plugins.analysis.core.model.IssuesDetail.issues-detail"/>
30-
31-
</bs:page>
32-
53+
</l:run-subpage>
3354
</j:jelly>

plugin/src/main/resources/io/jenkins/plugins/analysis/core/model/Messages.properties

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,3 +80,5 @@ Tab.Namespace=Namespace
8080
Messages.View.Name=Messages
8181

8282
ConsoleLog.View.Title=Console Output (lines {0}-{1})
83+
84+
ResultAction.Badge={0} {0,choice,1#warning|1<warnings}

0 commit comments

Comments
 (0)