Skip to content

Commit

Permalink
JENKINS-28708 Allow aborting builds from pipeline views
Browse files Browse the repository at this point in the history
  • Loading branch information
tommysdk committed Nov 20, 2018
1 parent ec00b37 commit 19f76af
Show file tree
Hide file tree
Showing 20 changed files with 269 additions and 326 deletions.
24 changes: 24 additions & 0 deletions src/main/java/se/diabol/jenkins/pipeline/DeliveryPipelineView.java
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,7 @@ public class DeliveryPipelineView extends View implements PipelineView {
private boolean showTotalBuildTime = false;
private boolean allowRebuild = false;
private boolean allowPipelineStart = false;
private boolean allowAbort = false;
private boolean showDescription = false;
private boolean showPromotions = false;
private boolean showTestResults = false;
Expand Down Expand Up @@ -211,6 +212,15 @@ public void setAllowPipelineStart(boolean allowPipelineStart) {
this.allowPipelineStart = allowPipelineStart;
}

@Exported
public boolean isAllowAbort() {
return allowAbort;
}

public void setAllowAbort(boolean allowAbort) {
this.allowAbort = allowAbort;
}

@Exported
public boolean isAllowManualTriggers() {
return allowManualTriggers;
Expand Down Expand Up @@ -482,6 +492,20 @@ public void triggerRebuild(String projectName, String buildId) {
build.getAction(ParametersAction.class));
}

@Override
public void abortBuild(String projectName, String buildId) throws TriggerException {
AbstractProject project = ProjectUtil.getProject(projectName, Jenkins.getInstance());
if (!project.hasPermission(Item.CANCEL)) {
throw new BadCredentialsException("Not authorized to abort build");
}
AbstractBuild build = project.getBuildByNumber(Integer.parseInt(buildId));
try {
build.doStop();
} catch (IOException | ServletException e) {
throw new TriggerException("Could not abort build");
}
}

protected static String triggerExceptionMessage(final String projectName, final String upstreamName,
final String buildId) {
String message = "Could not trigger manual build " + projectName + " for upstream " + upstreamName
Expand Down
22 changes: 20 additions & 2 deletions src/main/java/se/diabol/jenkins/pipeline/PipelineApi.java
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ public void doManualStep(StaplerRequest request,
StaplerResponse response,
@QueryParameter String project,
@QueryParameter String upstream,
@QueryParameter String buildId) throws IOException, ServletException {
@QueryParameter String buildId) {
if (project != null && upstream != null && buildId != null) {
try {
view.triggerManual(project, upstream, buildId);
Expand All @@ -65,7 +65,7 @@ public void doManualStep(StaplerRequest request,
public void doRebuildStep(StaplerRequest request,
StaplerResponse response,
@QueryParameter String project,
@QueryParameter String buildId) throws IOException, ServletException {
@QueryParameter String buildId) {
if (project != null && buildId != null) {
try {
view.triggerRebuild(project, buildId);
Expand All @@ -87,4 +87,22 @@ public void doInputStep(StaplerRequest request,
doManualStep(request, response, project, upstream, buildId);
}

@SuppressWarnings("UnusedDeclaration")
public void doAbortBuild(StaplerRequest request,
StaplerResponse response,
@QueryParameter String project,
@QueryParameter String buildId) {
if (project != null && buildId != null) {
try {
view.abortBuild(project, buildId);
} catch (AuthenticationException e) {
response.setStatus(SC_FORBIDDEN);
} catch (TriggerException e) {
response.setStatus(SC_NOT_ACCEPTABLE);
}
} else {
response.setStatus(SC_NOT_ACCEPTABLE);
}
}

}
2 changes: 2 additions & 0 deletions src/main/java/se/diabol/jenkins/pipeline/PipelineView.java
Original file line number Diff line number Diff line change
Expand Up @@ -26,4 +26,6 @@ void triggerManual(String projectName, String upstreamName, String buildId)
throws TriggerException, AuthenticationException;

void triggerRebuild(String projectName, String buildId);

void abortBuild(String projectName, String buildId) throws TriggerException, AuthenticationException;
}
32 changes: 30 additions & 2 deletions src/main/java/se/diabol/jenkins/workflow/WorkflowPipelineView.java
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@
import hudson.util.RunList;
import jenkins.model.Jenkins;
import org.acegisecurity.AuthenticationException;
import org.acegisecurity.BadCredentialsException;
import org.jenkinsci.plugins.workflow.job.WorkflowJob;
import org.jenkinsci.plugins.workflow.job.WorkflowRun;
import org.jenkinsci.plugins.workflow.support.steps.input.InputAction;
Expand Down Expand Up @@ -64,6 +65,7 @@
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import java.util.Optional;
import java.util.Set;
import java.util.logging.Level;
import java.util.logging.Logger;
Expand All @@ -85,6 +87,7 @@ public class WorkflowPipelineView extends View implements PipelineView {
private int noOfColumns = 1;
private String sorting = NONE_SORTER;
private boolean allowPipelineStart = false;
private boolean allowAbort = false;
private boolean showChanges = false;
private String theme = DEFAULT_THEME;
private int maxNumberOfVisiblePipelines = -1;
Expand Down Expand Up @@ -152,6 +155,15 @@ public void setAllowPipelineStart(boolean allowPipelineStart) {
this.allowPipelineStart = allowPipelineStart;
}

@Exported
public boolean isAllowAbort() {
return allowAbort;
}

public void setAllowAbort(boolean allowAbort) {
this.allowAbort = allowAbort;
}

public boolean isShowChanges() {
return showChanges;
}
Expand Down Expand Up @@ -303,8 +315,7 @@ public Item doCreateItem(StaplerRequest req, StaplerResponse rsp) throws IOExcep
}

@Override
public void triggerManual(String projectName, String upstreamName, String buildId)
throws TriggerException, AuthenticationException {
public void triggerManual(String projectName, String upstreamName, String buildId) throws AuthenticationException {
LOG.fine("Manual/Input step called for project: " + projectName + " and build id: " + buildId);

WorkflowJob workflowJob;
Expand All @@ -329,6 +340,23 @@ public void triggerRebuild(String projectName, String buildId) {
LOG.log(Level.SEVERE, "Rebuild not implemented for workflow/pipeline projects");
}

@Override
public void abortBuild(String projectName, String buildId) throws TriggerException {
try {
WorkflowJob workflowJob = ProjectUtil.getWorkflowJob(projectName, getOwnerItemGroup());
if (!workflowJob.hasAbortPermission()) {
throw new BadCredentialsException("Not authorized to abort build");
}
RunList<WorkflowRun> builds = workflowJob.getBuilds();
Optional<WorkflowRun> run = builds.stream()
.filter(r -> Integer.toString(r.getNumber()).equals(buildId))
.findFirst();
run.ifPresent(WorkflowRun::doStop);
} catch (PipelineException e) {
throw new TriggerException("Could not abort build");
}
}

@Override
public Collection<TopLevelItem> getItems() {
Set<TopLevelItem> jobs = Sets.newHashSet();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,10 @@
<f:checkbox/>
</f:entry>

<f:entry title="Allow cancelling pipeline builds" field="allowAbort">
<f:checkbox/>
</f:entry>

<f:entry title="Show avatars" field="showAvatars">
<f:checkbox/>
</f:entry>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
<div>
Allow cancelling a running job from the delivery pipeline view.
</div>
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,9 @@
<f:entry title="Enable start of new pipeline build" field="allowPipelineStart">
<f:checkbox/>
</f:entry>
<f:entry title="Allow cancelling pipeline builds" field="allowAbort">
<f:checkbox/>
</f:entry>
<f:entry title="Show commit messages" field="showChanges">
<f:checkbox/>
</f:entry>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
<div>
Allow cancelling a running job from the delivery pipeline view.
</div>
45 changes: 45 additions & 0 deletions src/main/webapp/pipe.js
Original file line number Diff line number Diff line change
Expand Up @@ -171,6 +171,14 @@ function pipelineUtils() {
consoleLogLink = 'console';
}

var showAbortButton = false;
if (data.allowAbort) {
progressClass += ' task-abortable';
if (progressClass.indexOf('task-progress-running') !== -1) {
showAbortButton = true;
}
}

html.push(
'<div id="' + id + '" class="status stage-task ' + task.status.type + '">'
+ '<div class="task-progress ' + progressClass + '" style="width: ' + progress + '%">'
Expand All @@ -190,9 +198,18 @@ function pipelineUtils() {
html.push('</div>');
}
if (task.requiringInput) {
showAbortButton = true;
html.push('<div class="task-manual" id="input-' + id + '" title="Specify input" onclick="specifyInput(\'' + id + '\', \'' + component.fullJobName + '\', \'' + task.buildId + '\', \'' + view.viewUrl + '\')">');
html.push('</div>');
}
if (showAbortButton) {
var projectName = component.fullJobName;
if (typeof projectName === "undefined") {
projectName = task.id;
}
html.push('<div class="task-abort" id="abort-' + id + '" title="Abort progress" onclick="abortBuild(\'' + id + '\', \'' + projectName + '\', \'' + task.buildId + '\', \'' + view.viewUrl + '\')">');
html.push('</div>');
}
}

html.push('</div><div class="task-details">');
Expand Down Expand Up @@ -660,6 +677,34 @@ function specifyInput(taskId, project, buildId, viewUrl) {
});
}

function abortBuild(taskId, project, buildId, viewUrl) {
Q('#abort-' + taskId).hide();
var formData = {project: project, upstream: 'N/A', buildId: buildId}, before;

var before;
if (crumb.value !== null && crumb.value !== '') {
console.info('Crumb found and will be added to request header');
before = function(xhr){xhr.setRequestHeader(crumb.fieldName, crumb.value);}
} else {
console.info('Crumb not needed');
before = function(xhr){}
}

Q.ajax({
url: rootURL + '/' + viewUrl + 'api/abortBuild',
type: 'POST',
data: formData,
beforeSend: before,
timeout: 20000,
success: function (data, textStatus, jqXHR) {
console.info('Successfully aborted build of ' + project + '!')
},
error: function (jqXHR, textStatus, errorThrown) {
window.alert('Could not abort build! error: ' + errorThrown + ' status: ' + textStatus)
}
});
}

function triggerParameterizedBuild(url, taskId) {
console.info('Job is parameterized');
window.location.href = rootURL + '/' + url + 'build?delay=0sec';
Expand Down
Binary file added src/main/webapp/themes/contrast/abort.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
24 changes: 22 additions & 2 deletions src/main/webapp/themes/contrast/pipeline-common.css
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,7 @@ div.stage a:visited {
}

div.stage-task {
position: relative;
white-space: nowrap;
margin: 2px 2px 2px 2px;
}
Expand Down Expand Up @@ -171,7 +172,22 @@ div.task-rebuild {
float: right;
padding-right: 5px;
z-index: 100;
}

div.task-abort {
display: table-cell;
background-color: #808080;
background: url("abort.png") no-repeat;
background-size: 100%;
position: absolute;
bottom: 6px;
right: 3px;
cursor: pointer;
height: 14px;
width: 14px;
float: right;
margin-left: 5px;
z-index: 100;
}

.timestamp {
Expand All @@ -183,6 +199,10 @@ div.task-rebuild {
padding-left: 20px;
}

.task-abortable .duration {
padding-right: 20px;
}

div.task-progress-running {
background-color: lightskyblue;
animation-duration: 1s;
Expand Down Expand Up @@ -398,7 +418,7 @@ div.aggregatedChangesPanelInner > ul {
.pagination {
display:block;
text-align:left;
clear:both;
clear:both;
font-family:Arial, Helvetica, sans-serif;
font-size:14px;
font-weight:normal;
Expand All @@ -420,7 +440,7 @@ div.aggregatedChangesPanelInner > ul {
.pagination a:hover {
background-color:#DDEEFF;
border:1px solid #BBDDFF;
color:#0072BC;
color:#0072BC;
}

.pagination .active_link a {
Expand Down
9 changes: 9 additions & 0 deletions src/main/webapp/themes/contrast/pipeline-fullscreen.css
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,15 @@ div.task-progress {
min-height: 36px;
}

div.task-abort {
bottom: 4px;
}

div.task-details {
padding-top: 5px;
}


.stage-name {
padding: 10px 10px 10px 10px;
}
Expand Down
Binary file added src/main/webapp/themes/default/abort.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
21 changes: 21 additions & 0 deletions src/main/webapp/themes/default/pipeline-common.css
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,7 @@ div.stage a:visited {
}

div.stage-task {
position: relative;
white-space: nowrap;
margin: 2px 2px 2px 2px;
}
Expand Down Expand Up @@ -173,6 +174,22 @@ div.task-rebuild {
z-index: 100;
}

div.task-abort {
display: table-cell;
background-color: #808080;
background: url("abort.png") no-repeat;
background-size: 100%;
position: absolute;
bottom: 6px;
right: 3px;
cursor: pointer;
height: 14px;
width: 14px;
float: right;
margin-left: 5px;
z-index: 100;
}

.timestamp {
display: table-cell;
}
Expand All @@ -182,6 +199,10 @@ div.task-rebuild {
padding-left: 20px;
}

.task-abortable .duration {
padding-right: 20px;
}

div.task-progress-running {
background-color: lightskyblue;
animation-duration: 1s;
Expand Down
8 changes: 8 additions & 0 deletions src/main/webapp/themes/default/pipeline-fullscreen.css
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,14 @@ div.task-progress {
min-height: 36px;
}

div.task-abort {
bottom: 4px;
}

div.task-details {
padding-top: 5px;
}

.stage-name {
padding: 10px 10px 10px 10px;
}
Expand Down
Binary file added src/main/webapp/themes/overview/abort.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading

0 comments on commit 19f76af

Please sign in to comment.