Bug 1575941 - Fix error message when task action not available

This commit is contained in:
Cameron Dawson 2019-09-09 10:38:49 -07:00
Родитель d4631afe64
Коммит 9b095eb04b
8 изменённых файлов: 193 добавлений и 109 удалений

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

@ -1,5 +1,5 @@
import { displayNumber } from '../../ui/perfherder/helpers';
import { getRevisionUrl } from '../../ui/helpers/url';
import { displayNumber } from '../../../ui/perfherder/helpers';
import { getRevisionUrl } from '../../../ui/helpers/url';
describe('getRevisionUrl helper', () => {
test('escapes some html symbols', () => {

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

@ -0,0 +1,19 @@
import { getAction } from '../../../ui/helpers/taskcluster';
describe('taskcluster helper', () => {
const results = [{ name: 'foo' }, { name: 'bar' }, { name: 'baz' }];
test('getAction finds the right action', () => {
const action = getAction(results, 'baz');
expect(action).toEqual({ name: 'baz' });
});
test('getAction throws exception when action is missing', () => {
const results = [{ name: 'foo' }, { name: 'bar' }, { name: 'baz' }];
expect(() => getAction(results, 'meh')).toThrow(
"'meh' action is not available for this task. Available: foo, bar, baz",
);
});
});

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

@ -0,0 +1,42 @@
import { fetchMock } from 'fetch-mock';
import PushModel from '../../../ui/models/push';
import { getProjectUrl } from '../../../ui/helpers/location';
describe('PushModel', () => {
afterEach(() => {
fetchMock.reset();
});
describe('taskcluster actions', () => {
beforeEach(() => {
fetchMock.mock(
getProjectUrl('/push/decisiontask/?push_ids=548880', 'autoland'),
{ '548880': { id: 'U-lI3jzPTkWFplfJPz6cJA', run: '0' } },
);
});
test('getDecisionTaskId', async () => {
const decisionTaskId = await PushModel.getDecisionTaskId(
548880,
() => {},
);
expect(decisionTaskId).toStrictEqual({
id: 'U-lI3jzPTkWFplfJPz6cJA',
run: '0',
});
});
test('getDecisionTaskMap', async () => {
const decisionTaskMap = await PushModel.getDecisionTaskMap(
[548880],
() => {},
);
expect(decisionTaskMap).toStrictEqual({
'548880': { id: 'U-lI3jzPTkWFplfJPz6cJA', run: '0' },
});
});
});
});

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

@ -46,4 +46,18 @@ const taskcluster = (() => {
};
})();
export const getAction = (actionArray, actionName) => {
const action = actionArray.find(result => result.name === actionName);
if (!action) {
throw Error(
`'${actionName}' action is not available for this task. Available: ${actionArray
.map(act => act.name)
.join(', ')}`,
);
}
return action;
};
export default taskcluster;

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

@ -21,6 +21,7 @@ import TaskclusterModel from '../../../models/taskcluster';
import CustomJobActions from '../../CustomJobActions';
import { notify } from '../../redux/stores/notifications';
import { pinJob } from '../../redux/stores/pinnedJobs';
import { getAction } from '../../../helpers/taskcluster';
import LogUrls from './LogUrls';
@ -87,39 +88,41 @@ class ActionBar extends React.PureComponent {
notify,
);
TaskclusterModel.load(decisionTaskId, selectedJobFull).then(results => {
const geckoprofile = results.actions.find(
result => result.name === 'geckoprofile',
);
try {
const geckoprofile = getAction(results.actions, 'geckoprofile');
if (
geckoprofile === undefined ||
!Object.prototype.hasOwnProperty.call(geckoprofile, 'kind')
) {
return notify(
'Job was scheduled without taskcluster support for GeckoProfiles',
);
}
TaskclusterModel.submit({
action: geckoprofile,
decisionTaskId,
taskId: results.originalTaskId,
task: results.originalTask,
input: {},
staticActionVariables: results.staticActionVariables,
}).then(
() => {
notify(
'Request sent to collect gecko profile job via actions.json',
'success',
if (
geckoprofile === undefined ||
!Object.prototype.hasOwnProperty.call(geckoprofile, 'kind')
) {
return notify(
'Job was scheduled without taskcluster support for GeckoProfiles',
);
},
e => {
// The full message is too large to fit in a Treeherder
// notification box.
notify(formatTaskclusterError(e), 'danger', { sticky: true });
},
);
}
TaskclusterModel.submit({
action: geckoprofile,
decisionTaskId,
taskId: results.originalTaskId,
task: results.originalTask,
input: {},
staticActionVariables: results.staticActionVariables,
}).then(
() => {
notify(
'Request sent to collect gecko profile job via actions.json',
'success',
);
},
e => {
// The full message is too large to fit in a Treeherder
// notification box.
notify(formatTaskclusterError(e), 'danger', { sticky: true });
},
);
} catch (e) {
notify(formatTaskclusterError(e), 'danger', { sticky: true });
}
});
};
@ -164,18 +167,13 @@ class ActionBar extends React.PureComponent {
return;
}
if (
selectedJobFull.build_system_type === 'taskcluster' ||
selectedJobFull.reason.startsWith('Created by BBB for task')
) {
const { id: decisionTaskId } = await PushModel.getDecisionTaskId(
selectedJobFull.push_id,
notify,
);
TaskclusterModel.load(decisionTaskId, selectedJobFull).then(results => {
const backfilltask = results.actions.find(
result => result.name === 'backfill',
);
const { id: decisionTaskId } = await PushModel.getDecisionTaskId(
selectedJobFull.push_id,
notify,
);
TaskclusterModel.load(decisionTaskId, selectedJobFull).then(results => {
try {
const backfilltask = getAction(results.actions, 'backfill');
return TaskclusterModel.submit({
action: backfilltask,
@ -193,10 +191,10 @@ class ActionBar extends React.PureComponent {
notify(formatTaskclusterError(e), 'danger', { sticky: true });
},
);
});
} else {
notify('Unable to backfill this job type!', 'danger', { sticky: true });
}
} catch (e) {
notify(formatTaskclusterError(e), 'danger', { sticky: true });
}
});
};
isolateJob = async () => {
@ -228,13 +226,11 @@ class ActionBar extends React.PureComponent {
return;
}
if (
selectedJobFull.build_system_type === 'taskcluster' ||
selectedJobFull.reason.startsWith('Created by BBB for task')
) {
TaskclusterModel.load(decisionTaskId, selectedJobFull).then(results => {
const isolationtask = results.actions.find(
result => result.name === 'isolate-test-failures',
TaskclusterModel.load(decisionTaskId, selectedJobFull).then(results => {
try {
const isolationtask = getAction(
results.actions,
'isolate-test-failures',
);
if (!isolationtask) {
@ -283,10 +279,10 @@ class ActionBar extends React.PureComponent {
notify(formatTaskclusterError(e), 'danger', { sticky: true });
},
);
});
} else {
notify('Unable to isolate this job type!', 'danger', { sticky: true });
}
} catch (e) {
notify(formatTaskclusterError(e), 'danger', { sticky: true });
}
});
};
// Can we backfill? At the moment, this only ensures we're not in a 'try' repo.
@ -337,11 +333,10 @@ class ActionBar extends React.PureComponent {
notify,
);
const results = await TaskclusterModel.load(decisionTaskId, job);
const interactiveTask = results.actions.find(
result => result.name === 'create-interactive',
);
try {
const interactiveTask = getAction(results.actions, 'create-interactive');
await TaskclusterModel.submit({
action: interactiveTask,
decisionTaskId,

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

@ -90,7 +90,7 @@ class PushActionMenu extends React.PureComponent {
);
}
PushModel.triggerAllTalosJobs(times, pushId)
PushModel.triggerAllTalosJobs(times, pushId, notify)
.then(msg => {
notify(msg, 'success');
})

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

@ -7,6 +7,7 @@ import { formatTaskclusterError } from '../helpers/errorMessage';
import { addAggregateFields } from '../helpers/job';
import { getProjectUrl } from '../helpers/location';
import { getData } from '../helpers/http';
import { getAction } from '../helpers/taskcluster';
import PushModel from './push';
import TaskclusterModel from './taskcluster';
@ -112,9 +113,7 @@ export default class JobModel {
// to control whether new task are created, or existing ones re-run. We fall back
// to `add-new-jobs` to support pushing old revision to try, where the duplicating
// the release tasks impacted is unlikely to cause problems.
retriggerAction = results.actions.find(
action => action.name === 'add-new-jobs',
);
retriggerAction = getAction(results.actions, 'add-new-jobs');
actionInput = {
tasks: taskLabels,
};
@ -140,7 +139,11 @@ export default class JobModel {
});
}
} catch (e) {
notify(`Unable to retrigger/add ${jobTerm}`, 'danger', { sticky: true });
notify(
`Unable to retrigger/add ${jobTerm}. ${formatTaskclusterError(e)}`,
'danger',
{ sticky: true },
);
}
}
@ -150,11 +153,10 @@ export default class JobModel {
notify,
);
const results = await TaskclusterModel.load(decisionTaskId);
const cancelAllTask = results.actions.find(
result => result.name === 'cancel-all',
);
try {
const cancelAllTask = getAction(results.actions, 'cancel-all');
await TaskclusterModel.submit({
action: cancelAllTask,
decisionTaskId,
@ -204,11 +206,10 @@ export default class JobModel {
job.taskcluster_metadata = tcMetadataMap[job.id];
const decisionTaskId = taskIdMap[job.push_id].id;
const results = await TaskclusterModel.load(decisionTaskId, job);
const cancelTask = results.actions.find(
result => result.name === 'cancel',
);
try {
const cancelTask = getAction(results.actions, 'cancel');
await TaskclusterModel.submit({
action: cancelTask,
decisionTaskId,

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

@ -5,6 +5,8 @@ import { thMaxPushFetchSize } from '../helpers/constants';
import { getData } from '../helpers/http';
import { getProjectUrl, getUrlParam } from '../helpers/location';
import { createQueryParams, pushEndpoint } from '../helpers/url';
import { formatTaskclusterError } from '../helpers/errorMessage';
import { getAction } from '../helpers/taskcluster';
import TaskclusterModel from './taskcluster';
@ -71,24 +73,32 @@ export default class PushModel {
return TaskclusterModel.load(decisionTaskId).then(results => {
const actionTaskId = slugid();
const missingTestsTask = results.actions.find(
action => action.name === 'run-missing-tests',
);
return TaskclusterModel.submit({
action: missingTestsTask,
actionTaskId,
decisionTaskId,
taskId: null,
task: null,
input: {},
staticActionVariables: results.staticActionVariables,
}).then(
notify(
`Request sent to trigger missing jobs (${actionTaskId})`,
'success',
),
);
try {
const missingTestsTask = getAction(
results.actions,
'run-missing-tests',
);
return TaskclusterModel.submit({
action: missingTestsTask,
actionTaskId,
decisionTaskId,
taskId: null,
task: null,
input: {},
staticActionVariables: results.staticActionVariables,
}).then(
notify(
`Request sent to trigger missing jobs (${actionTaskId})`,
'success',
),
);
} catch (e) {
// The full message is too large to fit in a Treeherder
// notification box.
notify(formatTaskclusterError(e), 'danger', { sticky: true });
}
});
}
@ -100,31 +110,34 @@ export default class PushModel {
return TaskclusterModel.load(decisionTaskId).then(results => {
const actionTaskId = slugid();
const allTalosTask = results.actions.find(
action => action.name === 'run-all-talos',
);
return TaskclusterModel.submit({
action: allTalosTask,
actionTaskId,
decisionTaskId,
taskId: null,
task: null,
input: { times },
staticActionVariables: results.staticActionVariables,
}).then(
() =>
`Request sent to trigger all talos jobs ${times} time(s) via actions.json (${actionTaskId})`,
);
try {
const allTalosTask = getAction(results.actions, 'run-all-talos');
return TaskclusterModel.submit({
action: allTalosTask,
actionTaskId,
decisionTaskId,
taskId: null,
task: null,
input: { times },
staticActionVariables: results.staticActionVariables,
}).then(
() =>
`Request sent to trigger all talos jobs ${times} time(s) via actions.json (${actionTaskId})`,
);
} catch (e) {
// The full message is too large to fit in a Treeherder
// notification box.
notify(formatTaskclusterError(e), 'danger', { sticky: true });
}
});
}
static triggerNewJobs(jobs, decisionTaskId) {
return TaskclusterModel.load(decisionTaskId).then(results => {
const actionTaskId = slugid();
const addNewJobsTask = results.actions.find(
action => action.name === 'add-new-jobs',
);
const addNewJobsTask = getAction(results.actions, 'add-new-jobs');
return TaskclusterModel.submit({
action: addNewJobsTask,