Skip to content

Commit 0f7702e

Browse files
authored
Redirect to new build on rebuild (#854)
1 parent 8a7f439 commit 0f7702e

File tree

5 files changed

+121
-37
lines changed

5 files changed

+121
-37
lines changed

src/main/java/io/jenkins/plugins/pipelinegraphview/consoleview/PipelineConsoleViewAction.java

Lines changed: 65 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
import com.fasterxml.jackson.core.JsonProcessingException;
44
import com.fasterxml.jackson.databind.ObjectMapper;
5+
import edu.umd.cs.findbugs.annotations.CheckForNull;
56
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
67
import hudson.Plugin;
78
import hudson.console.AnnotatedLargeText;
@@ -34,7 +35,6 @@
3435
import java.util.ArrayList;
3536
import java.util.HashMap;
3637
import java.util.List;
37-
import java.util.concurrent.ExecutionException;
3838
import jenkins.model.Jenkins;
3939
import net.sf.json.JSONObject;
4040
import org.jenkins.ui.icon.IconSpec;
@@ -364,40 +364,81 @@ public boolean isRestartFromStageAvailable() {
364364
*/
365365
@RequirePOST
366366
@JavaScriptMethod
367-
public boolean doRerun() throws IOException, ExecutionException {
368-
if (run != null) {
369-
run.checkPermission(Item.BUILD);
370-
ReplayAction replayAction = run.getAction(ReplayAction.class);
371-
Queue.Item item =
372-
replayAction.run2(replayAction.getOriginalScript(), replayAction.getOriginalLoadedScripts());
367+
public HttpResponse doRerun() {
368+
if (run == null) {
369+
return HttpResponses.errorJSON(Messages.scheduled_failure());
370+
}
371+
run.checkPermission(Item.BUILD);
372+
373+
if (!run.getParent().isBuildable()) {
374+
return HttpResponses.errorJSON(Messages.scheduled_failure());
375+
}
376+
ReplayAction replayAction = run.getAction(ReplayAction.class);
377+
Queue.Item item = replayAction.run2(replayAction.getOriginalScript(), replayAction.getOriginalLoadedScripts());
378+
379+
if (item == null) {
380+
return HttpResponses.errorJSON(Messages.scheduled_failure());
381+
}
373382

374-
if (item == null) {
375-
return false;
383+
JSONObject obj = new JSONObject();
384+
obj.put("message", Messages.scheduled_success());
385+
obj.put("queueId", item.getId());
386+
return HttpResponses.okJSON(obj);
387+
}
388+
389+
@SuppressWarnings("unused")
390+
@GET
391+
@WebMethod(name = "nextBuild")
392+
public HttpResponse hasNextBuild(StaplerRequest2 req) {
393+
if (run == null) {
394+
return HttpResponses.errorJSON("No run to check for next build");
395+
}
396+
run.checkPermission(Item.READ);
397+
String queueId = req.getParameter("queueId");
398+
if (queueId == null || queueId.isBlank()) {
399+
return HttpResponses.errorJSON("No queueId provided");
400+
}
401+
long id = Long.parseLong(queueId);
402+
logger.debug("Searching for build with queueId: {}", id);
403+
WorkflowRun nextRun = findBuildByQueueId(id);
404+
if (nextRun == null) {
405+
return HttpResponses.okJSON();
406+
}
407+
408+
JSONObject obj = new JSONObject();
409+
obj.put("nextBuildUrl", nextRun.getUrl() + URL_NAME + "/");
410+
return HttpResponses.okJSON(obj);
411+
}
412+
413+
private @CheckForNull WorkflowRun findBuildByQueueId(long queueId) {
414+
for (WorkflowRun build : run.getParent().getBuilds()) {
415+
if (build.getNumber() <= this.run.getNumber()) {
416+
break;
417+
}
418+
if (build.getQueueId() == queueId) {
419+
return build;
376420
}
377-
return true;
378421
}
379-
return false;
422+
return null;
380423
}
381424

382425
/**
383426
* Handles the cancel request.
384427
*/
385428
@RequirePOST
386429
@JavaScriptMethod
387-
public HttpResponse doCancel() throws IOException, ExecutionException {
388-
if (run != null) {
389-
run.checkPermission(getCancelPermission());
390-
if (run.isBuilding()) {
391-
run.doStop();
392-
return HttpResponses.okJSON();
393-
} else {
394-
String message = Result.ABORTED.equals(run.getResult())
395-
? Messages.run_alreadyCancelled()
396-
: Messages.run_isFinished();
397-
return HttpResponses.errorJSON(message);
398-
}
430+
public HttpResponse doCancel() {
431+
if (run == null) {
432+
return HttpResponses.errorJSON("No run to cancel");
433+
}
434+
run.checkPermission(getCancelPermission());
435+
if (run.isBuilding()) {
436+
run.doStop();
437+
return HttpResponses.okJSON();
399438
}
400-
return HttpResponses.errorJSON("No run to cancel");
439+
String message =
440+
Result.ABORTED.equals(run.getResult()) ? Messages.run_alreadyCancelled() : Messages.run_isFinished();
441+
return HttpResponses.errorJSON(message);
401442
}
402443

403444
public String getFullProjectDisplayName() {

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

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,9 @@ FlowNodeWrapper.noStage=System Generated
3434
run.alreadyCancelled=Run was already cancelled
3535
run.isFinished=Run is already finished
3636

37+
scheduled.success=Build scheduled
38+
scheduled.failure=Could not schedule a build
39+
3740
# Settings
3841
settings=Settings
3942
settings.showStageName=Show stage names

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@
2626
<j:set var="proxyId" value="${h.generateId()}" />
2727
<st:bind value="${it}" var="rerunAction${proxyId}"/>
2828
<div class="jenkins-split-button">
29-
<button id="pgv-rerun" data-success-message="${%Build scheduled}"
29+
<button id="pgv-rerun"
3030
data-proxy-name="rerunAction${proxyId}"
3131
class="jenkins-button jenkins-!-build-color">
3232
<div class="jenkins-dropdown__item__icon">

src/main/webapp/js/build.js

Lines changed: 43 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,52 @@
1+
/* global notificationBar */
2+
13
const rerunButton = document.getElementById("pgv-rerun");
24

35
if (rerunButton) {
46
rerunButton.addEventListener("click", (event) => {
57
event.preventDefault();
8+
9+
async function redirectToNextBuild(queueId) {
10+
while (true) {
11+
try {
12+
const response = await fetch(`nextBuild?queueId=${queueId}`);
13+
if (!response.ok) {
14+
throw new Error(
15+
`Failed to fetch next build data: ${response.status} - ${response.statusText}`,
16+
);
17+
}
18+
const { status, data, message } = await response.json();
19+
if (status === "ok") {
20+
if (data?.nextBuildUrl) {
21+
let root = document.querySelector("head").dataset.rooturl;
22+
if (!root.endsWith("/")) {
23+
root += "/";
24+
}
25+
window.location = `${root}${data.nextBuildUrl}`;
26+
break;
27+
}
28+
} else {
29+
console.warn("Error in next build response:", message);
30+
}
31+
} catch (error) {
32+
console.error("Error fetching next build data:", error);
33+
}
34+
await new Promise((resolve) => setTimeout(resolve, 1000));
35+
}
36+
}
37+
638
const rerunAction = window[`${rerunButton.dataset.proxyName}`];
7-
rerunAction.doRerun(function (success) {
8-
const result = success.responseJSON;
9-
if (result) {
10-
notificationBar.show(
11-
rerunButton.dataset.successMessage,
12-
notificationBar.SUCCESS,
13-
);
39+
rerunAction.doRerun(async function (response) {
40+
const { status, data, message } = response.responseJSON;
41+
if (status === "ok") {
42+
notificationBar.show(data.message, notificationBar.SUCCESS);
43+
await redirectToNextBuild(data.queueId);
44+
} else {
45+
const failMessage =
46+
status === "error" && message
47+
? message
48+
: "Unknown error occurred while trying to rerun the build.";
49+
notificationBar.show(failMessage, notificationBar.WARNING);
1450
}
1551
});
1652
});

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

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
11
package io.jenkins.plugins.pipelinegraphview;
22

3+
import static com.microsoft.playwright.assertions.PlaywrightAssertions.assertThat;
34
import static org.awaitility.Awaitility.await;
45
import static org.hamcrest.CoreMatchers.is;
6+
import static org.hamcrest.Matchers.hasSize;
57
import static org.junit.jupiter.api.Assertions.assertEquals;
68

79
import com.microsoft.playwright.Page;
@@ -30,19 +32,21 @@ class PipelineGraphViewRebuildTest {
3032
void rerunButtonStartsNewBuild(Page p, JenkinsConfiguredWithCodeRule j) throws Exception {
3133
WorkflowRun run =
3234
TestUtils.createAndRunJob(j, "hello_world", "helloWorldScriptedPipeline.jenkinsfile", Result.SUCCESS);
35+
WorkflowJob job = run.getParent();
3336

34-
PipelineOverviewPage op = new PipelineJobPage(p, run.getParent())
37+
PipelineOverviewPage op = new PipelineJobPage(p, job)
3538
.goTo()
3639
.hasBuilds(1)
3740
.nthBuild(0)
3841
.goToBuild()
3942
.goToPipelineOverview();
4043

41-
String currentUrl = p.url();
44+
String jobUrl = j.getURL() + job.getUrl();
45+
assertThat(p).hasURL(jobUrl + "1/pipeline-overview/");
46+
4247
op.rerun();
4348

44-
String newUrl = p.url();
45-
assertEquals(currentUrl, newUrl);
49+
assertThat(p).hasURL(jobUrl + "2/pipeline-overview/");
4650

4751
waitUntilBuildIsComplete(j, run);
4852
}
@@ -110,7 +114,7 @@ void restartFromStageButtonRedirects(Page p, JenkinsConfiguredWithCodeRule j) th
110114
private static void waitUntilBuildIsComplete(JenkinsConfiguredWithCodeRule j, WorkflowRun run) {
111115
await().until(() -> j.jenkins.getQueue().isEmpty(), is(true));
112116
WorkflowJob parent = run.getParent();
113-
await().until(() -> parent.getBuilds().size(), is(2));
117+
await().until(parent::getBuilds, hasSize(2));
114118

115119
WorkflowRun lastBuild = parent.getLastBuild();
116120
await().until(() -> lastBuild, RunMatchers.completed());

0 commit comments

Comments
 (0)