diff --git a/src/main/java/io/jenkins/plugins/checks/BuildStatusChecksPublisher.java b/src/main/java/io/jenkins/plugins/checks/BuildStatusChecksPublisher.java new file mode 100644 index 00000000..b7694c0b --- /dev/null +++ b/src/main/java/io/jenkins/plugins/checks/BuildStatusChecksPublisher.java @@ -0,0 +1,106 @@ +package io.jenkins.plugins.checks; + +import edu.umd.cs.findbugs.annotations.NonNull; + +import hudson.Extension; +import hudson.model.Queue; +import hudson.model.Result; +import hudson.model.Run; +import hudson.model.TaskListener; + +import hudson.model.listeners.RunListener; +import hudson.model.queue.QueueListener; +import io.jenkins.plugins.checks.api.ChecksConclusion; +import io.jenkins.plugins.checks.api.ChecksDetails.ChecksDetailsBuilder; +import io.jenkins.plugins.checks.api.ChecksPublisher; +import io.jenkins.plugins.checks.api.ChecksPublisherFactory; +import io.jenkins.plugins.checks.api.ChecksStatus; + +/** + * A publisher which publishes different statuses through the checks API based on the stage of the {@link Queue.Item} + * or {@link Run}. + */ +public class BuildStatusChecksPublisher { + private static final String CHECKS_NAME = "Jenkins"; + + private static void publish(final ChecksPublisher publisher, final ChecksStatus status, + final ChecksConclusion conclusion) { + publisher.publish(new ChecksDetailsBuilder() + .withName(CHECKS_NAME) + .withStatus(status) + .withConclusion(conclusion) + .build()); + } + + /** + * {@inheritDoc} + * + * Listens to the queue and publishes checks in "queued" state for entering items. + */ + @Extension + public static class JobScheduledListener extends QueueListener { + /** + * {@inheritDoc} + * + * When a job enters queue, creates the check on "queued". + */ + @Override + public void onEnterWaiting(Queue.WaitingItem wi) { + publish(ChecksPublisherFactory.fromItem(wi), ChecksStatus.QUEUED, ChecksConclusion.NONE); + } + } + + /** + * {@inheritDoc} + * + * Listens to the run and publishes checks for started and completed run. + */ + @Extension + public static class JobStartedListener extends RunListener> { + /** + * {@inheritDoc} + * + * When a job starts, updates the check to "in progress". + */ + @Override + public void onStarted(final Run run, final TaskListener listener) { + publish(ChecksPublisherFactory.fromRun(run), ChecksStatus.IN_PROGRESS, ChecksConclusion.NONE); + } + + /** + * {@inheritDoc} + * + * When a job completes, completes the check. + */ + @Override + public void onCompleted(final Run run, @NonNull final TaskListener listener) { + publish(ChecksPublisherFactory.fromRun(run), ChecksStatus.COMPLETED, extractConclusion(run)); + } + + private ChecksConclusion extractConclusion(final Run run) { + Result result = run.getResult(); + if (result == null) { + throw new IllegalStateException("No result when the run completes, run: " + run.toString()); + } + + if (result.isBetterOrEqualTo(Result.SUCCESS)) { + return ChecksConclusion.SUCCESS; + } + else if (result.isBetterOrEqualTo(Result.UNSTABLE)) { + return ChecksConclusion.NEUTRAL; + } + else if (result.isBetterOrEqualTo(Result.FAILURE)) { + return ChecksConclusion.FAILURE; + } + else if (result.isBetterOrEqualTo(Result.NOT_BUILT)) { + return ChecksConclusion.SKIPPED; + } + else if (result.isBetterOrEqualTo(Result.ABORTED)) { + return ChecksConclusion.CANCELED; + } + else { + throw new IllegalStateException("Unsupported run result: " + result); + } + } + } +} diff --git a/src/main/java/io/jenkins/plugins/checks/JobListener.java b/src/main/java/io/jenkins/plugins/checks/JobListener.java deleted file mode 100644 index 1f6920a6..00000000 --- a/src/main/java/io/jenkins/plugins/checks/JobListener.java +++ /dev/null @@ -1,67 +0,0 @@ -package io.jenkins.plugins.checks; - -import edu.umd.cs.findbugs.annotations.NonNull; - -import hudson.Extension; -import hudson.model.Run; -import hudson.model.TaskListener; -import hudson.model.listeners.RunListener; - -import io.jenkins.plugins.checks.api.ChecksConclusion; -import io.jenkins.plugins.checks.api.ChecksDetails.ChecksDetailsBuilder; -import io.jenkins.plugins.checks.api.ChecksPublisher; -import io.jenkins.plugins.checks.api.ChecksPublisherFactory; -import io.jenkins.plugins.checks.api.ChecksStatus; - -// TODO: Refactor to remove the redundant code -/** - * A listener which publishes different statuses through the checks API based on the stage of the {@link Run}. - */ -@Extension -public class JobListener extends RunListener> { - private static final String CHECKS_NAME = "Jenkins"; - - /** - * {@inheritDoc} - * - * When a job is initializing, creates a check to keep track of the {@code run}. - */ - @Override - public void onInitialize(final Run run) { - ChecksPublisher publisher = ChecksPublisherFactory.fromRun(run); - publisher.publish(new ChecksDetailsBuilder() - .withName(CHECKS_NAME) - .withStatus(ChecksStatus.QUEUED) - .build()); - } - - /** - * {@inheritDoc} - * - * When a job is starting, updates the check of the {@code run} to started. - */ - @Override - public void onStarted(final Run run, final TaskListener listener) { - ChecksPublisher publisher = ChecksPublisherFactory.fromRun(run); - publisher.publish(new ChecksDetailsBuilder() - .withName(CHECKS_NAME) - .withStatus(ChecksStatus.IN_PROGRESS) - .build()); - } - - /** - * {@inheritDoc} - * - * When a job is completed, completes the check of the {@code run}. - */ - @Override - public void onCompleted(final Run run, @NonNull final TaskListener listener) { - ChecksPublisher publisher = ChecksPublisherFactory.fromRun(run); - // TODO: extract result from run - publisher.publish(new ChecksDetailsBuilder() - .withName(CHECKS_NAME) - .withStatus(ChecksStatus.COMPLETED) - .withConclusion(ChecksConclusion.SUCCESS) - .build()); - } -} diff --git a/src/main/java/io/jenkins/plugins/checks/api/ChecksPublisherFactory.java b/src/main/java/io/jenkins/plugins/checks/api/ChecksPublisherFactory.java index 9503dd70..50ecc2ab 100644 --- a/src/main/java/io/jenkins/plugins/checks/api/ChecksPublisherFactory.java +++ b/src/main/java/io/jenkins/plugins/checks/api/ChecksPublisherFactory.java @@ -3,6 +3,7 @@ import java.util.List; import java.util.Optional; +import hudson.model.Queue; import org.kohsuke.accmod.Restricted; import org.kohsuke.accmod.restrictions.Beta; import hudson.ExtensionPoint; @@ -25,6 +26,15 @@ public abstract class ChecksPublisherFactory implements ExtensionPoint { */ protected abstract Optional createPublisher(Run run); + /** + * Creates a {@link ChecksPublisher} according to the {@link hudson.scm.SCM} used by the {@link Run}. + * + * @param item + * an item in the queue + * @return the created {@link ChecksPublisher} + */ + protected abstract Optional createPublisher(Queue.Item item); + /** * Returns a suitable publisher for the run. * @@ -41,6 +51,22 @@ public static ChecksPublisher fromRun(final Run run) { .orElse(new NullChecksPublisher()); } + /** + * Returns a suitable publisher for the waiting item. + * + * @param item + * an item in the queue + * @return a publisher suitable for the run + */ + public static ChecksPublisher fromItem(Queue.Item item) { + return findAllPublisherFactories().stream() + .map(factory -> factory.createPublisher(item)) + .filter(Optional::isPresent) + .map(Optional::get) + .findFirst() + .orElse(new NullChecksPublisher()); + } + private static List findAllPublisherFactories() { return new JenkinsFacade().getExtensionsFor(ChecksPublisherFactory.class); } diff --git a/src/test/java/io/jenkins/plugins/checks/api/ChecksPublisherFactoryITest.java b/src/test/java/io/jenkins/plugins/checks/api/ChecksPublisherFactoryITest.java index cea90879..45367ea7 100644 --- a/src/test/java/io/jenkins/plugins/checks/api/ChecksPublisherFactoryITest.java +++ b/src/test/java/io/jenkins/plugins/checks/api/ChecksPublisherFactoryITest.java @@ -2,12 +2,16 @@ import hudson.model.FreeStyleBuild; import hudson.model.FreeStyleProject; +import hudson.model.Queue; import hudson.model.Run; import io.jenkins.plugins.checks.api.ChecksPublisher.NullChecksPublisher; import org.junit.Rule; import org.junit.Test; import org.jvnet.hudson.test.JenkinsRule; +import java.util.Calendar; +import java.util.Collections; + import static org.assertj.core.api.Assertions.assertThat; /** @@ -21,17 +25,32 @@ public class ChecksPublisherFactoryITest { public JenkinsRule rule = new JenkinsRule(); /** - * A {@link NullChecksPublisher} should be returned when creating the {@link ChecksPublisher} with a {@link Run} - * of {@link FreeStyleProject}. + * A {@link NullChecksPublisher} should be returned when creating the {@link ChecksPublisher} for a {@link Run} but + * no implementation for the checks api is provided. * - * @throws Exception if fails to freestyle project or build + * @throws Exception if fails to create freestyle project or build */ @Test - public void shouldReturnNullChecksPublisherWhenUseFreestyleRun() throws Exception { + public void shouldReturnNullChecksPublisherForRunWhenNoImplementationIsProvided() throws Exception { FreeStyleProject job = rule.createFreeStyleProject(); FreeStyleBuild run = rule.buildAndAssertSuccess(job); ChecksPublisher publisher = ChecksPublisherFactory.fromRun(run); assertThat(publisher).isInstanceOf(NullChecksPublisher.class); } + + /** + * A {@link NullChecksPublisher} should be returned when creating the {@link ChecksPublisher} for an + * {@link Queue.Item} but no implementation for the checks api is provided. + * + * @throws Exception if fails to create freestyle project + */ + @Test + public void shouldReturnNullChecksPublisherForQueueItemWhenNoImplementationIsProvided() throws Exception { + FreeStyleProject job = rule.createFreeStyleProject(); + Queue.Item item = new Queue.WaitingItem(Calendar.getInstance(), job, Collections.emptyList()); + + ChecksPublisher publisher = ChecksPublisherFactory.fromItem(item); + assertThat(publisher).isInstanceOf(NullChecksPublisher.class); + } }