Skip to content

Commit

Permalink
Merge pull request #309 from jenkinsci/stage-result
Browse files Browse the repository at this point in the history
[JENKINS-72059] Add new quality gate options to alter the stage only
  • Loading branch information
uhafner authored Feb 23, 2024
2 parents 026388a + afb0e59 commit 2ad27f7
Show file tree
Hide file tree
Showing 19 changed files with 630 additions and 48 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ jobs:
check-latest: true
cache: 'maven'
- name: Set up Maven
uses: stCarolas/setup-maven@v4.5
uses: stCarolas/setup-maven@v5
with:
maven-version: 3.9.6
- name: Build with Maven
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/codeql.yml
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ jobs:
cache: maven

- name: Set up Maven
uses: stCarolas/setup-maven@v4.5
uses: stCarolas/setup-maven@v5
with:
maven-version: 3.9.6

Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/coverage.yml
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ jobs:
check-latest: true
cache: 'maven'
- name: Set up Maven
uses: stCarolas/setup-maven@v4.5
uses: stCarolas/setup-maven@v5
with:
maven-version: 3.9.6
- name: Generate coverage with JaCoCo
Expand Down
87 changes: 87 additions & 0 deletions .github/workflows/update-badges.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
name: 'Quality badges'

on:
push:
branches:
- main

jobs:
coverage:

runs-on: [ubuntu-latest]
name: Update quality badges

steps:
- uses: actions/checkout@v4
- name: Set up JDK 21
uses: actions/setup-java@v4
with:
distribution: 'temurin'
java-version: '21'
check-latest: true
cache: 'maven'
- name: Set up Maven
uses: stCarolas/setup-maven@v5
with:
maven-version: 3.9.6
- name: Build and test with Maven
run: mvn -V --color always -ntp clean verify -Dgpg.skip -Ppit -Pdepgraph | tee maven.log
- name: Run Quality Monitor
uses: uhafner/quality-monitor@v1
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
- name: Write metrics to GitHub output
id: metrics
run: |
cat metrics.env >> "${GITHUB_OUTPUT}"
mkdir -p badges
- name: Generate the badge SVG image for the line coverage
uses: emibcn/[email protected]
with:
label: 'Lines'
status: ${{ steps.metrics.outputs.line }}%
color: 'green'
path: badges/line-coverage.svg
- name: Generate the badge SVG image for the branch coverage
uses: emibcn/[email protected]
with:
label: 'Branches'
status: ${{ steps.metrics.outputs.branch }}%
color: 'green'
path: badges/branch-coverage.svg
- name: Generate the badge SVG image for the mutation coverage
uses: emibcn/[email protected]
with:
label: 'Mutations'
status: ${{ steps.metrics.outputs.mutation }}%
color: 'green'
path: badges/mutation-coverage.svg
- name: Generate the badge SVG image for the style warnings
uses: emibcn/[email protected]
with:
label: 'Warnings'
status: ${{ steps.metrics.outputs.style }}
color: 'orange'
path: badges/style.svg
- name: Generate the badge SVG image for the bugs
uses: emibcn/[email protected]
with:
label: 'Bugs'
status: ${{ steps.metrics.outputs.bugs }}
color: 'orange'
path: badges/bugs.svg
- name: Commit updated badges
continue-on-error: true
run: |
git config --local user.email "[email protected]"
git config --local user.name "GitHub Action"
git add badges/*.svg
git commit -m "Update badges with results from latest autograding" || true
git add doc/dependency-graph.puml
git commit -m "Update dependency graph to latest versions from POM" || true
- name: Push updated badges to GitHub repository
uses: ad-m/github-push-action@master
if: ${{ success() }}
with:
github_token: ${{ secrets.GITHUB_TOKEN }}
branch: main
22 changes: 21 additions & 1 deletion src/main/java/io/jenkins/plugins/util/AbstractExecution.java
Original file line number Diff line number Diff line change
Expand Up @@ -125,7 +125,7 @@ protected TaskListener getTaskListener() throws InterruptedException, IOExceptio

/**
* Returns the default charset for the specified encoding string. If the default encoding is empty or {@code null},
* or if the charset is not valid then the default encoding of the platform is returned.
* or if the charset is not valid, then the default encoding of the platform is returned.
*
* @param charset
* identifier of the character set
Expand All @@ -144,8 +144,28 @@ protected Charset getCharset(final String charset) {
* if the user canceled the execution
* @throws IOException
* if the required {@link FlowNode} instance is not found
* @deprecated use {@link #createResultHandler()} instead
*/
@Deprecated
protected StageResultHandler createStageResultHandler() throws InterruptedException, IOException {
return createPipelineResultHandler();
}

