зеркало из 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 mappedGroupFixture from '../mock/mappedGroup';
|
||||
import mappedGroupDupsFixture from '../mock/mappedGroupDups';
|
||||
import { addAggregateFields } from '../../../ui/helpers/job';
|
||||
|
||||
describe('JobGroup component', () => {
|
||||
let countGroup;
|
||||
|
@ -15,6 +16,11 @@ describe('JobGroup component', () => {
|
|||
const filterModel = new FilterModel();
|
||||
const pushGroupState = 'collapsed';
|
||||
|
||||
beforeAll(() => {
|
||||
mappedGroupFixture.jobs.forEach(job => addAggregateFields(job));
|
||||
mappedGroupDupsFixture.jobs.forEach(job => addAggregateFields(job));
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
countGroup = cloneDeep(mappedGroupFixture);
|
||||
dupGroup = cloneDeep(mappedGroupDupsFixture);
|
||||
|
|
|
@ -12,7 +12,8 @@ import FilterModel from '../../../ui/models/filter';
|
|||
import { store } from '../../../ui/job-view/redux/store';
|
||||
import { PinnedJobs } from '../../../ui/job-view/context/PinnedJobs';
|
||||
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 = {
|
||||
id: 494796,
|
||||
|
@ -34,170 +35,13 @@ const testPush = {
|
|||
jobsLoaded: true,
|
||||
};
|
||||
|
||||
const testPlatforms = [
|
||||
{
|
||||
name: 'Linux x64',
|
||||
option: 'opt',
|
||||
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,
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
beforeAll(() => {
|
||||
platforms.forEach(platform =>
|
||||
platform.groups.forEach(group =>
|
||||
group.jobs.forEach(job => addAggregateFields(job)),
|
||||
),
|
||||
);
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
cleanup();
|
||||
|
@ -209,7 +53,7 @@ const testPushJobs = filterModel => (
|
|||
<PinnedJobs>
|
||||
<PushJobs
|
||||
push={testPush}
|
||||
platforms={testPlatforms}
|
||||
platforms={platforms}
|
||||
repoName="try"
|
||||
filterModel={filterModel}
|
||||
pushGroupState=""
|
||||
|
|
|
@ -30,12 +30,17 @@ import {
|
|||
fetchNextPushes,
|
||||
updateRange,
|
||||
} from '../../../../ui/job-view/redux/stores/pushes';
|
||||
import { addAggregateFields } from '../../../../ui/helpers/job';
|
||||
|
||||
const mockStore = configureMockStore([thunk]);
|
||||
|
||||
describe('Pushes Redux store', () => {
|
||||
const repoName = 'autoland';
|
||||
|
||||
beforeAll(() => {
|
||||
Object.values(jobMap).forEach(job => addAggregateFields(job));
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
cleanup();
|
||||
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
|
||||
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_symbol: { name: 'job symbol', matchType: thMatchType.exactstr },
|
||||
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
|
||||
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.
|
||||
// These also apply to result "groupings" like ``failures`` and ``in progress``
|
||||
// for the colored filter chicklets on the nav bar.
|
||||
|
@ -40,18 +32,12 @@ export const getBtnClass = function getBtnClass(
|
|||
let btnClass = btnClasses[resultStatus] || 'btn-default';
|
||||
|
||||
// handle if a job is classified
|
||||
// TODO: Check if the parseInt() is really needed here.
|
||||
const classificationId = parseInt(failureClassificationId, 10);
|
||||
if (classificationId > 1) {
|
||||
if (failureClassificationId > 1) {
|
||||
btnClass += '-classified';
|
||||
}
|
||||
return btnClass;
|
||||
};
|
||||
|
||||
export const getJobBtnClass = function getJobBtnClass(job) {
|
||||
return getBtnClass(getStatus(job), job.failure_classification_id);
|
||||
};
|
||||
|
||||
export const isReftest = function isReftest(job) {
|
||||
const {
|
||||
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
|
||||
// so we can search for it as one token (useful when
|
||||
// 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 [
|
||||
thPlatformMap[job.platform] || job.platform,
|
||||
job.platform_option,
|
||||
job.job_group_name === 'unknown' ? undefined : job.job_group_name,
|
||||
job.job_type_name,
|
||||
`${symbolInfo}(${job.job_type_symbol})`,
|
||||
job.title = [
|
||||
thPlatformMap[platform] || platform,
|
||||
platform_option,
|
||||
job_group_name === 'unknown' ? undefined : job_group_name,
|
||||
job_type_name,
|
||||
`${symbolInfo}(${job_type_symbol})`,
|
||||
]
|
||||
.filter(item => typeof item !== 'undefined')
|
||||
.join(' ')
|
||||
.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) {
|
||||
|
@ -227,9 +245,3 @@ export const getJobSearchStrHref = function getJobSearchStrHref(jobSearchStr) {
|
|||
|
||||
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 { withPinnedJobs } from '../context/PinnedJobs';
|
||||
import { addAggregateFields } from '../../helpers/job';
|
||||
import { getLogViewerUrl, getReftestUrl } from '../../helpers/url';
|
||||
import BugJobMapModel from '../../models/bugJobMap';
|
||||
import BugSuggestionsModel from '../../models/bugSuggestions';
|
||||
|
@ -29,6 +30,7 @@ class DetailsPanel extends React.Component {
|
|||
this.selectJobController = null;
|
||||
|
||||
this.state = {
|
||||
selectedJobFull: null,
|
||||
jobDetails: [],
|
||||
jobLogUrls: [],
|
||||
jobDetailLoading: false,
|
||||
|
@ -182,7 +184,7 @@ class DetailsPanel extends React.Component {
|
|||
);
|
||||
|
||||
const jobDetailPromise = JobDetailModel.getJobDetails(
|
||||
{ job_guid: selectedJob.job_guid },
|
||||
{ job_id: selectedJob.id },
|
||||
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
|
||||
// is what we'll pass to the rest of the details panel. It has extra fields like
|
||||
// taskcluster_metadata.
|
||||
Object.assign(selectedJob, jobResult);
|
||||
const selectedJobFull = jobResult;
|
||||
const jobRevision = push.revision;
|
||||
|
||||
jobDetails = jobDetailResult;
|
||||
addAggregateFields(selectedJobFull);
|
||||
|
||||
// incorporate the buildername into the job details if this is a buildbot job
|
||||
// (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 },
|
||||
];
|
||||
}
|
||||
jobDetails = jobDetailResult;
|
||||
|
||||
// the third result comes from the jobLogUrl promise
|
||||
// exclude the json log URLs
|
||||
|
@ -292,6 +284,7 @@ class DetailsPanel extends React.Component {
|
|||
|
||||
this.setState(
|
||||
{
|
||||
selectedJobFull,
|
||||
jobLogUrls,
|
||||
jobDetails,
|
||||
logParseStatus,
|
||||
|
@ -325,9 +318,9 @@ class DetailsPanel extends React.Component {
|
|||
classificationMap,
|
||||
classificationTypes,
|
||||
isPinBoardVisible,
|
||||
selectedJob,
|
||||
} = this.props;
|
||||
const {
|
||||
selectedJobFull,
|
||||
jobDetails,
|
||||
jobRevision,
|
||||
jobLogUrls,
|
||||
|
@ -351,19 +344,19 @@ class DetailsPanel extends React.Component {
|
|||
<div
|
||||
id="details-panel"
|
||||
style={{ height: `${detailsPanelHeight}px` }}
|
||||
className={selectedJob ? 'details-panel-slide' : 'hidden'}
|
||||
className={selectedJobFull ? 'details-panel-slide' : 'hidden'}
|
||||
>
|
||||
<PinBoard
|
||||
repoName={repoName}
|
||||
currentRepo={currentRepo}
|
||||
isLoggedIn={user.isLoggedIn || false}
|
||||
classificationTypes={classificationTypes}
|
||||
selectedJob={selectedJob}
|
||||
selectedJobFull={selectedJobFull}
|
||||
/>
|
||||
{!!selectedJob && (
|
||||
{!!selectedJobFull && (
|
||||
<div id="details-panel-content">
|
||||
<SummaryPanel
|
||||
selectedJob={selectedJob}
|
||||
selectedJobFull={selectedJobFull}
|
||||
repoName={repoName}
|
||||
currentRepo={currentRepo}
|
||||
classificationMap={classificationMap}
|
||||
|
@ -380,7 +373,7 @@ class DetailsPanel extends React.Component {
|
|||
/>
|
||||
<span className="job-tabs-divider" />
|
||||
<TabsPanel
|
||||
selectedJob={selectedJob}
|
||||
selectedJobFull={selectedJobFull}
|
||||
jobDetails={jobDetails}
|
||||
perfJobDetail={perfJobDetail}
|
||||
repoName={repoName}
|
||||
|
|
|
@ -7,11 +7,7 @@ import { faPlusSquare, faTimes } from '@fortawesome/free-solid-svg-icons';
|
|||
|
||||
import { thEvents } from '../../helpers/constants';
|
||||
import { formatModelError } from '../../helpers/errorMessage';
|
||||
import {
|
||||
getJobBtnClass,
|
||||
getHoverText,
|
||||
findJobInstance,
|
||||
} from '../../helpers/job';
|
||||
import { findJobInstance } from '../../helpers/job';
|
||||
import { isSHAorCommit } from '../../helpers/revision';
|
||||
import { getBugUrl } from '../../helpers/url';
|
||||
import BugJobMapModel from '../../models/bugJobMap';
|
||||
|
@ -356,7 +352,7 @@ class PinBoard extends React.Component {
|
|||
|
||||
render() {
|
||||
const {
|
||||
selectedJob,
|
||||
selectedJobFull,
|
||||
revisionTips,
|
||||
isLoggedIn,
|
||||
isPinBoardVisible,
|
||||
|
@ -372,7 +368,7 @@ class PinBoard extends React.Component {
|
|||
failureClassificationComment,
|
||||
} = this.props;
|
||||
const { enteringBugNumber, newBugNumber } = this.state;
|
||||
const selectedJobId = selectedJob ? selectedJob.id : null;
|
||||
const selectedJobId = selectedJobFull ? selectedJobFull.id : null;
|
||||
|
||||
return (
|
||||
<div id="pinboard-panel" className={isPinBoardVisible ? '' : 'hidden'}>
|
||||
|
@ -387,12 +383,12 @@ class PinBoard extends React.Component {
|
|||
{Object.values(pinnedJobs).map(job => (
|
||||
<span className="btn-group" key={job.id}>
|
||||
<span
|
||||
className={`btn pinned-job ${getJobBtnClass(job)} ${
|
||||
className={`btn pinned-job ${job.btnClass} ${
|
||||
selectedJobId === job.id
|
||||
? 'btn-lg selected-job'
|
||||
: 'btn-xs'
|
||||
}`}
|
||||
title={getHoverText(job)}
|
||||
title={job.hoverText}
|
||||
onClick={() => setSelectedJob(job)}
|
||||
data-job-id={job.job_id}
|
||||
>
|
||||
|
@ -644,13 +640,13 @@ PinBoard.propTypes = {
|
|||
currentRepo: PropTypes.object.isRequired,
|
||||
failureClassificationId: PropTypes.number.isRequired,
|
||||
failureClassificationComment: PropTypes.string.isRequired,
|
||||
selectedJob: PropTypes.object,
|
||||
selectedJobFull: PropTypes.object,
|
||||
email: PropTypes.string,
|
||||
revisionTips: PropTypes.array,
|
||||
};
|
||||
|
||||
PinBoard.defaultProps = {
|
||||
selectedJob: null,
|
||||
selectedJobFull: null,
|
||||
email: null,
|
||||
revisionTips: [],
|
||||
};
|
||||
|
|
|
@ -71,21 +71,23 @@ class ActionBar extends React.PureComponent {
|
|||
};
|
||||
|
||||
canCancel = () => {
|
||||
const { selectedJob } = this.props;
|
||||
return selectedJob.state === 'pending' || selectedJob.state === 'running';
|
||||
const { selectedJobFull } = this.props;
|
||||
return (
|
||||
selectedJobFull.state === 'pending' || selectedJobFull.state === 'running'
|
||||
);
|
||||
};
|
||||
|
||||
createGeckoProfile = async () => {
|
||||
const { user, selectedJob, notify } = this.props;
|
||||
const { user, selectedJobFull, notify } = this.props;
|
||||
if (!user.isLoggedIn) {
|
||||
return notify('Must be logged in to create a gecko profile', 'danger');
|
||||
}
|
||||
|
||||
const { id: decisionTaskId } = await PushModel.getDecisionTaskId(
|
||||
selectedJob.push_id,
|
||||
selectedJobFull.push_id,
|
||||
notify,
|
||||
);
|
||||
TaskclusterModel.load(decisionTaskId, selectedJob).then(results => {
|
||||
TaskclusterModel.load(decisionTaskId, selectedJobFull).then(results => {
|
||||
const geckoprofile = results.actions.find(
|
||||
result => result.name === 'geckoprofile',
|
||||
);
|
||||
|
@ -141,7 +143,7 @@ class ActionBar extends React.PureComponent {
|
|||
};
|
||||
|
||||
backfillJob = async () => {
|
||||
const { user, selectedJob, notify } = this.props;
|
||||
const { user, selectedJobFull, notify } = this.props;
|
||||
|
||||
if (!this.canBackfill()) {
|
||||
return;
|
||||
|
@ -153,21 +155,21 @@ class ActionBar extends React.PureComponent {
|
|||
return;
|
||||
}
|
||||
|
||||
if (!selectedJob.id) {
|
||||
if (!selectedJobFull.id) {
|
||||
notify('Job not yet loaded for backfill', 'warning');
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (
|
||||
selectedJob.build_system_type === 'taskcluster' ||
|
||||
selectedJob.reason.startsWith('Created by BBB for task')
|
||||
selectedJobFull.build_system_type === 'taskcluster' ||
|
||||
selectedJobFull.reason.startsWith('Created by BBB for task')
|
||||
) {
|
||||
const { id: decisionTaskId } = await PushModel.getDecisionTaskId(
|
||||
selectedJob.push_id,
|
||||
selectedJobFull.push_id,
|
||||
notify,
|
||||
);
|
||||
TaskclusterModel.load(decisionTaskId, selectedJob).then(results => {
|
||||
TaskclusterModel.load(decisionTaskId, selectedJobFull).then(results => {
|
||||
const backfilltask = results.actions.find(
|
||||
result => result.name === 'backfill',
|
||||
);
|
||||
|
@ -195,13 +197,13 @@ class ActionBar extends React.PureComponent {
|
|||
};
|
||||
|
||||
isolateJob = async () => {
|
||||
const { user, selectedJob, notify } = this.props;
|
||||
const { user, selectedJobFull, notify } = this.props;
|
||||
const { id: decisionTaskId } = await PushModel.getDecisionTaskId(
|
||||
selectedJob.push_id,
|
||||
selectedJobFull.push_id,
|
||||
notify,
|
||||
);
|
||||
|
||||
if (!isTestIsolatable(selectedJob)) {
|
||||
if (!isTestIsolatable(selectedJobFull)) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -211,23 +213,23 @@ class ActionBar extends React.PureComponent {
|
|||
return;
|
||||
}
|
||||
|
||||
if (!selectedJob.id) {
|
||||
if (!selectedJobFull.id) {
|
||||
notify('Job not yet loaded for isolation', 'warning');
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (selectedJob.state !== 'completed') {
|
||||
if (selectedJobFull.state !== 'completed') {
|
||||
notify('Job not yet completed. Try again later.', 'warning');
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (
|
||||
selectedJob.build_system_type === 'taskcluster' ||
|
||||
selectedJob.reason.startsWith('Created by BBB for task')
|
||||
selectedJobFull.build_system_type === 'taskcluster' ||
|
||||
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(
|
||||
result => result.name === 'isolate-test-failures',
|
||||
);
|
||||
|
@ -316,8 +318,8 @@ class ActionBar extends React.PureComponent {
|
|||
};
|
||||
|
||||
createInteractiveTask = async () => {
|
||||
const { user, selectedJob, repoName, notify } = this.props;
|
||||
const jobId = selectedJob.id;
|
||||
const { user, selectedJobFull, repoName, notify } = this.props;
|
||||
const jobId = selectedJobFull.id;
|
||||
|
||||
if (!user.isLoggedIn) {
|
||||
return notify(
|
||||
|
@ -373,7 +375,7 @@ class ActionBar extends React.PureComponent {
|
|||
};
|
||||
|
||||
cancelJob = () => {
|
||||
this.cancelJobs([this.props.selectedJob]);
|
||||
this.cancelJobs([this.props.selectedJobFull]);
|
||||
};
|
||||
|
||||
toggleCustomJobActions = () => {
|
||||
|
@ -384,7 +386,7 @@ class ActionBar extends React.PureComponent {
|
|||
|
||||
render() {
|
||||
const {
|
||||
selectedJob,
|
||||
selectedJobFull,
|
||||
logViewerUrl,
|
||||
logViewerFullUrl,
|
||||
jobLogUrls,
|
||||
|
@ -407,7 +409,7 @@ class ActionBar extends React.PureComponent {
|
|||
id="pin-job-btn"
|
||||
title="Add this job to the pinboard"
|
||||
className="btn icon-blue"
|
||||
onClick={() => pinJob(selectedJob)}
|
||||
onClick={() => pinJob(selectedJobFull)}
|
||||
>
|
||||
<FontAwesomeIcon icon={faThumbtack} title="Pin job" />
|
||||
</Button>
|
||||
|
@ -422,12 +424,12 @@ class ActionBar extends React.PureComponent {
|
|||
}
|
||||
className={`btn ${user.isLoggedIn ? 'icon-green' : 'disabled'}`}
|
||||
disabled={!user.isLoggedIn}
|
||||
onClick={() => this.retriggerJob([selectedJob])}
|
||||
onClick={() => this.retriggerJob([selectedJobFull])}
|
||||
>
|
||||
<FontAwesomeIcon icon={faRedo} title="Retrigger job" />
|
||||
</Button>
|
||||
</li>
|
||||
{isReftest(selectedJob) &&
|
||||
{isReftest(selectedJobFull) &&
|
||||
jobLogUrls.map(jobLogUrl => (
|
||||
<li key={`reftest-${jobLogUrl.id}`}>
|
||||
<a
|
||||
|
@ -484,7 +486,7 @@ class ActionBar extends React.PureComponent {
|
|||
Backfill
|
||||
</span>
|
||||
</li>
|
||||
{selectedJob.taskcluster_metadata && (
|
||||
{selectedJobFull.taskcluster_metadata && (
|
||||
<React.Fragment>
|
||||
<li>
|
||||
<a
|
||||
|
@ -492,7 +494,7 @@ class ActionBar extends React.PureComponent {
|
|||
rel="noopener noreferrer"
|
||||
className="dropdown-item"
|
||||
href={getInspectTaskUrl(
|
||||
selectedJob.taskcluster_metadata.task_id,
|
||||
selectedJobFull.taskcluster_metadata.task_id,
|
||||
)}
|
||||
>
|
||||
Inspect Task
|
||||
|
@ -506,7 +508,7 @@ class ActionBar extends React.PureComponent {
|
|||
Create Interactive Task
|
||||
</Button>
|
||||
</li>
|
||||
{isPerfTest(selectedJob) && (
|
||||
{isPerfTest(selectedJobFull) && (
|
||||
<li>
|
||||
<Button
|
||||
className="dropdown-item py-2"
|
||||
|
@ -516,7 +518,7 @@ class ActionBar extends React.PureComponent {
|
|||
</Button>
|
||||
</li>
|
||||
)}
|
||||
{isTestIsolatable(selectedJob) && (
|
||||
{isTestIsolatable(selectedJobFull) && (
|
||||
<li>
|
||||
<Button
|
||||
className="dropdown-item py-2"
|
||||
|
@ -542,8 +544,8 @@ class ActionBar extends React.PureComponent {
|
|||
</nav>
|
||||
{customJobActionsShowing && (
|
||||
<CustomJobActions
|
||||
job={selectedJob}
|
||||
pushId={selectedJob.push_id}
|
||||
job={selectedJobFull}
|
||||
pushId={selectedJobFull.push_id}
|
||||
isLoggedIn={user.isLoggedIn}
|
||||
toggle={this.toggleCustomJobActions}
|
||||
/>
|
||||
|
@ -557,7 +559,7 @@ ActionBar.propTypes = {
|
|||
pinJob: PropTypes.func.isRequired,
|
||||
user: PropTypes.object.isRequired,
|
||||
repoName: PropTypes.string.isRequired,
|
||||
selectedJob: PropTypes.object.isRequired,
|
||||
selectedJobFull: PropTypes.object.isRequired,
|
||||
logParseStatus: PropTypes.string.isRequired,
|
||||
notify: PropTypes.func.isRequired,
|
||||
jobLogUrls: PropTypes.array,
|
||||
|
|
|
@ -1,28 +1,26 @@
|
|||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
import { getStatus } from '../../../helpers/job';
|
||||
|
||||
function StatusPanel(props) {
|
||||
const { selectedJob } = props;
|
||||
const shadingClass = `result-status-shading-${getStatus(selectedJob)}`;
|
||||
const { selectedJobFull } = props;
|
||||
const shadingClass = `result-status-shading-${selectedJobFull.resultStatus}`;
|
||||
|
||||
return (
|
||||
<li id="result-status-pane" className={`small ${shadingClass}`}>
|
||||
<div>
|
||||
<strong>Result:</strong>
|
||||
<span> {selectedJob.result}</span>
|
||||
<span> {selectedJobFull.result}</span>
|
||||
</div>
|
||||
<div>
|
||||
<strong>State:</strong>
|
||||
<span> {selectedJob.state}</span>
|
||||
<span> {selectedJobFull.state}</span>
|
||||
</div>
|
||||
</li>
|
||||
);
|
||||
}
|
||||
|
||||
StatusPanel.propTypes = {
|
||||
selectedJob: PropTypes.object.isRequired,
|
||||
selectedJobFull: PropTypes.object.isRequired,
|
||||
};
|
||||
|
||||
export default StatusPanel;
|
||||
|
|
|
@ -13,7 +13,7 @@ class SummaryPanel extends React.PureComponent {
|
|||
render() {
|
||||
const {
|
||||
repoName,
|
||||
selectedJob,
|
||||
selectedJobFull,
|
||||
latestClassification,
|
||||
bugs,
|
||||
jobLogUrls,
|
||||
|
@ -38,7 +38,7 @@ class SummaryPanel extends React.PureComponent {
|
|||
return (
|
||||
<div id="summary-panel" role="region" aria-label="Summary">
|
||||
<ActionBar
|
||||
selectedJob={selectedJob}
|
||||
selectedJobFull={selectedJobFull}
|
||||
repoName={repoName}
|
||||
logParseStatus={logParseStatus}
|
||||
isTryRepo={currentRepo.is_try_repo}
|
||||
|
@ -65,15 +65,15 @@ class SummaryPanel extends React.PureComponent {
|
|||
<ul className="list-unstyled">
|
||||
{latestClassification && (
|
||||
<ClassificationsPanel
|
||||
job={selectedJob}
|
||||
job={selectedJobFull}
|
||||
classification={latestClassification}
|
||||
classificationMap={classificationMap}
|
||||
bugs={bugs}
|
||||
currentRepo={currentRepo}
|
||||
/>
|
||||
)}
|
||||
<StatusPanel selectedJob={selectedJob} />
|
||||
<JobInfo job={selectedJob} extraFields={logStatus} />
|
||||
<StatusPanel selectedJobFull={selectedJobFull} />
|
||||
<JobInfo job={selectedJobFull} extraFields={logStatus} />
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -88,7 +88,7 @@ SummaryPanel.propTypes = {
|
|||
user: PropTypes.object.isRequired,
|
||||
currentRepo: PropTypes.object.isRequired,
|
||||
classificationMap: PropTypes.object.isRequired,
|
||||
selectedJob: PropTypes.object.isRequired,
|
||||
selectedJobFull: PropTypes.object.isRequired,
|
||||
latestClassification: PropTypes.object,
|
||||
jobLogUrls: PropTypes.array,
|
||||
jobDetailLoading: PropTypes.bool,
|
||||
|
|
|
@ -175,9 +175,13 @@ class AnnotationsTab extends React.Component {
|
|||
};
|
||||
|
||||
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();
|
||||
|
||||
classification.destroy().then(
|
||||
|
@ -247,16 +251,10 @@ AnnotationsTab.propTypes = {
|
|||
classifications: PropTypes.array.isRequired,
|
||||
recalculateUnclassifiedCounts: 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(
|
||||
mapStateToProps,
|
||||
null,
|
||||
{ notify, recalculateUnclassifiedCounts },
|
||||
)(AnnotationsTab);
|
||||
|
|
|
@ -6,7 +6,7 @@ import { faSpinner } from '@fortawesome/free-solid-svg-icons';
|
|||
|
||||
import { thMaxPushFetchSize } from '../../../helpers/constants';
|
||||
import { toDateStr, toShortDateStr } from '../../../helpers/display';
|
||||
import { getBtnClass, getStatus } from '../../../helpers/job';
|
||||
import { addAggregateFields } from '../../../helpers/job';
|
||||
import { getJobsUrl } from '../../../helpers/url';
|
||||
import JobModel from '../../../models/job';
|
||||
import PushModel from '../../../models/push';
|
||||
|
@ -42,7 +42,7 @@ class SimilarJobsTab extends React.Component {
|
|||
|
||||
getSimilarJobs = async () => {
|
||||
const { page, similarJobs, selectedSimilarJob } = this.state;
|
||||
const { repoName, selectedJob, notify } = this.props;
|
||||
const { repoName, selectedJobFull, notify } = this.props;
|
||||
const options = {
|
||||
// get one extra to detect if there are more jobs that can be loaded (hasNextPage)
|
||||
count: this.pageSize + 1,
|
||||
|
@ -52,14 +52,14 @@ class SimilarJobsTab extends React.Component {
|
|||
['filterBuildPlatformId', 'filterOptionCollectionHash'].forEach(key => {
|
||||
if (this.state[key]) {
|
||||
const field = this.filterMap[key];
|
||||
options[field] = selectedJob[field];
|
||||
options[field] = selectedJobFull[field];
|
||||
}
|
||||
});
|
||||
|
||||
const {
|
||||
data: newSimilarJobs,
|
||||
failureStatus,
|
||||
} = await JobModel.getSimilarJobs(selectedJob.id, options);
|
||||
} = await JobModel.getSimilarJobs(selectedJobFull.id, options);
|
||||
|
||||
if (!failureStatus) {
|
||||
this.setState({ hasNextPage: newSimilarJobs.length > this.pageSize });
|
||||
|
@ -119,8 +119,7 @@ class SimilarJobsTab extends React.Component {
|
|||
const { repoName, classificationMap } = this.props;
|
||||
|
||||
JobModel.get(repoName, job.id).then(nextJob => {
|
||||
nextJob.result_status = getStatus(nextJob);
|
||||
nextJob.duration = (nextJob.end_timestamp - nextJob.start_timestamp) / 60;
|
||||
addAggregateFields(nextJob);
|
||||
nextJob.failure_classification =
|
||||
classificationMap[nextJob.failure_classification_id];
|
||||
|
||||
|
@ -155,7 +154,6 @@ class SimilarJobsTab extends React.Component {
|
|||
filterBuildPlatformId,
|
||||
isLoading,
|
||||
} = this.state;
|
||||
const button_class = job => getBtnClass(getStatus(job));
|
||||
const selectedSimilarJobId = selectedSimilarJob
|
||||
? selectedSimilarJob.id
|
||||
: null;
|
||||
|
@ -187,9 +185,7 @@ class SimilarJobsTab extends React.Component {
|
|||
>
|
||||
<td>
|
||||
<button
|
||||
className={`btn btn-similar-jobs btn-xs ${button_class(
|
||||
similarJob,
|
||||
)}`}
|
||||
className={`btn btn-similar-jobs btn-xs ${similarJob.btnClass}`}
|
||||
type="button"
|
||||
>
|
||||
{similarJob.job_type_symbol}
|
||||
|
@ -250,7 +246,7 @@ class SimilarJobsTab extends React.Component {
|
|||
<tbody>
|
||||
<tr>
|
||||
<th>Result</th>
|
||||
<td>{selectedSimilarJob.result_status}</td>
|
||||
<td>{selectedSimilarJob.resultStatus}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Build</th>
|
||||
|
@ -329,16 +325,10 @@ SimilarJobsTab.propTypes = {
|
|||
repoName: PropTypes.string.isRequired,
|
||||
classificationMap: PropTypes.object.isRequired,
|
||||
notify: PropTypes.func.isRequired,
|
||||
selectedJob: PropTypes.object,
|
||||
selectedJobFull: PropTypes.object.isRequired,
|
||||
};
|
||||
|
||||
SimilarJobsTab.defaultProps = {
|
||||
selectedJob: null,
|
||||
};
|
||||
|
||||
const mapStateToProps = ({ selectedJob: { selectedJob } }) => ({ selectedJob });
|
||||
|
||||
export default connect(
|
||||
mapStateToProps,
|
||||
null,
|
||||
{ notify },
|
||||
)(SimilarJobsTab);
|
||||
|
|
|
@ -11,7 +11,6 @@ import {
|
|||
} from '@fortawesome/free-solid-svg-icons';
|
||||
|
||||
import { thEvents } from '../../../helpers/constants';
|
||||
import { getStatus } from '../../../helpers/job';
|
||||
import JobDetails from '../../../shared/JobDetails';
|
||||
import { withPinnedJobs } from '../../context/PinnedJobs';
|
||||
import { clearSelectedJob } from '../../redux/stores/selectedJob';
|
||||
|
@ -31,23 +30,23 @@ class TabsPanel extends React.Component {
|
|||
}
|
||||
|
||||
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
|
||||
// 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.
|
||||
if (
|
||||
state.jobId !== selectedJob.id ||
|
||||
state.jobId !== selectedJobFull.id ||
|
||||
state.perfJobDetailSize !== perfJobDetail.length
|
||||
) {
|
||||
const tabIndex = TabsPanel.getDefaultTabIndex(
|
||||
getStatus(selectedJob),
|
||||
selectedJobFull.resultStatus,
|
||||
!!perfJobDetail.length,
|
||||
);
|
||||
|
||||
return {
|
||||
tabIndex,
|
||||
jobId: selectedJob.id,
|
||||
jobId: selectedJobFull.id,
|
||||
perfJobDetailSize: perfJobDetail.length,
|
||||
};
|
||||
}
|
||||
|
@ -115,6 +114,7 @@ class TabsPanel extends React.Component {
|
|||
logViewerFullUrl,
|
||||
reftestUrl,
|
||||
clearSelectedJob,
|
||||
selectedJobFull,
|
||||
} = this.props;
|
||||
const { tabIndex } = this.state;
|
||||
|
||||
|
@ -192,6 +192,7 @@ class TabsPanel extends React.Component {
|
|||
logParseStatus={logParseStatus}
|
||||
logViewerFullUrl={logViewerFullUrl}
|
||||
reftestUrl={reftestUrl}
|
||||
selectedJobFull={selectedJobFull}
|
||||
/>
|
||||
</TabPanel>
|
||||
<TabPanel>
|
||||
|
@ -199,12 +200,14 @@ class TabsPanel extends React.Component {
|
|||
classificationMap={classificationMap}
|
||||
classifications={classifications}
|
||||
bugs={bugs}
|
||||
selectedJobFull={selectedJobFull}
|
||||
/>
|
||||
</TabPanel>
|
||||
<TabPanel>
|
||||
<SimilarJobsTab
|
||||
repoName={repoName}
|
||||
classificationMap={classificationMap}
|
||||
selectedJobFull={selectedJobFull}
|
||||
/>
|
||||
</TabPanel>
|
||||
{!!perfJobDetail.length && (
|
||||
|
@ -232,9 +235,9 @@ TabsPanel.propTypes = {
|
|||
countPinnedJobs: PropTypes.number.isRequired,
|
||||
bugs: PropTypes.array.isRequired,
|
||||
clearSelectedJob: PropTypes.func.isRequired,
|
||||
selectedJobFull: PropTypes.object.isRequired,
|
||||
perfJobDetail: PropTypes.array,
|
||||
suggestions: PropTypes.array,
|
||||
selectedJob: PropTypes.object,
|
||||
jobRevision: PropTypes.string,
|
||||
errors: PropTypes.array,
|
||||
bugSuggestionsLoading: PropTypes.bool,
|
||||
|
@ -246,7 +249,6 @@ TabsPanel.propTypes = {
|
|||
|
||||
TabsPanel.defaultProps = {
|
||||
suggestions: [],
|
||||
selectedJob: null,
|
||||
errors: [],
|
||||
bugSuggestionsLoading: false,
|
||||
jobLogUrls: [],
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { connect } from 'react-redux';
|
||||
import Highlighter from 'react-highlight-words';
|
||||
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
||||
import { faThumbtack } from '@fortawesome/free-solid-svg-icons';
|
||||
|
@ -10,7 +9,14 @@ import { getBugUrl } from '../../../../helpers/url';
|
|||
import { withPinnedJobs } from '../../../context/PinnedJobs';
|
||||
|
||||
function BugListItem(props) {
|
||||
const { bug, suggestion, bugClassName, title, selectedJob, addBug } = props;
|
||||
const {
|
||||
bug,
|
||||
suggestion,
|
||||
bugClassName,
|
||||
title,
|
||||
selectedJobFull,
|
||||
addBug,
|
||||
} = props;
|
||||
const bugUrl = getBugUrl(bug.id);
|
||||
|
||||
return (
|
||||
|
@ -18,7 +24,7 @@ function BugListItem(props) {
|
|||
<button
|
||||
className="btn btn-xs btn-light-bordered"
|
||||
type="button"
|
||||
onClick={() => addBug(bug, selectedJob)}
|
||||
onClick={() => addBug(bug, selectedJobFull)}
|
||||
title="add to list of bugs to associate with all pinned jobs"
|
||||
>
|
||||
<FontAwesomeIcon icon={faThumbtack} title="Select bug" />
|
||||
|
@ -47,7 +53,7 @@ BugListItem.propTypes = {
|
|||
bug: PropTypes.object.isRequired,
|
||||
suggestion: PropTypes.object.isRequired,
|
||||
addBug: PropTypes.func.isRequired,
|
||||
selectedJob: PropTypes.object.isRequired,
|
||||
selectedJobFull: PropTypes.object.isRequired,
|
||||
bugClassName: PropTypes.string,
|
||||
title: PropTypes.string,
|
||||
};
|
||||
|
@ -57,6 +63,4 @@ BugListItem.defaultProps = {
|
|||
title: null,
|
||||
};
|
||||
|
||||
const mapStateToProps = ({ selectedJob: { selectedJob } }) => ({ selectedJob });
|
||||
|
||||
export default connect(mapStateToProps)(withPinnedJobs(BugListItem));
|
||||
export default withPinnedJobs(BugListItem);
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { connect } from 'react-redux';
|
||||
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
||||
import { faSpinner } from '@fortawesome/free-solid-svg-icons';
|
||||
|
||||
|
@ -24,9 +23,9 @@ class FailureSummaryTab extends React.Component {
|
|||
}
|
||||
|
||||
fileBug = suggestion => {
|
||||
const { selectedJob, pinJob } = this.props;
|
||||
const { selectedJobFull, pinJob } = this.props;
|
||||
|
||||
pinJob(selectedJob);
|
||||
pinJob(selectedJobFull);
|
||||
this.setState({
|
||||
isBugFilerOpen: true,
|
||||
suggestion,
|
||||
|
@ -54,7 +53,7 @@ class FailureSummaryTab extends React.Component {
|
|||
errors,
|
||||
logViewerFullUrl,
|
||||
bugSuggestionsLoading,
|
||||
selectedJob,
|
||||
selectedJobFull,
|
||||
reftestUrl,
|
||||
} = this.props;
|
||||
const { isBugFilerOpen, suggestion } = this.state;
|
||||
|
@ -70,6 +69,7 @@ class FailureSummaryTab extends React.Component {
|
|||
index={index}
|
||||
suggestion={suggestion}
|
||||
toggleBugFiler={() => this.fileBug(suggestion)}
|
||||
selectedJobFull={selectedJobFull}
|
||||
/>
|
||||
))}
|
||||
|
||||
|
@ -150,9 +150,9 @@ class FailureSummaryTab extends React.Component {
|
|||
suggestions={suggestions}
|
||||
fullLog={jobLogUrls[0].url}
|
||||
parsedLog={logViewerFullUrl}
|
||||
reftestUrl={isReftest(selectedJob) ? reftestUrl : ''}
|
||||
reftestUrl={isReftest(selectedJobFull) ? reftestUrl : ''}
|
||||
successCallback={this.bugFilerCallback}
|
||||
jobGroupName={selectedJob.job_group_name}
|
||||
jobGroupName={selectedJobFull.job_group_name}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
|
@ -163,8 +163,8 @@ class FailureSummaryTab extends React.Component {
|
|||
FailureSummaryTab.propTypes = {
|
||||
addBug: PropTypes.func.isRequired,
|
||||
pinJob: PropTypes.func.isRequired,
|
||||
selectedJobFull: PropTypes.object.isRequired,
|
||||
suggestions: PropTypes.array,
|
||||
selectedJob: PropTypes.object,
|
||||
errors: PropTypes.array,
|
||||
bugSuggestionsLoading: PropTypes.bool,
|
||||
jobLogUrls: PropTypes.array,
|
||||
|
@ -175,7 +175,6 @@ FailureSummaryTab.propTypes = {
|
|||
|
||||
FailureSummaryTab.defaultProps = {
|
||||
suggestions: [],
|
||||
selectedJob: null,
|
||||
reftestUrl: null,
|
||||
errors: [],
|
||||
bugSuggestionsLoading: false,
|
||||
|
@ -184,6 +183,4 @@ FailureSummaryTab.defaultProps = {
|
|||
logViewerFullUrl: null,
|
||||
};
|
||||
|
||||
const mapStateToProps = ({ selectedJob: { selectedJob } }) => ({ selectedJob });
|
||||
|
||||
export default connect(mapStateToProps)(withPinnedJobs(FailureSummaryTab));
|
||||
export default withPinnedJobs(FailureSummaryTab);
|
||||
|
|
|
@ -22,7 +22,7 @@ export default class SuggestionsListItem extends React.Component {
|
|||
};
|
||||
|
||||
render() {
|
||||
const { suggestion, toggleBugFiler } = this.props;
|
||||
const { suggestion, toggleBugFiler, selectedJobFull } = this.props;
|
||||
const { suggestionShowMore } = this.state;
|
||||
|
||||
return (
|
||||
|
@ -42,7 +42,12 @@ export default class SuggestionsListItem extends React.Component {
|
|||
{suggestion.valid_open_recent && (
|
||||
<ul className="list-unstyled failure-summary-bugs">
|
||||
{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>
|
||||
)}
|
||||
|
@ -68,6 +73,7 @@ export default class SuggestionsListItem extends React.Component {
|
|||
suggestion={suggestion}
|
||||
bugClassName={bug.resolution !== '' ? 'strike-through' : ''}
|
||||
title={bug.resolution !== '' ? bug.resolution : ''}
|
||||
selectedJobFull={selectedJobFull}
|
||||
/>
|
||||
))}
|
||||
</ul>
|
||||
|
@ -87,6 +93,7 @@ export default class SuggestionsListItem extends React.Component {
|
|||
}
|
||||
|
||||
SuggestionsListItem.propTypes = {
|
||||
selectedJobFull: PropTypes.object.isRequired,
|
||||
suggestion: PropTypes.object.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 faStarSolid } from '@fortawesome/free-solid-svg-icons';
|
||||
|
||||
import { getBtnClass, findJobInstance } from '../../helpers/job';
|
||||
import { findJobInstance } from '../../helpers/job';
|
||||
import { getSelectedJobId, getUrlParam } from '../../helpers/location';
|
||||
|
||||
export default class JobButtonComponent extends React.Component {
|
||||
|
@ -80,24 +80,19 @@ export default class JobButtonComponent extends React.Component {
|
|||
}
|
||||
|
||||
render() {
|
||||
const { job, resultStatus } = this.props;
|
||||
const { job } = this.props;
|
||||
const { isSelected, isRunnableSelected } = this.state;
|
||||
const {
|
||||
state,
|
||||
job_type_name,
|
||||
failure_classification_id,
|
||||
end_timestamp,
|
||||
start_timestamp,
|
||||
ref_data_name,
|
||||
visible,
|
||||
id,
|
||||
job_type_symbol,
|
||||
btnClass,
|
||||
} = job;
|
||||
|
||||
if (!visible) return null;
|
||||
const runnable = state === 'runnable';
|
||||
const btnClass = getBtnClass(resultStatus, failure_classification_id);
|
||||
let title = `${resultStatus} | ${job_type_name}`;
|
||||
let classifiedIcon = null;
|
||||
|
||||
if (failure_classification_id > 1) {
|
||||
|
@ -105,20 +100,14 @@ export default class JobButtonComponent extends React.Component {
|
|||
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 attributes = {
|
||||
'data-job-id': id,
|
||||
title,
|
||||
title: job.hoverText,
|
||||
};
|
||||
|
||||
if (runnable) {
|
||||
classes.push('runnable-job-btn', 'runnable');
|
||||
attributes['data-buildername'] = ref_data_name;
|
||||
if (isRunnableSelected) {
|
||||
classes.push('runnable-job-btn-selected');
|
||||
}
|
||||
|
|
|
@ -3,7 +3,6 @@ import PropTypes from 'prop-types';
|
|||
import countBy from 'lodash/countBy';
|
||||
|
||||
import { thFailureResults } from '../../helpers/constants';
|
||||
import { getBtnClass, getStatus } from '../../helpers/job';
|
||||
import { getSelectedJobId, getUrlParam } from '../../helpers/location';
|
||||
|
||||
import JobButton from './JobButton';
|
||||
|
@ -72,14 +71,15 @@ export class JobGroupComponent extends React.Component {
|
|||
const stateCounts = {};
|
||||
const typeSymbolCounts = countBy(jobs, 'job_type_symbol');
|
||||
jobs.forEach(job => {
|
||||
if (!job.visible) return;
|
||||
const status = getStatus(job);
|
||||
const { resultStatus, visible, btnClass } = job;
|
||||
if (!visible) return;
|
||||
|
||||
let countInfo = {
|
||||
btnClass: getBtnClass(status, job.failure_classification_id),
|
||||
countText: status,
|
||||
btnClass,
|
||||
countText: resultStatus,
|
||||
};
|
||||
if (
|
||||
thFailureResults.includes(status) ||
|
||||
thFailureResults.includes(resultStatus) ||
|
||||
(typeSymbolCounts[job.job_type_symbol] > 1 && duplicateJobsVisible)
|
||||
) {
|
||||
// render the job itself, not a count
|
||||
|
@ -142,7 +142,7 @@ export class JobGroupComponent extends React.Component {
|
|||
job={job}
|
||||
filterModel={filterModel}
|
||||
visible={job.visible}
|
||||
resultStatus={getStatus(job)}
|
||||
resultStatus={job.resultStatus}
|
||||
failureClassificationId={job.failure_classification_id}
|
||||
repoName={repoName}
|
||||
filterPlatformCb={filterPlatformCb}
|
||||
|
|
|
@ -1,8 +1,6 @@
|
|||
import PropTypes from 'prop-types';
|
||||
import React from 'react';
|
||||
|
||||
import { getStatus } from '../../helpers/job';
|
||||
|
||||
import JobButton from './JobButton';
|
||||
import JobGroup from './JobGroup';
|
||||
|
||||
|
@ -43,7 +41,7 @@ export default class JobsAndGroups extends React.Component {
|
|||
filterModel={filterModel}
|
||||
repoName={repoName}
|
||||
visible={job.visible}
|
||||
resultStatus={getStatus(job)}
|
||||
resultStatus={job.resultStatus}
|
||||
failureClassificationId={job.failure_classification_id}
|
||||
filterPlatformCb={filterPlatformCb}
|
||||
key={job.id}
|
||||
|
|
|
@ -129,12 +129,12 @@ class Push extends React.PureComponent {
|
|||
}
|
||||
};
|
||||
|
||||
toggleSelectedRunnableJob = buildername => {
|
||||
toggleSelectedRunnableJob = signature => {
|
||||
const { selectedRunnableJobs } = this.state;
|
||||
const jobIndex = selectedRunnableJobs.indexOf(buildername);
|
||||
const jobIndex = selectedRunnableJobs.indexOf(signature);
|
||||
|
||||
if (jobIndex === -1) {
|
||||
selectedRunnableJobs.push(buildername);
|
||||
selectedRunnableJobs.push(signature);
|
||||
} else {
|
||||
selectedRunnableJobs.splice(jobIndex, 1);
|
||||
}
|
||||
|
|
|
@ -89,7 +89,7 @@ class PushJobs extends React.Component {
|
|||
handleRunnableClick = jobInstance => {
|
||||
const { toggleSelectedRunnableJob } = this.props;
|
||||
|
||||
toggleSelectedRunnableJob(jobInstance.props.job.ref_data_name);
|
||||
toggleSelectedRunnableJob(jobInstance.props.job.signature);
|
||||
jobInstance.toggleRunnableSelected();
|
||||
};
|
||||
|
||||
|
|
|
@ -62,7 +62,7 @@ class App extends React.PureComponent {
|
|||
JobModel.get(repoName, jobId)
|
||||
.then(async job => {
|
||||
// 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;
|
||||
// other properties, in order of appearance
|
||||
// Test to disable successful steps checkbox on taskcluster jobs
|
||||
|
|
|
@ -5,7 +5,7 @@ import {
|
|||
thFailureResults,
|
||||
thPlatformMap,
|
||||
} from '../helpers/constants';
|
||||
import { getStatus, isClassified } from '../helpers/job';
|
||||
import { isClassified } from '../helpers/job';
|
||||
import {
|
||||
arraysEqual,
|
||||
matchesDefaults,
|
||||
|
@ -196,10 +196,11 @@ export default class FilterModel {
|
|||
showJob = job => {
|
||||
// when runnable jobs have been added to a resultset, they should be
|
||||
// shown regardless of settings for classified or result state
|
||||
const status = getStatus(job);
|
||||
if (status !== 'runnable') {
|
||||
const { resultStatus } = job;
|
||||
|
||||
if (resultStatus !== 'runnable') {
|
||||
// test against resultStatus and classifiedState
|
||||
if (!this.urlParams.resultStatus.includes(status)) {
|
||||
if (!this.urlParams.resultStatus.includes(resultStatus)) {
|
||||
return false;
|
||||
}
|
||||
if (!this._checkClassifiedStateFilters(job)) {
|
||||
|
@ -273,9 +274,9 @@ export default class FilterModel {
|
|||
}`;
|
||||
}
|
||||
|
||||
if (field === 'searchStr') {
|
||||
// lazily get this to avoid storing redundant information
|
||||
return job.getSearchStr();
|
||||
if (field === 'resultStatus') {
|
||||
// don't check this here.
|
||||
return null;
|
||||
}
|
||||
return job[field];
|
||||
};
|
||||
|
|
|
@ -2,9 +2,9 @@ import { slugid } from 'taskcluster-client-web';
|
|||
import groupBy from 'lodash/groupBy';
|
||||
import keyBy from 'lodash/keyBy';
|
||||
|
||||
import { thPlatformMap } from '../helpers/constants';
|
||||
import { createQueryParams, getApiUrl } from '../helpers/url';
|
||||
import { formatTaskclusterError } from '../helpers/errorMessage';
|
||||
import { addAggregateFields } from '../helpers/job';
|
||||
import { getProjectUrl } from '../helpers/location';
|
||||
import { getData } from '../helpers/http';
|
||||
|
||||
|
@ -15,45 +15,12 @@ const uri = '/jobs/';
|
|||
|
||||
// JobModel is the js counterpart of job
|
||||
export default class JobModel {
|
||||
constructor(props) {
|
||||
Object.assign(this, props);
|
||||
}
|
||||
|
||||
getTitle() {
|
||||
// we want to join the group and type information together
|
||||
// so we can search for it as one token (useful when
|
||||
// we want to do a search on something like `fxup-esr(`)
|
||||
let symbolInfo = this.job_group_symbol === '?' ? '' : this.job_group_symbol;
|
||||
symbolInfo += `(${this.job_type_symbol})`;
|
||||
|
||||
return [
|
||||
thPlatformMap[this.platform] || this.platform,
|
||||
this.platform_option,
|
||||
this.job_group_name === 'unknown' ? undefined : this.job_group_name,
|
||||
this.job_type_name,
|
||||
symbolInfo,
|
||||
]
|
||||
.filter(item => typeof item !== 'undefined')
|
||||
.join(' ');
|
||||
}
|
||||
|
||||
getSearchStr() {
|
||||
return [
|
||||
this.getTitle(),
|
||||
this.ref_data_name,
|
||||
this.signature !== this.ref_data_name ? this.signature : undefined,
|
||||
]
|
||||
.filter(item => typeof item !== 'undefined')
|
||||
.join(' ');
|
||||
}
|
||||
|
||||
static async getList(options, config = {}) {
|
||||
// The `uri` config allows to fetch a list of jobs from an arbitrary
|
||||
// endpoint e.g. the similar jobs endpoint. It defaults to the job
|
||||
// list endpoint.
|
||||
const { fetchAll, uri: configUri } = config;
|
||||
const jobUri = configUri || getProjectUrl(uri);
|
||||
|
||||
const { data, failureStatus } = await getData(
|
||||
`${jobUri}${options ? createQueryParams(options) : ''}`,
|
||||
);
|
||||
|
@ -81,17 +48,16 @@ export default class JobModel {
|
|||
if (job_property_names) {
|
||||
// the results came as list of fields
|
||||
// we need to convert them to objects
|
||||
itemList = results.map(
|
||||
elem =>
|
||||
new JobModel(
|
||||
job_property_names.reduce(
|
||||
(prev, prop, i) => ({ ...prev, [prop]: elem[i] }),
|
||||
{},
|
||||
),
|
||||
itemList = results.map(elem =>
|
||||
addAggregateFields(
|
||||
job_property_names.reduce(
|
||||
(prev, prop, i) => ({ ...prev, [prop]: elem[i] }),
|
||||
{},
|
||||
),
|
||||
),
|
||||
);
|
||||
} else {
|
||||
itemList = results.map(job_obj => new JobModel(job_obj));
|
||||
itemList = results.map(job_obj => addAggregateFields(job_obj));
|
||||
}
|
||||
return { data: [...itemList, ...nextPagesJobs], failureStatus: null };
|
||||
}
|
||||
|
@ -104,7 +70,7 @@ export default class JobModel {
|
|||
async response => {
|
||||
if (response.ok) {
|
||||
const job = await response.json();
|
||||
return new JobModel(job);
|
||||
return addAggregateFields(job);
|
||||
}
|
||||
const text = await response.text();
|
||||
throw Error(`Loading job with id ${pk} : ${text}`);
|
||||
|
|
|
@ -1,8 +1,7 @@
|
|||
import { addAggregateFields } from '../helpers/job';
|
||||
import { getRunnableJobsURL } from '../helpers/url';
|
||||
import { escapeId } from '../helpers/aggregateId';
|
||||
|
||||
import JobModel from './job';
|
||||
|
||||
export default class RunnableJobModel {
|
||||
constructor(data) {
|
||||
Object.assign(this, data);
|
||||
|
@ -12,23 +11,22 @@ export default class RunnableJobModel {
|
|||
const uri = getRunnableJobsURL(params.decision_task_id);
|
||||
const rawJobs = await fetch(uri).then(response => response.json());
|
||||
|
||||
return Object.entries(rawJobs).map(
|
||||
([key, value]) =>
|
||||
new JobModel({
|
||||
build_platform: value.platform || '',
|
||||
build_system_type: 'taskcluster',
|
||||
job_group_name: value.groupName || '',
|
||||
job_group_symbol: value.groupSymbol || '',
|
||||
job_type_name: key,
|
||||
job_type_symbol: value.symbol,
|
||||
platform: value.platform || '',
|
||||
platform_option: Object.keys(value.collection).join(' '),
|
||||
ref_data_name: key,
|
||||
state: 'runnable',
|
||||
result: 'runnable',
|
||||
push_id: params.push_id,
|
||||
id: escapeId(params.push_id + key),
|
||||
}),
|
||||
return Object.entries(rawJobs).map(([key, value]) =>
|
||||
addAggregateFields({
|
||||
build_platform: value.platform || '',
|
||||
build_system_type: 'taskcluster',
|
||||
job_group_name: value.groupName || '',
|
||||
job_group_symbol: value.groupSymbol || '',
|
||||
job_type_name: key,
|
||||
job_type_symbol: value.symbol,
|
||||
platform: value.platform || '',
|
||||
platform_option: Object.keys(value.collection).join(' '),
|
||||
signature: key,
|
||||
state: 'runnable',
|
||||
result: 'runnable',
|
||||
push_id: params.push_id,
|
||||
id: escapeId(params.push_id + key),
|
||||
}),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,23 +2,16 @@ import React from 'react';
|
|||
import PropTypes from 'prop-types';
|
||||
|
||||
import { getInspectTaskUrl } from '../helpers/url';
|
||||
import { getSearchStr, getJobSearchStrHref } from '../helpers/job';
|
||||
import { getJobSearchStrHref } from '../helpers/job';
|
||||
import { toDateStr } from '../helpers/display';
|
||||
|
||||
const getTimeFields = function getTimeFields(job) {
|
||||
// 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 = [
|
||||
{ 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) {
|
||||
timeFields.push({ title: 'Started', value: toDateStr(start_timestamp) });
|
||||
}
|
||||
|
@ -36,7 +29,16 @@ const getTimeFields = function getTimeFields(job) {
|
|||
export default class JobInfo extends React.PureComponent {
|
||||
render() {
|
||||
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);
|
||||
|
||||
return (
|
||||
|
@ -47,44 +49,43 @@ export default class JobInfo extends React.PureComponent {
|
|||
<React.Fragment>
|
||||
<a
|
||||
title="Filter jobs with this unique SHA signature"
|
||||
href={getJobSearchStrHref(job.signature)}
|
||||
href={getJobSearchStrHref(signature)}
|
||||
>
|
||||
(sig)
|
||||
</a>
|
||||
:
|
||||
<a
|
||||
title="Filter jobs containing these keywords"
|
||||
href={getJobSearchStrHref(jobSearchStr)}
|
||||
href={getJobSearchStrHref(searchStr)}
|
||||
>
|
||||
{jobSearchStr}
|
||||
{searchStr}
|
||||
</a>
|
||||
</React.Fragment>
|
||||
) : (
|
||||
<span>{job.getTitle()}</span>
|
||||
<span>{title}</span>
|
||||
)}
|
||||
</li>
|
||||
{job.taskcluster_metadata && (
|
||||
{taskcluster_metadata && (
|
||||
<li className="small">
|
||||
<strong>Task: </strong>
|
||||
<a
|
||||
id="taskInfo"
|
||||
href={getInspectTaskUrl(job.taskcluster_metadata.task_id)}
|
||||
href={getInspectTaskUrl(taskcluster_metadata.task_id)}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
{job.taskcluster_metadata.task_id}
|
||||
{taskcluster_metadata.task_id}
|
||||
</a>
|
||||
</li>
|
||||
)}
|
||||
<li className="small">
|
||||
<strong>Build: </strong>
|
||||
<span>{`${job.build_architecture} ${
|
||||
job.build_platform
|
||||
} ${job.build_os || ''}`}</span>
|
||||
<span>{`${build_architecture} ${build_platform} ${build_os ||
|
||||
''}`}</span>
|
||||
</li>
|
||||
<li className="small">
|
||||
<strong>Job name: </strong>
|
||||
<span>{job.job_type_name}</span>
|
||||
<span>{job_type_name}</span>
|
||||
</li>
|
||||
{[...timeFields, ...extraFields].map(field => (
|
||||
<li className="small" key={`${field.title}${field.value}`}>
|
||||
|
|
Загрузка…
Ссылка в новой задаче