Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add failure to title #76

Merged
merged 7 commits into from
Feb 6, 2021
Merged
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,20 +1,25 @@
package io.jenkins.plugins.checks.status;

import edu.umd.cs.findbugs.annotations.CheckForNull;
import edu.umd.cs.findbugs.annotations.NonNull;
import hudson.model.Result;
import hudson.model.Run;
import io.jenkins.plugins.checks.api.ChecksOutput;
import io.jenkins.plugins.checks.api.TruncatedString;
import org.apache.commons.collections.iterators.ReverseListIterator;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.tuple.Pair;
import org.jenkinsci.plugins.workflow.actions.*;
import org.jenkinsci.plugins.workflow.flow.FlowExecution;
import org.jenkinsci.plugins.workflow.graph.FlowNode;
import org.jenkinsci.plugins.workflow.graph.StepNode;
import org.jenkinsci.plugins.workflow.steps.StepDescriptor;
import org.jenkinsci.plugins.workflow.support.visualization.table.FlowGraphTable;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Optional;
@@ -137,6 +142,8 @@ ChecksOutput extractOutput() {
.withTruncationText(TRUNCATED_MESSAGE);
indentationStack.clear();

List<String> potentialTitles = new ArrayList<>();

table.getRows().forEach(row -> {
final FlowNode flowNode = row.getNode();

@@ -153,17 +160,85 @@ ChecksOutput extractOutput() {
final Pair<String, String> nodeInfo = stageOrBranchName.map(s -> processStageOrBranchRow(row, s))
.orElseGet(() -> processErrorOrWarningRow(row, errorAction, warningAction));

// the last title will be used in the ChecksOutput (if any are found)
if (!stageOrBranchName.isPresent()) {
potentialTitles.add(getPotentialTitle(flowNode, errorAction));
timja marked this conversation as resolved.
Show resolved Hide resolved
}

textBuilder.addText(nodeInfo.getLeft());
summaryBuilder.addText(nodeInfo.getRight());
});


String title = extractOutputTitle();
timja marked this conversation as resolved.
Show resolved Hide resolved
if (!potentialTitles.isEmpty()) {
title = potentialTitles.get(potentialTitles.size() - 1);
}

return new ChecksOutput.ChecksOutputBuilder()
.withTitle(extractOutputTitle())
.withTitle(title)
.withSummary(summaryBuilder.build())
.withText(textBuilder.build())
.build();
}

private String getPotentialTitle(FlowNode flowNode, ErrorAction errorAction) {
final String whereBuildFailed = String.format("%s in '%s' step", errorAction == null ? "warning" : "error", flowNode.getDisplayFunctionName());

List<FlowNode> enclosingStagesAndParallels = getEnclosingStagesAndParallels(flowNode);
List<String> enclosingBlockNames = getEnclosingBlockNames(enclosingStagesAndParallels);

return StringUtils.join(new ReverseListIterator(enclosingBlockNames), "/") + ": " + whereBuildFailed;
}

private static boolean isStageNode(@NonNull FlowNode node) {
if (node instanceof StepNode) {
StepDescriptor d = ((StepNode) node).getDescriptor();
return d != null && d.getFunctionName().equals("stage");
} else {
return false;
}
}

/**
* Get the stage and parallel branch start node IDs (not the body nodes) for this node, innermost first.
* @param node A flownode.
* @return A nonnull, possibly empty list of stage/parallel branch start nodes, innermost first.
*/
@NonNull
private static List<FlowNode> getEnclosingStagesAndParallels(FlowNode node) {
List<FlowNode> enclosingBlocks = new ArrayList<>();
for (FlowNode enclosing : node.getEnclosingBlocks()) {
if (enclosing != null && enclosing.getAction(LabelAction.class) != null) {
if (isStageNode(enclosing) ||
(enclosing.getAction(ThreadNameAction.class) != null)) {
enclosingBlocks.add(enclosing);
}
}
}

return enclosingBlocks;
}

@NonNull
private static List<String> getEnclosingBlockNames(@NonNull List<FlowNode> nodes) {
List<String> names = new ArrayList<>();
for (FlowNode n : nodes) {
ThreadNameAction threadNameAction = n.getPersistentAction(ThreadNameAction.class);
LabelAction labelAction = n.getPersistentAction(LabelAction.class);
if (threadNameAction != null) {
// If we're on a parallel branch with the same name as the previous (inner) node, that generally
// means we're in a Declarative parallel stages situation, so don't add the redundant branch name.
if (names.isEmpty() || !threadNameAction.getThreadName().equals(names.get(names.size()-1))) {
names.add(threadNameAction.getThreadName());
}
} else if (labelAction != null) {
names.add(labelAction.getDisplayName());
}
}
return names;
}

@CheckForNull
private static String getLog(final FlowNode flowNode) {
LogAction logAction = flowNode.getAction(LogAction.class);
@@ -174,7 +249,10 @@ private static String getLog(final FlowNode flowNode) {
if (logAction.getLogText().writeLogTo(0, out) == 0) {
return null;
}
return out.toString(StandardCharsets.UTF_8.toString());

String outputString = out.toString(StandardCharsets.UTF_8.toString());
// strip ansi color codes
return outputString.replaceAll("\u001B\\[[;\\d]*m", "");
}
catch (IOException e) {
LOGGER.log(Level.WARNING, String.format("Failed to extract logs for step '%s'", flowNode.getDisplayName()).replaceAll("[\r\n]", ""), e);
Original file line number Diff line number Diff line change
@@ -172,7 +172,7 @@ public void shouldPublishStageDetails() {
// Details 6, p1s1 has finished and emitted unstable
details = checksDetails.get(6);
assertThat(details.getOutput()).isPresent().get().satisfies(output -> {
assertThat(output.getTitle()).isPresent().get().isEqualTo("Unstable");
assertThat(output.getTitle()).isPresent().get().isEqualTo("In parallel/p1/p1s1: warning in 'unstable' step");
assertThat(output.getSummary()).isPresent().get().asString().isEqualToIgnoringNewLines(""
+ "### `In parallel / p1 / p1s1 / Set stage result to unstable`\n"
+ "Warning in `unstable` step, with arguments `something went wrong`.\n"
@@ -195,7 +195,7 @@ public void shouldPublishStageDetails() {
assertThat(details.getStatus()).isEqualTo(ChecksStatus.COMPLETED);
assertThat(details.getConclusion()).isEqualTo(ChecksConclusion.FAILURE);
assertThat(details.getOutput()).isPresent().get().satisfies(output -> {
assertThat(output.getTitle()).isPresent().get().isEqualTo("Failure");
assertThat(output.getTitle()).isPresent().get().isEqualTo("Fails: error in 'archiveArtifacts' step");
assertThat(output.getSummary()).isPresent().get().asString().matches(Pattern.compile(".*"
+ "### `In parallel / p1 / p1s1 / Set stage result to unstable`\\s+"
+ "Warning in `unstable` step, with arguments `something went wrong`\\.\\s+"