зеркало из https://github.com/mozilla/treeherder.git
Rename details selectedJob to selectedJobFull (#5230)
* Rename details panel ``selectedJob`` to ``selectedJobFull`` The job that's passed in the DetailsPanel has a bunch of extra fields that are not in the normal downloaded list of jobs. So I wanted to depict that ``selectedJob`` is not the same thing as what you see in the DetailsPanel. * Stop using Redux where not necessary I was using Redux to assign the selectedJob in a few details classes when I should have just passed it where it was needed. * New addAggregateFields function Instead of using a more heavy weight JobModel for each job, we just persist some fields that were getting constantly calculated over and over. This was especially true during filtering and re-rendering. * Remove some cruft leftover from Buildbot.
This commit is contained in:
Родитель
9bf2dc7d91
Коммит
5d4ca44e7d
|
@ -7,6 +7,7 @@ import { JobGroupComponent } from '../../../ui/job-view/pushes/JobGroup';
|
||||||
import FilterModel from '../../../ui/models/filter';
|
import FilterModel from '../../../ui/models/filter';
|
||||||
import mappedGroupFixture from '../mock/mappedGroup';
|
import mappedGroupFixture from '../mock/mappedGroup';
|
||||||
import mappedGroupDupsFixture from '../mock/mappedGroupDups';
|
import mappedGroupDupsFixture from '../mock/mappedGroupDups';
|
||||||
|
import { addAggregateFields } from '../../../ui/helpers/job';
|
||||||
|
|
||||||
describe('JobGroup component', () => {
|
describe('JobGroup component', () => {
|
||||||
let countGroup;
|
let countGroup;
|
||||||
|
@ -15,6 +16,11 @@ describe('JobGroup component', () => {
|
||||||
const filterModel = new FilterModel();
|
const filterModel = new FilterModel();
|
||||||
const pushGroupState = 'collapsed';
|
const pushGroupState = 'collapsed';
|
||||||
|
|
||||||
|
beforeAll(() => {
|
||||||
|
mappedGroupFixture.jobs.forEach(job => addAggregateFields(job));
|
||||||
|
mappedGroupDupsFixture.jobs.forEach(job => addAggregateFields(job));
|
||||||
|
});
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
countGroup = cloneDeep(mappedGroupFixture);
|
countGroup = cloneDeep(mappedGroupFixture);
|
||||||
dupGroup = cloneDeep(mappedGroupDupsFixture);
|
dupGroup = cloneDeep(mappedGroupDupsFixture);
|
||||||
|
|
|
@ -12,7 +12,8 @@ import FilterModel from '../../../ui/models/filter';
|
||||||
import { store } from '../../../ui/job-view/redux/store';
|
import { store } from '../../../ui/job-view/redux/store';
|
||||||
import { PinnedJobs } from '../../../ui/job-view/context/PinnedJobs';
|
import { PinnedJobs } from '../../../ui/job-view/context/PinnedJobs';
|
||||||
import { getUrlParam, setUrlParam } from '../../../ui/helpers/location';
|
import { getUrlParam, setUrlParam } from '../../../ui/helpers/location';
|
||||||
import JobModel from '../../../ui/models/job';
|
import platforms from '../mock/platforms';
|
||||||
|
import { addAggregateFields } from '../../../ui/helpers/job';
|
||||||
|
|
||||||
const testPush = {
|
const testPush = {
|
||||||
id: 494796,
|
id: 494796,
|
||||||
|
@ -34,170 +35,13 @@ const testPush = {
|
||||||
jobsLoaded: true,
|
jobsLoaded: true,
|
||||||
};
|
};
|
||||||
|
|
||||||
const testPlatforms = [
|
beforeAll(() => {
|
||||||
{
|
platforms.forEach(platform =>
|
||||||
name: 'Linux x64',
|
platform.groups.forEach(group =>
|
||||||
option: 'opt',
|
group.jobs.forEach(job => addAggregateFields(job)),
|
||||||
groups: [
|
),
|
||||||
{
|
);
|
||||||
name: 'Coverity Static Analysis',
|
});
|
||||||
tier: 2,
|
|
||||||
symbol: 'coverity',
|
|
||||||
mapKey: '494796coverity2linux64opt',
|
|
||||||
jobs: [
|
|
||||||
new JobModel({
|
|
||||||
build_architecture: '-',
|
|
||||||
build_os: '-',
|
|
||||||
build_platform: 'linux64',
|
|
||||||
build_platform_id: 106,
|
|
||||||
build_system_type: 'taskcluster',
|
|
||||||
end_timestamp: 1560356302,
|
|
||||||
failure_classification_id: 1,
|
|
||||||
id: 250970255,
|
|
||||||
job_group_description: '',
|
|
||||||
job_group_id: 947,
|
|
||||||
job_group_name: 'Coverity Static Analysis',
|
|
||||||
job_group_symbol: 'coverity',
|
|
||||||
job_guid: '2d180d39-8ac5-4200-995b-3f5c7b614596/0',
|
|
||||||
job_type_description: '',
|
|
||||||
job_type_id: 190421,
|
|
||||||
job_type_name: 'source-test-coverity-coverity',
|
|
||||||
job_type_symbol: 'cvsa',
|
|
||||||
last_modified: '2019-06-12T16:18:26.649628',
|
|
||||||
machine_name: 'i-0a2f82a56303c8ec2',
|
|
||||||
machine_platform_architecture: '-',
|
|
||||||
machine_platform_os: '-',
|
|
||||||
option_collection_hash: '102210fe594ee9b33d82058545b1ed14f4c8206e',
|
|
||||||
platform: 'linux64',
|
|
||||||
push_id: 494796,
|
|
||||||
reason: 'scheduled',
|
|
||||||
ref_data_name: '7542013e03efecbabf4b0bb931646f4fbff3a413',
|
|
||||||
result: 'success',
|
|
||||||
result_set_id: 494796,
|
|
||||||
signature: '7542013e03efecbabf4b0bb931646f4fbff3a413',
|
|
||||||
start_timestamp: 1560354928,
|
|
||||||
state: 'completed',
|
|
||||||
submit_timestamp: 1560354914,
|
|
||||||
tier: 2,
|
|
||||||
who: 'reviewbot@noreply.mozilla.org',
|
|
||||||
platform_option: 'opt',
|
|
||||||
visible: true,
|
|
||||||
selected: false,
|
|
||||||
}),
|
|
||||||
],
|
|
||||||
visible: true,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'Gecko Decision Task',
|
|
||||||
option: 'opt',
|
|
||||||
groups: [
|
|
||||||
{
|
|
||||||
name: 'unknown',
|
|
||||||
tier: 1,
|
|
||||||
symbol: '',
|
|
||||||
mapKey: '4947961gecko-decisionopt',
|
|
||||||
jobs: [
|
|
||||||
new JobModel({
|
|
||||||
build_architecture: '-',
|
|
||||||
build_os: '-',
|
|
||||||
build_platform: 'gecko-decision',
|
|
||||||
build_platform_id: 107,
|
|
||||||
build_system_type: 'taskcluster',
|
|
||||||
end_timestamp: 1560354927,
|
|
||||||
failure_classification_id: 1,
|
|
||||||
id: 250970109,
|
|
||||||
job_group_description: '',
|
|
||||||
job_group_id: 2,
|
|
||||||
job_group_name: 'unknown',
|
|
||||||
job_group_symbol: '?',
|
|
||||||
job_guid: '7dd39d25-8990-44d2-8ba4-b2a3b319cc4d/0',
|
|
||||||
job_type_description: '',
|
|
||||||
job_type_id: 6689,
|
|
||||||
job_type_name: 'Gecko Decision Task',
|
|
||||||
job_type_symbol: 'D',
|
|
||||||
last_modified: '2019-06-12T15:55:29.549008',
|
|
||||||
machine_name: 'i-080c18493f1aa3d95',
|
|
||||||
machine_platform_architecture: '-',
|
|
||||||
machine_platform_os: '-',
|
|
||||||
option_collection_hash: '102210fe594ee9b33d82058545b1ed14f4c8206e',
|
|
||||||
platform: 'gecko-decision',
|
|
||||||
push_id: 494796,
|
|
||||||
reason: 'scheduled',
|
|
||||||
ref_data_name: '2aa083621bb989d6acf1151667288d5fe9616178',
|
|
||||||
result: 'success',
|
|
||||||
result_set_id: 494796,
|
|
||||||
signature: '2aa083621bb989d6acf1151667288d5fe9616178',
|
|
||||||
start_timestamp: 1560354846,
|
|
||||||
state: 'completed',
|
|
||||||
submit_timestamp: 1560354844,
|
|
||||||
tier: 1,
|
|
||||||
who: 'reviewbot@noreply.mozilla.org',
|
|
||||||
platform_option: 'opt',
|
|
||||||
visible: true,
|
|
||||||
selected: false,
|
|
||||||
}),
|
|
||||||
],
|
|
||||||
visible: true,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'Linting',
|
|
||||||
option: 'opt',
|
|
||||||
groups: [
|
|
||||||
{
|
|
||||||
name: 'unknown',
|
|
||||||
tier: 1,
|
|
||||||
symbol: '',
|
|
||||||
mapKey: '4947961lintopt',
|
|
||||||
jobs: [
|
|
||||||
new JobModel({
|
|
||||||
build_architecture: '-',
|
|
||||||
build_os: '-',
|
|
||||||
build_platform: 'lint',
|
|
||||||
build_platform_id: 144,
|
|
||||||
build_system_type: 'taskcluster',
|
|
||||||
end_timestamp: 1560355013,
|
|
||||||
failure_classification_id: 1,
|
|
||||||
id: 250970251,
|
|
||||||
job_group_description: '',
|
|
||||||
job_group_id: 2,
|
|
||||||
job_group_name: 'unknown',
|
|
||||||
job_group_symbol: '?',
|
|
||||||
job_guid: '5df35b83-aff9-4ddf-b8c3-48eff52736f3/0',
|
|
||||||
job_type_description: '',
|
|
||||||
job_type_id: 114754,
|
|
||||||
job_type_name: 'source-test-mozlint-codespell',
|
|
||||||
job_type_symbol: 'spell',
|
|
||||||
last_modified: '2019-06-12T15:56:54.537683',
|
|
||||||
machine_name: 'i-081959e7fae55d041',
|
|
||||||
machine_platform_architecture: '-',
|
|
||||||
machine_platform_os: '-',
|
|
||||||
option_collection_hash: '102210fe594ee9b33d82058545b1ed14f4c8206e',
|
|
||||||
platform: 'lint',
|
|
||||||
push_id: 494796,
|
|
||||||
reason: 'scheduled',
|
|
||||||
ref_data_name: '6c2e8db7978ca4d5c0e38522552da4bc9b2e6b8b',
|
|
||||||
result: 'success',
|
|
||||||
result_set_id: 494796,
|
|
||||||
signature: '6c2e8db7978ca4d5c0e38522552da4bc9b2e6b8b',
|
|
||||||
start_timestamp: 1560354928,
|
|
||||||
state: 'completed',
|
|
||||||
submit_timestamp: 1560354914,
|
|
||||||
tier: 1,
|
|
||||||
who: 'reviewbot@noreply.mozilla.org',
|
|
||||||
platform_option: 'opt',
|
|
||||||
visible: true,
|
|
||||||
selected: false,
|
|
||||||
}),
|
|
||||||
],
|
|
||||||
visible: true,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
afterEach(() => {
|
afterEach(() => {
|
||||||
cleanup();
|
cleanup();
|
||||||
|
@ -209,7 +53,7 @@ const testPushJobs = filterModel => (
|
||||||
<PinnedJobs>
|
<PinnedJobs>
|
||||||
<PushJobs
|
<PushJobs
|
||||||
push={testPush}
|
push={testPush}
|
||||||
platforms={testPlatforms}
|
platforms={platforms}
|
||||||
repoName="try"
|
repoName="try"
|
||||||
filterModel={filterModel}
|
filterModel={filterModel}
|
||||||
pushGroupState=""
|
pushGroupState=""
|
||||||
|
|
|
@ -30,12 +30,17 @@ import {
|
||||||
fetchNextPushes,
|
fetchNextPushes,
|
||||||
updateRange,
|
updateRange,
|
||||||
} from '../../../../ui/job-view/redux/stores/pushes';
|
} from '../../../../ui/job-view/redux/stores/pushes';
|
||||||
|
import { addAggregateFields } from '../../../../ui/helpers/job';
|
||||||
|
|
||||||
const mockStore = configureMockStore([thunk]);
|
const mockStore = configureMockStore([thunk]);
|
||||||
|
|
||||||
describe('Pushes Redux store', () => {
|
describe('Pushes Redux store', () => {
|
||||||
const repoName = 'autoland';
|
const repoName = 'autoland';
|
||||||
|
|
||||||
|
beforeAll(() => {
|
||||||
|
Object.values(jobMap).forEach(job => addAggregateFields(job));
|
||||||
|
});
|
||||||
|
|
||||||
afterEach(() => {
|
afterEach(() => {
|
||||||
cleanup();
|
cleanup();
|
||||||
fetchMock.reset();
|
fetchMock.reset();
|
||||||
|
|
|
@ -0,0 +1,164 @@
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"name": "Linux x64",
|
||||||
|
"option": "opt",
|
||||||
|
"groups": [
|
||||||
|
{
|
||||||
|
"name": "Coverity Static Analysis",
|
||||||
|
"tier": 2,
|
||||||
|
"symbol": "coverity",
|
||||||
|
"mapKey": "494796coverity2linux64opt",
|
||||||
|
"jobs": [
|
||||||
|
{
|
||||||
|
"build_architecture": "-",
|
||||||
|
"build_os": "-",
|
||||||
|
"build_platform": "linux64",
|
||||||
|
"build_platform_id": 106,
|
||||||
|
"build_system_type": "taskcluster",
|
||||||
|
"end_timestamp": 1560356302,
|
||||||
|
"failure_classification_id": 1,
|
||||||
|
"id": 250970255,
|
||||||
|
"job_group_description": "",
|
||||||
|
"job_group_id": 947,
|
||||||
|
"job_group_name": "Coverity Static Analysis",
|
||||||
|
"job_group_symbol": "coverity",
|
||||||
|
"job_guid": "2d180d39-8ac5-4200-995b-3f5c7b614596/0",
|
||||||
|
"job_type_description": "",
|
||||||
|
"job_type_id": 190421,
|
||||||
|
"job_type_name": "source-test-coverity-coverity",
|
||||||
|
"job_type_symbol": "cvsa",
|
||||||
|
"last_modified": "2019-06-12T16:18:26.649628",
|
||||||
|
"machine_name": "i-0a2f82a56303c8ec2",
|
||||||
|
"machine_platform_architecture": "-",
|
||||||
|
"machine_platform_os": "-",
|
||||||
|
"option_collection_hash": "102210fe594ee9b33d82058545b1ed14f4c8206e",
|
||||||
|
"platform": "linux64",
|
||||||
|
"push_id": 494796,
|
||||||
|
"reason": "scheduled",
|
||||||
|
"ref_data_name": "7542013e03efecbabf4b0bb931646f4fbff3a413",
|
||||||
|
"result": "success",
|
||||||
|
"result_set_id": 494796,
|
||||||
|
"signature": "7542013e03efecbabf4b0bb931646f4fbff3a413",
|
||||||
|
"start_timestamp": 1560354928,
|
||||||
|
"state": "completed",
|
||||||
|
"submit_timestamp": 1560354914,
|
||||||
|
"tier": 2,
|
||||||
|
"who": "reviewbot@noreply.mozilla.org",
|
||||||
|
"platform_option": "opt",
|
||||||
|
"visible": true,
|
||||||
|
"selected": false
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"visible": true
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Gecko Decision Task",
|
||||||
|
"option": "opt",
|
||||||
|
"groups": [
|
||||||
|
{
|
||||||
|
"name": "unknown",
|
||||||
|
"tier": 1,
|
||||||
|
"symbol": "",
|
||||||
|
"mapKey": "4947961gecko-decisionopt",
|
||||||
|
"jobs": [
|
||||||
|
{
|
||||||
|
"build_architecture": "-",
|
||||||
|
"build_os": "-",
|
||||||
|
"build_platform": "gecko-decision",
|
||||||
|
"build_platform_id": 107,
|
||||||
|
"build_system_type": "taskcluster",
|
||||||
|
"end_timestamp": 1560354927,
|
||||||
|
"failure_classification_id": 1,
|
||||||
|
"id": 250970109,
|
||||||
|
"job_group_description": "",
|
||||||
|
"job_group_id": 2,
|
||||||
|
"job_group_name": "unknown",
|
||||||
|
"job_group_symbol": "?",
|
||||||
|
"job_guid": "7dd39d25-8990-44d2-8ba4-b2a3b319cc4d/0",
|
||||||
|
"job_type_description": "",
|
||||||
|
"job_type_id": 6689,
|
||||||
|
"job_type_name": "Gecko Decision Task",
|
||||||
|
"job_type_symbol": "D",
|
||||||
|
"last_modified": "2019-06-12T15:55:29.549008",
|
||||||
|
"machine_name": "i-080c18493f1aa3d95",
|
||||||
|
"machine_platform_architecture": "-",
|
||||||
|
"machine_platform_os": "-",
|
||||||
|
"option_collection_hash": "102210fe594ee9b33d82058545b1ed14f4c8206e",
|
||||||
|
"platform": "gecko-decision",
|
||||||
|
"push_id": 494796,
|
||||||
|
"reason": "scheduled",
|
||||||
|
"ref_data_name": "2aa083621bb989d6acf1151667288d5fe9616178",
|
||||||
|
"result": "success",
|
||||||
|
"result_set_id": 494796,
|
||||||
|
"signature": "2aa083621bb989d6acf1151667288d5fe9616178",
|
||||||
|
"start_timestamp": 1560354846,
|
||||||
|
"state": "completed",
|
||||||
|
"submit_timestamp": 1560354844,
|
||||||
|
"tier": 1,
|
||||||
|
"who": "reviewbot@noreply.mozilla.org",
|
||||||
|
"platform_option": "opt",
|
||||||
|
"visible": true,
|
||||||
|
"selected": false
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"visible": true
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Linting",
|
||||||
|
"option": "opt",
|
||||||
|
"groups": [
|
||||||
|
{
|
||||||
|
"name": "unknown",
|
||||||
|
"tier": 1,
|
||||||
|
"symbol": "",
|
||||||
|
"mapKey": "4947961lintopt",
|
||||||
|
"jobs": [
|
||||||
|
{
|
||||||
|
"build_architecture": "-",
|
||||||
|
"build_os": "-",
|
||||||
|
"build_platform": "lint",
|
||||||
|
"build_platform_id": 144,
|
||||||
|
"build_system_type": "taskcluster",
|
||||||
|
"end_timestamp": 1560355013,
|
||||||
|
"failure_classification_id": 1,
|
||||||
|
"id": 250970251,
|
||||||
|
"job_group_description": "",
|
||||||
|
"job_group_id": 2,
|
||||||
|
"job_group_name": "unknown",
|
||||||
|
"job_group_symbol": "?",
|
||||||
|
"job_guid": "5df35b83-aff9-4ddf-b8c3-48eff52736f3/0",
|
||||||
|
"job_type_description": "",
|
||||||
|
"job_type_id": 114754,
|
||||||
|
"job_type_name": "source-test-mozlint-codespell",
|
||||||
|
"job_type_symbol": "spell",
|
||||||
|
"last_modified": "2019-06-12T15:56:54.537683",
|
||||||
|
"machine_name": "i-081959e7fae55d041",
|
||||||
|
"machine_platform_architecture": "-",
|
||||||
|
"machine_platform_os": "-",
|
||||||
|
"option_collection_hash": "102210fe594ee9b33d82058545b1ed14f4c8206e",
|
||||||
|
"platform": "lint",
|
||||||
|
"push_id": 494796,
|
||||||
|
"reason": "scheduled",
|
||||||
|
"ref_data_name": "6c2e8db7978ca4d5c0e38522552da4bc9b2e6b8b",
|
||||||
|
"result": "success",
|
||||||
|
"result_set_id": 494796,
|
||||||
|
"signature": "6c2e8db7978ca4d5c0e38522552da4bc9b2e6b8b",
|
||||||
|
"start_timestamp": 1560354928,
|
||||||
|
"state": "completed",
|
||||||
|
"submit_timestamp": 1560354914,
|
||||||
|
"tier": 1,
|
||||||
|
"who": "reviewbot@noreply.mozilla.org",
|
||||||
|
"platform_option": "opt",
|
||||||
|
"visible": true,
|
||||||
|
"selected": false
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"visible": true
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
|
@ -15,8 +15,6 @@ export const thMatchType = {
|
||||||
|
|
||||||
// choices available for the field filters
|
// choices available for the field filters
|
||||||
export const thFieldChoices = {
|
export const thFieldChoices = {
|
||||||
ref_data_name: { name: 'buildername/jobname', matchType: thMatchType.substr },
|
|
||||||
build_system_type: { name: 'build system', matchType: thMatchType.substr },
|
|
||||||
job_type_name: { name: 'job name', matchType: thMatchType.substr },
|
job_type_name: { name: 'job name', matchType: thMatchType.substr },
|
||||||
job_type_symbol: { name: 'job symbol', matchType: thMatchType.exactstr },
|
job_type_symbol: { name: 'job symbol', matchType: thMatchType.exactstr },
|
||||||
job_group_name: { name: 'group name', matchType: thMatchType.substr },
|
job_group_name: { name: 'group name', matchType: thMatchType.substr },
|
||||||
|
|
|
@ -22,14 +22,6 @@ const btnClasses = {
|
||||||
// failure classification ids that should be shown in "unclassified" mode
|
// failure classification ids that should be shown in "unclassified" mode
|
||||||
export const thUnclassifiedIds = [1, 7];
|
export const thUnclassifiedIds = [1, 7];
|
||||||
|
|
||||||
// The result will be unknown unless the state is complete, so much check both.
|
|
||||||
// TODO: We should consider storing either pending or running in the result,
|
|
||||||
// even when the job isn't complete. It would simplify a lot of UI code and
|
|
||||||
// I can't think of a reason that would hurt anything.
|
|
||||||
export const getStatus = function getStatus(job) {
|
|
||||||
return job.state === 'completed' ? job.result : job.state;
|
|
||||||
};
|
|
||||||
|
|
||||||
// Get the CSS class for job buttons as well as jobs that show in the pinboard.
|
// Get the CSS class for job buttons as well as jobs that show in the pinboard.
|
||||||
// These also apply to result "groupings" like ``failures`` and ``in progress``
|
// These also apply to result "groupings" like ``failures`` and ``in progress``
|
||||||
// for the colored filter chicklets on the nav bar.
|
// for the colored filter chicklets on the nav bar.
|
||||||
|
@ -40,18 +32,12 @@ export const getBtnClass = function getBtnClass(
|
||||||
let btnClass = btnClasses[resultStatus] || 'btn-default';
|
let btnClass = btnClasses[resultStatus] || 'btn-default';
|
||||||
|
|
||||||
// handle if a job is classified
|
// handle if a job is classified
|
||||||
// TODO: Check if the parseInt() is really needed here.
|
if (failureClassificationId > 1) {
|
||||||
const classificationId = parseInt(failureClassificationId, 10);
|
|
||||||
if (classificationId > 1) {
|
|
||||||
btnClass += '-classified';
|
btnClass += '-classified';
|
||||||
}
|
}
|
||||||
return btnClass;
|
return btnClass;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const getJobBtnClass = function getJobBtnClass(job) {
|
|
||||||
return getBtnClass(getStatus(job), job.failure_classification_id);
|
|
||||||
};
|
|
||||||
|
|
||||||
export const isReftest = function isReftest(job) {
|
export const isReftest = function isReftest(job) {
|
||||||
const {
|
const {
|
||||||
job_group_name: gName,
|
job_group_name: gName,
|
||||||
|
@ -203,22 +189,54 @@ export const findJobInstance = function findJobInstance(jobId, scrollTo) {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
export const getSearchStr = function getSearchStr(job) {
|
export const addAggregateFields = function addAggregateFields(job) {
|
||||||
|
const {
|
||||||
|
job_group_name,
|
||||||
|
job_group_symbol,
|
||||||
|
job_type_name,
|
||||||
|
job_type_symbol,
|
||||||
|
state,
|
||||||
|
result,
|
||||||
|
platform,
|
||||||
|
platform_option,
|
||||||
|
signature,
|
||||||
|
duration,
|
||||||
|
failure_classification_id,
|
||||||
|
submit_timestamp,
|
||||||
|
start_timestamp,
|
||||||
|
end_timestamp,
|
||||||
|
} = job;
|
||||||
|
|
||||||
|
job.resultStatus = state === 'completed' ? result : state;
|
||||||
// we want to join the group and type information together
|
// we want to join the group and type information together
|
||||||
// so we can search for it as one token (useful when
|
// so we can search for it as one token (useful when
|
||||||
// we want to do a search on something like `fxup-esr(`)
|
// we want to do a search on something like `fxup-esr(`)
|
||||||
const symbolInfo = job.job_group_symbol === '?' ? '' : job.job_group_symbol;
|
const symbolInfo = job_group_symbol === '?' ? '' : job_group_symbol;
|
||||||
|
|
||||||
return [
|
job.title = [
|
||||||
thPlatformMap[job.platform] || job.platform,
|
thPlatformMap[platform] || platform,
|
||||||
job.platform_option,
|
platform_option,
|
||||||
job.job_group_name === 'unknown' ? undefined : job.job_group_name,
|
job_group_name === 'unknown' ? undefined : job_group_name,
|
||||||
job.job_type_name,
|
job_type_name,
|
||||||
`${symbolInfo}(${job.job_type_symbol})`,
|
`${symbolInfo}(${job_type_symbol})`,
|
||||||
]
|
]
|
||||||
.filter(item => typeof item !== 'undefined')
|
.filter(item => typeof item !== 'undefined')
|
||||||
.join(' ')
|
.join(' ')
|
||||||
.toLowerCase();
|
.toLowerCase();
|
||||||
|
job.searchStr = `${job.title} ${signature}`;
|
||||||
|
|
||||||
|
if (!duration) {
|
||||||
|
// If start time is 0, then duration should be from requesttime to now
|
||||||
|
// If we have starttime and no endtime, then duration should be starttime to now
|
||||||
|
// If we have both starttime and endtime, then duration will be between those two
|
||||||
|
const endtime = end_timestamp || Date.now() / 1000;
|
||||||
|
const starttime = start_timestamp || submit_timestamp;
|
||||||
|
job.duration = Math.round((endtime - starttime) / 60, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
job.hoverText = `${job_type_name} - ${job.resultStatus} - ${job.duration} mins`;
|
||||||
|
job.btnClass = getBtnClass(job.resultStatus, failure_classification_id);
|
||||||
|
return job;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const getJobSearchStrHref = function getJobSearchStrHref(jobSearchStr) {
|
export const getJobSearchStrHref = function getJobSearchStrHref(jobSearchStr) {
|
||||||
|
@ -227,9 +245,3 @@ export const getJobSearchStrHref = function getJobSearchStrHref(jobSearchStr) {
|
||||||
|
|
||||||
return `${uiJobsUrlBase}?${params.toString()}`;
|
return `${uiJobsUrlBase}?${params.toString()}`;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const getHoverText = function getHoverText(job) {
|
|
||||||
const duration = Math.round((job.end_timestamp - job.start_timestamp) / 60);
|
|
||||||
|
|
||||||
return `${job.job_type_name} - ${getStatus(job)} - ${duration} mins`;
|
|
||||||
};
|
|
||||||
|
|
|
@ -5,6 +5,7 @@ import { connect } from 'react-redux';
|
||||||
|
|
||||||
import { thEvents, thBugSuggestionLimit } from '../../helpers/constants';
|
import { thEvents, thBugSuggestionLimit } from '../../helpers/constants';
|
||||||
import { withPinnedJobs } from '../context/PinnedJobs';
|
import { withPinnedJobs } from '../context/PinnedJobs';
|
||||||
|
import { addAggregateFields } from '../../helpers/job';
|
||||||
import { getLogViewerUrl, getReftestUrl } from '../../helpers/url';
|
import { getLogViewerUrl, getReftestUrl } from '../../helpers/url';
|
||||||
import BugJobMapModel from '../../models/bugJobMap';
|
import BugJobMapModel from '../../models/bugJobMap';
|
||||||
import BugSuggestionsModel from '../../models/bugSuggestions';
|
import BugSuggestionsModel from '../../models/bugSuggestions';
|
||||||
|
@ -29,6 +30,7 @@ class DetailsPanel extends React.Component {
|
||||||
this.selectJobController = null;
|
this.selectJobController = null;
|
||||||
|
|
||||||
this.state = {
|
this.state = {
|
||||||
|
selectedJobFull: null,
|
||||||
jobDetails: [],
|
jobDetails: [],
|
||||||
jobLogUrls: [],
|
jobLogUrls: [],
|
||||||
jobDetailLoading: false,
|
jobDetailLoading: false,
|
||||||
|
@ -182,7 +184,7 @@ class DetailsPanel extends React.Component {
|
||||||
);
|
);
|
||||||
|
|
||||||
const jobDetailPromise = JobDetailModel.getJobDetails(
|
const jobDetailPromise = JobDetailModel.getJobDetails(
|
||||||
{ job_guid: selectedJob.job_guid },
|
{ job_id: selectedJob.id },
|
||||||
this.selectJobController.signal,
|
this.selectJobController.signal,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -211,22 +213,12 @@ class DetailsPanel extends React.Component {
|
||||||
// This version of the job has more information than what we get in the main job list. This
|
// 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
|
// is what we'll pass to the rest of the details panel. It has extra fields like
|
||||||
// taskcluster_metadata.
|
// taskcluster_metadata.
|
||||||
Object.assign(selectedJob, jobResult);
|
const selectedJobFull = jobResult;
|
||||||
const jobRevision = push.revision;
|
const jobRevision = push.revision;
|
||||||
|
|
||||||
jobDetails = jobDetailResult;
|
addAggregateFields(selectedJobFull);
|
||||||
|
|
||||||
// incorporate the buildername into the job details if this is a buildbot job
|
jobDetails = jobDetailResult;
|
||||||
// (i.e. it has a buildbot request id)
|
|
||||||
const buildbotRequestIdDetail = jobDetails.find(
|
|
||||||
detail => detail.title === 'buildbot_request_id',
|
|
||||||
);
|
|
||||||
if (buildbotRequestIdDetail) {
|
|
||||||
jobDetails = [
|
|
||||||
...jobDetails,
|
|
||||||
{ title: 'Buildername', value: selectedJob.ref_data_name },
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
||||||
// the third result comes from the jobLogUrl promise
|
// the third result comes from the jobLogUrl promise
|
||||||
// exclude the json log URLs
|
// exclude the json log URLs
|
||||||
|
@ -292,6 +284,7 @@ class DetailsPanel extends React.Component {
|
||||||
|
|
||||||
this.setState(
|
this.setState(
|
||||||
{
|
{
|
||||||
|
selectedJobFull,
|
||||||
jobLogUrls,
|
jobLogUrls,
|
||||||
jobDetails,
|
jobDetails,
|
||||||
logParseStatus,
|
logParseStatus,
|
||||||
|
@ -325,9 +318,9 @@ class DetailsPanel extends React.Component {
|
||||||
classificationMap,
|
classificationMap,
|
||||||
classificationTypes,
|
classificationTypes,
|
||||||
isPinBoardVisible,
|
isPinBoardVisible,
|
||||||
selectedJob,
|
|
||||||
} = this.props;
|
} = this.props;
|
||||||
const {
|
const {
|
||||||
|
selectedJobFull,
|
||||||
jobDetails,
|
jobDetails,
|
||||||
jobRevision,
|
jobRevision,
|
||||||
jobLogUrls,
|
jobLogUrls,
|
||||||
|
@ -351,19 +344,19 @@ class DetailsPanel extends React.Component {
|
||||||
<div
|
<div
|
||||||
id="details-panel"
|
id="details-panel"
|
||||||
style={{ height: `${detailsPanelHeight}px` }}
|
style={{ height: `${detailsPanelHeight}px` }}
|
||||||
className={selectedJob ? 'details-panel-slide' : 'hidden'}
|
className={selectedJobFull ? 'details-panel-slide' : 'hidden'}
|
||||||
>
|
>
|
||||||
<PinBoard
|
<PinBoard
|
||||||
repoName={repoName}
|
repoName={repoName}
|
||||||
currentRepo={currentRepo}
|
currentRepo={currentRepo}
|
||||||
isLoggedIn={user.isLoggedIn || false}
|
isLoggedIn={user.isLoggedIn || false}
|
||||||
classificationTypes={classificationTypes}
|
classificationTypes={classificationTypes}
|
||||||
selectedJob={selectedJob}
|
selectedJobFull={selectedJobFull}
|
||||||
/>
|
/>
|
||||||
{!!selectedJob && (
|
{!!selectedJobFull && (
|
||||||
<div id="details-panel-content">
|
<div id="details-panel-content">
|
||||||
<SummaryPanel
|
<SummaryPanel
|
||||||
selectedJob={selectedJob}
|
selectedJobFull={selectedJobFull}
|
||||||
repoName={repoName}
|
repoName={repoName}
|
||||||
currentRepo={currentRepo}
|
currentRepo={currentRepo}
|
||||||
classificationMap={classificationMap}
|
classificationMap={classificationMap}
|
||||||
|
@ -380,7 +373,7 @@ class DetailsPanel extends React.Component {
|
||||||
/>
|
/>
|
||||||
<span className="job-tabs-divider" />
|
<span className="job-tabs-divider" />
|
||||||
<TabsPanel
|
<TabsPanel
|
||||||
selectedJob={selectedJob}
|
selectedJobFull={selectedJobFull}
|
||||||
jobDetails={jobDetails}
|
jobDetails={jobDetails}
|
||||||
perfJobDetail={perfJobDetail}
|
perfJobDetail={perfJobDetail}
|
||||||
repoName={repoName}
|
repoName={repoName}
|
||||||
|
|
|
@ -7,11 +7,7 @@ import { faPlusSquare, faTimes } from '@fortawesome/free-solid-svg-icons';
|
||||||
|
|
||||||
import { thEvents } from '../../helpers/constants';
|
import { thEvents } from '../../helpers/constants';
|
||||||
import { formatModelError } from '../../helpers/errorMessage';
|
import { formatModelError } from '../../helpers/errorMessage';
|
||||||
import {
|
import { findJobInstance } from '../../helpers/job';
|
||||||
getJobBtnClass,
|
|
||||||
getHoverText,
|
|
||||||
findJobInstance,
|
|
||||||
} from '../../helpers/job';
|
|
||||||
import { isSHAorCommit } from '../../helpers/revision';
|
import { isSHAorCommit } from '../../helpers/revision';
|
||||||
import { getBugUrl } from '../../helpers/url';
|
import { getBugUrl } from '../../helpers/url';
|
||||||
import BugJobMapModel from '../../models/bugJobMap';
|
import BugJobMapModel from '../../models/bugJobMap';
|
||||||
|
@ -356,7 +352,7 @@ class PinBoard extends React.Component {
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const {
|
const {
|
||||||
selectedJob,
|
selectedJobFull,
|
||||||
revisionTips,
|
revisionTips,
|
||||||
isLoggedIn,
|
isLoggedIn,
|
||||||
isPinBoardVisible,
|
isPinBoardVisible,
|
||||||
|
@ -372,7 +368,7 @@ class PinBoard extends React.Component {
|
||||||
failureClassificationComment,
|
failureClassificationComment,
|
||||||
} = this.props;
|
} = this.props;
|
||||||
const { enteringBugNumber, newBugNumber } = this.state;
|
const { enteringBugNumber, newBugNumber } = this.state;
|
||||||
const selectedJobId = selectedJob ? selectedJob.id : null;
|
const selectedJobId = selectedJobFull ? selectedJobFull.id : null;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div id="pinboard-panel" className={isPinBoardVisible ? '' : 'hidden'}>
|
<div id="pinboard-panel" className={isPinBoardVisible ? '' : 'hidden'}>
|
||||||
|
@ -387,12 +383,12 @@ class PinBoard extends React.Component {
|
||||||
{Object.values(pinnedJobs).map(job => (
|
{Object.values(pinnedJobs).map(job => (
|
||||||
<span className="btn-group" key={job.id}>
|
<span className="btn-group" key={job.id}>
|
||||||
<span
|
<span
|
||||||
className={`btn pinned-job ${getJobBtnClass(job)} ${
|
className={`btn pinned-job ${job.btnClass} ${
|
||||||
selectedJobId === job.id
|
selectedJobId === job.id
|
||||||
? 'btn-lg selected-job'
|
? 'btn-lg selected-job'
|
||||||
: 'btn-xs'
|
: 'btn-xs'
|
||||||
}`}
|
}`}
|
||||||
title={getHoverText(job)}
|
title={job.hoverText}
|
||||||
onClick={() => setSelectedJob(job)}
|
onClick={() => setSelectedJob(job)}
|
||||||
data-job-id={job.job_id}
|
data-job-id={job.job_id}
|
||||||
>
|
>
|
||||||
|
@ -644,13 +640,13 @@ PinBoard.propTypes = {
|
||||||
currentRepo: PropTypes.object.isRequired,
|
currentRepo: PropTypes.object.isRequired,
|
||||||
failureClassificationId: PropTypes.number.isRequired,
|
failureClassificationId: PropTypes.number.isRequired,
|
||||||
failureClassificationComment: PropTypes.string.isRequired,
|
failureClassificationComment: PropTypes.string.isRequired,
|
||||||
selectedJob: PropTypes.object,
|
selectedJobFull: PropTypes.object,
|
||||||
email: PropTypes.string,
|
email: PropTypes.string,
|
||||||
revisionTips: PropTypes.array,
|
revisionTips: PropTypes.array,
|
||||||
};
|
};
|
||||||
|
|
||||||
PinBoard.defaultProps = {
|
PinBoard.defaultProps = {
|
||||||
selectedJob: null,
|
selectedJobFull: null,
|
||||||
email: null,
|
email: null,
|
||||||
revisionTips: [],
|
revisionTips: [],
|
||||||
};
|
};
|
||||||
|
|
|
@ -71,21 +71,23 @@ class ActionBar extends React.PureComponent {
|
||||||
};
|
};
|
||||||
|
|
||||||
canCancel = () => {
|
canCancel = () => {
|
||||||
const { selectedJob } = this.props;
|
const { selectedJobFull } = this.props;
|
||||||
return selectedJob.state === 'pending' || selectedJob.state === 'running';
|
return (
|
||||||
|
selectedJobFull.state === 'pending' || selectedJobFull.state === 'running'
|
||||||
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
createGeckoProfile = async () => {
|
createGeckoProfile = async () => {
|
||||||
const { user, selectedJob, notify } = this.props;
|
const { user, selectedJobFull, notify } = this.props;
|
||||||
if (!user.isLoggedIn) {
|
if (!user.isLoggedIn) {
|
||||||
return notify('Must be logged in to create a gecko profile', 'danger');
|
return notify('Must be logged in to create a gecko profile', 'danger');
|
||||||
}
|
}
|
||||||
|
|
||||||
const { id: decisionTaskId } = await PushModel.getDecisionTaskId(
|
const { id: decisionTaskId } = await PushModel.getDecisionTaskId(
|
||||||
selectedJob.push_id,
|
selectedJobFull.push_id,
|
||||||
notify,
|
notify,
|
||||||
);
|
);
|
||||||
TaskclusterModel.load(decisionTaskId, selectedJob).then(results => {
|
TaskclusterModel.load(decisionTaskId, selectedJobFull).then(results => {
|
||||||
const geckoprofile = results.actions.find(
|
const geckoprofile = results.actions.find(
|
||||||
result => result.name === 'geckoprofile',
|
result => result.name === 'geckoprofile',
|
||||||
);
|
);
|
||||||
|
@ -141,7 +143,7 @@ class ActionBar extends React.PureComponent {
|
||||||
};
|
};
|
||||||
|
|
||||||
backfillJob = async () => {
|
backfillJob = async () => {
|
||||||
const { user, selectedJob, notify } = this.props;
|
const { user, selectedJobFull, notify } = this.props;
|
||||||
|
|
||||||
if (!this.canBackfill()) {
|
if (!this.canBackfill()) {
|
||||||
return;
|
return;
|
||||||
|
@ -153,21 +155,21 @@ class ActionBar extends React.PureComponent {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!selectedJob.id) {
|
if (!selectedJobFull.id) {
|
||||||
notify('Job not yet loaded for backfill', 'warning');
|
notify('Job not yet loaded for backfill', 'warning');
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (
|
if (
|
||||||
selectedJob.build_system_type === 'taskcluster' ||
|
selectedJobFull.build_system_type === 'taskcluster' ||
|
||||||
selectedJob.reason.startsWith('Created by BBB for task')
|
selectedJobFull.reason.startsWith('Created by BBB for task')
|
||||||
) {
|
) {
|
||||||
const { id: decisionTaskId } = await PushModel.getDecisionTaskId(
|
const { id: decisionTaskId } = await PushModel.getDecisionTaskId(
|
||||||
selectedJob.push_id,
|
selectedJobFull.push_id,
|
||||||
notify,
|
notify,
|
||||||
);
|
);
|
||||||
TaskclusterModel.load(decisionTaskId, selectedJob).then(results => {
|
TaskclusterModel.load(decisionTaskId, selectedJobFull).then(results => {
|
||||||
const backfilltask = results.actions.find(
|
const backfilltask = results.actions.find(
|
||||||
result => result.name === 'backfill',
|
result => result.name === 'backfill',
|
||||||
);
|
);
|
||||||
|
@ -195,13 +197,13 @@ class ActionBar extends React.PureComponent {
|
||||||
};
|
};
|
||||||
|
|
||||||
isolateJob = async () => {
|
isolateJob = async () => {
|
||||||
const { user, selectedJob, notify } = this.props;
|
const { user, selectedJobFull, notify } = this.props;
|
||||||
const { id: decisionTaskId } = await PushModel.getDecisionTaskId(
|
const { id: decisionTaskId } = await PushModel.getDecisionTaskId(
|
||||||
selectedJob.push_id,
|
selectedJobFull.push_id,
|
||||||
notify,
|
notify,
|
||||||
);
|
);
|
||||||
|
|
||||||
if (!isTestIsolatable(selectedJob)) {
|
if (!isTestIsolatable(selectedJobFull)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -211,23 +213,23 @@ class ActionBar extends React.PureComponent {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!selectedJob.id) {
|
if (!selectedJobFull.id) {
|
||||||
notify('Job not yet loaded for isolation', 'warning');
|
notify('Job not yet loaded for isolation', 'warning');
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (selectedJob.state !== 'completed') {
|
if (selectedJobFull.state !== 'completed') {
|
||||||
notify('Job not yet completed. Try again later.', 'warning');
|
notify('Job not yet completed. Try again later.', 'warning');
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (
|
if (
|
||||||
selectedJob.build_system_type === 'taskcluster' ||
|
selectedJobFull.build_system_type === 'taskcluster' ||
|
||||||
selectedJob.reason.startsWith('Created by BBB for task')
|
selectedJobFull.reason.startsWith('Created by BBB for task')
|
||||||
) {
|
) {
|
||||||
TaskclusterModel.load(decisionTaskId, selectedJob).then(results => {
|
TaskclusterModel.load(decisionTaskId, selectedJobFull).then(results => {
|
||||||
const isolationtask = results.actions.find(
|
const isolationtask = results.actions.find(
|
||||||
result => result.name === 'isolate-test-failures',
|
result => result.name === 'isolate-test-failures',
|
||||||
);
|
);
|
||||||
|
@ -316,8 +318,8 @@ class ActionBar extends React.PureComponent {
|
||||||
};
|
};
|
||||||
|
|
||||||
createInteractiveTask = async () => {
|
createInteractiveTask = async () => {
|
||||||
const { user, selectedJob, repoName, notify } = this.props;
|
const { user, selectedJobFull, repoName, notify } = this.props;
|
||||||
const jobId = selectedJob.id;
|
const jobId = selectedJobFull.id;
|
||||||
|
|
||||||
if (!user.isLoggedIn) {
|
if (!user.isLoggedIn) {
|
||||||
return notify(
|
return notify(
|
||||||
|
@ -373,7 +375,7 @@ class ActionBar extends React.PureComponent {
|
||||||
};
|
};
|
||||||
|
|
||||||
cancelJob = () => {
|
cancelJob = () => {
|
||||||
this.cancelJobs([this.props.selectedJob]);
|
this.cancelJobs([this.props.selectedJobFull]);
|
||||||
};
|
};
|
||||||
|
|
||||||
toggleCustomJobActions = () => {
|
toggleCustomJobActions = () => {
|
||||||
|
@ -384,7 +386,7 @@ class ActionBar extends React.PureComponent {
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const {
|
const {
|
||||||
selectedJob,
|
selectedJobFull,
|
||||||
logViewerUrl,
|
logViewerUrl,
|
||||||
logViewerFullUrl,
|
logViewerFullUrl,
|
||||||
jobLogUrls,
|
jobLogUrls,
|
||||||
|
@ -407,7 +409,7 @@ class ActionBar extends React.PureComponent {
|
||||||
id="pin-job-btn"
|
id="pin-job-btn"
|
||||||
title="Add this job to the pinboard"
|
title="Add this job to the pinboard"
|
||||||
className="btn icon-blue"
|
className="btn icon-blue"
|
||||||
onClick={() => pinJob(selectedJob)}
|
onClick={() => pinJob(selectedJobFull)}
|
||||||
>
|
>
|
||||||
<FontAwesomeIcon icon={faThumbtack} title="Pin job" />
|
<FontAwesomeIcon icon={faThumbtack} title="Pin job" />
|
||||||
</Button>
|
</Button>
|
||||||
|
@ -422,12 +424,12 @@ class ActionBar extends React.PureComponent {
|
||||||
}
|
}
|
||||||
className={`btn ${user.isLoggedIn ? 'icon-green' : 'disabled'}`}
|
className={`btn ${user.isLoggedIn ? 'icon-green' : 'disabled'}`}
|
||||||
disabled={!user.isLoggedIn}
|
disabled={!user.isLoggedIn}
|
||||||
onClick={() => this.retriggerJob([selectedJob])}
|
onClick={() => this.retriggerJob([selectedJobFull])}
|
||||||
>
|
>
|
||||||
<FontAwesomeIcon icon={faRedo} title="Retrigger job" />
|
<FontAwesomeIcon icon={faRedo} title="Retrigger job" />
|
||||||
</Button>
|
</Button>
|
||||||
</li>
|
</li>
|
||||||
{isReftest(selectedJob) &&
|
{isReftest(selectedJobFull) &&
|
||||||
jobLogUrls.map(jobLogUrl => (
|
jobLogUrls.map(jobLogUrl => (
|
||||||
<li key={`reftest-${jobLogUrl.id}`}>
|
<li key={`reftest-${jobLogUrl.id}`}>
|
||||||
<a
|
<a
|
||||||
|
@ -484,7 +486,7 @@ class ActionBar extends React.PureComponent {
|
||||||
Backfill
|
Backfill
|
||||||
</span>
|
</span>
|
||||||
</li>
|
</li>
|
||||||
{selectedJob.taskcluster_metadata && (
|
{selectedJobFull.taskcluster_metadata && (
|
||||||
<React.Fragment>
|
<React.Fragment>
|
||||||
<li>
|
<li>
|
||||||
<a
|
<a
|
||||||
|
@ -492,7 +494,7 @@ class ActionBar extends React.PureComponent {
|
||||||
rel="noopener noreferrer"
|
rel="noopener noreferrer"
|
||||||
className="dropdown-item"
|
className="dropdown-item"
|
||||||
href={getInspectTaskUrl(
|
href={getInspectTaskUrl(
|
||||||
selectedJob.taskcluster_metadata.task_id,
|
selectedJobFull.taskcluster_metadata.task_id,
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
Inspect Task
|
Inspect Task
|
||||||
|
@ -506,7 +508,7 @@ class ActionBar extends React.PureComponent {
|
||||||
Create Interactive Task
|
Create Interactive Task
|
||||||
</Button>
|
</Button>
|
||||||
</li>
|
</li>
|
||||||
{isPerfTest(selectedJob) && (
|
{isPerfTest(selectedJobFull) && (
|
||||||
<li>
|
<li>
|
||||||
<Button
|
<Button
|
||||||
className="dropdown-item py-2"
|
className="dropdown-item py-2"
|
||||||
|
@ -516,7 +518,7 @@ class ActionBar extends React.PureComponent {
|
||||||
</Button>
|
</Button>
|
||||||
</li>
|
</li>
|
||||||
)}
|
)}
|
||||||
{isTestIsolatable(selectedJob) && (
|
{isTestIsolatable(selectedJobFull) && (
|
||||||
<li>
|
<li>
|
||||||
<Button
|
<Button
|
||||||
className="dropdown-item py-2"
|
className="dropdown-item py-2"
|
||||||
|
@ -542,8 +544,8 @@ class ActionBar extends React.PureComponent {
|
||||||
</nav>
|
</nav>
|
||||||
{customJobActionsShowing && (
|
{customJobActionsShowing && (
|
||||||
<CustomJobActions
|
<CustomJobActions
|
||||||
job={selectedJob}
|
job={selectedJobFull}
|
||||||
pushId={selectedJob.push_id}
|
pushId={selectedJobFull.push_id}
|
||||||
isLoggedIn={user.isLoggedIn}
|
isLoggedIn={user.isLoggedIn}
|
||||||
toggle={this.toggleCustomJobActions}
|
toggle={this.toggleCustomJobActions}
|
||||||
/>
|
/>
|
||||||
|
@ -557,7 +559,7 @@ ActionBar.propTypes = {
|
||||||
pinJob: PropTypes.func.isRequired,
|
pinJob: PropTypes.func.isRequired,
|
||||||
user: PropTypes.object.isRequired,
|
user: PropTypes.object.isRequired,
|
||||||
repoName: PropTypes.string.isRequired,
|
repoName: PropTypes.string.isRequired,
|
||||||
selectedJob: PropTypes.object.isRequired,
|
selectedJobFull: PropTypes.object.isRequired,
|
||||||
logParseStatus: PropTypes.string.isRequired,
|
logParseStatus: PropTypes.string.isRequired,
|
||||||
notify: PropTypes.func.isRequired,
|
notify: PropTypes.func.isRequired,
|
||||||
jobLogUrls: PropTypes.array,
|
jobLogUrls: PropTypes.array,
|
||||||
|
|
|
@ -1,28 +1,26 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
|
|
||||||
import { getStatus } from '../../../helpers/job';
|
|
||||||
|
|
||||||
function StatusPanel(props) {
|
function StatusPanel(props) {
|
||||||
const { selectedJob } = props;
|
const { selectedJobFull } = props;
|
||||||
const shadingClass = `result-status-shading-${getStatus(selectedJob)}`;
|
const shadingClass = `result-status-shading-${selectedJobFull.resultStatus}`;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<li id="result-status-pane" className={`small ${shadingClass}`}>
|
<li id="result-status-pane" className={`small ${shadingClass}`}>
|
||||||
<div>
|
<div>
|
||||||
<strong>Result:</strong>
|
<strong>Result:</strong>
|
||||||
<span> {selectedJob.result}</span>
|
<span> {selectedJobFull.result}</span>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<strong>State:</strong>
|
<strong>State:</strong>
|
||||||
<span> {selectedJob.state}</span>
|
<span> {selectedJobFull.state}</span>
|
||||||
</div>
|
</div>
|
||||||
</li>
|
</li>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
StatusPanel.propTypes = {
|
StatusPanel.propTypes = {
|
||||||
selectedJob: PropTypes.object.isRequired,
|
selectedJobFull: PropTypes.object.isRequired,
|
||||||
};
|
};
|
||||||
|
|
||||||
export default StatusPanel;
|
export default StatusPanel;
|
||||||
|
|
|
@ -13,7 +13,7 @@ class SummaryPanel extends React.PureComponent {
|
||||||
render() {
|
render() {
|
||||||
const {
|
const {
|
||||||
repoName,
|
repoName,
|
||||||
selectedJob,
|
selectedJobFull,
|
||||||
latestClassification,
|
latestClassification,
|
||||||
bugs,
|
bugs,
|
||||||
jobLogUrls,
|
jobLogUrls,
|
||||||
|
@ -38,7 +38,7 @@ class SummaryPanel extends React.PureComponent {
|
||||||
return (
|
return (
|
||||||
<div id="summary-panel" role="region" aria-label="Summary">
|
<div id="summary-panel" role="region" aria-label="Summary">
|
||||||
<ActionBar
|
<ActionBar
|
||||||
selectedJob={selectedJob}
|
selectedJobFull={selectedJobFull}
|
||||||
repoName={repoName}
|
repoName={repoName}
|
||||||
logParseStatus={logParseStatus}
|
logParseStatus={logParseStatus}
|
||||||
isTryRepo={currentRepo.is_try_repo}
|
isTryRepo={currentRepo.is_try_repo}
|
||||||
|
@ -65,15 +65,15 @@ class SummaryPanel extends React.PureComponent {
|
||||||
<ul className="list-unstyled">
|
<ul className="list-unstyled">
|
||||||
{latestClassification && (
|
{latestClassification && (
|
||||||
<ClassificationsPanel
|
<ClassificationsPanel
|
||||||
job={selectedJob}
|
job={selectedJobFull}
|
||||||
classification={latestClassification}
|
classification={latestClassification}
|
||||||
classificationMap={classificationMap}
|
classificationMap={classificationMap}
|
||||||
bugs={bugs}
|
bugs={bugs}
|
||||||
currentRepo={currentRepo}
|
currentRepo={currentRepo}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
<StatusPanel selectedJob={selectedJob} />
|
<StatusPanel selectedJobFull={selectedJobFull} />
|
||||||
<JobInfo job={selectedJob} extraFields={logStatus} />
|
<JobInfo job={selectedJobFull} extraFields={logStatus} />
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -88,7 +88,7 @@ SummaryPanel.propTypes = {
|
||||||
user: PropTypes.object.isRequired,
|
user: PropTypes.object.isRequired,
|
||||||
currentRepo: PropTypes.object.isRequired,
|
currentRepo: PropTypes.object.isRequired,
|
||||||
classificationMap: PropTypes.object.isRequired,
|
classificationMap: PropTypes.object.isRequired,
|
||||||
selectedJob: PropTypes.object.isRequired,
|
selectedJobFull: PropTypes.object.isRequired,
|
||||||
latestClassification: PropTypes.object,
|
latestClassification: PropTypes.object,
|
||||||
jobLogUrls: PropTypes.array,
|
jobLogUrls: PropTypes.array,
|
||||||
jobDetailLoading: PropTypes.bool,
|
jobDetailLoading: PropTypes.bool,
|
||||||
|
|
|
@ -175,9 +175,13 @@ class AnnotationsTab extends React.Component {
|
||||||
};
|
};
|
||||||
|
|
||||||
deleteClassification = classification => {
|
deleteClassification = classification => {
|
||||||
const { selectedJob, recalculateUnclassifiedCounts, notify } = this.props;
|
const {
|
||||||
|
selectedJobFull,
|
||||||
|
recalculateUnclassifiedCounts,
|
||||||
|
notify,
|
||||||
|
} = this.props;
|
||||||
|
|
||||||
selectedJob.failure_classification_id = 1;
|
selectedJobFull.failure_classification_id = 1;
|
||||||
recalculateUnclassifiedCounts();
|
recalculateUnclassifiedCounts();
|
||||||
|
|
||||||
classification.destroy().then(
|
classification.destroy().then(
|
||||||
|
@ -247,16 +251,10 @@ AnnotationsTab.propTypes = {
|
||||||
classifications: PropTypes.array.isRequired,
|
classifications: PropTypes.array.isRequired,
|
||||||
recalculateUnclassifiedCounts: PropTypes.func.isRequired,
|
recalculateUnclassifiedCounts: PropTypes.func.isRequired,
|
||||||
notify: PropTypes.func.isRequired,
|
notify: PropTypes.func.isRequired,
|
||||||
selectedJob: PropTypes.object,
|
selectedJobFull: PropTypes.object.isRequired,
|
||||||
};
|
};
|
||||||
|
|
||||||
AnnotationsTab.defaultProps = {
|
|
||||||
selectedJob: null,
|
|
||||||
};
|
|
||||||
|
|
||||||
const mapStateToProps = ({ selectedJob: { selectedJob } }) => ({ selectedJob });
|
|
||||||
|
|
||||||
export default connect(
|
export default connect(
|
||||||
mapStateToProps,
|
null,
|
||||||
{ notify, recalculateUnclassifiedCounts },
|
{ notify, recalculateUnclassifiedCounts },
|
||||||
)(AnnotationsTab);
|
)(AnnotationsTab);
|
||||||
|
|
|
@ -6,7 +6,7 @@ import { faSpinner } from '@fortawesome/free-solid-svg-icons';
|
||||||
|
|
||||||
import { thMaxPushFetchSize } from '../../../helpers/constants';
|
import { thMaxPushFetchSize } from '../../../helpers/constants';
|
||||||
import { toDateStr, toShortDateStr } from '../../../helpers/display';
|
import { toDateStr, toShortDateStr } from '../../../helpers/display';
|
||||||
import { getBtnClass, getStatus } from '../../../helpers/job';
|
import { addAggregateFields } from '../../../helpers/job';
|
||||||
import { getJobsUrl } from '../../../helpers/url';
|
import { getJobsUrl } from '../../../helpers/url';
|
||||||
import JobModel from '../../../models/job';
|
import JobModel from '../../../models/job';
|
||||||
import PushModel from '../../../models/push';
|
import PushModel from '../../../models/push';
|
||||||
|
@ -42,7 +42,7 @@ class SimilarJobsTab extends React.Component {
|
||||||
|
|
||||||
getSimilarJobs = async () => {
|
getSimilarJobs = async () => {
|
||||||
const { page, similarJobs, selectedSimilarJob } = this.state;
|
const { page, similarJobs, selectedSimilarJob } = this.state;
|
||||||
const { repoName, selectedJob, notify } = this.props;
|
const { repoName, selectedJobFull, notify } = this.props;
|
||||||
const options = {
|
const options = {
|
||||||
// get one extra to detect if there are more jobs that can be loaded (hasNextPage)
|
// get one extra to detect if there are more jobs that can be loaded (hasNextPage)
|
||||||
count: this.pageSize + 1,
|
count: this.pageSize + 1,
|
||||||
|
@ -52,14 +52,14 @@ class SimilarJobsTab extends React.Component {
|
||||||
['filterBuildPlatformId', 'filterOptionCollectionHash'].forEach(key => {
|
['filterBuildPlatformId', 'filterOptionCollectionHash'].forEach(key => {
|
||||||
if (this.state[key]) {
|
if (this.state[key]) {
|
||||||
const field = this.filterMap[key];
|
const field = this.filterMap[key];
|
||||||
options[field] = selectedJob[field];
|
options[field] = selectedJobFull[field];
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
const {
|
const {
|
||||||
data: newSimilarJobs,
|
data: newSimilarJobs,
|
||||||
failureStatus,
|
failureStatus,
|
||||||
} = await JobModel.getSimilarJobs(selectedJob.id, options);
|
} = await JobModel.getSimilarJobs(selectedJobFull.id, options);
|
||||||
|
|
||||||
if (!failureStatus) {
|
if (!failureStatus) {
|
||||||
this.setState({ hasNextPage: newSimilarJobs.length > this.pageSize });
|
this.setState({ hasNextPage: newSimilarJobs.length > this.pageSize });
|
||||||
|
@ -119,8 +119,7 @@ class SimilarJobsTab extends React.Component {
|
||||||
const { repoName, classificationMap } = this.props;
|
const { repoName, classificationMap } = this.props;
|
||||||
|
|
||||||
JobModel.get(repoName, job.id).then(nextJob => {
|
JobModel.get(repoName, job.id).then(nextJob => {
|
||||||
nextJob.result_status = getStatus(nextJob);
|
addAggregateFields(nextJob);
|
||||||
nextJob.duration = (nextJob.end_timestamp - nextJob.start_timestamp) / 60;
|
|
||||||
nextJob.failure_classification =
|
nextJob.failure_classification =
|
||||||
classificationMap[nextJob.failure_classification_id];
|
classificationMap[nextJob.failure_classification_id];
|
||||||
|
|
||||||
|
@ -155,7 +154,6 @@ class SimilarJobsTab extends React.Component {
|
||||||
filterBuildPlatformId,
|
filterBuildPlatformId,
|
||||||
isLoading,
|
isLoading,
|
||||||
} = this.state;
|
} = this.state;
|
||||||
const button_class = job => getBtnClass(getStatus(job));
|
|
||||||
const selectedSimilarJobId = selectedSimilarJob
|
const selectedSimilarJobId = selectedSimilarJob
|
||||||
? selectedSimilarJob.id
|
? selectedSimilarJob.id
|
||||||
: null;
|
: null;
|
||||||
|
@ -187,9 +185,7 @@ class SimilarJobsTab extends React.Component {
|
||||||
>
|
>
|
||||||
<td>
|
<td>
|
||||||
<button
|
<button
|
||||||
className={`btn btn-similar-jobs btn-xs ${button_class(
|
className={`btn btn-similar-jobs btn-xs ${similarJob.btnClass}`}
|
||||||
similarJob,
|
|
||||||
)}`}
|
|
||||||
type="button"
|
type="button"
|
||||||
>
|
>
|
||||||
{similarJob.job_type_symbol}
|
{similarJob.job_type_symbol}
|
||||||
|
@ -250,7 +246,7 @@ class SimilarJobsTab extends React.Component {
|
||||||
<tbody>
|
<tbody>
|
||||||
<tr>
|
<tr>
|
||||||
<th>Result</th>
|
<th>Result</th>
|
||||||
<td>{selectedSimilarJob.result_status}</td>
|
<td>{selectedSimilarJob.resultStatus}</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<th>Build</th>
|
<th>Build</th>
|
||||||
|
@ -329,16 +325,10 @@ SimilarJobsTab.propTypes = {
|
||||||
repoName: PropTypes.string.isRequired,
|
repoName: PropTypes.string.isRequired,
|
||||||
classificationMap: PropTypes.object.isRequired,
|
classificationMap: PropTypes.object.isRequired,
|
||||||
notify: PropTypes.func.isRequired,
|
notify: PropTypes.func.isRequired,
|
||||||
selectedJob: PropTypes.object,
|
selectedJobFull: PropTypes.object.isRequired,
|
||||||
};
|
};
|
||||||
|
|
||||||
SimilarJobsTab.defaultProps = {
|
|
||||||
selectedJob: null,
|
|
||||||
};
|
|
||||||
|
|
||||||
const mapStateToProps = ({ selectedJob: { selectedJob } }) => ({ selectedJob });
|
|
||||||
|
|
||||||
export default connect(
|
export default connect(
|
||||||
mapStateToProps,
|
null,
|
||||||
{ notify },
|
{ notify },
|
||||||
)(SimilarJobsTab);
|
)(SimilarJobsTab);
|
||||||
|
|
|
@ -11,7 +11,6 @@ import {
|
||||||
} from '@fortawesome/free-solid-svg-icons';
|
} from '@fortawesome/free-solid-svg-icons';
|
||||||
|
|
||||||
import { thEvents } from '../../../helpers/constants';
|
import { thEvents } from '../../../helpers/constants';
|
||||||
import { getStatus } from '../../../helpers/job';
|
|
||||||
import JobDetails from '../../../shared/JobDetails';
|
import JobDetails from '../../../shared/JobDetails';
|
||||||
import { withPinnedJobs } from '../../context/PinnedJobs';
|
import { withPinnedJobs } from '../../context/PinnedJobs';
|
||||||
import { clearSelectedJob } from '../../redux/stores/selectedJob';
|
import { clearSelectedJob } from '../../redux/stores/selectedJob';
|
||||||
|
@ -31,23 +30,23 @@ class TabsPanel extends React.Component {
|
||||||
}
|
}
|
||||||
|
|
||||||
static getDerivedStateFromProps(props, state) {
|
static getDerivedStateFromProps(props, state) {
|
||||||
const { perfJobDetail, selectedJob } = props;
|
const { perfJobDetail, selectedJobFull } = props;
|
||||||
|
|
||||||
// This fires every time the props change. But we only want to figure out the new default
|
// This fires every time the props change. But we only want to figure out the new default
|
||||||
// tab when we get a new job. However, the job could change, then later, the perf details fetch
|
// tab when we get a new job. However, the job could change, then later, the perf details fetch
|
||||||
// returns. So we need to check for a change in the size of the perfJobDetail too.
|
// returns. So we need to check for a change in the size of the perfJobDetail too.
|
||||||
if (
|
if (
|
||||||
state.jobId !== selectedJob.id ||
|
state.jobId !== selectedJobFull.id ||
|
||||||
state.perfJobDetailSize !== perfJobDetail.length
|
state.perfJobDetailSize !== perfJobDetail.length
|
||||||
) {
|
) {
|
||||||
const tabIndex = TabsPanel.getDefaultTabIndex(
|
const tabIndex = TabsPanel.getDefaultTabIndex(
|
||||||
getStatus(selectedJob),
|
selectedJobFull.resultStatus,
|
||||||
!!perfJobDetail.length,
|
!!perfJobDetail.length,
|
||||||
);
|
);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
tabIndex,
|
tabIndex,
|
||||||
jobId: selectedJob.id,
|
jobId: selectedJobFull.id,
|
||||||
perfJobDetailSize: perfJobDetail.length,
|
perfJobDetailSize: perfJobDetail.length,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -115,6 +114,7 @@ class TabsPanel extends React.Component {
|
||||||
logViewerFullUrl,
|
logViewerFullUrl,
|
||||||
reftestUrl,
|
reftestUrl,
|
||||||
clearSelectedJob,
|
clearSelectedJob,
|
||||||
|
selectedJobFull,
|
||||||
} = this.props;
|
} = this.props;
|
||||||
const { tabIndex } = this.state;
|
const { tabIndex } = this.state;
|
||||||
|
|
||||||
|
@ -192,6 +192,7 @@ class TabsPanel extends React.Component {
|
||||||
logParseStatus={logParseStatus}
|
logParseStatus={logParseStatus}
|
||||||
logViewerFullUrl={logViewerFullUrl}
|
logViewerFullUrl={logViewerFullUrl}
|
||||||
reftestUrl={reftestUrl}
|
reftestUrl={reftestUrl}
|
||||||
|
selectedJobFull={selectedJobFull}
|
||||||
/>
|
/>
|
||||||
</TabPanel>
|
</TabPanel>
|
||||||
<TabPanel>
|
<TabPanel>
|
||||||
|
@ -199,12 +200,14 @@ class TabsPanel extends React.Component {
|
||||||
classificationMap={classificationMap}
|
classificationMap={classificationMap}
|
||||||
classifications={classifications}
|
classifications={classifications}
|
||||||
bugs={bugs}
|
bugs={bugs}
|
||||||
|
selectedJobFull={selectedJobFull}
|
||||||
/>
|
/>
|
||||||
</TabPanel>
|
</TabPanel>
|
||||||
<TabPanel>
|
<TabPanel>
|
||||||
<SimilarJobsTab
|
<SimilarJobsTab
|
||||||
repoName={repoName}
|
repoName={repoName}
|
||||||
classificationMap={classificationMap}
|
classificationMap={classificationMap}
|
||||||
|
selectedJobFull={selectedJobFull}
|
||||||
/>
|
/>
|
||||||
</TabPanel>
|
</TabPanel>
|
||||||
{!!perfJobDetail.length && (
|
{!!perfJobDetail.length && (
|
||||||
|
@ -232,9 +235,9 @@ TabsPanel.propTypes = {
|
||||||
countPinnedJobs: PropTypes.number.isRequired,
|
countPinnedJobs: PropTypes.number.isRequired,
|
||||||
bugs: PropTypes.array.isRequired,
|
bugs: PropTypes.array.isRequired,
|
||||||
clearSelectedJob: PropTypes.func.isRequired,
|
clearSelectedJob: PropTypes.func.isRequired,
|
||||||
|
selectedJobFull: PropTypes.object.isRequired,
|
||||||
perfJobDetail: PropTypes.array,
|
perfJobDetail: PropTypes.array,
|
||||||
suggestions: PropTypes.array,
|
suggestions: PropTypes.array,
|
||||||
selectedJob: PropTypes.object,
|
|
||||||
jobRevision: PropTypes.string,
|
jobRevision: PropTypes.string,
|
||||||
errors: PropTypes.array,
|
errors: PropTypes.array,
|
||||||
bugSuggestionsLoading: PropTypes.bool,
|
bugSuggestionsLoading: PropTypes.bool,
|
||||||
|
@ -246,7 +249,6 @@ TabsPanel.propTypes = {
|
||||||
|
|
||||||
TabsPanel.defaultProps = {
|
TabsPanel.defaultProps = {
|
||||||
suggestions: [],
|
suggestions: [],
|
||||||
selectedJob: null,
|
|
||||||
errors: [],
|
errors: [],
|
||||||
bugSuggestionsLoading: false,
|
bugSuggestionsLoading: false,
|
||||||
jobLogUrls: [],
|
jobLogUrls: [],
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import { connect } from 'react-redux';
|
|
||||||
import Highlighter from 'react-highlight-words';
|
import Highlighter from 'react-highlight-words';
|
||||||
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
||||||
import { faThumbtack } from '@fortawesome/free-solid-svg-icons';
|
import { faThumbtack } from '@fortawesome/free-solid-svg-icons';
|
||||||
|
@ -10,7 +9,14 @@ import { getBugUrl } from '../../../../helpers/url';
|
||||||
import { withPinnedJobs } from '../../../context/PinnedJobs';
|
import { withPinnedJobs } from '../../../context/PinnedJobs';
|
||||||
|
|
||||||
function BugListItem(props) {
|
function BugListItem(props) {
|
||||||
const { bug, suggestion, bugClassName, title, selectedJob, addBug } = props;
|
const {
|
||||||
|
bug,
|
||||||
|
suggestion,
|
||||||
|
bugClassName,
|
||||||
|
title,
|
||||||
|
selectedJobFull,
|
||||||
|
addBug,
|
||||||
|
} = props;
|
||||||
const bugUrl = getBugUrl(bug.id);
|
const bugUrl = getBugUrl(bug.id);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
@ -18,7 +24,7 @@ function BugListItem(props) {
|
||||||
<button
|
<button
|
||||||
className="btn btn-xs btn-light-bordered"
|
className="btn btn-xs btn-light-bordered"
|
||||||
type="button"
|
type="button"
|
||||||
onClick={() => addBug(bug, selectedJob)}
|
onClick={() => addBug(bug, selectedJobFull)}
|
||||||
title="add to list of bugs to associate with all pinned jobs"
|
title="add to list of bugs to associate with all pinned jobs"
|
||||||
>
|
>
|
||||||
<FontAwesomeIcon icon={faThumbtack} title="Select bug" />
|
<FontAwesomeIcon icon={faThumbtack} title="Select bug" />
|
||||||
|
@ -47,7 +53,7 @@ BugListItem.propTypes = {
|
||||||
bug: PropTypes.object.isRequired,
|
bug: PropTypes.object.isRequired,
|
||||||
suggestion: PropTypes.object.isRequired,
|
suggestion: PropTypes.object.isRequired,
|
||||||
addBug: PropTypes.func.isRequired,
|
addBug: PropTypes.func.isRequired,
|
||||||
selectedJob: PropTypes.object.isRequired,
|
selectedJobFull: PropTypes.object.isRequired,
|
||||||
bugClassName: PropTypes.string,
|
bugClassName: PropTypes.string,
|
||||||
title: PropTypes.string,
|
title: PropTypes.string,
|
||||||
};
|
};
|
||||||
|
@ -57,6 +63,4 @@ BugListItem.defaultProps = {
|
||||||
title: null,
|
title: null,
|
||||||
};
|
};
|
||||||
|
|
||||||
const mapStateToProps = ({ selectedJob: { selectedJob } }) => ({ selectedJob });
|
export default withPinnedJobs(BugListItem);
|
||||||
|
|
||||||
export default connect(mapStateToProps)(withPinnedJobs(BugListItem));
|
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import { connect } from 'react-redux';
|
|
||||||
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
||||||
import { faSpinner } from '@fortawesome/free-solid-svg-icons';
|
import { faSpinner } from '@fortawesome/free-solid-svg-icons';
|
||||||
|
|
||||||
|
@ -24,9 +23,9 @@ class FailureSummaryTab extends React.Component {
|
||||||
}
|
}
|
||||||
|
|
||||||
fileBug = suggestion => {
|
fileBug = suggestion => {
|
||||||
const { selectedJob, pinJob } = this.props;
|
const { selectedJobFull, pinJob } = this.props;
|
||||||
|
|
||||||
pinJob(selectedJob);
|
pinJob(selectedJobFull);
|
||||||
this.setState({
|
this.setState({
|
||||||
isBugFilerOpen: true,
|
isBugFilerOpen: true,
|
||||||
suggestion,
|
suggestion,
|
||||||
|
@ -54,7 +53,7 @@ class FailureSummaryTab extends React.Component {
|
||||||
errors,
|
errors,
|
||||||
logViewerFullUrl,
|
logViewerFullUrl,
|
||||||
bugSuggestionsLoading,
|
bugSuggestionsLoading,
|
||||||
selectedJob,
|
selectedJobFull,
|
||||||
reftestUrl,
|
reftestUrl,
|
||||||
} = this.props;
|
} = this.props;
|
||||||
const { isBugFilerOpen, suggestion } = this.state;
|
const { isBugFilerOpen, suggestion } = this.state;
|
||||||
|
@ -70,6 +69,7 @@ class FailureSummaryTab extends React.Component {
|
||||||
index={index}
|
index={index}
|
||||||
suggestion={suggestion}
|
suggestion={suggestion}
|
||||||
toggleBugFiler={() => this.fileBug(suggestion)}
|
toggleBugFiler={() => this.fileBug(suggestion)}
|
||||||
|
selectedJobFull={selectedJobFull}
|
||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
|
|
||||||
|
@ -150,9 +150,9 @@ class FailureSummaryTab extends React.Component {
|
||||||
suggestions={suggestions}
|
suggestions={suggestions}
|
||||||
fullLog={jobLogUrls[0].url}
|
fullLog={jobLogUrls[0].url}
|
||||||
parsedLog={logViewerFullUrl}
|
parsedLog={logViewerFullUrl}
|
||||||
reftestUrl={isReftest(selectedJob) ? reftestUrl : ''}
|
reftestUrl={isReftest(selectedJobFull) ? reftestUrl : ''}
|
||||||
successCallback={this.bugFilerCallback}
|
successCallback={this.bugFilerCallback}
|
||||||
jobGroupName={selectedJob.job_group_name}
|
jobGroupName={selectedJobFull.job_group_name}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
@ -163,8 +163,8 @@ class FailureSummaryTab extends React.Component {
|
||||||
FailureSummaryTab.propTypes = {
|
FailureSummaryTab.propTypes = {
|
||||||
addBug: PropTypes.func.isRequired,
|
addBug: PropTypes.func.isRequired,
|
||||||
pinJob: PropTypes.func.isRequired,
|
pinJob: PropTypes.func.isRequired,
|
||||||
|
selectedJobFull: PropTypes.object.isRequired,
|
||||||
suggestions: PropTypes.array,
|
suggestions: PropTypes.array,
|
||||||
selectedJob: PropTypes.object,
|
|
||||||
errors: PropTypes.array,
|
errors: PropTypes.array,
|
||||||
bugSuggestionsLoading: PropTypes.bool,
|
bugSuggestionsLoading: PropTypes.bool,
|
||||||
jobLogUrls: PropTypes.array,
|
jobLogUrls: PropTypes.array,
|
||||||
|
@ -175,7 +175,6 @@ FailureSummaryTab.propTypes = {
|
||||||
|
|
||||||
FailureSummaryTab.defaultProps = {
|
FailureSummaryTab.defaultProps = {
|
||||||
suggestions: [],
|
suggestions: [],
|
||||||
selectedJob: null,
|
|
||||||
reftestUrl: null,
|
reftestUrl: null,
|
||||||
errors: [],
|
errors: [],
|
||||||
bugSuggestionsLoading: false,
|
bugSuggestionsLoading: false,
|
||||||
|
@ -184,6 +183,4 @@ FailureSummaryTab.defaultProps = {
|
||||||
logViewerFullUrl: null,
|
logViewerFullUrl: null,
|
||||||
};
|
};
|
||||||
|
|
||||||
const mapStateToProps = ({ selectedJob: { selectedJob } }) => ({ selectedJob });
|
export default withPinnedJobs(FailureSummaryTab);
|
||||||
|
|
||||||
export default connect(mapStateToProps)(withPinnedJobs(FailureSummaryTab));
|
|
||||||
|
|
|
@ -22,7 +22,7 @@ export default class SuggestionsListItem extends React.Component {
|
||||||
};
|
};
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const { suggestion, toggleBugFiler } = this.props;
|
const { suggestion, toggleBugFiler, selectedJobFull } = this.props;
|
||||||
const { suggestionShowMore } = this.state;
|
const { suggestionShowMore } = this.state;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
@ -42,7 +42,12 @@ export default class SuggestionsListItem extends React.Component {
|
||||||
{suggestion.valid_open_recent && (
|
{suggestion.valid_open_recent && (
|
||||||
<ul className="list-unstyled failure-summary-bugs">
|
<ul className="list-unstyled failure-summary-bugs">
|
||||||
{suggestion.bugs.open_recent.map(bug => (
|
{suggestion.bugs.open_recent.map(bug => (
|
||||||
<BugListItem key={bug.id} bug={bug} suggestion={suggestion} />
|
<BugListItem
|
||||||
|
key={bug.id}
|
||||||
|
bug={bug}
|
||||||
|
suggestion={suggestion}
|
||||||
|
selectedJobFull={selectedJobFull}
|
||||||
|
/>
|
||||||
))}
|
))}
|
||||||
</ul>
|
</ul>
|
||||||
)}
|
)}
|
||||||
|
@ -68,6 +73,7 @@ export default class SuggestionsListItem extends React.Component {
|
||||||
suggestion={suggestion}
|
suggestion={suggestion}
|
||||||
bugClassName={bug.resolution !== '' ? 'strike-through' : ''}
|
bugClassName={bug.resolution !== '' ? 'strike-through' : ''}
|
||||||
title={bug.resolution !== '' ? bug.resolution : ''}
|
title={bug.resolution !== '' ? bug.resolution : ''}
|
||||||
|
selectedJobFull={selectedJobFull}
|
||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
</ul>
|
</ul>
|
||||||
|
@ -87,6 +93,7 @@ export default class SuggestionsListItem extends React.Component {
|
||||||
}
|
}
|
||||||
|
|
||||||
SuggestionsListItem.propTypes = {
|
SuggestionsListItem.propTypes = {
|
||||||
|
selectedJobFull: PropTypes.object.isRequired,
|
||||||
suggestion: PropTypes.object.isRequired,
|
suggestion: PropTypes.object.isRequired,
|
||||||
toggleBugFiler: PropTypes.func.isRequired,
|
toggleBugFiler: PropTypes.func.isRequired,
|
||||||
};
|
};
|
||||||
|
|
|
@ -4,7 +4,7 @@ import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
||||||
import { faStar as faStarRegular } from '@fortawesome/free-regular-svg-icons';
|
import { faStar as faStarRegular } from '@fortawesome/free-regular-svg-icons';
|
||||||
import { faStar as faStarSolid } from '@fortawesome/free-solid-svg-icons';
|
import { faStar as faStarSolid } from '@fortawesome/free-solid-svg-icons';
|
||||||
|
|
||||||
import { getBtnClass, findJobInstance } from '../../helpers/job';
|
import { findJobInstance } from '../../helpers/job';
|
||||||
import { getSelectedJobId, getUrlParam } from '../../helpers/location';
|
import { getSelectedJobId, getUrlParam } from '../../helpers/location';
|
||||||
|
|
||||||
export default class JobButtonComponent extends React.Component {
|
export default class JobButtonComponent extends React.Component {
|
||||||
|
@ -80,24 +80,19 @@ export default class JobButtonComponent extends React.Component {
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const { job, resultStatus } = this.props;
|
const { job } = this.props;
|
||||||
const { isSelected, isRunnableSelected } = this.state;
|
const { isSelected, isRunnableSelected } = this.state;
|
||||||
const {
|
const {
|
||||||
state,
|
state,
|
||||||
job_type_name,
|
|
||||||
failure_classification_id,
|
failure_classification_id,
|
||||||
end_timestamp,
|
|
||||||
start_timestamp,
|
|
||||||
ref_data_name,
|
|
||||||
visible,
|
visible,
|
||||||
id,
|
id,
|
||||||
job_type_symbol,
|
job_type_symbol,
|
||||||
|
btnClass,
|
||||||
} = job;
|
} = job;
|
||||||
|
|
||||||
if (!visible) return null;
|
if (!visible) return null;
|
||||||
const runnable = state === 'runnable';
|
const runnable = state === 'runnable';
|
||||||
const btnClass = getBtnClass(resultStatus, failure_classification_id);
|
|
||||||
let title = `${resultStatus} | ${job_type_name}`;
|
|
||||||
let classifiedIcon = null;
|
let classifiedIcon = null;
|
||||||
|
|
||||||
if (failure_classification_id > 1) {
|
if (failure_classification_id > 1) {
|
||||||
|
@ -105,20 +100,14 @@ export default class JobButtonComponent extends React.Component {
|
||||||
failure_classification_id === 7 ? faStarRegular : faStarSolid;
|
failure_classification_id === 7 ? faStarRegular : faStarSolid;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (state === 'completed') {
|
|
||||||
const duration = Math.round((end_timestamp - start_timestamp) / 60);
|
|
||||||
title += ` (${duration} mins)`;
|
|
||||||
}
|
|
||||||
|
|
||||||
const classes = ['btn', btnClass, 'filter-shown'];
|
const classes = ['btn', btnClass, 'filter-shown'];
|
||||||
const attributes = {
|
const attributes = {
|
||||||
'data-job-id': id,
|
'data-job-id': id,
|
||||||
title,
|
title: job.hoverText,
|
||||||
};
|
};
|
||||||
|
|
||||||
if (runnable) {
|
if (runnable) {
|
||||||
classes.push('runnable-job-btn', 'runnable');
|
classes.push('runnable-job-btn', 'runnable');
|
||||||
attributes['data-buildername'] = ref_data_name;
|
|
||||||
if (isRunnableSelected) {
|
if (isRunnableSelected) {
|
||||||
classes.push('runnable-job-btn-selected');
|
classes.push('runnable-job-btn-selected');
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,7 +3,6 @@ import PropTypes from 'prop-types';
|
||||||
import countBy from 'lodash/countBy';
|
import countBy from 'lodash/countBy';
|
||||||
|
|
||||||
import { thFailureResults } from '../../helpers/constants';
|
import { thFailureResults } from '../../helpers/constants';
|
||||||
import { getBtnClass, getStatus } from '../../helpers/job';
|
|
||||||
import { getSelectedJobId, getUrlParam } from '../../helpers/location';
|
import { getSelectedJobId, getUrlParam } from '../../helpers/location';
|
||||||
|
|
||||||
import JobButton from './JobButton';
|
import JobButton from './JobButton';
|
||||||
|
@ -72,14 +71,15 @@ export class JobGroupComponent extends React.Component {
|
||||||
const stateCounts = {};
|
const stateCounts = {};
|
||||||
const typeSymbolCounts = countBy(jobs, 'job_type_symbol');
|
const typeSymbolCounts = countBy(jobs, 'job_type_symbol');
|
||||||
jobs.forEach(job => {
|
jobs.forEach(job => {
|
||||||
if (!job.visible) return;
|
const { resultStatus, visible, btnClass } = job;
|
||||||
const status = getStatus(job);
|
if (!visible) return;
|
||||||
|
|
||||||
let countInfo = {
|
let countInfo = {
|
||||||
btnClass: getBtnClass(status, job.failure_classification_id),
|
btnClass,
|
||||||
countText: status,
|
countText: resultStatus,
|
||||||
};
|
};
|
||||||
if (
|
if (
|
||||||
thFailureResults.includes(status) ||
|
thFailureResults.includes(resultStatus) ||
|
||||||
(typeSymbolCounts[job.job_type_symbol] > 1 && duplicateJobsVisible)
|
(typeSymbolCounts[job.job_type_symbol] > 1 && duplicateJobsVisible)
|
||||||
) {
|
) {
|
||||||
// render the job itself, not a count
|
// render the job itself, not a count
|
||||||
|
@ -142,7 +142,7 @@ export class JobGroupComponent extends React.Component {
|
||||||
job={job}
|
job={job}
|
||||||
filterModel={filterModel}
|
filterModel={filterModel}
|
||||||
visible={job.visible}
|
visible={job.visible}
|
||||||
resultStatus={getStatus(job)}
|
resultStatus={job.resultStatus}
|
||||||
failureClassificationId={job.failure_classification_id}
|
failureClassificationId={job.failure_classification_id}
|
||||||
repoName={repoName}
|
repoName={repoName}
|
||||||
filterPlatformCb={filterPlatformCb}
|
filterPlatformCb={filterPlatformCb}
|
||||||
|
|
|
@ -1,8 +1,6 @@
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
|
||||||
import { getStatus } from '../../helpers/job';
|
|
||||||
|
|
||||||
import JobButton from './JobButton';
|
import JobButton from './JobButton';
|
||||||
import JobGroup from './JobGroup';
|
import JobGroup from './JobGroup';
|
||||||
|
|
||||||
|
@ -43,7 +41,7 @@ export default class JobsAndGroups extends React.Component {
|
||||||
filterModel={filterModel}
|
filterModel={filterModel}
|
||||||
repoName={repoName}
|
repoName={repoName}
|
||||||
visible={job.visible}
|
visible={job.visible}
|
||||||
resultStatus={getStatus(job)}
|
resultStatus={job.resultStatus}
|
||||||
failureClassificationId={job.failure_classification_id}
|
failureClassificationId={job.failure_classification_id}
|
||||||
filterPlatformCb={filterPlatformCb}
|
filterPlatformCb={filterPlatformCb}
|
||||||
key={job.id}
|
key={job.id}
|
||||||
|
|
|
@ -129,12 +129,12 @@ class Push extends React.PureComponent {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
toggleSelectedRunnableJob = buildername => {
|
toggleSelectedRunnableJob = signature => {
|
||||||
const { selectedRunnableJobs } = this.state;
|
const { selectedRunnableJobs } = this.state;
|
||||||
const jobIndex = selectedRunnableJobs.indexOf(buildername);
|
const jobIndex = selectedRunnableJobs.indexOf(signature);
|
||||||
|
|
||||||
if (jobIndex === -1) {
|
if (jobIndex === -1) {
|
||||||
selectedRunnableJobs.push(buildername);
|
selectedRunnableJobs.push(signature);
|
||||||
} else {
|
} else {
|
||||||
selectedRunnableJobs.splice(jobIndex, 1);
|
selectedRunnableJobs.splice(jobIndex, 1);
|
||||||
}
|
}
|
||||||
|
|
|
@ -89,7 +89,7 @@ class PushJobs extends React.Component {
|
||||||
handleRunnableClick = jobInstance => {
|
handleRunnableClick = jobInstance => {
|
||||||
const { toggleSelectedRunnableJob } = this.props;
|
const { toggleSelectedRunnableJob } = this.props;
|
||||||
|
|
||||||
toggleSelectedRunnableJob(jobInstance.props.job.ref_data_name);
|
toggleSelectedRunnableJob(jobInstance.props.job.signature);
|
||||||
jobInstance.toggleRunnableSelected();
|
jobInstance.toggleRunnableSelected();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -62,7 +62,7 @@ class App extends React.PureComponent {
|
||||||
JobModel.get(repoName, jobId)
|
JobModel.get(repoName, jobId)
|
||||||
.then(async job => {
|
.then(async job => {
|
||||||
// set the title of the browser window/tab
|
// set the title of the browser window/tab
|
||||||
document.title = job.getTitle();
|
document.title = job.title;
|
||||||
const rawLogUrl = job.logs && job.logs.length ? job.logs[0].url : null;
|
const rawLogUrl = job.logs && job.logs.length ? job.logs[0].url : null;
|
||||||
// other properties, in order of appearance
|
// other properties, in order of appearance
|
||||||
// Test to disable successful steps checkbox on taskcluster jobs
|
// Test to disable successful steps checkbox on taskcluster jobs
|
||||||
|
|
|
@ -5,7 +5,7 @@ import {
|
||||||
thFailureResults,
|
thFailureResults,
|
||||||
thPlatformMap,
|
thPlatformMap,
|
||||||
} from '../helpers/constants';
|
} from '../helpers/constants';
|
||||||
import { getStatus, isClassified } from '../helpers/job';
|
import { isClassified } from '../helpers/job';
|
||||||
import {
|
import {
|
||||||
arraysEqual,
|
arraysEqual,
|
||||||
matchesDefaults,
|
matchesDefaults,
|
||||||
|
@ -196,10 +196,11 @@ export default class FilterModel {
|
||||||
showJob = job => {
|
showJob = job => {
|
||||||
// when runnable jobs have been added to a resultset, they should be
|
// when runnable jobs have been added to a resultset, they should be
|
||||||
// shown regardless of settings for classified or result state
|
// shown regardless of settings for classified or result state
|
||||||
const status = getStatus(job);
|
const { resultStatus } = job;
|
||||||
if (status !== 'runnable') {
|
|
||||||
|
if (resultStatus !== 'runnable') {
|
||||||
// test against resultStatus and classifiedState
|
// test against resultStatus and classifiedState
|
||||||
if (!this.urlParams.resultStatus.includes(status)) {
|
if (!this.urlParams.resultStatus.includes(resultStatus)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
if (!this._checkClassifiedStateFilters(job)) {
|
if (!this._checkClassifiedStateFilters(job)) {
|
||||||
|
@ -273,9 +274,9 @@ export default class FilterModel {
|
||||||
}`;
|
}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (field === 'searchStr') {
|
if (field === 'resultStatus') {
|
||||||
// lazily get this to avoid storing redundant information
|
// don't check this here.
|
||||||
return job.getSearchStr();
|
return null;
|
||||||
}
|
}
|
||||||
return job[field];
|
return job[field];
|
||||||
};
|
};
|
||||||
|
|
|
@ -2,9 +2,9 @@ import { slugid } from 'taskcluster-client-web';
|
||||||
import groupBy from 'lodash/groupBy';
|
import groupBy from 'lodash/groupBy';
|
||||||
import keyBy from 'lodash/keyBy';
|
import keyBy from 'lodash/keyBy';
|
||||||
|
|
||||||
import { thPlatformMap } from '../helpers/constants';
|
|
||||||
import { createQueryParams, getApiUrl } from '../helpers/url';
|
import { createQueryParams, getApiUrl } from '../helpers/url';
|
||||||
import { formatTaskclusterError } from '../helpers/errorMessage';
|
import { formatTaskclusterError } from '../helpers/errorMessage';
|
||||||
|
import { addAggregateFields } from '../helpers/job';
|
||||||
import { getProjectUrl } from '../helpers/location';
|
import { getProjectUrl } from '../helpers/location';
|
||||||
import { getData } from '../helpers/http';
|
import { getData } from '../helpers/http';
|
||||||
|
|
||||||
|
@ -15,45 +15,12 @@ const uri = '/jobs/';
|
||||||
|
|
||||||
// JobModel is the js counterpart of job
|
// JobModel is the js counterpart of job
|
||||||
export default class JobModel {
|
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 async getList(options, config = {}) {
|
static async getList(options, config = {}) {
|
||||||
// The `uri` config allows to fetch a list of jobs from an arbitrary
|
// 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
|
// endpoint e.g. the similar jobs endpoint. It defaults to the job
|
||||||
// list endpoint.
|
// list endpoint.
|
||||||
const { fetchAll, uri: configUri } = config;
|
const { fetchAll, uri: configUri } = config;
|
||||||
const jobUri = configUri || getProjectUrl(uri);
|
const jobUri = configUri || getProjectUrl(uri);
|
||||||
|
|
||||||
const { data, failureStatus } = await getData(
|
const { data, failureStatus } = await getData(
|
||||||
`${jobUri}${options ? createQueryParams(options) : ''}`,
|
`${jobUri}${options ? createQueryParams(options) : ''}`,
|
||||||
);
|
);
|
||||||
|
@ -81,17 +48,16 @@ export default class JobModel {
|
||||||
if (job_property_names) {
|
if (job_property_names) {
|
||||||
// the results came as list of fields
|
// the results came as list of fields
|
||||||
// we need to convert them to objects
|
// we need to convert them to objects
|
||||||
itemList = results.map(
|
itemList = results.map(elem =>
|
||||||
elem =>
|
addAggregateFields(
|
||||||
new JobModel(
|
job_property_names.reduce(
|
||||||
job_property_names.reduce(
|
(prev, prop, i) => ({ ...prev, [prop]: elem[i] }),
|
||||||
(prev, prop, i) => ({ ...prev, [prop]: elem[i] }),
|
{},
|
||||||
{},
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
|
),
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
itemList = results.map(job_obj => new JobModel(job_obj));
|
itemList = results.map(job_obj => addAggregateFields(job_obj));
|
||||||
}
|
}
|
||||||
return { data: [...itemList, ...nextPagesJobs], failureStatus: null };
|
return { data: [...itemList, ...nextPagesJobs], failureStatus: null };
|
||||||
}
|
}
|
||||||
|
@ -104,7 +70,7 @@ export default class JobModel {
|
||||||
async response => {
|
async response => {
|
||||||
if (response.ok) {
|
if (response.ok) {
|
||||||
const job = await response.json();
|
const job = await response.json();
|
||||||
return new JobModel(job);
|
return addAggregateFields(job);
|
||||||
}
|
}
|
||||||
const text = await response.text();
|
const text = await response.text();
|
||||||
throw Error(`Loading job with id ${pk} : ${text}`);
|
throw Error(`Loading job with id ${pk} : ${text}`);
|
||||||
|
|
|
@ -1,8 +1,7 @@
|
||||||
|
import { addAggregateFields } from '../helpers/job';
|
||||||
import { getRunnableJobsURL } from '../helpers/url';
|
import { getRunnableJobsURL } from '../helpers/url';
|
||||||
import { escapeId } from '../helpers/aggregateId';
|
import { escapeId } from '../helpers/aggregateId';
|
||||||
|
|
||||||
import JobModel from './job';
|
|
||||||
|
|
||||||
export default class RunnableJobModel {
|
export default class RunnableJobModel {
|
||||||
constructor(data) {
|
constructor(data) {
|
||||||
Object.assign(this, data);
|
Object.assign(this, data);
|
||||||
|
@ -12,23 +11,22 @@ export default class RunnableJobModel {
|
||||||
const uri = getRunnableJobsURL(params.decision_task_id);
|
const uri = getRunnableJobsURL(params.decision_task_id);
|
||||||
const rawJobs = await fetch(uri).then(response => response.json());
|
const rawJobs = await fetch(uri).then(response => response.json());
|
||||||
|
|
||||||
return Object.entries(rawJobs).map(
|
return Object.entries(rawJobs).map(([key, value]) =>
|
||||||
([key, value]) =>
|
addAggregateFields({
|
||||||
new JobModel({
|
build_platform: value.platform || '',
|
||||||
build_platform: value.platform || '',
|
build_system_type: 'taskcluster',
|
||||||
build_system_type: 'taskcluster',
|
job_group_name: value.groupName || '',
|
||||||
job_group_name: value.groupName || '',
|
job_group_symbol: value.groupSymbol || '',
|
||||||
job_group_symbol: value.groupSymbol || '',
|
job_type_name: key,
|
||||||
job_type_name: key,
|
job_type_symbol: value.symbol,
|
||||||
job_type_symbol: value.symbol,
|
platform: value.platform || '',
|
||||||
platform: value.platform || '',
|
platform_option: Object.keys(value.collection).join(' '),
|
||||||
platform_option: Object.keys(value.collection).join(' '),
|
signature: key,
|
||||||
ref_data_name: key,
|
state: 'runnable',
|
||||||
state: 'runnable',
|
result: 'runnable',
|
||||||
result: 'runnable',
|
push_id: params.push_id,
|
||||||
push_id: params.push_id,
|
id: escapeId(params.push_id + key),
|
||||||
id: escapeId(params.push_id + key),
|
}),
|
||||||
}),
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,23 +2,16 @@ import React from 'react';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
|
|
||||||
import { getInspectTaskUrl } from '../helpers/url';
|
import { getInspectTaskUrl } from '../helpers/url';
|
||||||
import { getSearchStr, getJobSearchStrHref } from '../helpers/job';
|
import { getJobSearchStrHref } from '../helpers/job';
|
||||||
import { toDateStr } from '../helpers/display';
|
import { toDateStr } from '../helpers/display';
|
||||||
|
|
||||||
const getTimeFields = function getTimeFields(job) {
|
const getTimeFields = function getTimeFields(job) {
|
||||||
// time fields to show in detail panel, but that should be grouped together
|
// time fields to show in detail panel, but that should be grouped together
|
||||||
const { end_timestamp, start_timestamp, submit_timestamp } = job;
|
const { end_timestamp, start_timestamp, submit_timestamp, duration } = job;
|
||||||
const timeFields = [
|
const timeFields = [
|
||||||
{ title: 'Requested', value: toDateStr(submit_timestamp) },
|
{ title: 'Requested', value: toDateStr(submit_timestamp) },
|
||||||
];
|
];
|
||||||
|
|
||||||
// If start time is 0, then duration should be from requesttime to now
|
|
||||||
// If we have starttime and no endtime, then duration should be starttime to now
|
|
||||||
// If we have both starttime and endtime, then duration will be between those two
|
|
||||||
const endtime = end_timestamp || Date.now() / 1000;
|
|
||||||
const starttime = start_timestamp || submit_timestamp;
|
|
||||||
const duration = `${Math.round((endtime - starttime) / 60, 0)} minute(s)`;
|
|
||||||
|
|
||||||
if (start_timestamp) {
|
if (start_timestamp) {
|
||||||
timeFields.push({ title: 'Started', value: toDateStr(start_timestamp) });
|
timeFields.push({ title: 'Started', value: toDateStr(start_timestamp) });
|
||||||
}
|
}
|
||||||
|
@ -36,7 +29,16 @@ const getTimeFields = function getTimeFields(job) {
|
||||||
export default class JobInfo extends React.PureComponent {
|
export default class JobInfo extends React.PureComponent {
|
||||||
render() {
|
render() {
|
||||||
const { job, extraFields, showJobFilters } = this.props;
|
const { job, extraFields, showJobFilters } = this.props;
|
||||||
const jobSearchStr = getSearchStr(job);
|
const {
|
||||||
|
searchStr,
|
||||||
|
signature,
|
||||||
|
title,
|
||||||
|
taskcluster_metadata,
|
||||||
|
build_platform,
|
||||||
|
job_type_name,
|
||||||
|
build_architecture,
|
||||||
|
build_os,
|
||||||
|
} = job;
|
||||||
const timeFields = getTimeFields(job);
|
const timeFields = getTimeFields(job);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
@ -47,44 +49,43 @@ export default class JobInfo extends React.PureComponent {
|
||||||
<React.Fragment>
|
<React.Fragment>
|
||||||
<a
|
<a
|
||||||
title="Filter jobs with this unique SHA signature"
|
title="Filter jobs with this unique SHA signature"
|
||||||
href={getJobSearchStrHref(job.signature)}
|
href={getJobSearchStrHref(signature)}
|
||||||
>
|
>
|
||||||
(sig)
|
(sig)
|
||||||
</a>
|
</a>
|
||||||
:
|
:
|
||||||
<a
|
<a
|
||||||
title="Filter jobs containing these keywords"
|
title="Filter jobs containing these keywords"
|
||||||
href={getJobSearchStrHref(jobSearchStr)}
|
href={getJobSearchStrHref(searchStr)}
|
||||||
>
|
>
|
||||||
{jobSearchStr}
|
{searchStr}
|
||||||
</a>
|
</a>
|
||||||
</React.Fragment>
|
</React.Fragment>
|
||||||
) : (
|
) : (
|
||||||
<span>{job.getTitle()}</span>
|
<span>{title}</span>
|
||||||
)}
|
)}
|
||||||
</li>
|
</li>
|
||||||
{job.taskcluster_metadata && (
|
{taskcluster_metadata && (
|
||||||
<li className="small">
|
<li className="small">
|
||||||
<strong>Task: </strong>
|
<strong>Task: </strong>
|
||||||
<a
|
<a
|
||||||
id="taskInfo"
|
id="taskInfo"
|
||||||
href={getInspectTaskUrl(job.taskcluster_metadata.task_id)}
|
href={getInspectTaskUrl(taskcluster_metadata.task_id)}
|
||||||
target="_blank"
|
target="_blank"
|
||||||
rel="noopener noreferrer"
|
rel="noopener noreferrer"
|
||||||
>
|
>
|
||||||
{job.taskcluster_metadata.task_id}
|
{taskcluster_metadata.task_id}
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
)}
|
)}
|
||||||
<li className="small">
|
<li className="small">
|
||||||
<strong>Build: </strong>
|
<strong>Build: </strong>
|
||||||
<span>{`${job.build_architecture} ${
|
<span>{`${build_architecture} ${build_platform} ${build_os ||
|
||||||
job.build_platform
|
''}`}</span>
|
||||||
} ${job.build_os || ''}`}</span>
|
|
||||||
</li>
|
</li>
|
||||||
<li className="small">
|
<li className="small">
|
||||||
<strong>Job name: </strong>
|
<strong>Job name: </strong>
|
||||||
<span>{job.job_type_name}</span>
|
<span>{job_type_name}</span>
|
||||||
</li>
|
</li>
|
||||||
{[...timeFields, ...extraFields].map(field => (
|
{[...timeFields, ...extraFields].map(field => (
|
||||||
<li className="small" key={`${field.title}${field.value}`}>
|
<li className="small" key={`${field.title}${field.value}`}>
|
||||||
|
|
Загрузка…
Ссылка в новой задаче