blob: 0988417b8a96c59e45cb8a700786a43707dac2a1 [file] [log] [blame]
/**
* @license
* Copyright (C) 2022 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import {
Category,
ChangeData,
CheckResult,
CheckRun,
ChecksProvider,
LinkIcon,
ResponseCode,
RunStatus,
} from '@gerritcodereview/typescript-api/checks';
import {PluginApi} from '@gerritcodereview/typescript-api/plugin';
export declare interface Config {
name: string;
url: string;
jobs: string[];
}
export declare interface Job {
exists: boolean;
builds: Build[];
}
export declare interface Build {
number: number;
url: string;
}
export declare interface BuildDetail {
number: number;
building: boolean;
result: string;
url: string;
}
export class ChecksFetcher implements ChecksProvider {
private plugin: PluginApi;
configs: Config[] | null;
constructor(pluginApi: PluginApi) {
this.plugin = pluginApi;
this.configs = null;
}
async fetch(changeData: ChangeData) {
if (this.configs === null) {
await this.fetchConfig(changeData)
.then(result => {
this.configs = result;
})
.catch(reason => {
throw reason;
});
}
if (this.configs === null) {
return {
responseCode: ResponseCode.OK,
runs: [],
};
}
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))
)
);
}
}
}
return {
responseCode: ResponseCode.OK,
runs: checkRuns,
};
}
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
.restApi()
.get<Config[]>(
`/projects/${encodeURIComponent(changeData.repo)}/${pluginName}~config`
);
}
async fetchJobInfo(url: string): Promise<Job> {
let response: Response;
try {
response = await fetch(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 fetch(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,
};
return run;
}
}