Skip to content

Commit c1412b3

Browse files
authored
Merge pull request #143 from jenkinsci/api
Add rest api to get history for agents and jobs in xml or json format
2 parents 7a74f72 + 550e15f commit c1412b3

File tree

13 files changed

+396
-313
lines changed

13 files changed

+396
-313
lines changed

README.md

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,4 +50,11 @@ Alternatively you can directly jump to a run via the input field at the top.
5050
Runs can be filtered by result and/or by agent used.
5151

5252

53-
![trend.png](docs/trend.png)
53+
![trend.png](docs/trend.png)
54+
55+
56+
## API
57+
The plugin exposes a REST API to get the extended build history for agents and jobs in JSON and XML format similar to other Jenkins APIs.
58+
Go to `/job/<jobname>/extendedBuildHistory/api/` or
59+
`/computer/<agentname>/extendedBuildHistory/api/` for more details.
60+

src/main/java/io/jenkins/plugins/agent_build_history/AgentBuildHistory.java

Lines changed: 26 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
44
import hudson.model.AbstractBuild;
55
import hudson.model.Action;
6+
import hudson.model.Api;
67
import hudson.model.Computer;
78
import hudson.model.Job;
89
import hudson.model.Node;
@@ -57,18 +58,6 @@ public static String getCookieValue(StaplerRequest2 req, String name, String def
5758
return defaultValue; // Fallback to default if cookie not found
5859
}
5960

60-
private static int getRequestInteger(StaplerRequest2 req, String name, int defaultValue) {
61-
try {
62-
String value = req.getParameter(name);
63-
if (value == null) {
64-
return defaultValue;
65-
}
66-
return Integer.parseInt(req.getParameter(name));
67-
} catch (NumberFormatException e) {
68-
return defaultValue;
69-
}
70-
}
71-
7261
/*
7362
* used by jelly
7463
*/
@@ -92,6 +81,28 @@ public int getTotalPages() {
9281
return totalPages;
9382
}
9483

