Skip to content

Commit b947e12

Browse files
authored
Merge branch 'main' into get-handler
2 parents a6a9105 + 459b9d5 commit b947e12

File tree

11 files changed

+438
-6
lines changed

11 files changed

+438
-6
lines changed

README.md

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
Pipeline Agent Build History
22
==========
33

4+
## Agent History
45
The classical `Build History` for an agent that is available in Jenkins core is not able to show runs of Pipeline jobs
56
that have been executed on an agent. This plugin fills this gap by providing an additional link on an agents side panel
67
opening a view that shows runs of pipeline jobs but also all the other job types that are available in the original
@@ -29,3 +30,13 @@ node('mynode') {
2930
```
3031
this will produce on the agent `mynode` the below lines
3132
![img.png](docs/img.png)
33+
34+
35+
## Job History
36+
Similarly, the `Trend` page of a pipeline job is not able to show information about the agents that have been used in
37+
a run. The plugin adds a link in a jobs sidepanel to a view with an `Extended Build History`. For each run all the used
38+
agents are listed, together with the label expression used in the `node` step declaration and the duration of the `node`
39+
step. When more than one agent was used in a run, click on the down arrow to show all used agents of that run.
40+
41+
42+
![trend.png](docs/trend.png)

docs/trend.png

152 KB
Loading

pom.xml

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
<parent>
66
<groupId>org.jenkins-ci.plugins</groupId>
77
<artifactId>plugin</artifactId>
8-
<version>4.75</version>
8+
<version>4.76</version>
99
<relativePath/>
1010
</parent>
1111

@@ -43,7 +43,7 @@
4343
<dependency>
4444
<groupId>io.jenkins.tools.bom</groupId>
4545
<artifactId>bom-weekly</artifactId>
46-
<version>2612.v3d6a_2128c0ef</version>
46+
<version>2661.vb_b_60650f6d97</version>
4747
<type>pom</type>
4848
<scope>import</scope>
4949
</dependency>
@@ -71,6 +71,11 @@
7171
<groupId>org.jenkins-ci.plugins.workflow</groupId>
7272
<artifactId>workflow-cps</artifactId>
7373
</dependency>
74+
<dependency>
75+
<groupId>org.kohsuke</groupId>
76+
<artifactId>access-modifier-suppressions</artifactId>
77+
<version>1.33</version>
78+
</dependency>
7479
</dependencies>
7580

7681
<repositories>
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
package io.jenkins.plugins.agent_build_history;
2+
3+
import edu.umd.cs.findbugs.annotations.NonNull;
4+
import hudson.Extension;
5+
import hudson.model.Action;
6+
import java.util.Collection;
7+
import java.util.Collections;
8+
import jenkins.model.TransientActionFactory;
9+
import org.jenkinsci.plugins.workflow.job.WorkflowJob;
10+
11+
@Extension
12+
public class ExtendenJobHistoryFactory extends TransientActionFactory<WorkflowJob> {
13+
@Override
14+
public Class<WorkflowJob> type() {
15+
return WorkflowJob.class;
16+
}
17+
18+
@NonNull
19+
@Override
20+
public Collection<? extends Action> createFor(@NonNull WorkflowJob target) {
21+
return Collections.singletonList(new WorkflowJobHistoryAction(target));
22+
}
23+
}
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
package io.jenkins.plugins.agent_build_history;
2+
3+
import hudson.model.Action;
4+
import org.jenkinsci.plugins.workflow.job.WorkflowJob;
5+
6+
public class WorkflowJobHistoryAction implements Action {
7+
8+
private final WorkflowJob job;
9+
10+
public WorkflowJobHistoryAction(WorkflowJob job) {
11+
this.job = job;
12+
}
13+
14+
public WorkflowJob getJob() {
15+
return job;
16+
}
17+
18+
@Override
19+
public String getIconFileName() {
20+
return "symbol-file-tray-stacked-outline plugin-ionicons-api";
21+
}
22+
23+
@Override
24+
public String getDisplayName() {
25+
return "Extended Build History";
26+
}
27+
28+
@Override
29+
public String getUrlName() {
30+
return "extendedBuildHistory";
31+
}
32+
33+
public WorkflowJobTrend getHandler() {
34+
return new WorkflowJobTrend(job.getBuilds());
35+
}
36+
}
Lines changed: 133 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,133 @@
1+
package io.jenkins.plugins.agent_build_history;
2+
3+
import hudson.Util;
4+
import hudson.model.BallColor;
5+
import hudson.model.Node;
6+
import java.util.ArrayList;
7+
import java.util.List;
8+
import jenkins.console.ConsoleUrlProvider;
9+
import jenkins.model.Jenkins;
10+
import jenkins.util.ProgressiveRendering;
11+
import net.sf.json.JSON;
12+
import net.sf.json.JSONArray;
13+
import net.sf.json.JSONObject;
14+
import org.jenkinsci.plugins.workflow.actions.TimingAction;
15+
import org.jenkinsci.plugins.workflow.cps.actions.ArgumentsActionImpl;
16+
import org.jenkinsci.plugins.workflow.cps.nodes.StepStartNode;
17+
import org.jenkinsci.plugins.workflow.flow.FlowExecution;
18+
import org.jenkinsci.plugins.workflow.graph.BlockEndNode;
19+
import org.jenkinsci.plugins.workflow.graph.BlockStartNode;
20+
import org.jenkinsci.plugins.workflow.graph.FlowNode;
21+
import org.jenkinsci.plugins.workflow.graphanalysis.DepthFirstScanner;
22+
import org.jenkinsci.plugins.workflow.job.WorkflowRun;
23+
import org.jenkinsci.plugins.workflow.steps.StepDescriptor;
24+
import org.jenkinsci.plugins.workflow.support.actions.WorkspaceActionImpl;
25+
import org.jenkinsci.plugins.workflow.support.steps.ExecutorStep;
26+
import org.kohsuke.accmod.restrictions.suppressions.SuppressRestrictedWarnings;
27+
28+
public class WorkflowJobTrend extends ProgressiveRendering {
29+
/**
30+
* Since we cannot predict how many runs there will be, just show an ever-growing progress bar.
31+
* The first increment will be sized as if this many runs will be in the total,
32+
* but then like Zeno’s paradox we will never seem to finish until we actually do.
33+
*/
34+
private static final double MAX_LIKELY_RUNS = 20;
35+
private final List<JSONObject> results = new ArrayList<>();
36+
private final Iterable<? extends WorkflowRun> runs;
37+
38+
public WorkflowJobTrend(Iterable<? extends WorkflowRun> runs) {
39+
this.runs = runs;
40+
}
41+
@Override protected void compute() throws Exception {
42+
double decay = 1;
43+
for (WorkflowRun run : runs) {
44+
if (canceled()) {
45+
return;
46+
}
47+
JSONObject element = new JSONObject();
48+
calculate(run, element);
49+
synchronized (this) {
50+
results.add(element);
51+
}
52+
decay *= 1 - 1 / MAX_LIKELY_RUNS;
53+
progress(1 - decay);
54+
}
55+
}
56+
57+
@Override protected synchronized JSON data() {
58+
JSONArray d = JSONArray.fromObject(results);
59+
results.clear();
60+
return d;
61+
}
62+
63+
@SuppressRestrictedWarnings({ArgumentsActionImpl.class, hudson.model.Messages.class})
64+
private void calculate(WorkflowRun run, JSONObject element) {
65+
BallColor iconColor = run.getIconColor();
66+
element.put("iconName", iconColor.getIconName());
67+
element.put("iconColorOrdinal", iconColor.ordinal());
68+
element.put("iconColorDescription", iconColor.getDescription());
69+
element.put("url", run.getUrl());
70+
element.put("number", run.getNumber());
71+
element.put("displayName", run.getDisplayName());
72+
element.put("duration", run.getDuration());
73+
element.put("durationString", run.getDurationString());
74+
element.put("consoleUrl", ConsoleUrlProvider.getRedirectUrl(run));
75+
element.put("timestampString", run.getTimestampString());
76+
element.put("timestampString2", run.getTimestampString2());
77+
JSONArray agents = new JSONArray();
78+
FlowExecution flowExecution = run.getExecution();
79+
if (flowExecution != null) {
80+
for (FlowNode flowNode : new DepthFirstScanner().allNodes(flowExecution)) {
81+
JSONObject n = new JSONObject();
82+
WorkspaceActionImpl action = flowNode.getAction(WorkspaceActionImpl.class);
83+
if (action != null) {
84+
StepStartNode startNode = (StepStartNode) flowNode;
85+
StepDescriptor descriptor = startNode.getDescriptor();
86+
if (descriptor instanceof ExecutorStep.DescriptorImpl) {
87+
String nodeName = action.getNode();
88+
if (nodeName.equals("")) {
89+
n.put("builtOnStr", hudson.model.Messages.Hudson_Computer_DisplayName());
90+
} else {
91+
Node node = Jenkins.get().getNode(nodeName);
92+
if (node != null) {
93+
n.put("builtOn", node.getNodeName());
94+
n.put("builtOnStr", node.getDisplayName());
95+
} else {
96+
n.put("builtOnStr", nodeName);
97+
}
98+
}
99+
BlockEndNode endNode = startNode.getEndNode();
100+
n.put("duration", getDurationString(startNode, endNode));
101+
ArgumentsActionImpl args = flowNode.getAction(ArgumentsActionImpl.class);
102+
if (args != null) {
103+
String label = (String) args.getArgumentValue("label");
104+
if (label != null) {
105+
n.put("label", label);
106+
}
107+
}
108+
agents.add(n);
109+
}
110+
}
111+
}
112+
}
113+
element.put("agents", agents);
114+
}
115+
116+
public String getDurationString(BlockStartNode startNode, BlockEndNode endNode) {
117+
TimingAction startTime = startNode.getAction(TimingAction.class);
118+
if (startTime == null) {
119+
return "n/a";
120+
}
121+
long endTimeLong = 0;
122+
if (endNode != null) {
123+
TimingAction endTime = endNode.getAction(TimingAction.class);
124+
if (endTime != null) {
125+
endTimeLong = endTime.getStartTime();
126+
}
127+
}
128+
if (endTimeLong == 0) {
129+
return Messages.InProgressDuration(Util.getTimeSpanString(System.currentTimeMillis() - startTime.getStartTime()));
130+
}
131+
return Util.getTimeSpanString(endTimeLong - startTime.getStartTime());
132+
}
133+
}

src/main/resources/io/jenkins/plugins/agent_build_history/AgentBuildHistory/agentBuildHistory.css

Lines changed: 0 additions & 3 deletions
This file was deleted.

src/main/resources/io/jenkins/plugins/agent_build_history/AgentBuildHistory/index.jelly

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@
2626
</p>
2727

2828
<t:setIconSize/>
29-
<st:adjunct includes="io.jenkins.plugins.agent_build_history.AgentBuildHistory.agentBuildHistory" />
29+
<st:adjunct includes="io.jenkins.plugins.agent_build_history.agentBuildHistory" />
3030
<l:progressiveRendering handler="${it.handler}" callback="abhDisplayExtendedBuildHistory"/>
3131
<table class="jenkins-table ${iconSize == '16x16' ? 'jenkins-table--small' : iconSize == '24x24' ? 'jenkins-table--medium' : ''}"
3232
id="projectStatus" style="display: none;" data-icon-size-class="${iconSizeClass}"
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
<?jelly escape-by-default='true'?>
2+
<j:jelly xmlns:j="jelly:core" xmlns:st="jelly:stapler" xmlns:l="/lib/layout">
3+
<l:layout title="${%Extended Build History}">
4+
<st:include page="sidepanel.jelly" it="${it.job}"/>
5+
<st:adjunct includes="hudson.model.Job.buildTimeTrend_resources" />
6+
<l:main-panel>
7+
<template id="jenkins-build-status-icons">
8+
<l:icon src="symbol-status-blue" id="blue" />
9+
<l:icon src="symbol-status-blue-anime" id="blue-anime" />
10+
<l:icon src="symbol-status-yellow" id="yellow" />
11+
<l:icon src="symbol-status-yellow-anime" id="yellow-anime" />
12+
<l:icon src="symbol-status-red" id="red" />
13+
<l:icon src="symbol-status-red-anime" id="red-anime" />
14+
<l:icon src="symbol-status-nobuilt" id="nobuilt" />
15+
<l:icon src="symbol-status-nobuilt-anime" id="nobuilt-anime" />
16+
<l:icon src="symbol-status-aborted" id="aborted" />
17+
<l:icon src="symbol-status-aborted-anime" id="aborted-anime" />
18+
<l:icon src="symbol-status-disabled" id="disabled" />
19+
<l:icon src="symbol-status-disabled-anime" id="disabled-anime" />
20+
<l:icon src="symbol-terminal" id="console" alt="${%Console output}"/>
21+
<l:icon src="symbol-chevron-down" id="chevron-down" alt="${%Show all Agents}"/>
22+
</template>
23+
<h1>${%Extended Build History}</h1>
24+
<st:adjunct includes="io.jenkins.plugins.agent_build_history.agentBuildHistory" />
25+
<l:progressiveRendering handler="${it.handler}" callback="abhBuildTimeTrend"/>
26+
<div class="abh-trend">
27+
<table class="jenkins-table jenkins-table--small sortable abh-job-history" id="trend" style="width: auto;"
28+
data-show-agents-text="${%Show all agents}" data-hide-agents-text="${%Hide all agents}"
29+
data-no-label="${%No Label}">
30+
<thead>
31+
<tr>
32+
<th class="jenkins-table__cell--tight abh-center">${%S}</th>
33+
<th initialSortDir="up">${%Build}</th>
34+
<th>${%Time Since}</th>
35+
<th tooltip="${%Overall Build duration}">${%Duration}</th>
36+
<th data-sort-disable="true">${%Agents}</th>
37+
<th data-sort-disable="true" tooltip="${%The label expression used to select the agent}">${%Labels}</th>
38+
<th data-sort-disable="true" tooltip="${%The duration of the specific node step on the agent}">${%Agent Duration}</th>
39+
<th/>
40+
<th/>
41+
</tr>
42+
</thead>
43+
<tbody></tbody>
44+
</table>
45+
<img src="../buildTimeGraph/png" width="500" height="400" lazymap="../buildTimeGraph/map" alt="[${%Build time graph}]" />
46+
</div>
47+
</l:main-panel>
48+
</l:layout>
49+
</j:jelly>
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
.abh-center {
2+
text-align: center !important;
3+
}
4+
5+
.abh-agent-list, .abh-label-list, .abh-duration-list {
6+
display: flex;
7+
flex-direction: column;
8+
justify-content: center;
9+
}
10+
11+
.abh-trend {
12+
display: flex;
13+
column-gap: 20px;
14+
}
15+
16+
.abh-job-history tbody>tr>td {
17+
height: auto!important;
18+
}
19+
20+
.abh-agent-list__button {
21+
display: flex;
22+
height: 24px;
23+
align-items: center;
24+
}
25+
26+
.abh-agent-list__button > button {
27+
min-height: 20px;
28+
max-height: 20px;
29+
margin-left: 5px;
30+
padding: 0.3rem 0.3rem;
31+
}
32+
33+
.abh-agent-list__button[data-hidden=false] {
34+
rotate: 180deg;
35+
}

0 commit comments

Comments
 (0)