Use API exposed by gerritchangequery-plugin The gerritchangequery-plugin for Jenkins makes job runs working on a given patchset queryable. This makes searching for jobs relevant to the checks-API much more efficient. [1] https://review.gerrithub.io/admin/repos/tdraebing/gerritchangequery-plugin,general Change-Id: I96906668be85e998f9a2aedcd5e87531f393d966
diff --git a/src/main/java/com/google/gerrit/plugins/checks/jenkins/GetConfig.java b/src/main/java/com/google/gerrit/plugins/checks/jenkins/GetConfig.java index 245b5de..c6d71a7 100644 --- a/src/main/java/com/google/gerrit/plugins/checks/jenkins/GetConfig.java +++ b/src/main/java/com/google/gerrit/plugins/checks/jenkins/GetConfig.java
@@ -30,7 +30,6 @@ class GetConfig implements RestReadView<ProjectResource> { private static final String JENKINS_SECTION = "jenkins"; private static final String JENKINS_URL_KEY = "url"; - private static final String JENKINS_JOB_KEY = "job"; private final PluginConfigFactory config; private final String pluginName; @@ -50,7 +49,6 @@ JenkinsChecksConfig jenkinsCfg = new JenkinsChecksConfig(); jenkinsCfg.name = instance; jenkinsCfg.url = cfg.getString(JENKINS_SECTION, instance, JENKINS_URL_KEY); - jenkinsCfg.jobs = cfg.getStringList(JENKINS_SECTION, instance, JENKINS_JOB_KEY); result.add(jenkinsCfg); } return Response.ok(result); @@ -59,9 +57,5 @@ static class JenkinsChecksConfig { String name; String url; - //TODO(Thomas): It would be preferable to not have to configure any jobs, but - // to let Jenkins know which Jobs worked on a PatchSet. This will require - // additional changes in Jenkins however. - String[] jobs; } }
diff --git a/src/main/resources/Documentation/about.md b/src/main/resources/Documentation/about.md index 6d4f006..5633218 100644 --- a/src/main/resources/Documentation/about.md +++ b/src/main/resources/Documentation/about.md
@@ -1,23 +1,17 @@ Implementation of checks UI for Jenkins CI servers +================================================== This plugin registers a `ChecksProvider` with the Gerrit UI that will fetch build results for a change from configured Jenkins servers and provide them to the checks panel in a change screen. -Limitations +Requirements ----------- -Currently, only multibranch-pipeline jobs using the Gerrit SCM-source provided -by the link:https://plugins.jenkins.io/gerrit-code-review/[gerrit-code-review]- -plugin are supported. +The Jenkins servers have to support CORS-requests. This can for example be achieved +by using a reverse proxy that sets the `Access-Control-Allow-Origin` and +`Access-Control-Allow-Credentials` header. -The Jenkins Remote Access API does not provide all the information that could -be displayed in Gerrit, e.g. a result summary. Thus, as of now, this plugin -does not make full use of the checks API. As of right now, it will display the -following data in the UI: - -- Builds for the selected patchset including previous attempts -- Status of the build -- Result of the build -- Link to the build and its logs -- A result summary stating the result category used by the CI (e.g. `Result: UNSTABLE`) +The Jenkins server additionally needs to install the +[gerrit-change-query](https://review.gerrithub.io/admin/repos/tdraebing/gerritchangequery-plugin,general) +plugin that enables querying for job runs working on a given patchset.
diff --git a/src/main/resources/Documentation/config.md b/src/main/resources/Documentation/config.md index d87e2a0..50d9922 100644 --- a/src/main/resources/Documentation/config.md +++ b/src/main/resources/Documentation/config.md
@@ -11,8 +11,3 @@ jenkins.NAME.url : Base URL of Jenkins including protocol, e.g. https://gerrit-ci.gerritforge.com - -jenkins:NAME:job -: Name of the multibranch pipeline job using the Gerrit SCM source that should - be queried for a build for a patchset, e.g. Gerrit-verifier-pipeline. Can be - defined multiple times.
diff --git a/web/fetcher.ts b/web/fetcher.ts index 0eef435..a82eaca 100644 --- a/web/fetcher.ts +++ b/web/fetcher.ts
@@ -15,12 +15,12 @@ * limitations under the License. */ import { - Category, + Action, + ActionResult, ChangeData, CheckResult, CheckRun, ChecksProvider, - LinkIcon, ResponseCode, RunStatus, } from '@gerritcodereview/typescript-api/checks'; @@ -29,23 +29,35 @@ export declare interface Config { name: string; url: string; - jobs: string[]; } -export declare interface Job { - exists: boolean; - builds: Build[]; +export declare interface JenkinsCheckRun { + actions: JenkinsAction[]; + attempt: number; + change: number; + checkDescription: string; + checkLink: string; + checkName: string; + externalId: string; + finishedTimestamp: string; + labelName: string; + patchset: number; + results: CheckResult[]; + scheduledTimestamp: string; + startedTimestamp: string; + status: RunStatus; + statusDesciption: string; + statusLink: string; } -export declare interface Build { - number: number; - url: string; -} - -export declare interface BuildDetail { - number: number; - building: boolean; - result: string; +export declare interface JenkinsAction { + data: string; + disabled: boolean; + method: string; + name: string; + primary: boolean; + summary: boolean; + tooltip: string; url: string; } @@ -77,23 +89,17 @@ } const checkRuns: CheckRun[] = []; for (const jenkins of this.configs) { - for (const jenkinsJob of jenkins.jobs) { - // TODO: Requests to Jenkins should be proxied through the Gerrit backend - // to avoid CORS requests. - const job: Job = await this.fetchJobInfo( - this.buildJobApiUrl(jenkins.url, jenkinsJob, changeData) - ); - - for (const build of job.builds) { - checkRuns.push( - this.convert( - jenkinsJob, - changeData, - await this.fetchBuildInfo(this.buildBuildApiUrl(build.url)) - ) - ); - } - } + // TODO: Requests to Jenkins should be proxied through the Gerrit backend + // to avoid CORS requests. + await this.fetchFromJenkins( + `${jenkins.url}/gerrit/check-runs?change=${changeData.changeNumber}&patchset=${changeData.patchsetNumber}` + ) + .then(response => response.json()) + .then(data => { + data.runs.forEach((run: JenkinsCheckRun) => { + checkRuns.push(this.convert(run)); + }); + }); } return { @@ -102,33 +108,6 @@ }; } - buildJobApiUrl( - jenkinsUrl: string, - jenkinsJob: string, - changeData: ChangeData - ) { - let changeShard: string = changeData.changeNumber.toString().slice(-2); - if (changeShard.length === 1) { - changeShard = '0' + changeShard; - } - return ( - jenkinsUrl + - '/job/' + - jenkinsJob + - '/job/' + - changeShard + - '%252F' + - changeData.changeNumber.toString() + - '%252F' + - changeData.patchsetNumber.toString() + - '/api/json?tree=builds[number,url]' - ); - } - - buildBuildApiUrl(baseUrl: string) { - return baseUrl + 'api/json?tree=number,result,building,url'; - } - fetchConfig(changeData: ChangeData): Promise<Config[]> { const pluginName = encodeURIComponent(this.plugin.getPluginName()); return this.plugin @@ -138,86 +117,54 @@ ); } - async fetchJobInfo(url: string): Promise<Job> { - let response: Response; - try { - response = await this.fetchFromJenkins(url); - if (!response.ok) { - throw response.statusText; - } - } catch (e) { - return { - exists: false, - builds: [], - }; - } - const job: Job = await response.json(); - return job; - } - - async fetchBuildInfo(url: string): Promise<BuildDetail> { - const response = await this.fetchFromJenkins(url); - if (!response.ok) { - throw response.statusText; - } - const build: BuildDetail = await response.json(); - return build; - } - - convert( - checkName: string, - changeData: ChangeData, - build: BuildDetail - ): CheckRun { - let status: RunStatus; - const results: CheckResult[] = []; - - if (build.result !== null) { - status = RunStatus.COMPLETED; - let resultCategory: Category; - switch (build.result) { - case 'SUCCESS': - resultCategory = Category.SUCCESS; - break; - case 'FAILURE': - resultCategory = Category.ERROR; - break; - default: - resultCategory = Category.WARNING; - } - - const checkResult: CheckResult = { - category: resultCategory, - summary: `Result: ${build.result}`, - links: [ - { - url: build.url + '/console', - primary: true, - icon: LinkIcon.EXTERNAL, - }, - ], - }; - results.push(checkResult); - } else if (build.building) { - status = RunStatus.RUNNING; - } else { - status = RunStatus.RUNNABLE; - } - - const run: CheckRun = { - change: changeData.changeNumber, - patchset: changeData.patchsetNumber, - attempt: build.number, - checkName, - checkLink: build.url, - status, - results, + convert(run: JenkinsCheckRun): CheckRun { + const convertedRun: CheckRun = { + attempt: run.attempt, + change: run.change, + checkDescription: run.checkDescription, + checkLink: run.checkLink, + checkName: run.checkName, + externalId: run.externalId, + finishedTimestamp: new Date(run.finishedTimestamp), + labelName: run.labelName, + patchset: run.patchset, + results: run.results, + scheduledTimestamp: new Date(run.scheduledTimestamp), + startedTimestamp: new Date(run.startedTimestamp), + status: run.status, + statusDescription: run.statusDesciption, + statusLink: run.statusLink, }; - return run; + const actions: Action[] = []; + for (const action of run.actions) { + actions.push({ + name: action.name, + tooltip: action.tooltip, + primary: action.primary, + summary: action.summary, + disabled: action.disabled, + callback: () => this.rerun(action.url), + }); + } + convertedRun.actions = actions; + return convertedRun; } private fetchFromJenkins(url: string): Promise<Response> { - const options: RequestInit = { credentials: 'include' }; + const options: RequestInit = {credentials: 'include'}; return fetch(url, options); } + + private rerun(url: string): Promise<ActionResult> { + return this.fetchFromJenkins(url) + .then(_ => { + return { + message: 'Run triggered.', + shouldReload: true, + }; + }) + .catch(e => { + return {message: `Triggering the run failed: ${e.message}`}; + }); + } }