84+
@SuppressFBWarnings(value = "ST_WRITE_TO_STATIC_FROM_INSTANCE_METHOD")
85+
public Api getApi() {
86+
if (!loaded) {
87+
loaded = true;
88+
Timer.get().schedule(AgentBuildHistory::load, 0, TimeUnit.SECONDS);
89+
}
90+
RunListTable runListTable = new RunListTable(computer.getName());
91+
//Get Parameters from URL
92+
StaplerRequest2 req = Stapler.getCurrentRequest2();
93+
int pageSize = Utils.getRequestInteger(req, "limit", 100);
94+
int page = Utils.getRequestInteger(req, "page", 1);
95+
String statusFilter = req.getParameter("status") != null ? req.getParameter("status") : "all";
96+
List<String> indexLines = BuildHistoryFileManager.readIndexFile(computer.getName(), AgentBuildHistoryConfig.get().getStorageDir());
97+
98+
String uri = req.getRequestURI();
99+
if (!uri.endsWith("/api/")) {
100+
runListTable.setRuns(getExecutionsForNode(indexLines, computer.getName(), page, pageSize, "startTime", "desc", statusFilter));
101+
runListTable.compute();
102+
}
103+
return new Api(runListTable);
104+
}
105+
95106
@SuppressFBWarnings(value = "ST_WRITE_TO_STATIC_FROM_INSTANCE_METHOD")
96107
public RunListTable getHandler() {
97108
if (!loaded) {
@@ -101,8 +112,8 @@ public RunListTable getHandler() {
101112
RunListTable runListTable = new RunListTable(computer.getName());
102113
//Get Parameters from URL
103114
StaplerRequest2 req = Stapler.getCurrentRequest2();
104-
int page = getRequestInteger(req, "page", 1);
105-
int pageSize = getRequestInteger(req, "pageSize", Integer.parseInt(getCookieValue(req, "pageSize", "20")));
115+
int page = Utils.getRequestInteger(req, "page", 1);
116+
int pageSize = Utils.getRequestInteger(req, "pageSize", Integer.parseInt(getCookieValue(req, "pageSize", "20")));
106117
String sortColumn = req.getParameter("sortColumn") != null ? req.getParameter("sortColumn") : getCookieValue(req, "sortColumn", "startTime");
107118
String sortOrder = req.getParameter("sortOrder") != null ? req.getParameter("sortOrder") : getCookieValue(req, "sortOrder", "desc");
108119
String statusFilter = req.getParameter("status") != null ? req.getParameter("status") : "all";
@@ -112,6 +123,7 @@ public RunListTable getHandler() {
112123
LOGGER.finer("Found " + indexLines.size() + " entries for node " + computer.getName());
113124

114125
runListTable.setRuns(getExecutionsForNode(indexLines, computer.getName(), page, pageSize, sortColumn, sortOrder, statusFilter));
126+
runListTable.compute();
115127
return runListTable;
116128
}
117129

src/main/java/io/jenkins/plugins/agent_build_history/AgentExecution.java

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,8 @@
2020
import org.jenkinsci.plugins.workflow.job.WorkflowRun;
2121
import org.kohsuke.accmod.Restricted;
2222
import org.kohsuke.accmod.restrictions.NoExternalUse;
23+
import org.kohsuke.stapler.export.Exported;
24+
import org.kohsuke.stapler.export.ExportedBean;
2325

2426
@Restricted(NoExternalUse.class)
2527
public class AgentExecution implements Comparable<AgentExecution> {
@@ -82,6 +84,7 @@ public int compareTo(AgentExecution o) {
8284
return compare;
8385
}
8486

87+
@ExportedBean(defaultVisibility = 4)
8588
public class FlowNodeExecution implements Comparable<FlowNodeExecution> {
8689
private final String nodeId;
8790
private Status status;
@@ -134,6 +137,7 @@ public String getDurationString() {
134137
}
135138
}
136139

140+
@Exported
137141
public long getDuration() {
138142
long endTime = getNodeTime(getEndNode());
139143
if (endTime == 0) {
@@ -165,6 +169,7 @@ public int hashCode() {
165169
return Objects.hash(startTime);
166170
}
167171

172+
@Exported
168173
public String getNodeId() {
169174
return nodeId;
170175
}
@@ -173,6 +178,7 @@ public String getNodeName() {
173178
return nodeName;
174179
}
175180

181+
@Exported(name = "status")
176182
public Status getFlowNodeStatus() {
177183
long endTime = getNodeTime(getEndNode());
178184
if (endTime == 0) {
@@ -213,6 +219,11 @@ public String getStartTimeSince() {
213219
long duration = (new GregorianCalendar()).getTimeInMillis() - startTime;
214220
return Util.getTimeSpanString(duration);
215221
}
222+
223+
@Exported
224+
public long getStartTime() {
225+
return startTime;
226+
}
216227
}
217228

218229
public enum Status {
Lines changed: 82 additions & 81 deletions
Original file line numberDiff line numberDiff line change
@@ -1,31 +1,28 @@
11
package io.jenkins.plugins.agent_build_history;
22

33
import edu.umd.cs.findbugs.annotations.NonNull;
4-
import hudson.Functions;
5-
import hudson.model.BallColor;
64
import hudson.model.Cause;
5+
import hudson.model.Job;
6+
import hudson.model.Result;
77
import hudson.model.Run;
8-
import jenkins.console.ConsoleUrlProvider;
9-
import jenkins.util.ProgressiveRendering;
10-
import net.sf.json.JSON;
11-
import net.sf.json.JSONArray;
12-
import net.sf.json.JSONObject;
13-
import org.jenkinsci.plugins.workflow.job.WorkflowRun;
14-
import org.kohsuke.accmod.Restricted;
15-
import org.kohsuke.accmod.restrictions.NoExternalUse;
16-
178
import java.text.SimpleDateFormat;
189
import java.util.ArrayList;
1910
import java.util.Date;
2011
import java.util.List;
12+
import org.jenkinsci.plugins.workflow.job.WorkflowRun;
13+
import org.kohsuke.accmod.Restricted;
14+
import org.kohsuke.accmod.restrictions.NoExternalUse;
15+
import org.kohsuke.stapler.export.Exported;
16+
import org.kohsuke.stapler.export.ExportedBean;
2117

2218
@Restricted(NoExternalUse.class)
23-
public class RunListTable extends ProgressiveRendering {
19+
@ExportedBean(defaultVisibility = 2)
20+
public class RunListTable {
2421

2522
String computerName;
2623
private static final double MAX_LIKELY_RUNS = 20;
2724
private Iterable<AgentExecution> runs;
28-
private final List<JSONObject> results = new ArrayList<>();
25+
private final List<RunResult> results = new ArrayList<>();
2926

3027
public RunListTable(String computerName) {
3128
this.computerName = computerName;
@@ -35,93 +32,97 @@ public void setRuns(Iterable<AgentExecution> runs) {
3532
this.runs = runs;
3633
}
3734

35+
@Exported(name = "runs")
36+
public List<RunResult> getResults() {
37+
return results;
38+
}
39+
3840
@NonNull
39-
private JSONObject calculate(Run<?, ?> run, AgentExecution execution) {
40-
JSONObject element = new JSONObject();
41-
BallColor iconColor = run.getIconColor();
42-
element.put("iconColorOrdinal", iconColor.ordinal());
43-
element.put("iconColorDescription", iconColor.getDescription());
44-
element.put("url", run.getUrl());
45-
element.put("number", run.getNumber());
46-
element.put("consoleUrl", ConsoleUrlProvider.getRedirectUrl(run));
47-
element.put("iconName", run.getIconColor().getIconName());
48-
element.put("parentUrl", run.getParent().getUrl());
49-
element.put("parentFullDisplayName", Functions.breakableString(Functions.escape(run.getParent().getFullDisplayName())));
50-
element.put("displayName", run.getDisplayName());
51-
element.put("duration", run.getDuration());
52-
element.put("durationString", run.getDurationString());
53-
element.put("timestampString", run.getTimestampString());
54-
element.put("timestampString2", run.getTimestampString2());
55-
element.put("startTimeInMillis", run.getStartTimeInMillis());
56-
element.put("startTimeReadable", formatStartTime(run.getStartTimeInMillis()));
57-
Run.Summary buildStatusSummary = run.getBuildStatusSummary();
58-
element.put("buildStatusSummaryWorse", buildStatusSummary.isWorse);
59-
element.put("buildStatusSummaryMessage", buildStatusSummary.message);
41+
private RunResult calculate(Run<?, ?> run, AgentExecution execution) {
42+
RunResult result = new RunResult(run.getParent(), run);
6043
List<Cause> causeList = run.getCauses();
6144
if (!causeList.isEmpty()) {
62-
element.put("shortDescription", causeList.get(causeList.size() - 1).getShortDescription());
45+
result.setCause(causeList.get(causeList.size() - 1).getShortDescription());
6346
} else {
64-
element.put("shortDescription", "UnknownCause");
47+
result.setCause("UnknownCause");
6548
}
6649

67-
JSONArray flowNodes = new JSONArray();
6850
if (run instanceof WorkflowRun) {
6951
for (AgentExecution.FlowNodeExecution nodeExec : execution.getFlowNodes()) {
7052
if (nodeExec.getNodeName().equals(computerName)) {
71-
flowNodes.add(calculateFlowNode(nodeExec));
53+
result.addExecution(nodeExec);
7254
}
7355
}
7456
}
75-
element.put("flowNodes", flowNodes);
76-
return element;
57+
return result;
7758
}
7859

79-
private JSONObject calculateFlowNode(AgentExecution.FlowNodeExecution node) {
80-
JSONObject element = new JSONObject();
81-
element.put("duration", node.getDuration());
82-
element.put("durationString", node.getDurationString());
83-
element.put("startTime", node.getStartTimeString());
84-
element.put("startTimeString", node.getStartTimeSince());
85-
element.put("flowNodeId", node.getNodeId());
86-
element.put("flowNodeStatusWorse", node.getFlowNodeStatus().isWorse());
87-
element.put("flowNodeStatusMessage", node.getFlowNodeStatus().getMessage());
88-
return element;
89-
}
60+
@ExportedBean(defaultVisibility = 2)
61+
public static class RunResult {
62+
private final Job<?, ?> job;
63+
private final Run<?, ?> run;
64+
private String cause;
65+
private final List<AgentExecution.FlowNodeExecution> executions = new ArrayList<>();
9066

91-
private String formatStartTime(long startTimeInMillis) {
92-
SimpleDateFormat formatter = new SimpleDateFormat("yyyy/MM/dd HH:mm:ss");
93-
return formatter.format(new Date(startTimeInMillis));
67+
public RunResult(Job<?, ?> job, Run<?, ?> run) {
68+
this.job = job;
69+
this.run = run;
70+
}
71+
72+
public void addExecution(AgentExecution.FlowNodeExecution execution) {
73+
this.executions.add(execution);
74+
}
75+
76+
@Exported
77+
public List<AgentExecution.FlowNodeExecution> getExecutions() {
78+
return executions;
79+
}
80+
81+
public void setCause(String cause) {
82+
this.cause = cause;
83+
}
84+
85+
@Exported
86+
public String getCause() {
87+
return cause;
88+
}
89+
90+
@Exported
91+
public Job<?, ?> getJob() {
92+
return job;
93+
}
94+
95+
@Exported
96+
public String getFullName() {
97+
return job.getFullName();
98+
}
99+
100+
@Exported(name = "build")
101+
public Run<?, ?> getRun() {
102+
return run;
103+
}
104+
105+
@Exported
106+
public Result getResult() {
107+
return run.getResult();
108+
}
109+
110+
@Exported
111+
public int getNumber() {
112+
return run.getNumber();
113+
}
114+
115+
public String getStartTime() {
116+
SimpleDateFormat formatter = new SimpleDateFormat("yyyy/MM/dd HH:mm:ss");
117+
return formatter.format(new Date(run.getStartTimeInMillis()));
118+
}
94119
}
95120

96-
@Override
97-
protected void compute() throws Exception {
98-
double decay = 1;
99-
Functions functions = new Functions();
121+
void compute() {
100122
for (AgentExecution execution : runs) {
101-
if (canceled()) {
102-
return;
103-
}
104123
Run<?, ?> run = execution.getRun();
105-
JSONObject element = calculate(run, execution);
106-
String runId = functions.generateId();
107-
if (run instanceof WorkflowRun) {
108-
element.put("runId", runId);
109-
} else {
110-
element.put("runId", "");
111-
}
112-
synchronized (this) {
113-
results.add(element);
114-
}
115-
decay *= 1 - 1 / MAX_LIKELY_RUNS;
116-
progress(1 - decay);
124+
RunResult element = calculate(run, execution);
125+
results.add(element);
117126
}
118127
}
119-
120-
@NonNull
121-
@Override
122-
protected synchronized JSON data() {
123-
JSONArray d = JSONArray.fromObject(results);
124-
results.clear();
125-
return d;
126-
}
127128
}

src/main/java/io/jenkins/plugins/agent_build_history/Utils.java

Lines changed: 20 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,26 +3,42 @@
33
import hudson.model.Result;
44
import org.kohsuke.accmod.Restricted;
55
import org.kohsuke.accmod.restrictions.NoExternalUse;
6+
import org.kohsuke.stapler.StaplerRequest2;
67

78
@Restricted(NoExternalUse.class)
89
public class Utils {
910

1011
static boolean includeRun(Result result, String statusFilter) {
1112
if (!"all".equals(statusFilter) && result != null) {
12-
if ("success".equals(statusFilter) && result != Result.SUCCESS) {
13+
if ("success".equalsIgnoreCase(statusFilter) && result != Result.SUCCESS) {
1314
return false;
1415
}
15-
if ("unstable".equals(statusFilter) && result != Result.UNSTABLE) {
16+
if ("unstable".equalsIgnoreCase(statusFilter) && result != Result.UNSTABLE) {
1617
return false;
1718
}
18-
if ("failure".equals(statusFilter) && result != Result.FAILURE) {
19+
if ("failure".equalsIgnoreCase(statusFilter) && result != Result.FAILURE) {
1920
return false;
2021
}
21-
if ("aborted".equals(statusFilter) && result != Result.ABORTED) {
22+
if ("not_built".equalsIgnoreCase(statusFilter) && result != Result.NOT_BUILT) {
2223
return false;
2324
}
25+
return !"aborted".equalsIgnoreCase(statusFilter) || result == Result.ABORTED;
2426
}
2527
return true;
2628
}
2729

30+
public static int getRequestInteger(StaplerRequest2 req, String name, int defaultValue) {
31+
return getDefaultInt(req.getParameter(name), defaultValue);
32+
}
33+
34+
public static int getDefaultInt(String value, int defaultValue) {
35+
try {
36+
if (value == null) {
37+
return defaultValue;
38+
}
39+
return Integer.parseInt(value);
40+
} catch (NumberFormatException e) {
41+
return defaultValue;
42+
}
43+
}
2844
}

0 commit comments

Comments
 (0)