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 pipeline support #7

Merged
merged 22 commits into from
Aug 25, 2020
Merged
Show file tree
Hide file tree
Changes from 19 commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
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
29 changes: 29 additions & 0 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@

<!-- Jenkins Plug-in Dependencies Versions -->
<plugin-util-api.version>1.2.2</plugin-util-api.version>
<pipeline-stage-step.version>2.5</pipeline-stage-step.version>
</properties>

<licenses>
Expand Down Expand Up @@ -54,6 +55,33 @@
<scope>test</scope>
<type>test-jar</type>
</dependency>

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

<!-- node -->
<dependency>
<groupId>org.jenkins-ci.plugins.workflow</groupId>
<artifactId>workflow-durable-task-step</artifactId>
<scope>test</scope>
</dependency>
<!-- stage -->
<dependency>
<groupId>org.jenkins-ci.plugins</groupId>
<artifactId>pipeline-stage-step</artifactId>
<version>${pipeline-stage-step.version}</version>
<scope>test</scope>
</dependency>
</dependencies>

<build>
Expand All @@ -64,6 +92,7 @@
<configuration>
<packages combine.children="append">
<package>io.jenkins.plugins.checks.api</package>
<package>io.jenkins.plugins.checks.steps</package>
</packages>
<entryPointClassPackage>io.jenkins.plugins.checks.assertions</entryPointClassPackage>
</configuration>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -55,8 +55,8 @@ public void onEnterWaiting(final Queue.WaitingItem wi) {
return;
}

publish(ChecksPublisherFactory.fromJob((Job) wi.task, TaskListener.NULL), ChecksStatus.QUEUED,
ChecksConclusion.NONE);
publish(ChecksPublisherFactory.fromJob((Job)wi.task, TaskListener.NULL),
ChecksStatus.QUEUED, ChecksConclusion.NONE);
}
}