/**
* Creates a {@link ResultHandler} that sets build result of the {@link Run} or stage.
*
* @return a {@link ResultHandler} that sets the build result of the {@link Run} or stage
* @throws InterruptedException
* if the user canceled the execution
* @throws IOException
* if the required {@link FlowNode} instance is not found
*
*/
protected ResultHandler createResultHandler() throws InterruptedException, IOException {
return createPipelineResultHandler();
}

private PipelineResultHandler createPipelineResultHandler() throws IOException, InterruptedException {
return new PipelineResultHandler(getRun(), getContext().get(FlowNode.class));

Check warning on line 169 in src/main/java/io/jenkins/plugins/util/AbstractExecution.java

View workflow job for this annotation

GitHub Actions / Quality Monitor

Not covered lines

Lines 35-169 are not covered by tests
}
}
20 changes: 20 additions & 0 deletions src/main/java/io/jenkins/plugins/util/NullResultHandler.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package io.jenkins.plugins.util;

import hudson.model.Result;

/**
* A {@link ResultHandler} that does nothing.
*
* @author Ullrich Hafner
*/
public class NullResultHandler implements ResultHandler {
@Override
public void publishResult(final QualityGateStatus status, final String message) {
// empty
}

@Override
public void publishResult(final Result result, final String message) {
// empty
}

Check warning on line 19 in src/main/java/io/jenkins/plugins/util/NullResultHandler.java

View workflow job for this annotation

GitHub Actions / Quality Monitor

Not covered lines

Lines 10-19 are not covered by tests
}
33 changes: 30 additions & 3 deletions src/main/java/io/jenkins/plugins/util/PipelineResultHandler.java
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,13 @@
import hudson.model.Run;

