Bug 1566077 - Improve getting decision task ID (#5360)

This commit is contained in:
Cameron Dawson 2019-09-17 14:42:57 -07:00 коммит произвёл GitHub
Родитель b462c6be5e
Коммит aff331f3d3
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: 4AEE18F83AFDEB23
15 изменённых файлов: 247 добавлений и 125 удалений

Просмотреть файл

@ -1,9 +1,11 @@
import { fetchMock } from 'fetch-mock';
import JobModel from '../../../ui/models/job';
import { decisionTaskIdCache } from '../../../ui/models/push';
import { getApiUrl } from '../../../ui/helpers/url';
import paginatedJobListFixtureOne from '../mock/job_list/pagination/page_1';
import paginatedJobListFixtureTwo from '../mock/job_list/pagination/page_2';
import { getProjectUrl } from '../../../ui/helpers/location';
describe('JobModel', () => {
afterEach(() => {
@ -40,12 +42,40 @@ describe('JobModel', () => {
});
});
describe('retriggering ', () => {
describe('Taskcluster actions', () => {
const decisionTaskMap = {
'526443': { id: 'LVTawdmFR2-uJiWWS2NxSw', run: '0' },
};
const tcActionsUrl =
'https://queue.taskcluster.net/v1/task/LVTawdmFR2-uJiWWS2NxSw/artifacts/public%2Factions.json';
const tcTaskUrl = 'https://queue.taskcluster.net/v1/task/TASKID';
const decisionTaskMapUrl = getProjectUrl(
'/push/decisiontask/?push_ids=526443',
'autoland',
);
const notify = () => {};
const testJobs = [
{ id: 123, push_id: 526443, job_type_name: 'foo', task_id: 'TASKID' },
];
beforeEach(() => {
fetchMock.mock(
getApiUrl('/jobs/?push_id=526443'),
paginatedJobListFixtureOne,
);
fetchMock.mock(
getApiUrl('/taskclustermetadata/?job_ids=123'),
paginatedJobListFixtureOne,
);
fetchMock.mock(decisionTaskMapUrl, decisionTaskMap);
fetchMock.get(tcActionsUrl, { version: 1, actions: [{ name: 'foo' }] });
fetchMock.get(tcTaskUrl, {});
// Must clear the cache, because we save each time we
// call the API for a decision task id.
Object.keys(decisionTaskIdCache).forEach(
prop => delete decisionTaskIdCache[prop],
);
});
test('jobs should have required fields', async () => {
@ -55,5 +85,61 @@ describe('JobModel', () => {
expect(signature).toBe('2aa083621bb989d6acf1151667288d5fe9616178');
expect(job_type_name).toBe('Gecko Decision Task');
});
test('retrigger uses passed-in decisionTaskMap', async () => {
await JobModel.retrigger(
testJobs,
'autoland',
notify,
1,
decisionTaskMap,
);
expect(fetchMock.called(decisionTaskMapUrl)).toBe(false);
expect(fetchMock.called(tcTaskUrl)).toBe(false);
expect(fetchMock.called(tcActionsUrl)).toBe(true);
});
test('retrigger calls for decision task when not passed-in', async () => {
await JobModel.retrigger(testJobs, 'autoland', notify, 1);
expect(fetchMock.called(decisionTaskMapUrl)).toBe(true);
expect(fetchMock.called(tcTaskUrl)).toBe(false);
expect(fetchMock.called(tcActionsUrl)).toBe(true);
});
test('cancel uses passed-in decisionTask', async () => {
await JobModel.cancel(testJobs, 'autoland', () => {}, decisionTaskMap);
expect(fetchMock.called(decisionTaskMapUrl)).toBe(false);
expect(fetchMock.called(tcTaskUrl)).toBe(true);
expect(fetchMock.called(tcActionsUrl)).toBe(true);
});
test('cancel calls for decision task when not passed-in', async () => {
await JobModel.cancel(testJobs, 'autoland', () => {});
expect(fetchMock.called(decisionTaskMapUrl)).toBe(true);
expect(fetchMock.called(tcTaskUrl)).toBe(true);
expect(fetchMock.called(tcActionsUrl)).toBe(true);
});
test('cancelAll uses passed-in decisionTask', async () => {
const decisionTask = { id: 'LVTawdmFR2-uJiWWS2NxSw', run: '0' };
await JobModel.cancelAll(526443, 'autoland', () => {}, decisionTask);
expect(fetchMock.called(decisionTaskMapUrl)).toBe(false);
expect(fetchMock.called(tcTaskUrl)).toBe(false);
expect(fetchMock.called(tcActionsUrl)).toBe(true);
});
test('cancelAll calls for decision task when not passed-in', async () => {
await JobModel.cancelAll(526443, 'autoland', () => {});
expect(fetchMock.called(decisionTaskMapUrl)).toBe(true);
expect(fetchMock.called(tcTaskUrl)).toBe(false);
expect(fetchMock.called(tcActionsUrl)).toBe(true);
});
});
});

Просмотреть файл

@ -9,11 +9,14 @@ describe('PushModel', () => {
});
describe('taskcluster actions', () => {
const decisionTaskUrl = getProjectUrl(
'/push/decisiontask/?push_ids=548880',
'autoland',
);
beforeEach(() => {
fetchMock.mock(
getProjectUrl('/push/decisiontask/?push_ids=548880', 'autoland'),
{ '548880': { id: 'U-lI3jzPTkWFplfJPz6cJA', run: '0' } },
);
fetchMock.mock(decisionTaskUrl, {
'548880': { id: 'U-lI3jzPTkWFplfJPz6cJA', run: '0' },
});
});
test('getDecisionTaskId', async () => {
@ -26,6 +29,11 @@ describe('PushModel', () => {
id: 'U-lI3jzPTkWFplfJPz6cJA',
run: '0',
});
expect(fetchMock.calls(decisionTaskUrl)).toHaveLength(1);
await PushModel.getDecisionTaskId(548880, () => {});
// on second try, it was cached. So we still have just 1 call
expect(fetchMock.calls(decisionTaskUrl)).toHaveLength(1);
});
test('getDecisionTaskMap', async () => {

Просмотреть файл

@ -108,6 +108,7 @@ class JobsViewSet(viewsets.ReadOnlyModelViewSet):
'job_group',
'machine_platform',
'signature',
'taskcluster_metadata',
]
_query_field_names = [
'submit_time',
@ -128,6 +129,9 @@ class JobsViewSet(viewsets.ReadOnlyModelViewSet):
'signature__signature',
'state',
'tier',
'taskcluster_metadata__task_id',
'taskcluster_metadata__retry_id',
]
_output_field_names = [
'failure_classification_id',
@ -143,6 +147,8 @@ class JobsViewSet(viewsets.ReadOnlyModelViewSet):
'signature',
'state',
'tier',
'task_id',
'retry_id',
'duration',
'platform_option',
]
@ -177,7 +183,7 @@ class JobsProjectViewSet(viewsets.ViewSet):
'machine_platform',
'machine',
'signature',
'repository'
'repository',
]
_property_query_mapping = [
@ -289,6 +295,9 @@ class JobsProjectViewSet(viewsets.ViewSet):
resp["platform_option"] = platform_option
try:
resp['task_id'] = job.taskcluster_metadata.task_id
resp['retry_id'] = job.taskcluster_metadata.retry_id
# Keep for backwards compatability
resp['taskcluster_metadata'] = {
'task_id': job.taskcluster_metadata.task_id,
'retry_id': job.taskcluster_metadata.retry_id

Просмотреть файл

@ -22,7 +22,6 @@ import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { faCheckSquare } from '@fortawesome/free-regular-svg-icons';
import { formatTaskclusterError } from '../helpers/errorMessage';
import PushModel from '../models/push';
import TaskclusterModel from '../models/taskcluster';
import { notify } from './redux/stores/notifications';
@ -46,11 +45,8 @@ class CustomJobActions extends React.PureComponent {
}
async componentDidMount() {
const { pushId, job, notify } = this.props;
const { id: decisionTaskId } = await PushModel.getDecisionTaskId(
pushId,
notify,
);
const { pushId, job, notify, decisionTaskMap } = this.props;
const { id: decisionTaskId } = decisionTaskMap[pushId];
TaskclusterModel.load(decisionTaskId, job).then(results => {
const {
@ -308,6 +304,7 @@ CustomJobActions.propTypes = {
isLoggedIn: PropTypes.bool.isRequired,
notify: PropTypes.func.isRequired,
toggle: PropTypes.func.isRequired,
decisionTaskMap: PropTypes.object.isRequired,
job: PropTypes.object,
};
@ -315,7 +312,11 @@ CustomJobActions.defaultProps = {
job: null,
};
const mapStateToProps = ({ pushes: { decisionTaskMap } }) => ({
decisionTaskMap,
});
export default connect(
null,
mapStateToProps,
{ notify },
)(CustomJobActions);

Просмотреть файл

@ -211,8 +211,7 @@ class DetailsPanel extends React.Component {
phSeriesResult,
]) => {
// This version of the job has more information than what we get in the main job list. This
// is what we'll pass to the rest of the details panel. It has extra fields like
// taskcluster_metadata.
// is what we'll pass to the rest of the details panel.
// Don't update the job instance in the greater job field so as to not add the memory overhead
// of all the extra fields in ``selectedJobFull``. It's not that much for just one job, but as
// one selects job after job, over the course of a day, it can add up. Therefore, we keep

Просмотреть файл

@ -198,12 +198,17 @@ class PinBoard extends React.Component {
};
cancelAllPinnedJobs = () => {
const { notify, repoName, pinnedJobs } = this.props;
const { notify, repoName, pinnedJobs, decisionTaskMap } = this.props;
if (
window.confirm('This will cancel all the selected jobs. Are you sure?')
) {
JobModel.cancel(Object.values(pinnedJobs), repoName, notify);
JobModel.cancel(
Object.values(pinnedJobs),
repoName,
notify,
decisionTaskMap,
);
this.unPinAll();
}
};
@ -348,10 +353,11 @@ class PinBoard extends React.Component {
}
};
retriggerAllPinnedJobs = () => {
const { pinnedJobs, notify, repoName } = this.props;
retriggerAllPinnedJobs = async () => {
const { pinnedJobs, notify, repoName, decisionTaskMap } = this.props;
const jobs = Object.values(pinnedJobs);
JobModel.retrigger(Object.values(pinnedJobs), repoName, notify);
JobModel.retrigger(jobs, repoName, notify, 1, decisionTaskMap);
};
render() {
@ -630,6 +636,7 @@ class PinBoard extends React.Component {
PinBoard.propTypes = {
recalculateUnclassifiedCounts: PropTypes.func.isRequired,
decisionTaskMap: PropTypes.object.isRequired,
classificationTypes: PropTypes.array.isRequired,
isLoggedIn: PropTypes.bool.isRequired,
isPinBoardVisible: PropTypes.bool.isRequired,
@ -659,7 +666,7 @@ PinBoard.defaultProps = {
};
const mapStateToProps = ({
pushes: { revisionTips },
pushes: { revisionTips, decisionTaskMap },
pinnedJobs: {
isPinBoardVisible,
pinnedJobs,
@ -669,6 +676,7 @@ const mapStateToProps = ({
},
}) => ({
revisionTips,
decisionTaskMap,
isPinBoardVisible,
pinnedJobs,
pinnedJobBugs,

Просмотреть файл

@ -16,7 +16,6 @@ import { formatTaskclusterError } from '../../../helpers/errorMessage';
import { isReftest, isPerfTest, isTestIsolatable } from '../../../helpers/job';
import { getInspectTaskUrl, getReftestUrl } from '../../../helpers/url';
import JobModel from '../../../models/job';
import PushModel from '../../../models/push';
import TaskclusterModel from '../../../models/taskcluster';
import CustomJobActions from '../../CustomJobActions';
import { notify } from '../../redux/stores/notifications';
@ -78,15 +77,12 @@ class ActionBar extends React.PureComponent {
};
createGeckoProfile = async () => {
const { user, selectedJobFull, notify } = this.props;
const { user, selectedJobFull, notify, decisionTaskMap } = this.props;
if (!user.isLoggedIn) {
return notify('Must be logged in to create a gecko profile', 'danger');
}
const { id: decisionTaskId } = await PushModel.getDecisionTaskId(
selectedJobFull.push_id,
notify,
);
const decisionTaskId = decisionTaskMap[selectedJobFull.push_id];
TaskclusterModel.load(decisionTaskId, selectedJobFull).then(results => {
try {
const geckoprofile = getAction(results.actions, 'geckoprofile');
@ -126,8 +122,8 @@ class ActionBar extends React.PureComponent {
});
};
retriggerJob = jobs => {
const { user, repoName, notify } = this.props;
retriggerJob = async jobs => {
const { user, repoName, notify, decisionTaskMap } = this.props;
if (!user.isLoggedIn) {
return notify('Must be logged in to retrigger a job', 'danger');
@ -145,11 +141,11 @@ class ActionBar extends React.PureComponent {
});
});
JobModel.retrigger(jobs, repoName, notify);
JobModel.retrigger(jobs, repoName, notify, 1, decisionTaskMap);
};
backfillJob = async () => {
const { user, selectedJobFull, notify } = this.props;
const { user, selectedJobFull, notify, decisionTaskMap } = this.props;
if (!this.canBackfill()) {
return;
@ -167,10 +163,8 @@ class ActionBar extends React.PureComponent {
return;
}
const { id: decisionTaskId } = await PushModel.getDecisionTaskId(
selectedJobFull.push_id,
notify,
);
const { id: decisionTaskId } = decisionTaskMap[selectedJobFull.push_id];
TaskclusterModel.load(decisionTaskId, selectedJobFull).then(results => {
try {
const backfilltask = getAction(results.actions, 'backfill');
@ -198,11 +192,8 @@ class ActionBar extends React.PureComponent {
};
isolateJob = async () => {
const { user, selectedJobFull, notify } = this.props;
const { id: decisionTaskId } = await PushModel.getDecisionTaskId(
selectedJobFull.push_id,
notify,
);
const { user, selectedJobFull, notify, decisionTaskMap } = this.props;
const { id: decisionTaskId } = decisionTaskMap[selectedJobFull.push_id];
if (!isTestIsolatable(selectedJobFull)) {
return;
@ -317,8 +308,7 @@ class ActionBar extends React.PureComponent {
};
createInteractiveTask = async () => {
const { user, selectedJobFull, repoName, notify } = this.props;
const jobId = selectedJobFull.id;
const { user, selectedJobFull, notify, decisionTaskMap } = this.props;
if (!user.isLoggedIn) {
return notify(
@ -327,12 +317,11 @@ class ActionBar extends React.PureComponent {
);
}
const job = await JobModel.get(repoName, jobId);
const { id: decisionTaskId } = await PushModel.getDecisionTaskId(
job.push_id,
notify,
const { id: decisionTaskId } = decisionTaskMap[selectedJobFull.push_id];
const results = await TaskclusterModel.load(
decisionTaskId,
selectedJobFull,
);
const results = await TaskclusterModel.load(decisionTaskId, job);
try {
const interactiveTask = getAction(results.actions, 'create-interactive');
@ -360,7 +349,7 @@ class ActionBar extends React.PureComponent {
};
cancelJobs = jobs => {
const { user, repoName, notify } = this.props;
const { user, repoName, notify, decisionTaskMap } = this.props;
if (!user.isLoggedIn) {
return notify('Must be logged in to cancel a job', 'danger');
@ -369,6 +358,7 @@ class ActionBar extends React.PureComponent {
jobs.filter(({ state }) => state === 'pending' || state === 'running'),
repoName,
notify,
decisionTaskMap,
);
};
@ -490,16 +480,14 @@ class ActionBar extends React.PureComponent {
Backfill
</span>
</li>
{selectedJobFull.taskcluster_metadata && (
{selectedJobFull.task_id && (
<React.Fragment>
<li>
<a
target="_blank"
rel="noopener noreferrer"
className="dropdown-item pl-4"
href={getInspectTaskUrl(
selectedJobFull.taskcluster_metadata.task_id,
)}
href={getInspectTaskUrl(selectedJobFull.task_id)}
>
Inspect Task
</a>
@ -561,6 +549,7 @@ class ActionBar extends React.PureComponent {
ActionBar.propTypes = {
pinJob: PropTypes.func.isRequired,
decisionTaskMap: PropTypes.object.isRequired,
user: PropTypes.object.isRequired,
repoName: PropTypes.string.isRequired,
selectedJobFull: PropTypes.object.isRequired,
@ -579,7 +568,11 @@ ActionBar.defaultProps = {
jobLogUrls: [],
};
const mapStateToProps = ({ pushes: { decisionTaskMap } }) => ({
decisionTaskMap,
});
export default connect(
null,
mapStateToProps,
{ notify, pinJob },
)(ActionBar);

Просмотреть файл

@ -11,7 +11,6 @@ import {
import { getGroupMapKey } from '../../helpers/aggregateId';
import { getAllUrlParams, getUrlParam } from '../../helpers/location';
import JobModel from '../../models/job';
import PushModel from '../../models/push';
import RunnableJobModel from '../../models/runnableJob';
import { getRevisionTitle } from '../../helpers/revision';
import { getPercentComplete } from '../../helpers/display';
@ -317,12 +316,11 @@ class Push extends React.PureComponent {
};
showRunnableJobs = async () => {
const { push, repoName, notify } = this.props;
const { push, repoName, notify, decisionTaskMap } = this.props;
try {
const decisionTaskId = await PushModel.getDecisionTaskId(push.id, notify);
const jobList = await RunnableJobModel.getList(repoName, {
decision_task_id: decisionTaskId,
decisionTask: decisionTaskMap[push.id],
push_id: push.id,
});
@ -354,7 +352,7 @@ class Push extends React.PureComponent {
};
showFuzzyJobs = async () => {
const { push, repoName, notify } = this.props;
const { push, repoName, notify, decisionTaskMap } = this.props;
const createRegExp = (str, opts) =>
new RegExp(str.raw[0].replace(/\s/gm, ''), opts || '');
const excludedJobNames = createRegExp`
@ -367,11 +365,9 @@ class Push extends React.PureComponent {
test-verify|test-windows10-64-ux|toolchain|upload-generated-sources)`;
try {
const decisionTaskId = await PushModel.getDecisionTaskId(push.id, notify);
notify('Fetching runnable jobs... This could take a while...');
let fuzzyJobList = await RunnableJobModel.getList(repoName, {
decision_task_id: decisionTaskId,
decisionTask: decisionTaskMap[push.id],
});
fuzzyJobList = [
...new Set(
@ -390,7 +386,6 @@ class Push extends React.PureComponent {
this.setState({
fuzzyJobList,
filteredFuzzyList,
decisionTaskId: decisionTaskId.id,
});
this.toggleFuzzyModal();
} catch (error) {
@ -443,12 +438,12 @@ class Push extends React.PureComponent {
groupCountsExpanded,
isOnlyRevision,
pushHealthVisibility,
decisionTaskMap,
} = this.props;
const {
fuzzyJobList,
fuzzyModal,
filteredFuzzyList,
decisionTaskId,
watched,
runnableVisible,
pushGroupState,
@ -459,6 +454,8 @@ class Push extends React.PureComponent {
} = this.state;
const { id, push_timestamp, revision, author } = push;
const tipRevision = push.revisions[0];
const decisionTask = decisionTaskMap[push.id];
const decisionTaskId = decisionTask ? decisionTask.id : null;
if (isOnlyRevision) {
this.setSingleRevisionWindowTitle();
@ -554,10 +551,14 @@ Push.propTypes = {
notify: PropTypes.func.isRequired,
isOnlyRevision: PropTypes.bool.isRequired,
pushHealthVisibility: PropTypes.string.isRequired,
decisionTaskMap: PropTypes.object.isRequired,
};
const mapStateToProps = ({ pushes: { allUnclassifiedFailureCount } }) => ({
const mapStateToProps = ({
pushes: { allUnclassifiedFailureCount, decisionTaskMap },
}) => ({
allUnclassifiedFailureCount,
decisionTaskMap,
});
export default connect(

Просмотреть файл

@ -142,6 +142,7 @@ class PushHeader extends React.Component {
selectedRunnableJobs,
hideRunnableJobs,
notify,
decisionTaskMap,
} = this.props;
if (
@ -152,10 +153,7 @@ class PushHeader extends React.Component {
return;
}
if (isLoggedIn) {
const { id: decisionTaskId } = await PushModel.getDecisionTaskId(
pushId,
notify,
);
const { id: decisionTaskId } = decisionTaskMap[pushId];
PushModel.triggerNewJobs(selectedRunnableJobs, decisionTaskId)
.then(result => {
@ -172,18 +170,22 @@ class PushHeader extends React.Component {
};
cancelAllJobs = () => {
const { notify, repoName } = this.props;
if (
window.confirm(
'This will cancel all pending and running jobs for this push. It cannot be undone! Are you sure?',
)
) {
const { push, isLoggedIn } = this.props;
const {
notify,
repoName,
push,
isLoggedIn,
decisionTaskMap,
} = this.props;
if (!isLoggedIn) return;
JobModel.cancelAll(push.id, repoName, notify);
JobModel.cancelAll(push.id, repoName, notify, decisionTaskMap[push.id]);
}
};
@ -407,6 +409,7 @@ PushHeader.propTypes = {
notify: PropTypes.func.isRequired,
jobCounts: PropTypes.object.isRequired,
pushHealthVisibility: PropTypes.string.isRequired,
decisionTaskMap: PropTypes.object.isRequired,
watchState: PropTypes.string,
};
@ -414,7 +417,11 @@ PushHeader.defaultProps = {
watchState: 'none',
};
const mapStateToProps = ({ pushes: { decisionTaskMap } }) => ({
decisionTaskMap,
});
export default connect(
null,
mapStateToProps,
{ notify, setSelectedJob, pinJobs },
)(PushHeader);

Просмотреть файл

@ -168,11 +168,28 @@ const fetchNewJobs = () => {
};
};
const doUpdateJobMap = (jobList, jobMap, pushList) => {
const doUpdateJobMap = (jobList, jobMap, decisionTaskMap, pushList) => {
if (jobList.length) {
// lodash ``keyBy`` is significantly faster than doing a ``reduce``
return {
jobMap: { ...jobMap, ...keyBy(jobList, 'id') },
decisionTaskMap: {
...decisionTaskMap,
...keyBy(
jobList
.filter(
job =>
job.job_type_name.includes('Decision Task') &&
job.result === 'success',
)
.map(job => ({
push_id: job.push_id,
id: job.task_id,
run: job.retry_id,
})),
'push_id',
),
},
jobsLoaded: pushList.every(push => push.jobsLoaded),
};
}
@ -357,6 +374,7 @@ export const updateRange = range => {
export const initialState = {
pushList: [],
jobMap: {},
decisionTaskMap: {},
revisionTips: [],
jobsLoaded: false,
loadingPushes: true,
@ -367,7 +385,7 @@ export const initialState = {
export const reducer = (state = initialState, action) => {
const { jobList, pushResults, setFromchange } = action;
const { pushList, jobMap } = state;
const { pushList, jobMap, decisionTaskMap } = state;
switch (action.type) {
case LOADING:
return { ...state, loadingPushes: true };
@ -380,7 +398,10 @@ export const reducer = (state = initialState, action) => {
case RECALCULATE_UNCLASSIFIED_COUNTS:
return { ...state, ...doRecalculateUnclassifiedCounts(jobMap) };
case UPDATE_JOB_MAP:
return { ...state, ...doUpdateJobMap(jobList, jobMap, pushList) };
return {
...state,
...doUpdateJobMap(jobList, jobMap, decisionTaskMap, pushList),
};
default:
return state;
}

Просмотреть файл

@ -1,6 +1,5 @@
import { slugid } from 'taskcluster-client-web';
import groupBy from 'lodash/groupBy';
import keyBy from 'lodash/keyBy';
import { createQueryParams, getApiUrl } from '../helpers/url';
import { formatTaskclusterError } from '../helpers/errorMessage';
@ -84,14 +83,22 @@ export default class JobModel {
return JobModel.getList(options, config);
}
static async retrigger(jobs, repoName, notify, times = 1) {
static async retrigger(
jobs,
repoName,
notify,
times = 1,
decisionTaskIdMap = null,
) {
const jobTerm = jobs.length > 1 ? 'jobs' : 'job';
try {
notify(`Attempting to retrigger/add ${jobTerm} via actions.json`, 'info');
const pushIds = [...new Set(jobs.map(job => job.push_id))];
const taskIdMap = await PushModel.getDecisionTaskMap(pushIds, notify);
const taskIdMap =
decisionTaskIdMap ||
(await PushModel.getDecisionTaskMap(pushIds, notify));
const uniquePerPushJobs = groupBy(jobs, job => job.push_id);
// eslint-disable-next-line no-unused-vars
@ -147,11 +154,10 @@ export default class JobModel {
}
}
static async cancelAll(pushId, repoName, notify) {
const { id: decisionTaskId } = await PushModel.getDecisionTaskId(
pushId,
notify,
);
static async cancelAll(pushId, repoName, notify, decisionTask) {
const { id: decisionTaskId } =
decisionTask || (await PushModel.getDecisionTaskId(pushId, notify));
const results = await TaskclusterModel.load(decisionTaskId);
try {
@ -172,27 +178,14 @@ export default class JobModel {
notify('Request sent to cancel all jobs via action.json', 'success');
}
static async cancel(jobs, repoName, notify) {
static async cancel(jobs, repoName, notify, decisionTaskIdMap) {
const jobTerm = jobs.length > 1 ? 'jobs' : 'job';
const taskIdMap = await PushModel.getDecisionTaskMap(
[...new Set(jobs.map(job => job.push_id))],
notify,
);
// Only the selected job will have the ``taskcluster_metadata`` field
// which has the task_id we need. So we must fetch all the task_ids
// for the jobs in this list.
const jobIds = jobs.map(job => job.id);
const { data, failureStatus } = await getData(
`${getApiUrl('/taskclustermetadata/')}?job_ids=${jobIds.join(',')}`,
);
if (failureStatus) {
notify('Unable to cancel: Error getting task ids for jobs.', 'danger', {
sticky: true,
});
return;
}
const tcMetadataMap = keyBy(data, 'job');
const taskIdMap =
decisionTaskIdMap ||
(await PushModel.getDecisionTaskMap(
[...new Set(jobs.map(job => job.push_id))],
notify,
));
try {
notify(
@ -203,7 +196,6 @@ export default class JobModel {
/* eslint-disable no-await-in-loop */
// eslint-disable-next-line no-unused-vars
for (const job of jobs) {
job.taskcluster_metadata = tcMetadataMap[job.id];
const decisionTaskId = taskIdMap[job.push_id].id;
const results = await TaskclusterModel.load(decisionTaskId, job);

Просмотреть файл

@ -28,7 +28,7 @@ const convertDates = function convertDates(locationParams) {
return locationParams;
};
const decisionTaskIdCache = {};
export const decisionTaskIdCache = {};
export default class PushModel {
static async getList(options = {}) {
@ -65,11 +65,9 @@ export default class PushModel {
return fetch(getProjectUrl(`${pushEndpoint}${pk}/`, repoName));
}
static async triggerMissingJobs(pushId, notify) {
const { id: decisionTaskId } = await PushModel.getDecisionTaskId(
pushId,
notify,
);
static async triggerMissingJobs(pushId, notify, decisionTask) {
const decisionTaskId =
decisionTask || (await PushModel.getDecisionTaskId(pushId, notify)).id;
return TaskclusterModel.load(decisionTaskId).then(results => {
const actionTaskId = slugid();
@ -102,11 +100,9 @@ export default class PushModel {
});
}
static async triggerAllTalosJobs(times, pushId, notify) {
const { id: decisionTaskId } = await PushModel.getDecisionTaskId(
pushId,
notify,
);
static async triggerAllTalosJobs(times, pushId, notify, decisionTask) {
const decisionTaskId =
decisionTask || (await PushModel.getDecisionTaskId(pushId, notify)).id;
return TaskclusterModel.load(decisionTaskId).then(results => {
const actionTaskId = slugid();

Просмотреть файл

@ -8,7 +8,8 @@ export default class RunnableJobModel {
}
static async getList(repoName, params) {
const uri = getRunnableJobsURL(params.decision_task_id);
const { push_id, decisionTask } = params;
const uri = getRunnableJobsURL(decisionTask);
const rawJobs = await fetch(uri).then(response => response.json());
return Object.entries(rawJobs).map(([key, value]) =>
@ -24,8 +25,8 @@ export default class RunnableJobModel {
signature: key,
state: 'runnable',
result: 'runnable',
push_id: params.push_id,
id: escapeId(params.push_id + key),
push_id,
id: escapeId(push_id + key),
}),
);
}

Просмотреть файл

@ -88,8 +88,8 @@ export default class TaskclusterModel {
let originalTaskId;
let originalTaskPromise = Promise.resolve(null);
if (job && job.taskcluster_metadata) {
originalTaskId = job.taskcluster_metadata.task_id;
if (job) {
originalTaskId = job.task_id;
originalTaskPromise = fetch(
`https://queue.taskcluster.net/v1/task/${originalTaskId}`,
).then(async response => response.json());

Просмотреть файл

@ -35,7 +35,7 @@ export default class JobInfo extends React.PureComponent {
const {
signature,
title,
taskcluster_metadata,
task_id,
build_platform,
job_type_name,
build_architecture,
@ -67,16 +67,16 @@ export default class JobInfo extends React.PureComponent {
<span>{title}</span>
)}
</li>
{taskcluster_metadata && (
{task_id && (
<li className="small">
<strong>Task: </strong>
<a
id="taskInfo"
href={getInspectTaskUrl(taskcluster_metadata.task_id)}
href={getInspectTaskUrl(task_id)}
target="_blank"
rel="noopener noreferrer"
>
{taskcluster_metadata.task_id}
{task_id}
</a>
</li>
)}