Expand Down
23 changes: 17 additions & 6 deletions src/main/java/io/jenkins/plugins/checks/api/ChecksImage.java
Original file line number Diff line number Diff line change
Expand Up @@ -12,25 +12,26 @@
* An image of a check. Users may use a image to show the code coverage, issues trend, etc.
*/
@Restricted(Beta.class)
@SuppressWarnings("PMD.DataClass")
public class ChecksImage {
private final String alt;
private final String imageUrl;
private final String imageURL;
private final String caption;

/**
* Constructs an image with all parameters.
*
* @param alt
* the alternative text for the image
* @param imageUrl
* @param imageURL
* the full URL of the image
* @param caption
* a short description of the image
*/
@SuppressFBWarnings("NP_PARAMETER_MUST_BE_NONNULL_BUT_MARKED_AS_NULLABLE")
public ChecksImage(@Nullable final String alt, @Nullable final String imageUrl, @Nullable final String caption) {
public ChecksImage(@Nullable final String alt, @Nullable final String imageURL, @Nullable final String caption) {
this.alt = alt;
this.imageUrl = imageUrl;
this.imageURL = imageURL;
this.caption = caption;
}

Expand All @@ -45,11 +46,21 @@ public Optional<String> getAlt() {

/**
* Returns the image URL.
* TODO: Exists for backward-compatibility, will be removed in 1.0.0.
*
* @return the image URL
*/
public Optional<String> getImageUrl() {
return Optional.ofNullable(imageUrl);
return getImageURL();
}

/**
* Returns the image URL.
*
* @return the image URL
*/
public Optional<String> getImageURL() {
return Optional.ofNullable(imageURL);
}

/**
Expand All @@ -65,7 +76,7 @@ public Optional<String> getCaption() {
public String toString() {
return "ChecksImage{"
+ "alt='" + alt + '\''
+ ", imageUrl='" + imageUrl + '\''
+ ", imageUrl='" + imageURL + '\''
+ ", caption='" + caption + '\''
+ '}';
}
Expand Down
22 changes: 19 additions & 3 deletions src/main/java/io/jenkins/plugins/checks/api/ChecksPublisher.java
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import hudson.model.Job;
import hudson.model.TaskListener;
import io.jenkins.plugins.util.PluginLogger;
import org.kohsuke.accmod.Restricted;
import org.kohsuke.accmod.restrictions.Beta;

Expand All @@ -19,11 +20,26 @@ public abstract class ChecksPublisher {
public abstract void publish(ChecksDetails details);

/**
* A null publisher. This publisher will be returned by
* {@link ChecksPublisherFactory#fromJob(Job, TaskListener)} only when there is no suitable publisher for the given {@code run}.
* A null publisher. This publisher will be returned by {@link ChecksPublisherFactory#fromJob(Job, TaskListener)}
* only when there is no suitable publisher for the given {@code run}.
*/
public static class NullChecksPublisher extends ChecksPublisher {
private final PluginLogger logger;

/**
* Construct a null checks publisher with {@link PluginLogger}.
* @param logger
* the plugin logger
*/
public NullChecksPublisher(final PluginLogger logger) {
super();

this.logger = logger;
}

@Override
public void publish(final ChecksDetails details) { }
public void publish(final ChecksDetails details) {
logger.log("No suitable checks publisher found.");
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
import hudson.model.Job;
import hudson.model.Run;
import hudson.model.TaskListener;
import io.jenkins.plugins.util.PluginLogger;
import org.kohsuke.accmod.Restricted;
import org.kohsuke.accmod.restrictions.Beta;
import hudson.ExtensionPoint;
Expand Down Expand Up @@ -105,7 +106,7 @@ static ChecksPublisher fromRun(final Run<?, ?> run, final TaskListener listener,
.filter(Optional::isPresent)
.map(Optional::get)
.findFirst()
.orElse(new NullChecksPublisher());
.orElse(new NullChecksPublisher(createLogger(listener)));
}

@VisibleForTesting
Expand All @@ -116,10 +117,14 @@ static ChecksPublisher fromJob(final Job<?, ?> job, final TaskListener listener,
.filter(Optional::isPresent)
.map(Optional::get)
.findFirst()
.orElse(new NullChecksPublisher());
.orElse(new NullChecksPublisher(createLogger(listener)));
}

private static List<ChecksPublisherFactory> findAllPublisherFactories(final JenkinsFacade jenkinsFacade) {
return jenkinsFacade.getExtensionsFor(ChecksPublisherFactory.class);
}

private static PluginLogger createLogger(final TaskListener listener) {
return new PluginLogger(listener.getLogger(), "Checks API");
}
}
197 changes: 197 additions & 0 deletions src/main/java/io/jenkins/plugins/checks/steps/PublishChecksStep.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,197 @@
package io.jenkins.plugins.checks.steps;

import edu.hm.hafner.util.VisibleForTesting;
import edu.umd.cs.findbugs.annotations.NonNull;
import hudson.Extension;
import hudson.model.Run;
import hudson.model.TaskListener;
import hudson.util.ListBoxModel;
import io.jenkins.plugins.checks.api.*;
import org.apache.commons.lang3.StringUtils;
import org.jenkinsci.plugins.workflow.steps.*;
import org.kohsuke.stapler.DataBoundConstructor;
import org.kohsuke.stapler.DataBoundSetter;

import java.io.IOException;
import java.io.Serializable;
import java.util.*;

/**
* Pipeline step to publish customized checks.
*/
@SuppressWarnings("PMD.DataClass")
public class PublishChecksStep extends Step implements Serializable {
private static final long serialVersionUID = 1L;

private String name = StringUtils.EMPTY;
private String summary = StringUtils.EMPTY;
private String title = StringUtils.EMPTY;
private String text = StringUtils.EMPTY;
private String detailsURL = StringUtils.EMPTY;
private ChecksStatus status = ChecksStatus.COMPLETED;
private ChecksConclusion conclusion = ChecksConclusion.SUCCESS;

/**
* Constructor used for pipeline by Stapler.
*/
@DataBoundConstructor
public PublishChecksStep() {
super();
}

@DataBoundSetter
public void setName(final String name) {
this.name = name;
}

@DataBoundSetter
public void setSummary(final String summary) {
this.summary = summary;
}

@DataBoundSetter
public void setTitle(final String title) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it would make sense to add config.jelly and *-help.html files for the fields (you already have the text in the JavaDoc of the ChecksOutput classes). Then the online documentation (Snippet Generator and Online Reference) has some more details. You can open the Snippet Generator using the "Pipeline Syntax" link on a job page.

this.title = title;
}

@DataBoundSetter
public void setText(final String text) {
this.text = text;
}

@DataBoundSetter
public void setDetailsURL(final String detailsURL) {
uhafner marked this conversation as resolved.
Show resolved Hide resolved
this.detailsURL = detailsURL;
}

@DataBoundSetter
public void setStatus(final ChecksStatus status) {
this.status = status;
}

@DataBoundSetter
public void setConclusion(final ChecksConclusion conclusion) {
this.conclusion = conclusion;
}

public String getName() {
return name;
}

public String getSummary() {
return summary;
}

public String getTitle() {
return StringUtils.defaultIfEmpty(title, name);
}

public String getText() {
return text;
}

public String getDetailsURL() {
return detailsURL;
}

public ChecksStatus getStatus() {
return status;
}

public ChecksConclusion getConclusion() {
return conclusion;
}

@Override
public StepExecution start(final StepContext stepContext) {
return new PublishChecksStepExecution(stepContext, this);
}

/**
* This step's descriptor which defines function name, display name, and context.
*/
@Extension
public static class PublishChecksStepDescriptor extends StepDescriptor {
@Override
public String getFunctionName() {
return "publishChecks";
}

@Override
public Set<? extends Class<?>> getRequiredContext() {
return Collections.unmodifiableSet(new HashSet<>(Arrays.asList(Run.class, TaskListener.class)));
}

@NonNull
@Override
public String getDisplayName() {
return "Publish customized checks to SCM platforms";
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The text is shown in the Snippet Generator and Online Reference (and should be part of Messages)

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Optional (since we have no I18n yet in the whole project): it would make sense to create a Messages.properties file and add all UI labels (example: https://github.com/jenkinsci/forensics-api-plugin/blob/master/src/main/resources/io/jenkins/plugins/forensics/miner/Messages.properties). Then you can replace these strings with the constant in the generated Messages class (example: https://github.com/jenkinsci/forensics-api-plugin/blob/master/src/main/java/io/jenkins/plugins/forensics/miner/ForensicsJobAction.java#L34).

But we can postpone that part to the after GSoC work.

}

/**
* Fill the dropdown list model with all {@link ChecksStatus}es.
*
* @return a model with all {@link ChecksStatus}es.
*/
public ListBoxModel doFillStatusItems() {
ListBoxModel options = new ListBoxModel();
for (ChecksStatus status : ChecksStatus.values()) {
options.add(StringUtils.capitalize(status.name().toLowerCase(Locale.ENGLISH).replace("_", " ")),
XiongKezhi marked this conversation as resolved.
Show resolved Hide resolved
status.name());
}

return options;
}

/**
* Fill the dropdown list model with all {@link ChecksConclusion}s.
*
* @return a model with all {@link ChecksConclusion}s.
*/
public ListBoxModel doFillConclusionItems() {
ListBoxModel options = new ListBoxModel();
for (ChecksConclusion conclusion : ChecksConclusion.values()) {
options.add(StringUtils.capitalize(conclusion.name().toLowerCase(Locale.ENGLISH).replace("_", " ")),
conclusion.name());
}

return options;
}
}

/**
* This step's execution to actually publish checks.
*/
static class PublishChecksStepExecution extends SynchronousNonBlockingStepExecution<Void> {
private static final long serialVersionUID = 1L;
private final PublishChecksStep step;

PublishChecksStepExecution(final StepContext context, final PublishChecksStep step) {
super(context);
this.step = step;
}

@Override
protected Void run() throws IOException, InterruptedException {
ChecksPublisherFactory.fromRun(getContext().get(Run.class), getContext().get(TaskListener.class))
.publish(extractChecksDetails());

return null;
}

@VisibleForTesting
ChecksDetails extractChecksDetails() {
return new ChecksDetails.ChecksDetailsBuilder()
.withName(step.getName())
.withStatus(step.getStatus())
.withConclusion(step.getConclusion())
.withDetailsURL(step.getDetailsURL())
.withOutput(new ChecksOutput.ChecksOutputBuilder()
.withTitle(step.getTitle())
.withSummary(step.getSummary())
.withText(step.getText())
.build())
.build();
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
<div>
XiongKezhi marked this conversation as resolved.
Show resolved Hide resolved
The conclusion of the check, can be "ACTION_REQUIRED", "SKIPPED", "CANCELED", "TIME_OUT", "FAILURE", "NEUTRAL",
"SUCCESS" or "NONE". By default, "SUCCESS" will be used. When providing the conclusion other than "NONE", please
make sure the status is "COMPLETED".
</div>
Loading