Skip to content

Commit de37189

Browse files
authored
Add choices to rebuild button (jenkinsci#770)
1 parent d9674ce commit de37189

File tree

9 files changed

+218
-43
lines changed

9 files changed

+218
-43
lines changed

pom.xml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -111,6 +111,12 @@
111111
<version>1.52.0</version>
112112
<scope>test</scope>
113113
</dependency>
114+
<dependency>
115+
<groupId>com.sonyericsson.hudson.plugins.rebuild</groupId>
116+
<artifactId>rebuild</artifactId>
117+
<version>338.va_0a_b_50e29397</version>
118+
<scope>test</scope>
119+
</dependency>
114120
<dependency>
115121
<groupId>io.jenkins.configuration-as-code</groupId>
116122
<artifactId>test-harness</artifactId>

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

Lines changed: 23 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,11 @@
44

55
import com.fasterxml.jackson.core.JsonProcessingException;
66
import com.fasterxml.jackson.databind.ObjectMapper;
7+
import hudson.Plugin;
78
import hudson.model.Action;
89
import hudson.model.BallColor;
910
import hudson.model.Item;
11+
import hudson.model.ParametersDefinitionProperty;
1012
import hudson.model.Queue;
1113
import hudson.model.Result;
1214
import hudson.security.Permission;
@@ -17,8 +19,10 @@
1719
import java.util.concurrent.TimeoutException;
1820
import java.util.logging.Level;
1921
import java.util.logging.Logger;
22+
import jenkins.model.Jenkins;
2023
import net.sf.json.JSONObject;
2124
import org.jenkins.ui.icon.IconSpec;
25+
import org.jenkinsci.plugins.pipeline.modeldefinition.actions.RestartDeclarativePipelineAction;
2226
import org.jenkinsci.plugins.workflow.cps.replay.ReplayAction;
2327
import org.jenkinsci.plugins.workflow.job.WorkflowRun;
2428
import org.kohsuke.stapler.HttpResponse;
@@ -67,12 +71,29 @@ public String getBuildFullDisplayName() {
6771
return run.getFullDisplayName();
6872
}
6973

74+
public boolean isRebuildAvailable() {
75+
Plugin rebuildPlugin = Jenkins.get().getPlugin("rebuild");
76+
if (rebuildPlugin != null && rebuildPlugin.getWrapper().isEnabled()) {
77+
// limit rebuild to parameterized jobs otherwise it duplicates rerun's behaviour
78+
return run.getParent().getProperty(ParametersDefinitionProperty.class) != null;
79+
}
80+
return false;
81+
}
82+
83+
public boolean isRestartFromStageAvailable() {
84+
RestartDeclarativePipelineAction action = run.getAction(RestartDeclarativePipelineAction.class);
85+
if (action != null) {
86+
return action.isRestartEnabled();
87+
}
88+
return false;
89+
}
90+
7091
/**
71-
* Handles the rebuild request using ReplayAction feature
92+
* Handles the rerun request using ReplayAction feature
7293
*/
7394
@RequirePOST
7495
@JavaScriptMethod
75-
public boolean doRebuild() throws IOException, ExecutionException {
96+
public boolean doRerun() throws IOException, ExecutionException {
7697
if (run != null) {
7798
run.checkAnyPermission(Item.BUILD);
7899
ReplayAction replayAction = run.getAction(ReplayAction.class);

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

Lines changed: 53 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
<?xml version="1.0" encoding="UTF-8"?>
22

33
<?jelly escape-by-default='true'?>
4-
<j:jelly xmlns:j="jelly:core" xmlns:st="jelly:stapler" xmlns:l="/lib/layout" xmlns:t="/lib/hudson">
4+
<j:jelly xmlns:j="jelly:core" xmlns:st="jelly:stapler" xmlns:l="/lib/layout" xmlns:t="/lib/hudson" xmlns:dd="/lib/layout/dropdowns">
55
<l:layout title="${it.buildDisplayName} - ${it.fullProjectDisplayName}">
66
<l:main-panel>
77
<link rel="stylesheet" href="${resURL}/plugin/pipeline-graph-view/js/style.css" type="text/css" />
@@ -24,13 +24,58 @@
2424
<j:if test="${it.buildable}">
2525
<l:hasPermission permission="${it.permission}">
2626
<j:set var="proxyId" value="${h.generateId()}" />
27-
<st:bind value="${it}" var="rebuildAction${proxyId}"/>
28-
<button id="pgv-rebuild" data-success-message="${%Build scheduled}"
29-
data-proxy-name="rebuildAction${proxyId}"
30-
class="jenkins-button jenkins-!-build-color">
31-
<l:icon src="symbol-play-outline plugin-ionicons-api"/>
32-
${%Rebuild}
33-
</button>
27+
<st:bind value="${it}" var="rerunAction${proxyId}"/>
28+
<div class="jenkins-split-button">
29+
<button id="pgv-rerun" data-success-message="${%Build scheduled}"
30+
data-proxy-name="rerunAction${proxyId}"
31+
class="jenkins-button jenkins-!-build-color">
32+
<div class="jenkins-dropdown__item__icon">
33+
<l:icon src="symbol-refresh-outline plugin-ionicons-api"/>
34+
</div>
35+
${%Rerun}
36+
</button>
37+
<l:overflowButton id="pgv-rerun-overflow" tooltip="${null}"
38+
icon="symbol-chevron-down"
39+
clazz="jenkins-button jenkins-!-build-color">
40+
<dd:custom>
41+
<a id="pgv-replay" href="../replay" class="jenkins-dropdown__item pgv-dropdown-item">
42+
<div class="jenkins-dropdown__item__icon">
43+
<l:icon src="symbol-arrow-redo-outline plugin-ionicons-api"/>
44+
</div>
45+
<span>${%Replay}</span>
46+
<div class="pgv-dropdown-item__description">
47+
${%Edit Pipeline and replay}
48+
</div>
49+
</a>
50+
</dd:custom>
51+
<j:if test="${it.restartFromStageAvailable}">
52+
<dd:custom>
53+
<a id="pgv-restart" href="../restart" class="jenkins-dropdown__item pgv-dropdown-item">
54+
<div class="jenkins-dropdown__item__icon">
55+
<l:icon src="symbol-refresh-outline plugin-ionicons-api"/>
56+
</div>
57+
<span>${%Restart from Stage}</span>
58+
<div class="pgv-dropdown-item__description">
59+
${%Restart Pipeline from a specific Stage}
60+
</div>
61+
</a>
62+
</dd:custom>
63+
</j:if>
64+
<j:if test="${it.rebuildAvailable}">
65+
<dd:custom>
66+
<a id="pgv-rebuild" href="../rebuild/parameterized" class="jenkins-dropdown__item pgv-dropdown-item">
67+
<div class="jenkins-dropdown__item__icon">
68+
<l:icon src="symbol-play-outline plugin-ionicons-api"/>
69+
</div>
70+
<span>${%Rebuild}</span>
71+
<div class="pgv-dropdown-item__description">
72+
${%Edit parameters and rebuild}
73+
</div>
74+
</a>
75+
</dd:custom>
76+
</j:if>
77+
</l:overflowButton>
78+
</div>
3479
</l:hasPermission>
3580
</j:if>
3681
<l:hasPermission permission="${it.configurePermission}">
Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,12 @@
11
Build\ cancelled=Build cancelled
22
Cancel=Cancel
3+
Configure=Configure
34
Confirm=Are you sure you want to abort {0}?
5+
Edit\ parameters\ and\ rebuild=Edit parameters and rebuild
6+
Edit\ Pipeline\ and\ replay=Edit Pipeline and replay
7+
Rerun=Rerun
8+
Rerun\ now=Rerun now
9+
Replay=Replay
410
Rebuild=Rebuild
5-
Configure=Configure
11+
Restart\ from\ Stage=Restart from Stage
12+
Restart\ Pipeline\ from\ a\ specific\ Stage=Restart Pipeline from a specific Stage

src/main/webapp/js/build.js

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,13 @@
1-
const rebuildButton = document.getElementById('pgv-rebuild');
1+
const rerunButton = document.getElementById('pgv-rerun');
22

3-
if (rebuildButton) {
4-
rebuildButton.addEventListener('click', event => {
3+
if (rerunButton) {
4+
rerunButton.addEventListener('click', event => {
55
event.preventDefault();
6-
const rebuildAction = window[`${rebuildButton.dataset.proxyName}`];
7-
rebuildAction.doRebuild(function (success) {
6+
const rerunAction = window[`${rerunButton.dataset.proxyName}`];
7+
rerunAction.doRerun(function (success) {
88
const result = success.responseJSON;
99
if (result) {
10-
notificationBar.show(rebuildButton.dataset.successMessage, notificationBar.SUCCESS);
10+
notificationBar.show(rerunButton.dataset.successMessage, notificationBar.SUCCESS);
1111
}
1212
});
1313
})

src/main/webapp/js/style.css

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,3 +49,14 @@ h1 {
4949
}
5050
}
5151
}
52+
53+
.pgv-dropdown-item {
54+
display: grid;
55+
grid-template-columns: auto 1fr;
56+
row-gap: 0.25rem;
57+
58+
.pgv-dropdown-item__description {
59+
color: var(--text-color-secondary);
60+
grid-area: 2 / 2;
61+
}
62+
}

src/test/java/io/jenkins/plugins/pipelinegraphview/PipelineGraphViewRebuildTest.java

Lines changed: 74 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -4,62 +4,110 @@
44
import static org.hamcrest.CoreMatchers.is;
55
import static org.junit.jupiter.api.Assertions.assertEquals;
66

7-
import com.microsoft.playwright.Browser;
87
import com.microsoft.playwright.Page;
9-
import com.microsoft.playwright.Playwright;
10-
import com.microsoft.playwright.Response;
8+
import com.microsoft.playwright.junit.UsePlaywright;
119
import hudson.model.Result;
12-
import io.jenkins.plugins.pipelinegraphview.consoleview.PipelineConsoleViewAction;
10+
import io.jenkins.plugins.casc.misc.ConfiguredWithCode;
11+
import io.jenkins.plugins.casc.misc.JenkinsConfiguredWithCodeRule;
12+
import io.jenkins.plugins.casc.misc.junit.jupiter.WithJenkinsConfiguredWithCode;
13+
import io.jenkins.plugins.pipelinegraphview.playwright.PipelineJobPage;
14+
import io.jenkins.plugins.pipelinegraphview.playwright.PipelineOverviewPage;
15+
import io.jenkins.plugins.pipelinegraphview.playwright.PlaywrightConfig;
1316
import io.jenkins.plugins.pipelinegraphview.utils.TestUtils;
1417
import jenkins.test.RunMatchers;
1518
import org.jenkinsci.plugins.workflow.job.WorkflowJob;
1619
import org.jenkinsci.plugins.workflow.job.WorkflowRun;
1720
import org.junit.jupiter.api.Test;
1821
import org.jvnet.hudson.test.Issue;
19-
import org.jvnet.hudson.test.JenkinsRule;
20-
import org.jvnet.hudson.test.junit.jupiter.WithJenkins;
2122

22-
@WithJenkins
23+
@WithJenkinsConfiguredWithCode
24+
@UsePlaywright(PlaywrightConfig.class)
2325
class PipelineGraphViewRebuildTest {
2426

2527
@Issue("GH#330")
2628
@Test
27-
void rebuildButtonStartsNewBuild(JenkinsRule j) throws Exception {
29+
@ConfiguredWithCode("configure-appearance.yml")
30+
void rerunButtonStartsNewBuild(Page p, JenkinsConfiguredWithCodeRule j) throws Exception {
2831
WorkflowRun run =
2932
TestUtils.createAndRunJob(j, "hello_world", "helloWorldScriptedPipeline.jenkinsfile", Result.SUCCESS);
3033

31-
startBuildAndAssertPageIsTheSame(j, run);
34+
PipelineOverviewPage op = new PipelineJobPage(p, run.getParent())
35+
.goTo()
36+
.hasBuilds(1)
37+
.nthBuild(0)
38+
.goToBuild()
39+
.goToPipelineOverview();
40+
41+
String currentUrl = p.url();
42+
op.rerun();
43+
44+
String newUrl = p.url();
45+
assertEquals(currentUrl, newUrl);
46+
47+
waitUntilBuildIsComplete(j, run);
3248
}
3349

34-
@Issue("GH#330")
3550
@Test
36-
void rebuildButtonRedirectsForParameterizedJob(JenkinsRule j) throws Exception {
51+
@ConfiguredWithCode("configure-appearance.yml")
52+
void replayButtonRedirects(Page p, JenkinsConfiguredWithCodeRule j) throws Exception {
53+
WorkflowRun run =
54+
TestUtils.createAndRunJob(j, "hello_world", "helloWorldScriptedPipeline.jenkinsfile", Result.SUCCESS);
55+
56+
new PipelineJobPage(p, run.getParent())
57+
.goTo()
58+
.hasBuilds(1)
59+
.nthBuild(0)
60+
.goToBuild()
61+
.goToPipelineOverview()
62+
.replay();
63+
64+
String newUrl = p.url();
65+
String targetUrl = j.getURL() + run.getUrl() + "replay/";
66+
assertEquals(targetUrl, newUrl);
67+
}
68+
69+
@Issue("GH#617")
70+
@Test
71+
@ConfiguredWithCode("configure-appearance.yml")
72+
void rebuildButtonRedirectsForParameterizedJob(Page p, JenkinsConfiguredWithCodeRule j) throws Exception {
3773
WorkflowRun run = TestUtils.createAndRunJob(
3874
j, "echo_parameterized", "gh330_parameterizedBuild.jenkinsfile", Result.SUCCESS);
3975

40-
startBuildAndAssertPageIsTheSame(j, run);
41-
}
76+
new PipelineJobPage(p, run.getParent())
77+
.goTo()
78+
.hasBuilds(1)
79+
.nthBuild(0)
80+
.goToBuild()
81+
.goToPipelineOverview()
82+
.rebuild();
4283

43-
private static void startBuildAndAssertPageIsTheSame(JenkinsRule j, WorkflowRun run) throws Exception {
44-
try (Playwright playwright = Playwright.create();
45-
Browser browser = playwright.chromium().launch()) {
46-
Page page = browser.newPage();
47-
String urlName = new PipelineConsoleViewAction(run).getUrlName();
48-
Response navigate = page.navigate(j.getURL() + run.getUrl() + urlName);
49-
String currentUrl = navigate.url();
84+
String newUrl = p.url();
85+
String targetUrl = j.getURL() + run.getUrl() + "rebuild/parameterized";
86+
assertEquals(targetUrl, newUrl);
87+
}
5088

51-
page.click("#pgv-rebuild");
52-
String newUrl = page.url();
89+
@Test
90+
@ConfiguredWithCode("configure-appearance.yml")
91+
void restartFromStageButtonRedirects(Page p, JenkinsConfiguredWithCodeRule j) throws Exception {
92+
WorkflowRun run = TestUtils.createAndRunJob(
93+
j, "hello_world", "helloWorldDeclarativePipeline.jenkinsfile", Result.SUCCESS);
5394

54-
assertEquals(currentUrl, newUrl);
95+
new PipelineJobPage(p, run.getParent())
96+
.goTo()
97+
.hasBuilds(1)
98+
.nthBuild(0)
99+
.goToBuild()
100+
.goToPipelineOverview()
101+
.restartFromStage();
55102

56-
waitUntilBuildIsComplete(j, run);
57-
}
103+
String newUrl = p.url();
104+
String targetUrl = j.getURL() + run.getUrl() + "restart/";
105+
assertEquals(targetUrl, newUrl);
58106
}
59107

60108
// We don't care about the build result but Windows fails with
61109
// file locking issues if we don't wait for the build to finish.
62-
private static void waitUntilBuildIsComplete(JenkinsRule j, WorkflowRun run) {
110+
private static void waitUntilBuildIsComplete(JenkinsConfiguredWithCodeRule j, WorkflowRun run) {
63111
await().until(() -> j.jenkins.getQueue().isEmpty(), is(true));
64112
WorkflowJob parent = run.getParent();
65113
await().until(() -> parent.getBuilds().size(), is(2));

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

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -139,4 +139,31 @@ public PipelineOverviewPage cancel() {
139139
.click();
140140
return this;
141141
}
142+
143+
public PipelineOverviewPage rerun() {
144+
page.getByRole(AriaRole.BUTTON, new Page.GetByRoleOptions().setName("Rerun"))
145+
.click();
146+
return this;
147+
}
148+
149+
public PipelineOverviewPage replay() {
150+
page.click("#pgv-rerun-overflow");
151+
page.getByRole(AriaRole.LINK, new Page.GetByRoleOptions().setName("Replay"))
152+
.click();
153+
return this;
154+
}
155+
156+
public PipelineOverviewPage rebuild() {
157+
page.click("#pgv-rerun-overflow");
158+
page.getByRole(AriaRole.LINK, new Page.GetByRoleOptions().setName("Rebuild"))
159+
.click();
160+
return this;
161+
}
162+
163+
public PipelineOverviewPage restartFromStage() {
164+
page.click("#pgv-rerun-overflow");
165+
page.getByRole(AriaRole.LINK, new Page.GetByRoleOptions().setName("Restart from Stage"))
166+
.click();
167+
return this;
168+
}
142169
}
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
pipeline {
2+
agent none
3+
stages {
4+
stage("Say Hello") {
5+
steps {
6+
echo("Hello, World!")
7+
}
8+
}
9+
}
10+
}

0 commit comments

Comments
 (0)