Skip to content

Commit

Permalink
Merge 05e909c into 24dcafe
Browse files Browse the repository at this point in the history
  • Loading branch information
XiongKezhi authored Feb 21, 2021
2 parents 24dcafe + 05e909c commit 76fc732
Show file tree
Hide file tree
Showing 12 changed files with 175 additions and 34 deletions.
10 changes: 9 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,9 +38,17 @@ If enabled, the statuses will be published in different stages of a Jenkins buil
- publishChecks: you can publish checks directly in the pipeline script instead of depending on consumer plugins:

```
publishChecks name: 'example', title: 'Pipeline Check', summary: 'check through pipeline', text: 'you can publish checks in pipeline script', detailsURL: 'https://github.com/jenkinsci/checks-api-plugin#pipeline-usage'
publishChecks name: 'example', title: 'Pipeline Check', summary: 'check through pipeline',
text: 'you can publish checks in pipeline script',
detailsURL: 'https://github.com/jenkinsci/checks-api-plugin#pipeline-usage',
actions: [[label:'an-user-request-action', description:'actions allow users to request pre-defined behaviours', identifier:'an unique identifier']]
```

*To use customized actions, you will need to write a Jenkins plugin
If you want to add GitHub checks actions which are basically buttons on the checks report,
you need to extend [GHEventSubscriber](https://github.com/jenkinsci/github-plugin/blob/master/src/main/java/org/jenkinsci/plugins/github/extension/GHEventsSubscriber.java) to handle the event,
see [the handler](https://github.com/jenkinsci/github-checks-plugin/blob/ea060be67dad522ab6c31444fc4274955ac6e918/src/main/java/io/jenkins/plugins/checks/github/CheckRunGHEventSubscriber.java) for re-run requests as an example.*

- withChecks: you can inject the check's name into the closure for other steps to use:

```
Expand Down
8 changes: 7 additions & 1 deletion pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@

<properties>
<java.level>8</java.level>
<revision>1.5.1</revision>
<revision>1.6.0</revision>
<changelist>-SNAPSHOT</changelist>

<!-- Jenkins Plug-in Dependencies Versions -->
Expand Down Expand Up @@ -118,6 +118,12 @@
<versionFormat>\d+\.\d+\.\d+</versionFormat>
<analysisConfiguration>
<revapi.ignore combine.children="append">
<item>
<code>java.field.serialVersionUIDUnchanged</code>
<old>field io.jenkins.plugins.checks.steps.PublishChecksStep.serialVersionUID</old>
<serialVersionUID>1</serialVersionUID>
<justification>Adding actions list with an initial empty value should not break the compatibility.</justification>
</item>
</revapi.ignore>
</analysisConfiguration>
</configuration>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ public class ChecksAction {
*/
@SuppressFBWarnings("NP_PARAMETER_MUST_BE_NONNULL_BUT_MARKED_AS_NULLABLE")
public ChecksAction(@CheckForNull final String label, @CheckForNull final String description,
@CheckForNull final String identifier) {
@CheckForNull final String identifier) {
this.label = label;
this.description = description;
this.identifier = identifier;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@
import edu.hm.hafner.util.VisibleForTesting;
import edu.umd.cs.findbugs.annotations.NonNull;
import hudson.Extension;
import hudson.model.AbstractDescribableImpl;
import hudson.model.Descriptor;
import hudson.model.Run;
import hudson.model.TaskListener;
import hudson.util.ListBoxModel;
Expand Down Expand Up @@ -31,6 +33,7 @@ public class PublishChecksStep extends Step implements Serializable {
private String detailsURL = StringUtils.EMPTY;
private ChecksStatus status = ChecksStatus.COMPLETED;
private ChecksConclusion conclusion = ChecksConclusion.SUCCESS;
private List<StepChecksAction> actions = Collections.emptyList();

/**
* Constructor used for pipeline by Stapler.
Expand Down Expand Up @@ -86,6 +89,11 @@ public void setConclusion(final ChecksConclusion conclusion) {
this.conclusion = conclusion;
}

@DataBoundSetter
public void setActions(final List<StepChecksAction> actions) {
this.actions = actions;
}

public String getName() {
return name;
}
Expand Down Expand Up @@ -114,6 +122,10 @@ public ChecksConclusion getConclusion() {
return conclusion;
}

public List<StepChecksAction> getActions() {
return actions;
}

@Override
public StepExecution start(final StepContext stepContext) {
return new PublishChecksStepExecution(stepContext, this);
Expand Down Expand Up @@ -209,7 +221,64 @@ ChecksDetails extractChecksDetails() throws IOException, InterruptedException {
.withSummary(step.getSummary())
.withText(step.getText())
.build())
.withActions(step.getActions().stream()
.map(StepChecksAction::getAction)
.collect(Collectors.toList()))
.build();
}
}

/**
* A simple wrapper for {@link ChecksAction} to allow users add checks actions by {@link PublishChecksStep}.
*/
public static class StepChecksAction extends AbstractDescribableImpl<StepChecksAction> implements Serializable {
private static final long serialVersionUID = 1L;
private final String label;
private final String identifier;
private String description = StringUtils.EMPTY;

/**
* Creates an instance that wraps a newly constructed {@link ChecksAction} with according parameters.
*
* @param label
* label of the action to display in the checks report on SCMs
* @param identifier
* identifier for the action, useful to identify which action is requested by users
*/
@DataBoundConstructor
public StepChecksAction(final String label, final String identifier) {
super();

this.label = label;
this.identifier = identifier;
}

@DataBoundSetter
public void setDescription(final String description) {
this.description = description;
}

public String getLabel() {
return label;
}

public String getDescription() {
return description;
}

public String getIdentifier() {
return identifier;
}

public ChecksAction getAction() {
return new ChecksAction(label, description, identifier);
}

/**
* Descriptor for {@link StepChecksAction}, required for Pipeline Snippet Generator.
*/
@Extension
public static class StepChecksActionDescriptor extends Descriptor<StepChecksAction> {
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
<?jelly escape-by-default='true'?>
<j:jelly xmlns:j="jelly:core" xmlns:f="/lib/form">

<f:entry title="Label" field="label">
<f:textbox />
</f:entry>

<f:entry title="Identifier" field="identifier">
<f:textbox />
</f:entry>

<f:entry title="Description" field="description">
<f:textbox />
</f:entry>

</j:jelly>
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
<div>
Detailed description of the action's purpose, functionality, and so on.
</div>
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
<div>
The unique identifier for the action. Since for SCM platforms like GitHub, this is the only field that would be
sent back to your Jenkins instance when an action is requested, so you may need to use this field to have more
information besides the basic type of the action.
</div>
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
<div>
The label to be displayed on the checks report for this action.
</div>
Original file line number Diff line number Diff line change
Expand Up @@ -29,4 +29,16 @@
<f:select default="SUCCESS"/>
</f:entry>

<f:entry title="${%title.actions}">
<div id="actions">
<f:repeatableProperty field="actions" add="${%Add Actions}">
<f:entry>
<div align="right">
<f:repeatableDeleteButton/>
</div>
</f:entry>
</f:repeatableProperty>
</div>
</f:entry>

</j:jelly>
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,4 @@ title.text=Text
title.detailsURL=Details URL
title.status=Status
title.conclusion=Conclusion
title.actions=Actions
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package io.jenkins.plugins.checks.steps;

import io.jenkins.plugins.checks.api.ChecksAction;
import io.jenkins.plugins.checks.api.ChecksConclusion;
import io.jenkins.plugins.checks.api.ChecksDetails;
import io.jenkins.plugins.checks.api.ChecksOutput;
Expand Down Expand Up @@ -36,7 +37,8 @@ public void shouldPublishChecksWhenUsingPipeline() throws IOException {
WorkflowJob job = createPipeline();
job.setDefinition(asStage("publishChecks name: 'customized-check', "
+ "summary: 'customized check created in pipeline', title: 'Publish Checks Step', "
+ "text: 'Pipeline support for checks', status: 'IN_PROGRESS', conclusion: 'NONE'"));
+ "text: 'Pipeline support for checks', status: 'IN_PROGRESS', conclusion: 'NONE', "
+ "actions: [[label:'test-label', description:'test-desc', identifier:'test-id']]"));

assertThat(JenkinsRule.getLog(buildSuccessfully(job)))
.contains("[Pipeline] publishChecks");
Expand All @@ -49,6 +51,8 @@ public void shouldPublishChecksWhenUsingPipeline() throws IOException {
assertThat(details.getOutput()).isPresent();
assertThat(details.getStatus()).isEqualTo(ChecksStatus.IN_PROGRESS);
assertThat(details.getConclusion()).isEqualTo(ChecksConclusion.NONE);
assertThat(details.getActions()).usingFieldByFieldElementComparator().containsExactlyInAnyOrder(
new ChecksAction("test-label", "test-desc", "test-id"));

ChecksOutput output = details.getOutput().get();
assertThat(output.getTitle()).isPresent().get().isEqualTo("Publish Checks Step");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,35 +2,29 @@

import hudson.model.Run;
import hudson.model.TaskListener;
import io.jenkins.plugins.checks.api.ChecksAction;
import io.jenkins.plugins.checks.api.ChecksConclusion;
import io.jenkins.plugins.checks.api.ChecksDetails;
import io.jenkins.plugins.checks.api.ChecksOutput;
import io.jenkins.plugins.checks.api.ChecksStatus;
import org.apache.commons.lang3.StringUtils;
import org.jenkinsci.plugins.workflow.steps.StepContext;
import org.jenkinsci.plugins.workflow.steps.StepExecution;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;

import java.io.IOException;
import java.util.Objects;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;

import static org.assertj.core.api.Assertions.assertThat;
import static io.jenkins.plugins.checks.assertions.Assertions.assertThat;
import static org.mockito.Mockito.*;

class PublishChecksStepTest {

StepContext getStepContext() throws IOException, InterruptedException {
StepContext context = mock(StepContext.class);
when(context.get(Run.class)).thenReturn(mock(Run.class));
when(context.get(TaskListener.class)).thenReturn(TaskListener.NULL);
return context;
}

@Test
void shouldPublishCheckWithDefaultValues() throws IOException, InterruptedException {
StepExecution execution = new PublishChecksStep().start(getStepContext());
StepExecution execution = new PublishChecksStep().start(createStepContext());
assertThat(execution).isInstanceOf(PublishChecksStep.PublishChecksStepExecution.class);
assertThat(((PublishChecksStep.PublishChecksStepExecution)execution).extractChecksDetails())
.usingRecursiveComparison()
Expand All @@ -44,15 +38,16 @@ void shouldPublishCheckWithDefaultValues() throws IOException, InterruptedExcept
.withSummary(StringUtils.EMPTY)
.withText(StringUtils.EMPTY)
.build())
.withActions(Collections.emptyList())
.build());
}

@Test
void shouldPublishCheckWithStatusInProgress() throws IOException, InterruptedException {
PublishChecksStep step = getModifiedPublishChecksStepObject("an in progress build",
ChecksStatus.IN_PROGRESS, null);
PublishChecksStep step = createPublishChecksStep("an in progress build", ChecksStatus.IN_PROGRESS,
ChecksConclusion.NONE);

StepExecution execution = step.start(getStepContext());
StepExecution execution = step.start(createStepContext());
assertThat(execution).isInstanceOf(PublishChecksStep.PublishChecksStepExecution.class);
assertThat(((PublishChecksStep.PublishChecksStepExecution)execution).extractChecksDetails())
.usingRecursiveComparison()
Expand All @@ -70,11 +65,11 @@ void shouldPublishCheckWithStatusInProgress() throws IOException, InterruptedExc
}

@Test
void shouldPublishCheckWithStatusQueue() throws IOException, InterruptedException {
PublishChecksStep step = getModifiedPublishChecksStepObject("a queued build",
ChecksStatus.QUEUED, null);
void shouldPublishCheckWithStatusQueued() throws IOException, InterruptedException {
PublishChecksStep step = createPublishChecksStep("a queued build", ChecksStatus.QUEUED,
ChecksConclusion.NONE);

StepExecution execution = step.start(getStepContext());
StepExecution execution = step.start(createStepContext());
assertThat(execution).isInstanceOf(PublishChecksStep.PublishChecksStepExecution.class);
assertThat(((PublishChecksStep.PublishChecksStepExecution)execution).extractChecksDetails())
.usingRecursiveComparison()
Expand All @@ -93,10 +88,23 @@ void shouldPublishCheckWithStatusQueue() throws IOException, InterruptedExceptio

@Test
void shouldPublishCheckWithSetValues() throws IOException, InterruptedException {
PublishChecksStep step = getModifiedPublishChecksStepObject("a failed build",
ChecksStatus.IN_PROGRESS, ChecksConclusion.FAILURE);
PublishChecksStep step = createPublishChecksStep("a failed build", ChecksStatus.IN_PROGRESS,
ChecksConclusion.FAILURE);

List<PublishChecksStep.StepChecksAction> actions = Arrays.asList(
new PublishChecksStep.StepChecksAction("label-1", "identifier-1"),
new PublishChecksStep.StepChecksAction("label-2", "identifier-2"));
actions.get(1).setDescription("description-2");

step.setActions(actions);
assertThat(step.getActions().stream().map(PublishChecksStep.StepChecksAction::getLabel))
.containsExactlyInAnyOrder("label-1", "label-2");
assertThat(step.getActions().stream().map(PublishChecksStep.StepChecksAction::getDescription))
.containsExactlyInAnyOrder(StringUtils.EMPTY, "description-2");
assertThat(step.getActions().stream().map(PublishChecksStep.StepChecksAction::getIdentifier))
.containsExactlyInAnyOrder("identifier-1", "identifier-2");

StepExecution execution = step.start(getStepContext());
StepExecution execution = step.start(createStepContext());
assertThat(execution).isInstanceOf(PublishChecksStep.PublishChecksStepExecution.class);
assertThat(((PublishChecksStep.PublishChecksStepExecution)execution).extractChecksDetails())
.usingRecursiveComparison()
Expand All @@ -110,6 +118,9 @@ void shouldPublishCheckWithSetValues() throws IOException, InterruptedException
.withSummary("a check made by Jenkins")
.withText("a failed build")
.build())
.withActions(Arrays.asList(
new ChecksAction("label-1", "", "identifier-1"),
new ChecksAction("label-2", "description-2", "identifier-2")))
.build());
}

Expand All @@ -121,20 +132,23 @@ void shouldDefinePublishChecksStepDescriptorCorrectly() {
assertThat(descriptor.getRequiredContext().toArray()).containsExactlyInAnyOrder(Run.class, TaskListener.class);
}

private PublishChecksStep getModifiedPublishChecksStepObject(final String stepText, final ChecksStatus status,
final ChecksConclusion conclusion) {
private StepContext createStepContext() throws IOException, InterruptedException {
StepContext context = mock(StepContext.class);
when(context.get(Run.class)).thenReturn(mock(Run.class));
when(context.get(TaskListener.class)).thenReturn(TaskListener.NULL);
return context;
}

private PublishChecksStep createPublishChecksStep(final String stepText, final ChecksStatus status,
final ChecksConclusion conclusion) {
PublishChecksStep step = new PublishChecksStep();
step.setName("Jenkins");
step.setSummary("a check made by Jenkins");
step.setTitle("Jenkins Build");
step.setText(stepText);
if (Objects.nonNull(status)) {
step.setStatus(status);
}
if (Objects.nonNull(conclusion)) {
step.setConclusion(conclusion);
}
step.setDetailsURL("http://ci.jenkins.io");
step.setText(stepText);
step.setStatus(status);
step.setConclusion(conclusion);

return step;
}
Expand Down

0 comments on commit 76fc732

Please sign in to comment.