Skip to content

Commit

Permalink
Output pipeline summary
Browse files Browse the repository at this point in the history
  • Loading branch information
mrginglymus committed Dec 30, 2020
1 parent 460f8e1 commit e8595dd
Show file tree
Hide file tree
Showing 3 changed files with 209 additions and 93 deletions.
12 changes: 9 additions & 3 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -63,13 +63,19 @@

<dependency>
<groupId>org.jenkins-ci.plugins.workflow</groupId>
<artifactId>workflow-step-api</artifactId>
<artifactId>workflow-job</artifactId>
</dependency>
<dependency>
<groupId>io.jenkins.blueocean</groupId>
<artifactId>blueocean-pipeline-api-impl</artifactId>
<version>1.24.3</version>
</dependency>

<dependency>
<groupId>org.jenkins-ci.plugins.workflow</groupId>
<artifactId>workflow-job</artifactId>
<scope>test</scope>
<artifactId>workflow-step-api</artifactId>
</dependency>

<dependency>
<groupId>org.jenkins-ci.plugins.workflow</groupId>
<artifactId>workflow-cps</artifactId>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,29 +1,39 @@
package io.jenkins.plugins.checks.status;

import java.io.File;
import java.util.Optional;

import edu.umd.cs.findbugs.annotations.CheckForNull;

import hudson.Extension;
import hudson.FilePath;
import hudson.model.Job;
import hudson.model.Queue;
import hudson.model.Result;
import hudson.model.Run;
import hudson.model.TaskListener;
import hudson.model.listeners.RunListener;
import hudson.model.listeners.SCMListener;
import hudson.model.queue.QueueListener;
import hudson.scm.SCM;
import hudson.scm.SCMRevisionState;

import io.jenkins.blueocean.rest.hal.Link;
import io.jenkins.blueocean.rest.hal.LinkResolver;
import io.jenkins.blueocean.rest.impl.pipeline.PipelineNodeUtil;
import io.jenkins.plugins.checks.api.ChecksConclusion;
import io.jenkins.plugins.checks.api.ChecksDetails.ChecksDetailsBuilder;
import io.jenkins.plugins.checks.api.ChecksOutput;
import io.jenkins.plugins.checks.api.ChecksPublisher;
import io.jenkins.plugins.checks.api.ChecksPublisherFactory;
import io.jenkins.plugins.checks.api.ChecksStatus;
import io.jenkins.plugins.util.JenkinsFacade;
import org.apache.commons.lang3.StringUtils;
import org.jenkinsci.plugins.displayurlapi.DisplayURLProvider;
import org.jenkinsci.plugins.workflow.actions.ErrorAction;
import org.jenkinsci.plugins.workflow.actions.ThreadNameAction;
import org.jenkinsci.plugins.workflow.actions.WarningAction;
import org.jenkinsci.plugins.workflow.flow.GraphListener;
import org.jenkinsci.plugins.workflow.graph.FlowNode;
import org.jenkinsci.plugins.workflow.job.views.FlowGraphTableAction;
import org.jenkinsci.plugins.workflow.support.visualization.table.FlowGraphTable;

import java.io.IOException;
import java.util.Collections;
import java.util.Optional;
import java.util.Stack;
import java.util.stream.Stream;

