Skip to content

Commit 1610543

Browse files
stuartrowetimja
andauthored
Add cancel button to pipeline overview (jenkinsci#766)
Co-authored-by: Tim Jacomb <21194782+timja@users.noreply.github.com> Co-authored-by: Tim Jacomb <timjacomb1@gmail.com>
1 parent 45e9b7e commit 1610543

File tree

10 files changed

+162
-0
lines changed

10 files changed

+162
-0
lines changed

pom.xml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -111,6 +111,11 @@
111111
<version>1.52.0</version>
112112
<scope>test</scope>
113113
</dependency>
114+
<dependency>
115+
<groupId>io.jenkins.configuration-as-code</groupId>
116+
<artifactId>test-harness</artifactId>
117+
<scope>test</scope>
118+
</dependency>
114119
<dependency>
115120
<groupId>org.awaitility</groupId>
116121
<artifactId>awaitility</artifactId>

src/main/java/io/jenkins/plugins/pipelinegraphview/utils/AbstractPipelineViewAction.java

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,10 @@
88
import hudson.model.BallColor;
99
import hudson.model.Item;
1010
import hudson.model.Queue;
11+
import hudson.model.Result;
1112
import hudson.security.Permission;
1213
import hudson.util.HttpResponses;
14+
import io.jenkins.plugins.pipelinegraphview.Messages;
1315
import java.io.IOException;
1416
import java.util.concurrent.ExecutionException;
1517
import java.util.concurrent.TimeoutException;
@@ -41,10 +43,18 @@ public boolean isBuildable() {
4143
return run.getParent().isBuildable();
4244
}
4345

46+
public boolean isBuildInProgress() {
47+
return run.isBuilding();
48+
}
49+
4450
public Permission getPermission() {
4551
return run.getParent().BUILD;
4652
}
4753

54+
public Permission getCancelPermission() {
55+
return Item.CANCEL;
56+
}
57+
4858
public Permission getConfigurePermission() {
4959
return run.getParent().CONFIGURE;
5060
}
@@ -53,6 +63,10 @@ public String getBuildDisplayName() {
5363
return run.getDisplayName();
5464
}
5565

66+
public String getBuildFullDisplayName() {
67+
return run.getFullDisplayName();
68+
}
69+
5670
/**
5771
* Handles the rebuild request using ReplayAction feature
5872
*/
@@ -73,6 +87,27 @@ public boolean doRebuild() throws IOException, ExecutionException {
7387
return false;
7488
}
7589

90+
/**
91+
* Handles the cancel request.
92+
*/
93+
@RequirePOST
94+
@JavaScriptMethod
95+
public HttpResponse doCancel() throws IOException, ExecutionException {
96+
if (run != null) {
97+
run.checkPermission(getCancelPermission());
98+
if (run.isBuilding()) {
99+
run.doStop();
100+
return HttpResponses.okJSON();
101+
} else {
102+
String message = Result.ABORTED.equals(run.getResult())
103+
? Messages.run_alreadyCancelled()
104+
: Messages.run_isFinished();
105+
return HttpResponses.errorJSON(message);
106+
}
107+
}
108+
return HttpResponses.errorJSON("No run to cancel");
109+
}
110+
76111
public String getFullBuildDisplayName() {
77112
return run.getFullDisplayName();
78113
}

src/main/resources/io/jenkins/plugins/pipelinegraphview/Messages.properties

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,3 +28,6 @@ cause.user=Manually run by {0}
2828

2929
FlowNodeWrapper.parallel=Parallel
3030
FlowNodeWrapper.noStage=System Generated
31+
32+
run.alreadyCancelled=Run was already cancelled
33+
run.isFinished=Run is already finished

src/main/resources/io/jenkins/plugins/pipelinegraphview/consoleview/PipelineConsoleViewAction/index.jelly

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,18 @@
88
<j:out value="${h.generateConsoleAnnotationScriptAndStylesheet()}"/>
99

1010
<j:set var="controls">
11+
<j:if test="${it.buildInProgress}">
12+
<l:hasPermission permission="${it.cancelPermission}">
13+
<j:set var="proxyId" value="${h.generateId()}" />
14+
<st:bind value="${it}" var="cancelAction${proxyId}"/>
15+
<button id="pgv-cancel" data-confirm="${%Confirm(it.buildFullDisplayName)}"
16+
data-proxy-name="cancelAction${proxyId}"
17+
class="jenkins-button jenkins-!-error-color">
18+
<l:icon src="symbol-stop-circle-outline plugin-ionicons-api"/>
19+
${%Cancel}
20+
</button>
21+
</l:hasPermission>
22+
</j:if>
1123
<j:if test="${it.buildable}">
1224
<l:hasPermission permission="${it.permission}">
1325
<j:set var="proxyId" value="${h.generateId()}" />
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
Cancel=Cancel
2+
Confirm=Are you sure you want to abort {0}?

src/main/webapp/js/build.js

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,3 +12,48 @@ if (rebuildButton) {
1212
});
1313
})
1414
}
15+
16+
const cancelButton = document.getElementById('pgv-cancel');
17+
18+
if (cancelButton) {
19+
cancelButton.addEventListener('click', event => {
20+
event.preventDefault();
21+
const question = cancelButton.getAttribute("data-confirm");
22+
const execute = function () {
23+
const cancelAction = window[`${cancelButton.dataset.proxyName}`];
24+
cancelAction.doCancel();
25+
};
26+
27+
if (question != null) {
28+
/*eslint-disable no-undef*/
29+
dialog.confirm(question).then(() => {
30+
execute();
31+
return null;
32+
}).catch((error) => {
33+
console.error("Error in cancel confirm dialog:", error);
34+
})
35+
} else {
36+
execute();
37+
}
38+
39+
function updateCancelButton() {
40+
const buildCaption = document.querySelector(".jenkins-build-caption");
41+
const url = buildCaption.dataset.statusUrl;
42+
fetch(url).then((rsp) => {
43+
if (rsp.ok) {
44+
const isBuilding = rsp.headers.get("X-Building");
45+
if (isBuilding === "true") {
46+
setTimeout(updateCancelButton, 5000);
47+
} else {
48+
cancelButton.style.display = "none";
49+
}
50+
}
51+
return null;
52+
})
53+
.catch((error) => {
54+
console.error("Error fetching build caption statsus:", error);
55+
})
56+
}
57+
setTimeout(updateCancelButton, 5000);
58+
})
59+
}
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
package io.jenkins.plugins.pipelinegraphview;
2+
3+
import com.microsoft.playwright.Page;
4+
import com.microsoft.playwright.junit.UsePlaywright;
5+
import hudson.model.Result;
6+
import io.jenkins.plugins.casc.misc.ConfiguredWithCode;
7+
import io.jenkins.plugins.casc.misc.JenkinsConfiguredWithCodeRule;
8+
import io.jenkins.plugins.casc.misc.junit.jupiter.WithJenkinsConfiguredWithCode;
9+
import io.jenkins.plugins.pipelinegraphview.playwright.PipelineJobPage;
10+
import io.jenkins.plugins.pipelinegraphview.playwright.PlaywrightConfig;
11+
import io.jenkins.plugins.pipelinegraphview.utils.TestUtils;
12+
import org.jenkinsci.plugins.workflow.job.WorkflowRun;
13+
import org.jenkinsci.plugins.workflow.test.steps.SemaphoreStep;
14+
import org.junit.jupiter.api.Test;
15+
16+
@WithJenkinsConfiguredWithCode
17+
@UsePlaywright(PlaywrightConfig.class)
18+
class PipelineGraphViewCancelTest {
19+
20+
@Test
21+
@ConfiguredWithCode("configure-appearance.yml")
22+
void cancelButtonCancelsBuild(Page p, JenkinsConfiguredWithCodeRule j) throws Exception {
23+
WorkflowRun run = TestUtils.createAndRunJobNoWait(j, "indefiniteWait", "indefiniteWait.jenkinsfile")
24+
.waitForStart();
25+
SemaphoreStep.waitForStart("wait/1", run);
26+
new PipelineJobPage(p, run.getParent())
27+
.goTo()
28+
.hasBuilds(1)
29+
.nthBuild(0)
30+
.goToBuild()
31+
.goToPipelineOverview()
32+
.cancel();
33+
SemaphoreStep.success("wait/1", null);
34+
j.assertBuildStatus(Result.ABORTED, j.waitForCompletion(run));
35+
}
36+
}

src/test/java/io/jenkins/plugins/pipelinegraphview/playwright/PipelineOverviewPage.java

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -132,4 +132,11 @@ public PipelineOverviewPage stageIsVisibleInTree(String stage) {
132132
tree.stageIsVisible(stage);
133133
return this;
134134
}
135+
136+
public PipelineOverviewPage cancel() {
137+
page.click("#pgv-cancel");
138+
page.getByRole(AriaRole.BUTTON, new Page.GetByRoleOptions().setName("Yes"))
139+
.click();
140+
return this;
141+
}
135142
}
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
appearance:
2+
consoleUrlProvider:
3+
providers:
4+
- "pipelineGraphView"
5+
pipelineGraphView:
6+
showGraphOnBuildPage: true
7+
showGraphOnJobPage: true
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
pipeline {
2+
agent any
3+
stages {
4+
stage('Wait') {
5+
steps {
6+
semaphore 'wait'
7+
}
8+
}
9+
}
10+
}

0 commit comments

Comments
 (0)