treeherder/ui/models/job.js

193 строки
6.9 KiB
JavaScript

import { thPlatformMap } from '../helpers/constants';
import { createQueryParams, getProjectUrl } from '../helpers/url';
import { formatTaskclusterError } from '../helpers/errorMessage';
import TaskclusterModel from './taskcluster';
const uri = getProjectUrl('/jobs/');
// JobModel is the js counterpart of job
export default class JobModel {
constructor(props) {
Object.assign(this, props);
}
getTitle() {
// we want to join the group and type information together
// so we can search for it as one token (useful when
// we want to do a search on something like `fxup-esr(`)
let symbolInfo = (this.job_group_symbol === '?') ? '' :
this.job_group_symbol;
symbolInfo += '(' + this.job_type_symbol + ')';
return [
thPlatformMap[this.platform] || this.platform,
this.platform_option,
(this.job_group_name === 'unknown') ? undefined : this.job_group_name,
this.job_type_name,
symbolInfo,
].filter(item => typeof item !== 'undefined').join(' ');
}
getSearchStr() {
return [
this.getTitle(),
this.ref_data_name,
(this.signature !== this.ref_data_name) ? this.signature : undefined,
].filter(item => typeof item !== 'undefined').join(' ');
}
static getList(repoName, options, config) {
// a static method to retrieve a list of JobModel
config = config || {};
const fetch_all = config.fetch_all || false;
// The `uri` config allows to fetch a list of jobs from an arbitrary
// endpoint e.g. the similar jobs endpoint. It defaults to the job
// list endpoint.
const jobUri = config.uri || uri;
return fetch(`${jobUri}${options ? createQueryParams(options) : ''}`)
.then(async (resp) => {
if (resp.ok) {
const data = await resp.json();
let itemList;
let nextPagesJobs = [];
// if the number of elements returned equals the page size, fetch the next pages
if (fetch_all && (data.results.length === data.meta.count)) {
const count = parseInt(data.meta.count);
const offset = parseInt(data.meta.offset) + count;
const newOptions = { ...options, offset, count };
nextPagesJobs = await JobModel.getList(repoName, newOptions, config);
}
if ('job_property_names' in data) {
// the results came as list of fields
// we need to convert them to objects
itemList = data.results.map(elem => new JobModel(
data.job_property_names.reduce((prev, prop, i) => ({ ...prev, [prop]: elem[i] }), {}),
));
} else {
itemList = data.results.map(job_obj => new JobModel(job_obj));
}
return [...itemList, ...nextPagesJobs];
}
const text = await resp.text();
throw Error(text);
});
}
static get(repoName, pk, signal) {
// a static method to retrieve a single instance of JobModel
return fetch(`${uri}${pk}/`, { signal })
.then(async (response) => {
if (response.ok) {
const job = await response.json();
return new JobModel(job);
}
const text = await response.text();
throw Error(`Loading job with id ${pk} : ${text}`);
});
}
static getSimilarJobs(repoName, pk, options, config) {
config = config || {};
// The similar jobs endpoints returns the same type of objects as
// the job list endpoint, so let's reuse the getList method logic.
config.uri = `${uri}${pk}/similar_jobs/`;
return JobModel.getList(repoName, options, config);
}
static async retrigger(jobIds, repoName, getGeckoDecisionTaskId, notify) {
const jobTerm = jobIds.length > 1 ? 'jobs' : 'job';
try {
notify(`Attempting to retrigger ${jobTerm} via actions.json`, 'info');
/* eslint-disable no-await-in-loop */
for (const id of jobIds) {
const job = await JobModel.get(repoName, id);
const decisionTaskId = await getGeckoDecisionTaskId(job.push_id, repoName);
const results = await TaskclusterModel.load(decisionTaskId, job);
const retriggerTask = results.actions.find(result => result.name === 'retrigger');
try {
await TaskclusterModel.submit({
action: retriggerTask,
decisionTaskId,
taskId: results.originalTaskId,
input: {},
staticActionVariables: results.staticActionVariables,
});
} catch (e) {
// The full message is too large to fit in a Treeherder
// notification box.
notify(formatTaskclusterError(e), 'danger', { sticky: true });
}
}
/* eslint-enable no-await-in-loop */
notify(`Request sent to retrigger ${jobTerm} via action.json`, 'success');
} catch (e) {
notify(`Unable to retrigger ${jobTerm}`, 'danger', { sticky: true });
}
}
// Any jobId inside the push will do
static async cancelAll(jobId, repoName, getGeckoDecisionTaskId, notify) {
const job = await JobModel.get(repoName, jobId);
const decisionTaskId = await getGeckoDecisionTaskId(job.push_id);
const results = await TaskclusterModel.load(decisionTaskId);
const cancelAllTask = results.actions.find(result => result.name === 'cancel-all');
try {
await TaskclusterModel.submit({
action: cancelAllTask,
decisionTaskId,
input: {},
staticActionVariables: results.staticActionVariables,
});
} catch (e) {
// The full message is too large to fit in a Treeherder
// notification box.
notify(formatTaskclusterError(e), 'danger', { sticky: true });
}
notify('Request sent to cancel all jobs via action.json', 'success');
}
static async cancel(jobIds, repoName, getGeckoDecisionTaskId, notify) {
const jobTerm = jobIds.length > 1 ? 'jobs' : 'job';
try {
notify(`Attempting to cancel selected ${jobTerm} via actions.json`, 'info');
/* eslint-disable no-await-in-loop */
for (const id of jobIds) {
const job = await JobModel.get(repoName, id);
const decisionTaskId = await getGeckoDecisionTaskId(job.push_id, repoName);
const results = await TaskclusterModel.load(decisionTaskId, job);
const cancelTask = results.actions.find(result => result.name === 'cancel');
try {
await TaskclusterModel.submit({
action: cancelTask,
decisionTaskId,
taskId: results.originalTaskId,
input: {},
staticActionVariables: results.staticActionVariables,
});
} catch (e) {
// The full message is too large to fit in a Treeherder
// notification box.
notify(formatTaskclusterError(e), 'danger', { sticky: true });
}
}
/* eslint-enable no-await-in-loop */
notify(`Request sent to cancel ${jobTerm} via action.json`, 'success');
} catch (e) {
notify(`Unable to cancel ${jobTerm}`, 'danger', { sticky: true });
}
}
}