/**
* A publisher which publishes different statuses through the checks API based on the stage of the {@link Queue.Item}
Expand All @@ -34,12 +44,19 @@ public final class BuildStatusChecksPublisher {
private static final AbstractStatusChecksProperties DEFAULT_PROPERTIES = new DefaultStatusCheckProperties();

private static void publish(final ChecksPublisher publisher, final ChecksStatus status,
final ChecksConclusion conclusion, final String name) {
publisher.publish(new ChecksDetailsBuilder()
final ChecksConclusion conclusion, final String name, final String outputSummary) {
ChecksDetailsBuilder builder = new ChecksDetailsBuilder()
.withName(name)
.withStatus(status)
.withConclusion(conclusion)
.build());
.withConclusion(conclusion);

if (StringUtils.isNotEmpty(outputSummary)) {
builder.withOutput(new ChecksOutput.ChecksOutputBuilder()
.withTitle(name)
.withSummary(outputSummary)
.build());
}
publisher.publish(builder.build());
}

@Deprecated
Expand Down Expand Up @@ -80,59 +97,28 @@ public void onEnterWaiting(final Queue.WaitingItem wi) {
return;
}

final Job job = (Job)wi.task;
Optional<StatusChecksProperties> deprecatedProperties = findDeprecatedProperties(job);
if (deprecatedProperties.isPresent()) {
if (!deprecatedProperties.get().isSkip(job)) {
publish(ChecksPublisherFactory.fromJob(job, TaskListener.NULL), ChecksStatus.QUEUED,
ChecksConclusion.NONE, deprecatedProperties.get().getName(job));
}
}
else {
final AbstractStatusChecksProperties properties = findProperties(job);
if (!properties.isSkipped(job)) {
publish(ChecksPublisherFactory.fromJob(job, TaskListener.NULL), ChecksStatus.QUEUED,
ChecksConclusion.NONE, properties.getName(job));
}
}
final Job job = (Job) wi.task;
getChecksName(job).ifPresent(checksName -> publish(ChecksPublisherFactory.fromJob(job, TaskListener.NULL),
ChecksStatus.QUEUED, ChecksConclusion.NONE, checksName, null));
}
}

/**
* {@inheritDoc}
*
* <p>
* Listens to the SCM checkout and publishes checks.
* </p>
*/
@Extension
public static class JobCheckoutListener extends SCMListener {
/**
* {@inheritDoc}
* <p>
* When checkout finished, update the check to "in progress".
* </p>
*/
@Override
public void onCheckout(final Run<?, ?> run, final SCM scm, final FilePath workspace,
final TaskListener listener, @CheckForNull final File changelogFile,
@CheckForNull final SCMRevisionState pollingBaseline) {
final Job job = run.getParent();
final Optional<StatusChecksProperties> deprecatedProperties = findDeprecatedProperties(job);
if (deprecatedProperties.isPresent()) {
if (!deprecatedProperties.get().isSkip(job)) {
publish(ChecksPublisherFactory.fromRun(run, listener), ChecksStatus.IN_PROGRESS,
ChecksConclusion.NONE, deprecatedProperties.get().getName(job));
}
}
else {
final AbstractStatusChecksProperties properties = findProperties(job);
if (!properties.isSkipped(job)) {
publish(ChecksPublisherFactory.fromRun(run, listener), ChecksStatus.IN_PROGRESS,
ChecksConclusion.NONE, properties.getName(job));
}
}
}
protected static Optional<String> getChecksName(Run<?, ?> run) {
return getChecksName(run.getParent());
}

protected static Optional<String> getChecksName(Job<?, ?> job) {
return Stream.of(
findDeprecatedProperties(job)
.filter(p -> !p.isSkip(job))
.map(p -> p.getName(job)),
Optional.of(findProperties(job))
.filter(p -> !p.isSkipped(job))
.map(p -> p.getName(job))
)
.filter(Optional::isPresent)
.map(Optional::get)
.findFirst();
}

/**
Expand All @@ -153,21 +139,8 @@ public static class JobCompletedListener extends RunListener<Run<?, ?>> {
*/
@Override
public void onCompleted(final Run run, @CheckForNull final TaskListener listener) {
final Job job = run.getParent();
final Optional<StatusChecksProperties> deprecatedProperties = findDeprecatedProperties(job);
if (deprecatedProperties.isPresent()) {
if (!deprecatedProperties.get().isSkip(job)) {
publish(ChecksPublisherFactory.fromRun(run, listener), ChecksStatus.COMPLETED,
extractConclusion(run), deprecatedProperties.get().getName(job));
}
}
else {
final AbstractStatusChecksProperties properties = findProperties(job);
if (!properties.isSkipped(job)) {
publish(ChecksPublisherFactory.fromRun(run, listener), ChecksStatus.COMPLETED,
extractConclusion(run), properties.getName(job));
}
}
getChecksName(run).ifPresent(checksName -> publish(ChecksPublisherFactory.fromRun(run, listener),
ChecksStatus.COMPLETED, extractConclusion(run), checksName, getOutputSummary(run)));
}

private ChecksConclusion extractConclusion(final Run<?, ?> run) {
Expand All @@ -178,19 +151,110 @@ private ChecksConclusion extractConclusion(final Run<?, ?> run) {

if (result.isBetterOrEqualTo(Result.SUCCESS)) {
return ChecksConclusion.SUCCESS;
}
else if (result.isBetterOrEqualTo(Result.UNSTABLE) || result.isBetterOrEqualTo(Result.FAILURE)) {
} else if (result.isBetterOrEqualTo(Result.UNSTABLE) || result.isBetterOrEqualTo(Result.FAILURE)) {
return ChecksConclusion.FAILURE;
}
else if (result.isBetterOrEqualTo(Result.NOT_BUILT)) {
} else if (result.isBetterOrEqualTo(Result.NOT_BUILT)) {
return ChecksConclusion.SKIPPED;
}
else if (result.isBetterOrEqualTo(Result.ABORTED)) {
} else if (result.isBetterOrEqualTo(Result.ABORTED)) {
return ChecksConclusion.CANCELED;
}
else {
} else {
throw new IllegalStateException("Unsupported run result: " + result);
}
}
}

protected static String getOutputSummary(Run<?, ?> run) {
FlowGraphTableAction flowGraphTableAction = run.getAction(FlowGraphTableAction.class);

if (flowGraphTableAction == null) {
return null;
}

FlowGraphTable table = flowGraphTableAction.getFlowGraph();

Stack<Integer> indentationStack = new Stack<>();

StringBuilder builder = new StringBuilder();

String urlRoot = DisplayURLProvider.getDefault().getRoot();

table.getRows()
.forEach(row -> {
boolean isStage = PipelineNodeUtil.isStage(row.getNode());
boolean isParallel = PipelineNodeUtil.isParallelBranch(row.getNode());
ErrorAction errorAction = row.getNode().getError();
WarningAction warningAction = row.getNode().getPersistentAction(WarningAction.class);

if (!isStage &&
!isParallel &&
errorAction == null &&
warningAction == null) {
return;
}

if (isStage || isParallel) {
while (!indentationStack.isEmpty() && row.getTreeDepth() < indentationStack.peek()) {
indentationStack.pop();
}
if (indentationStack.isEmpty() || row.getTreeDepth() > indentationStack.peek()) {
indentationStack.push(row.getTreeDepth());
}
builder.append(String.join("", Collections.nCopies(indentationStack.size(), " ")));
builder.append("* ");

if (isParallel) {
builder.append(row.getNode().getAction(ThreadNameAction.class).getThreadName());
} else {
String displayName = row.getNode().getDisplayName();
Optional<Link> link = Optional.ofNullable(LinkResolver.resolveLink(row.getNode()));
if (link.isPresent()) {
builder.append(String.format("[%s](%s%s)", displayName, urlRoot, link.get()));
} else {
builder.append(displayName);
}
}

if (row.getNode().isActive()) {
builder.append(" *(running)*");
} else if (row.getDurationMillis() > 0) {
builder.append(String.format(" *(%s)*", row.getDurationString()));
}
} else {
builder.append("\n");
builder.append(String.join("", Collections.nCopies(indentationStack.size() + 1, " ")));
if (errorAction != null) {
builder.append(String.format("**Error**: *%s*", errorAction.getError()));
} else {
builder.append(String.format("**Unstable**: *%s*", warningAction.getMessage()));
}
}
builder.append("\n");

});
return builder.toString();
}

/**
* {@inheritDoc}
*/
@Extension
public static class ChecksGraphListener implements GraphListener.Synchronous {
@Override
public void onNewHead(FlowNode node) {
if (!PipelineNodeUtil.isStage(node) && !PipelineNodeUtil.isParallelBranch(node)) {
return;
}

Run<?, ?> run;
try {
run = (Run) node.getExecution().getOwner().getExecutable();
} catch (IOException e) {
return;
}

getChecksName(run).ifPresent(checksName -> publish(ChecksPublisherFactory.fromRun(run, TaskListener.NULL),
ChecksStatus.IN_PROGRESS, ChecksConclusion.NONE, checksName, getOutputSummary(run)));

}
}
}
Loading

0 comments on commit e8595dd

Please sign in to comment.