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 GitHub scm source implementation for status checks properties #66

Merged
Merged
Show file tree
Hide file tree
Changes from all 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
6 changes: 5 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,11 @@ This plugin has been installed, alone with the [General API Plugin](https://gith

![GitHub Status](docs/images/github-status.png)

By listening to the Jenkins builds, this plugin will automatically publish statuses (pending, in progress, and completed) to GitHub.
This plugin implements [the status checks feature from Checks API Plugin](https://github.com/jenkinsci/checks-api-plugin#build-status-check) to publish statuses (pending, in progress, and completed) to GitHub.

You can customize it by configuring the "Status Checks Properties" behavior for your GitHub Source SCM (similar behaviour for Git SCM will be provided soon):

![Status Checks Properties](docs/images/status-checks-properties.png)
Copy link
Member

Choose a reason for hiding this comment

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

might want to fix your spelling of customized in the screenshot


*Note: If you are using [GitHub Branch Source Plugin](https://github.com/jenkinsci/github-branch-source-plugin), it will also send status notifications to GitHub through [Status API](https://docs.github.com/en/rest/reference/repos#statuses).
You can disable it by using [Disable GitHub Multibranch Status Plugin](https://github.com/jenkinsci/disable-github-multibranch-status-plugin) or [Skip Notification Trait Plugin](https://github.com/jenkinsci/skip-notifications-trait-plugin).*
Expand Down
Binary file added docs/images/status-checks-properties.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
2 changes: 1 addition & 1 deletion pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@
<useBeta>true</useBeta>

<!-- Jenkins Plug-in Dependencies Versions -->
<checks-api.version>1.0.3</checks-api.version>
<checks-api.version>1.1.0-rc141.aaf1727a6ba0</checks-api.version>
<plugin-util-api.version>1.4.0</plugin-util-api.version>
<github-branch-source.version>2.9.1</github-branch-source.version>

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
package io.jenkins.plugins.checks.github;

import edu.hm.hafner.util.VisibleForTesting;
import hudson.Extension;
import hudson.model.Job;
import io.jenkins.plugins.checks.status.StatusChecksProperties;
import org.jenkinsci.plugins.github_branch_source.GitHubSCMSource;

import java.util.Optional;

/**
* Implementing {@link StatusChecksProperties} to retrieve properties from jobs with
* {@link GitHubSCMSourceStatusChecksTrait}.
*/
@Extension
public class GitHubSCMSourceStatusChecksProperties implements StatusChecksProperties {
private final SCMFacade scmFacade;

/**
* Default constructor.
*/
public GitHubSCMSourceStatusChecksProperties() {
this(new SCMFacade());
}

@VisibleForTesting
GitHubSCMSourceStatusChecksProperties(final SCMFacade scmFacade) {
this.scmFacade = scmFacade;
}

/**
* Only applicable to jobs using {@link GitHubSCMSource}.
*
* @param job
* a jenkins job
* @return true if {@code job} is using {@link GitHubSCMSource}
*/
@Override
public boolean isApplicable(final Job<?, ?> job) {
return scmFacade.findGitHubSCMSource(job).isPresent();
}

/**
* Returns the name configured by user in the {@link GitHubSCMSourceStatusChecksTrait}.
*
* @param job
* a jenkins job
* @return name of status checks if configured, or a default "Jenkins" will be used.
*/
@Override
public String getName(final Job<?, ?> job) {
return scmFacade.findGitHubSCMSource(job)
.map(gitHubSCMSource -> getStatusChecksTrait(gitHubSCMSource)
.map(GitHubSCMSourceStatusChecksTrait::getName).orElse("Jenkins"))
.orElse("Jenkins");
}

/**
* Returns if skip publishing status checks as user configured in the {@link GitHubSCMSourceStatusChecksTrait}.
*
* @param job
* a jenkins job
* @return true to skip publishing checks if configured.
*/
@Override
public boolean isSkip(final Job<?, ?> job) {
return scmFacade.findGitHubSCMSource(job)
.map(s -> getStatusChecksTrait(s)
.map(GitHubSCMSourceStatusChecksTrait::isSkip).orElse(false))
.orElse(false);

}

private Optional<GitHubSCMSourceStatusChecksTrait> getStatusChecksTrait(final GitHubSCMSource source) {
return source.getTraits()
.stream()
.filter(t -> t instanceof GitHubSCMSourceStatusChecksTrait)
.findFirst()
.map(t -> (GitHubSCMSourceStatusChecksTrait)t);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
package io.jenkins.plugins.checks.github;

import hudson.Extension;
import hudson.util.FormValidation;
import io.jenkins.plugins.checks.status.StatusChecksProperties;
import jenkins.scm.api.SCMSource;
import jenkins.scm.api.trait.SCMSourceContext;
import jenkins.scm.api.trait.SCMSourceTrait;
import jenkins.scm.api.trait.SCMSourceTraitDescriptor;
import org.apache.commons.lang3.StringUtils;
import org.jenkinsci.plugins.github_branch_source.GitHubSCMSource;
import org.jenkinsci.plugins.github_branch_source.GitHubSCMSourceContext;
import org.kohsuke.stapler.DataBoundConstructor;
import org.kohsuke.stapler.DataBoundSetter;
import org.kohsuke.stapler.QueryParameter;

/**
* Traits to control {@link io.jenkins.plugins.checks.status.StatusChecksProperties} for jobs using
* {@link GitHubSCMSource}.
*/
@SuppressWarnings("PMD.DataClass")
public class GitHubSCMSourceStatusChecksTrait extends SCMSourceTrait {
private boolean skip = false;
private String name = "Jenkins";

/**
* Constructor for stapler.
*/
@DataBoundConstructor
public GitHubSCMSourceStatusChecksTrait() {
super();
}

/**
* Defines the status checks name which is also used as identifier for GitHub checks.
*
* @return the name of status checks
*/
public String getName() {
return name;
}

/**
* Defines whether to skip publishing status checks.
*
* @return true to skip publishing checks
*/
public boolean isSkip() {
return skip;
}

/**
* Set the name of the status checks.
*
* @param name
* name of the checks
*/
@DataBoundSetter
public void setName(final String name) {
this.name = name;
}

/**
* Set if skip publishing status checks.
*
* @param skip
* true if skip
*/
@DataBoundSetter
public void setSkip(final boolean skip) {
this.skip = skip;
}

/**
* Descriptor implementation for {@link GitHubSCMSourceStatusChecksTrait}.
*/
@Extension
public static class DescriptorImpl extends SCMSourceTraitDescriptor {
/**
* Returns the display name.
*
* @return "Status Checks Properties"
*/
@Override
public String getDisplayName() {
return "Status Checks Properties";
}

/**
* The {@link GitHubSCMSourceStatusChecksTrait} is only applicable to {@link GitHubSCMSourceContext}.
*
* @return {@link GitHubSCMSourceContext}.class
*/
@Override
public Class<? extends SCMSourceContext> getContextClass() {
return GitHubSCMSourceContext.class;
}

/**
* The {@link GitHubSCMSourceStatusChecksTrait} is only applicable to {@link GitHubSCMSource}.
*
* @return {@link GitHubSCMSource}.class
*/
@Override
public Class<? extends SCMSource> getSourceClass() {
return GitHubSCMSource.class;
}

/**
* Checks if the name of status checks is valid.
*
* @param name
* name of status checks
* @return ok if the name is not empty
*/
public FormValidation doCheckName(@QueryParameter final String name) {
if (StringUtils.isBlank(name)) {
return FormValidation.error("Name should not be empty!");
}

return FormValidation.ok();
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
<?jelly escape-by-default='true'?>
<j:jelly xmlns:j="jelly:core" xmlns:f="/lib/form">

<f:entry title="Skip publishing status checks" field="skip">
<f:checkbox default="false"/>
</f:entry>

<f:entry title="Status checks name" field="name">
<f:textbox default="Jenkins"/>
</f:entry>

</j:jelly>
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
package io.jenkins.plugins.checks.github;

import hudson.model.Job;
import hudson.util.FormValidation;
import org.jenkinsci.plugins.github_branch_source.GitHubSCMSource;
import org.junit.jupiter.api.Test;

import java.util.Collections;

import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;

class GitHubSCMSourceStatusChecksPropertiesTest {
@Test
void shouldReturnPropertiesAsTraitConfigured() {
Job job = mock(Job.class);
SCMFacade scmFacade = mock(SCMFacade.class);
GitHubSCMSource source = mock(GitHubSCMSource.class);
GitHubSCMSourceStatusChecksTrait trait = new GitHubSCMSourceStatusChecksTrait();

when(scmFacade.findGitHubSCMSource(job)).thenReturn(java.util.Optional.ofNullable(source));
when(source.getTraits()).thenReturn(Collections.singletonList(trait));

trait.setName("status checks");
trait.setSkip(false);

GitHubSCMSourceStatusChecksProperties properties = new GitHubSCMSourceStatusChecksProperties(scmFacade);
assertThat(properties.isApplicable(job)).isTrue();
assertThat(properties.getName(job)).isEqualTo("status checks");
assertThat(properties.isSkip(job)).isFalse();
}

@Test
void shouldReturnDefaultPropertiesWhenNoTraitAdded() {
Job job = mock(Job.class);
SCMFacade scmFacade = mock(SCMFacade.class);
GitHubSCMSource source = mock(GitHubSCMSource.class);

when(scmFacade.findGitHubSCMSource(job)).thenReturn(java.util.Optional.ofNullable(source));

GitHubSCMSourceStatusChecksProperties properties = new GitHubSCMSourceStatusChecksProperties(scmFacade);
assertThat(properties.isApplicable(job)).isTrue();
assertThat(properties.getName(job)).isEqualTo("Jenkins");
assertThat(properties.isSkip(job)).isFalse();
}

@Test
void shouldNotApplicableToJobWithoutGitHubSCMSource() {
Job job = mock(Job.class);
assertThat(new GitHubSCMSourceStatusChecksProperties().isApplicable(job)).isFalse();
}

@Test
void shouldValidStatusChecksNameInDescriptor() {
GitHubSCMSourceStatusChecksTrait.DescriptorImpl descriptor = new GitHubSCMSourceStatusChecksTrait.DescriptorImpl();

assertThat(descriptor.doCheckName("Name Should Not Be Blank"))
.hasFieldOrPropertyWithValue("kind", FormValidation.Kind.OK);

assertThat(descriptor.doCheckName(" "))
.hasFieldOrPropertyWithValue("kind", FormValidation.Kind.ERROR)
.hasMessage("Name should not be empty!");
}
}