/**
* {@link StageResultHandler} that sets the overall build result of the {@link Run} and annotates the given Pipeline
* step with a {@link WarningAction}.
* A {@link ResultHandler} that sets the overall build result of the {@link Run} and annotates the given Pipeline
* stage with a {@link WarningAction}.
*
* @author Devin Nusbaum
*/
public class PipelineResultHandler implements StageResultHandler {
@SuppressWarnings("deprecation")
public class PipelineResultHandler implements StageResultHandler, ResultHandler {
private final Run<?, ?> run;
private final FlowNode flowNode;

Expand All @@ -30,10 +31,36 @@ public PipelineResultHandler(final Run<?, ?> run, final FlowNode flowNode) {

@Override
public void setResult(final Result result, final String message) {
publishResult(result, message);
}

@Override
public void publishResult(final Result result, final String message) {
run.setResult(result);

setStageResult(result, message);
}

private void setStageResult(final Result result, final String message) {
WarningAction existing = flowNode.getPersistentAction(WarningAction.class);
if (existing == null || existing.getResult().isBetterThan(result)) {
flowNode.addOrReplaceAction(new WarningAction(result).withMessage(message));
}
}

@Override
public void publishResult(final QualityGateStatus status, final String message) {
switch (status) {
case NOTE:
case ERROR:
setStageResult(status.getResult(), message);
break;
case WARNING:
case FAILED:
setResult(status.getResult(), message);
break;
default:
// ignore and do nothing
}
}
}
102 changes: 76 additions & 26 deletions src/main/java/io/jenkins/plugins/util/QualityGate.java
Original file line number Diff line number Diff line change
Expand Up @@ -4,65 +4,98 @@

import edu.hm.hafner.util.VisibleForTesting;

import org.kohsuke.stapler.AncestorInPath;
import org.kohsuke.stapler.DataBoundSetter;
import org.kohsuke.stapler.verb.POST;
import hudson.Extension;
import hudson.model.AbstractDescribableImpl;
import hudson.model.BuildableItem;
import hudson.model.Descriptor;
import hudson.model.FreeStyleProject;
import hudson.util.ListBoxModel;
import jenkins.model.Jenkins;

/**
* Defines a quality gate based on a specific threshold of code coverage in the current build. After a build has been
* finished, a set of {@link QualityGate quality gates} will be evaluated and the overall quality gate status will be
* reported in Jenkins UI.
* Defines a quality gate based on a specific threshold of a selected property in the current build. After a build has
* been finished, a set of {@link QualityGate quality gates} will be evaluated and the overall quality gate status will
* be reported in Jenkins UI. The criticality of the quality gate is determined by the
* {@link QualityGateCriticality criticality}. Subclasses must implement the {@link #getName()} method to provide a
* human-readable name of the quality gate. Additionally, subclasses must provide a {@link Descriptor} to describe the
* quality gate in the UI. Subclasses may add additional properties to configure the quality gate, these must be annotated with the
* {@link DataBoundSetter} annotation.
*
* @author Johannes Walter
*/
public abstract class QualityGate extends AbstractDescribableImpl<QualityGate> implements Serializable {
private static final long serialVersionUID = -397278599489426668L;

private final double threshold;
private double threshold = 0.0;
private QualityGateCriticality criticality = QualityGateCriticality.UNSTABLE;

/**
* Creates a new instance of {@link QualityGate}.
*
* @param threshold
* minimum or maximum value that triggers this quality gate
*/
protected QualityGate(final double threshold) {
protected QualityGate() {
super();

this.threshold = threshold;
}

/**
* Sets the criticality of this quality gate. When a quality gate has been missed, this property determines whether
* the result of the associated coverage stage will be marked as unstable or failure.
* Returns a human-readable name of the quality gate.
*
* @param criticality
* the criticality for this quality gate
* @return a human-readable name
*/
public abstract String getName();

/**
* Sets the threshold of the quality gate.
*
* @param threshold
* the threshold of the quality gate
*/
@DataBoundSetter
public final void setCriticality(final QualityGateCriticality criticality) {
this.criticality = criticality;
public final void setThreshold(final double threshold) {
this.threshold = threshold;
}

public final double getThreshold() {
return threshold;
}

private int asInt(final double value) {
return Double.valueOf(value).intValue();
}

/**
* Returns a human-readable name of the quality gate.
* Sets the threshold of the quality gate. This integer-based setter is required to bind a UI number element to this
* model object.
*
* @return a human-readable name
* @param integerThreshold
* the threshold of the quality gate
*/
public abstract String getName();
@DataBoundSetter
public final void setIntegerThreshold(final int integerThreshold) {
this.threshold = asInt(integerThreshold);
}

public final int getIntegerThreshold() {
return asInt(threshold);
}

@Override
public String toString() {
return getName() + String.format(" - %s: %f", getCriticality(), getThreshold());
}

public final double getThreshold() {
return threshold;
/**
* Sets the criticality of this quality gate. When a quality gate has been missed, this property determines whether
* the result of the associated coverage stage will be marked as unstable or failure.
*
* @param criticality
* the criticality for this quality gate
*/
@DataBoundSetter
public final void setCriticality(final QualityGateCriticality criticality) {
this.criticality = criticality;
}

public final QualityGateCriticality getCriticality() {
Expand All @@ -77,10 +110,16 @@ public final QualityGateStatus getStatus() {
* Determines the Jenkins build result if the quality gate is failed.
*/
public enum QualityGateCriticality {
/** The build will be marked as unstable. */
/** The stage will be marked with a warning. */
NOTE(QualityGateStatus.NOTE),

/** The stage and the build will be marked as unstable. */
UNSTABLE(QualityGateStatus.WARNING),

/** The build will be marked as failed. */
/** The stage will be marked as failed. */
ERROR(QualityGateStatus.ERROR),

/** The stage and the build will be marked as failed. */
FAILURE(QualityGateStatus.FAILED);

private final QualityGateStatus status;
Expand Down Expand Up @@ -124,15 +163,26 @@ public QualityGateDescriptor() {
/**
* Returns a model with all {@link QualityGateCriticality criticalities} that can be used in quality gates.
*
* @param project
* the project that is configured
*
* @return a model with all {@link QualityGateCriticality criticalities}.
*/
@POST
@SuppressWarnings("unused") // used by Stapler view data binding
public ListBoxModel doFillCriticalityItems() {
public ListBoxModel doFillCriticalityItems(@AncestorInPath final BuildableItem project) {
if (jenkins.hasPermission(Jenkins.READ)) {
ListBoxModel options = new ListBoxModel();
options.add(Messages.QualityGate_Unstable(), QualityGateCriticality.UNSTABLE.name());
options.add(Messages.QualityGate_Failure(), QualityGateCriticality.FAILURE.name());
if (project instanceof FreeStyleProject) {
options.add(Messages.QualityGate_Unstable(), QualityGateCriticality.UNSTABLE.name());
options.add(Messages.QualityGate_Failure(), QualityGateCriticality.FAILURE.name());
}
else {
options.add(Messages.QualityGate_UnstableStage(), QualityGateCriticality.NOTE.name());
options.add(Messages.QualityGate_UnstableRun(), QualityGateCriticality.UNSTABLE.name());
options.add(Messages.QualityGate_FailureStage(), QualityGateCriticality.ERROR.name());
options.add(Messages.QualityGate_FailureRun(), QualityGateCriticality.FAILURE.name());
}
return options;
}
return new ListBoxModel();

Check warning on line 188 in src/main/java/io/jenkins/plugins/util/QualityGate.java

View workflow job for this annotation

GitHub Actions / Quality Monitor

Not covered lines

Lines 174-188 are not covered by tests
Expand Down
Loading

0 comments on commit 2ad27f7

Please sign in to comment.