зеркало из https://github.com/mozilla/treeherder.git
Bug 1438555 - Fix 'n' after classifying going to top failure (#5063)
This commit is contained in:
Родитель
09b5077a0d
Коммит
d982fb2060
|
@ -1,4 +1,7 @@
|
|||
import FilterModel from '../../../ui/models/filter';
|
||||
import {
|
||||
getFilterUrlParamsWithDefaults,
|
||||
getNonFilterUrlParams,
|
||||
} from '../../../ui/models/filter';
|
||||
|
||||
describe('FilterModel', () => {
|
||||
const oldHash = window.location.hash;
|
||||
|
@ -10,7 +13,7 @@ describe('FilterModel', () => {
|
|||
describe('parsing an old url', () => {
|
||||
it('should parse the repo with defaults', () => {
|
||||
window.location.hash = '?repo=mozilla-inbound';
|
||||
const urlParams = FilterModel.getUrlParamsWithDefaults();
|
||||
const urlParams = getFilterUrlParamsWithDefaults();
|
||||
|
||||
expect(urlParams).toEqual({
|
||||
repo: ['mozilla-inbound'],
|
||||
|
@ -36,7 +39,7 @@ describe('FilterModel', () => {
|
|||
'filter-resultStatus=busted&filter-resultStatus=exception&' +
|
||||
'filter-resultStatus=success&filter-resultStatus=retry' +
|
||||
'&filter-resultStatus=runnable';
|
||||
const urlParams = FilterModel.getUrlParamsWithDefaults();
|
||||
const urlParams = getFilterUrlParamsWithDefaults();
|
||||
|
||||
expect(urlParams).toEqual({
|
||||
repo: ['mozilla-inbound'],
|
||||
|
@ -56,7 +59,10 @@ describe('FilterModel', () => {
|
|||
it('should parse searchStr params with tier and groupState intact', () => {
|
||||
window.location.hash =
|
||||
'?repo=mozilla-inbound&filter-searchStr=Linux%20x64%20debug%20build-linux64-base-toolchains%2Fdebug%20(Bb)&filter-tier=1&group_state=expanded';
|
||||
const urlParams = FilterModel.getUrlParamsWithDefaults();
|
||||
const urlParams = {
|
||||
...getNonFilterUrlParams(),
|
||||
...getFilterUrlParamsWithDefaults(),
|
||||
};
|
||||
|
||||
expect(urlParams).toEqual({
|
||||
repo: ['mozilla-inbound'],
|
||||
|
@ -80,13 +86,13 @@ describe('FilterModel', () => {
|
|||
'build-linux64-base-toolchains/debug',
|
||||
'(bb)',
|
||||
],
|
||||
group_state: ['expanded'],
|
||||
group_state: 'expanded',
|
||||
});
|
||||
});
|
||||
|
||||
it('should parse job field filters', () => {
|
||||
window.location.hash = '?repo=mozilla-inbound&filter-job_type_name=mochi';
|
||||
const urlParams = FilterModel.getUrlParamsWithDefaults();
|
||||
const urlParams = getFilterUrlParamsWithDefaults();
|
||||
|
||||
expect(urlParams).toEqual({
|
||||
repo: ['mozilla-inbound'],
|
||||
|
@ -113,7 +119,7 @@ describe('FilterModel', () => {
|
|||
window.location.hash =
|
||||
'?repo=mozilla-inbound&resultStatus=testfailed,busted,exception,success,retry,runnable&' +
|
||||
'searchStr=linux,x64,debug,build-linux64-base-toolchains%2Fdebug,(bb)';
|
||||
const urlParams = FilterModel.getUrlParamsWithDefaults();
|
||||
const urlParams = getFilterUrlParamsWithDefaults();
|
||||
|
||||
expect(urlParams).toEqual({
|
||||
repo: ['mozilla-inbound'],
|
||||
|
@ -140,7 +146,7 @@ describe('FilterModel', () => {
|
|||
it('should preserve the case in email addresses', () => {
|
||||
window.location.hash =
|
||||
'?repo=mozilla-inbound&author=VYV03354@nifty.ne.jp';
|
||||
const urlParams = FilterModel.getUrlParamsWithDefaults();
|
||||
const urlParams = getFilterUrlParamsWithDefaults();
|
||||
|
||||
expect(urlParams).toEqual({
|
||||
repo: ['mozilla-inbound'],
|
||||
|
|
|
@ -0,0 +1,258 @@
|
|||
import React from 'react';
|
||||
import { Provider } from 'react-redux';
|
||||
import {
|
||||
render,
|
||||
cleanup,
|
||||
fireEvent,
|
||||
waitForElement,
|
||||
} from 'react-testing-library';
|
||||
|
||||
import PushJobs from '../../../../ui/job-view/pushes/PushJobs';
|
||||
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';
|
||||
|
||||
const testPush = {
|
||||
id: 494796,
|
||||
revision: '1252c6014d122d48c6782310d5c3f4ae742751cb',
|
||||
author: 'reviewbot',
|
||||
revisions: [
|
||||
{
|
||||
result_set_id: 494796,
|
||||
repository_id: 4,
|
||||
revision: '1252c6014d122d48c6782310d5c3f4ae742751cb',
|
||||
author: 'pulselistener',
|
||||
comments:
|
||||
'try_task_config for code-review\nDifferential Diff: PHID-DIFF-iql6zm5yinpmva7jhjln',
|
||||
},
|
||||
],
|
||||
revision_count: 10,
|
||||
push_timestamp: 1560354779,
|
||||
repository_id: 4,
|
||||
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,
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
afterEach(() => {
|
||||
cleanup();
|
||||
setUrlParam('selectedJob', null);
|
||||
});
|
||||
|
||||
const testPushJobs = filterModel => (
|
||||
<Provider store={store}>
|
||||
<PinnedJobs>
|
||||
<PushJobs
|
||||
push={testPush}
|
||||
platforms={testPlatforms}
|
||||
repoName="try"
|
||||
filterModel={filterModel}
|
||||
pushGroupState=""
|
||||
toggleSelectedRunnableJob={() => {}}
|
||||
runnableVisible={false}
|
||||
duplicateJobsVisible={false}
|
||||
groupCountsExpanded={false}
|
||||
/>
|
||||
</PinnedJobs>
|
||||
,
|
||||
</Provider>
|
||||
);
|
||||
|
||||
test('select a job updates url', async () => {
|
||||
const { getByText } = render(testPushJobs(new FilterModel()));
|
||||
const spell = getByText('spell');
|
||||
|
||||
expect(spell).toBeInTheDocument();
|
||||
|
||||
fireEvent.mouseDown(spell);
|
||||
expect(spell).toHaveClass('selected-job');
|
||||
|
||||
const selJobId = getUrlParam('selectedJob');
|
||||
|
||||
expect(selJobId).toBe('250970251');
|
||||
});
|
||||
|
||||
test('filter change keeps selected job visible', async () => {
|
||||
const filterModel = new FilterModel();
|
||||
const { getByText, rerender } = render(testPushJobs(filterModel));
|
||||
const spell = await waitForElement(() => getByText('spell'));
|
||||
|
||||
expect(spell).toBeInTheDocument();
|
||||
|
||||
fireEvent.mouseDown(spell);
|
||||
expect(spell).toHaveClass('selected-job');
|
||||
|
||||
filterModel.addFilter('searchStr', 'linux');
|
||||
rerender(testPushJobs(new FilterModel()));
|
||||
|
||||
const spell2 = getByText('spell');
|
||||
|
||||
expect(spell2).toBeInTheDocument();
|
||||
expect(spell2).toHaveClass('filter-shown');
|
||||
expect(spell2).toHaveClass('selected-job');
|
||||
});
|
|
@ -5,7 +5,7 @@ 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 { getUrlParam } from '../../helpers/location';
|
||||
import { getSelectedJobId, getUrlParam } from '../../helpers/location';
|
||||
|
||||
export default class JobButtonComponent extends React.Component {
|
||||
constructor(props) {
|
||||
|
@ -53,7 +53,7 @@ export default class JobButtonComponent extends React.Component {
|
|||
}
|
||||
|
||||
setSelected(isSelected) {
|
||||
const { job, platform, filterPlatformCb, filterModel } = this.props;
|
||||
const { job, filterPlatformCb, filterModel } = this.props;
|
||||
// if a job was just classified, and we are in unclassified only mode,
|
||||
// then the job no longer meets the filter criteria. However, if it
|
||||
// is still selected, then it should stay visible so that next/previous
|
||||
|
@ -64,7 +64,7 @@ export default class JobButtonComponent extends React.Component {
|
|||
this.setState({ isSelected });
|
||||
// filterPlatformCb will keep a job and platform visible if it contains
|
||||
// the selected job, so we must pass in if this job is selected or not.
|
||||
filterPlatformCb(platform, isSelected ? job.id : null);
|
||||
filterPlatformCb(isSelected ? job.id : null);
|
||||
}
|
||||
|
||||
toggleRunnableSelected() {
|
||||
|
@ -74,9 +74,9 @@ export default class JobButtonComponent extends React.Component {
|
|||
}
|
||||
|
||||
refilter() {
|
||||
const { filterPlatformCb, platform } = this.props;
|
||||
const { filterPlatformCb } = this.props;
|
||||
|
||||
filterPlatformCb(platform);
|
||||
filterPlatformCb(getSelectedJobId());
|
||||
}
|
||||
|
||||
render() {
|
||||
|
@ -154,7 +154,6 @@ JobButtonComponent.propTypes = {
|
|||
repoName: PropTypes.string.isRequired,
|
||||
visible: PropTypes.bool.isRequired,
|
||||
resultStatus: PropTypes.string.isRequired,
|
||||
platform: PropTypes.object.isRequired,
|
||||
filterPlatformCb: PropTypes.func.isRequired,
|
||||
failureClassificationId: PropTypes.number, // runnable jobs won't have this
|
||||
};
|
||||
|
|
|
@ -115,7 +115,6 @@ export class JobGroupComponent extends React.Component {
|
|||
const {
|
||||
repoName,
|
||||
filterPlatformCb,
|
||||
platform,
|
||||
filterModel,
|
||||
group: {
|
||||
name: groupName,
|
||||
|
@ -147,7 +146,6 @@ export class JobGroupComponent extends React.Component {
|
|||
failureClassificationId={job.failure_classification_id}
|
||||
repoName={repoName}
|
||||
filterPlatformCb={filterPlatformCb}
|
||||
platform={platform}
|
||||
key={job.id}
|
||||
/>
|
||||
))}
|
||||
|
@ -179,7 +177,6 @@ JobGroupComponent.propTypes = {
|
|||
repoName: PropTypes.string.isRequired,
|
||||
filterModel: PropTypes.object.isRequired,
|
||||
filterPlatformCb: PropTypes.func.isRequired,
|
||||
platform: PropTypes.object.isRequired,
|
||||
pushGroupState: PropTypes.string.isRequired,
|
||||
duplicateJobsVisible: PropTypes.bool.isRequired,
|
||||
groupCountsExpanded: PropTypes.bool.isRequired,
|
||||
|
|
|
@ -11,7 +11,6 @@ export default class JobsAndGroups extends React.Component {
|
|||
const {
|
||||
groups,
|
||||
repoName,
|
||||
platform,
|
||||
filterPlatformCb,
|
||||
filterModel,
|
||||
pushGroupState,
|
||||
|
@ -30,7 +29,6 @@ export default class JobsAndGroups extends React.Component {
|
|||
repoName={repoName}
|
||||
filterModel={filterModel}
|
||||
filterPlatformCb={filterPlatformCb}
|
||||
platform={platform}
|
||||
key={group.mapKey}
|
||||
pushGroupState={pushGroupState}
|
||||
duplicateJobsVisible={duplicateJobsVisible}
|
||||
|
@ -48,7 +46,6 @@ export default class JobsAndGroups extends React.Component {
|
|||
resultStatus={getStatus(job)}
|
||||
failureClassificationId={job.failure_classification_id}
|
||||
filterPlatformCb={filterPlatformCb}
|
||||
platform={platform}
|
||||
key={job.id}
|
||||
/>
|
||||
));
|
||||
|
@ -63,7 +60,6 @@ JobsAndGroups.propTypes = {
|
|||
repoName: PropTypes.string.isRequired,
|
||||
filterModel: PropTypes.object.isRequired,
|
||||
filterPlatformCb: PropTypes.func.isRequired,
|
||||
platform: PropTypes.object.isRequired,
|
||||
pushGroupState: PropTypes.string.isRequired,
|
||||
duplicateJobsVisible: PropTypes.bool.isRequired,
|
||||
groupCountsExpanded: PropTypes.bool.isRequired,
|
||||
|
|
|
@ -99,7 +99,6 @@ export default class Platform extends React.PureComponent {
|
|||
groups={filteredPlatform.groups}
|
||||
repoName={repoName}
|
||||
filterPlatformCb={this.filterCb}
|
||||
platform={filteredPlatform}
|
||||
filterModel={filterModel}
|
||||
pushGroupState={pushGroupState}
|
||||
duplicateJobsVisible={duplicateJobsVisible}
|
||||
|
|
|
@ -13,39 +13,56 @@ import {
|
|||
thMatchType,
|
||||
thFilterDefaults,
|
||||
deprecated_thFilterPrefix,
|
||||
allFilterParams,
|
||||
} from '../helpers/filter';
|
||||
import { getAllUrlParams } from '../helpers/location';
|
||||
|
||||
export const getNonFilterUrlParams = () =>
|
||||
[...getAllUrlParams().entries()].reduce(
|
||||
(acc, [urlField, urlValue]) =>
|
||||
allFilterParams.includes(urlField.replace(deprecated_thFilterPrefix, ''))
|
||||
? acc
|
||||
: { ...acc, [urlField]: urlValue },
|
||||
{},
|
||||
);
|
||||
|
||||
export const getFilterUrlParamsWithDefaults = () => {
|
||||
// Group multiple values for the same field into an array of values.
|
||||
// This handles the transition from our old url params to this newer, more
|
||||
// terse version.
|
||||
// Also remove usage of the 'filter-' prefix.
|
||||
const groupedValues = [...getAllUrlParams().entries()].reduce(
|
||||
(acc, [urlField, urlValue]) => {
|
||||
const field = urlField.replace(deprecated_thFilterPrefix, '');
|
||||
if (!allFilterParams.includes(field)) {
|
||||
return acc;
|
||||
}
|
||||
const value =
|
||||
field === 'author' ? [urlValue] : urlValue.toLowerCase().split(/,| /);
|
||||
|
||||
return field in acc
|
||||
? { ...acc, [field]: [...acc[field], ...value] }
|
||||
: { ...acc, [field]: value };
|
||||
},
|
||||
{},
|
||||
);
|
||||
|
||||
return { ...cloneDeep(thFilterDefaults), ...groupedValues };
|
||||
};
|
||||
|
||||
export default class FilterModel {
|
||||
constructor() {
|
||||
this.urlParams = FilterModel.getUrlParamsWithDefaults();
|
||||
}
|
||||
|
||||
static getUrlParamsWithDefaults() {
|
||||
// Group multiple values for the same field into an array of values.
|
||||
// This handles the transition from our old url params to this newer, more
|
||||
// terse version.
|
||||
// Also remove usage of the 'filter-' prefix.
|
||||
const groupedValues = [...getAllUrlParams().entries()].reduce(
|
||||
(acc, [urlField, urlValue]) => {
|
||||
const field = urlField.replace(deprecated_thFilterPrefix, '');
|
||||
const value =
|
||||
field === 'author' ? [urlValue] : urlValue.toLowerCase().split(/,| /);
|
||||
|
||||
return field in acc
|
||||
? { ...acc, [field]: [...acc[field], ...value] }
|
||||
: { ...acc, [field]: value };
|
||||
},
|
||||
{},
|
||||
);
|
||||
|
||||
return { ...cloneDeep(thFilterDefaults), ...groupedValues };
|
||||
this.urlParams = getFilterUrlParamsWithDefaults();
|
||||
}
|
||||
|
||||
// If a param matches the defaults, then don't include it.
|
||||
getUrlParamsWithoutDefaults = () => {
|
||||
// ensure the repo param is always set
|
||||
const params = { repo: thDefaultRepo, ...this.urlParams };
|
||||
const params = {
|
||||
repo: thDefaultRepo,
|
||||
...getNonFilterUrlParams(),
|
||||
...this.urlParams,
|
||||
};
|
||||
|
||||
return Object.entries(params).reduce(
|
||||
(acc, [field, value]) =>
|
||||
|
|
Загрузка…
Ссылка в новой задаче