зеркало из https://github.com/mozilla/treeherder.git
Bug 1715790 - Alerts View - Test name is too long (#7185)
* Add platform column, move options into tags * Add sort for platform column, remove platform from title * Add unit tests, refactor tags options column
This commit is contained in:
Родитель
ffbff08476
Коммит
382b3cfe8e
|
@ -45,7 +45,7 @@
|
|||
"id": 1945375,
|
||||
"framework_id": 1,
|
||||
"signature_hash": "461af9d92db3f2d97dc6e4c4d47e7ad256356861",
|
||||
"machine_platform": "linux64-shippable-qr",
|
||||
"machine_platform": "windows10-64-qr",
|
||||
"suite": "tp5o_webext",
|
||||
"test": "responsiveness",
|
||||
"tags": ["benchmark", "warm"],
|
||||
|
@ -74,7 +74,7 @@
|
|||
"id": 1945376,
|
||||
"framework_id": 1,
|
||||
"signature_hash": "461af9d92db3f2d97dc6e4c4d47e7ad256356861",
|
||||
"machine_platform": "linux64-shippable-qr",
|
||||
"machine_platform": "macosx1015-64-shippable",
|
||||
"suite": "tp5a_webext",
|
||||
"test": "responsiveness",
|
||||
"tags": ["cold", "live"],
|
||||
|
|
|
@ -24,10 +24,15 @@ const frameworks = [
|
|||
},
|
||||
];
|
||||
|
||||
const alertTableRowTest = (tags, alert = testAlert) => {
|
||||
const alertTableRowTest = (tags, alert = testAlert, options) => {
|
||||
if (tags) {
|
||||
testAlert.series_signature.tags = [...tags];
|
||||
}
|
||||
|
||||
if (options) {
|
||||
testAlert.series_signature.extra_options = [...options];
|
||||
}
|
||||
|
||||
return render(
|
||||
<table>
|
||||
<tbody>
|
||||
|
@ -48,6 +53,26 @@ const alertTableRowTest = (tags, alert = testAlert) => {
|
|||
|
||||
afterEach(cleanup);
|
||||
|
||||
test('Test column contains only suite and test name', async () => {
|
||||
const { getByTestId } = alertTableRowTest(false, testAlert);
|
||||
const { suite, test } = testAlert.series_signature;
|
||||
|
||||
const alertTitle = await waitFor(() =>
|
||||
getByTestId(`alert ${testAlert.id} title`),
|
||||
);
|
||||
|
||||
expect(alertTitle.textContent).toBe(`${suite} ${test}`);
|
||||
});
|
||||
|
||||
test(`Platform column contains alerts's platform`, async () => {
|
||||
const { getByTestId } = alertTableRowTest(false, testAlert);
|
||||
const { machine_platform: machinePlatform } = testAlert.series_signature;
|
||||
|
||||
const alertPlatform = await waitFor(() => getByTestId(`alert-platform`));
|
||||
|
||||
expect(alertPlatform.textContent).toBe(machinePlatform);
|
||||
});
|
||||
|
||||
test("Alert item with no tags displays 'No tags'", async () => {
|
||||
const { getByText } = alertTableRowTest(['']);
|
||||
|
||||
|
@ -66,22 +91,25 @@ test('Alert item with 2 tags displays 2 tags', async () => {
|
|||
|
||||
test("Alert item with more than 2 tags displays '...' button", async () => {
|
||||
const testTags = ['tag1', 'tag2', 'tag3'];
|
||||
const { getByText } = alertTableRowTest(testTags);
|
||||
const { getByTestId } = alertTableRowTest(testTags);
|
||||
|
||||
const showMoreButton = await waitFor(() => getByText('...'));
|
||||
const showMoreButton = await waitFor(() => getByTestId('show-more-tags'));
|
||||
|
||||
expect(showMoreButton).toBeInTheDocument();
|
||||
expect(showMoreButton.textContent).toBe('...');
|
||||
});
|
||||
|
||||
test("Button '...' displays all the tags for an alert item", async () => {
|
||||
const testTags = ['tag1', 'tag2', 'tag3'];
|
||||
const { getByText, getAllByTestId } = alertTableRowTest(testTags);
|
||||
const { getByTestId, getAllByTestId } = alertTableRowTest(testTags);
|
||||
|
||||
let visibleTags = await waitFor(() => getAllByTestId(`alert-tag`));
|
||||
|
||||
expect(visibleTags).toHaveLength(2);
|
||||
|
||||
const showMoreButton = await waitFor(() => getByText('...'));
|
||||
const showMoreButton = await waitFor(() => getByTestId('show-more-tags'));
|
||||
|
||||
expect(showMoreButton.textContent).toBe('...');
|
||||
|
||||
fireEvent.click(showMoreButton);
|
||||
|
||||
visibleTags = await waitFor(() => getAllByTestId(`alert-tag`));
|
||||
|
@ -89,6 +117,54 @@ test("Button '...' displays all the tags for an alert item", async () => {
|
|||
expect(visibleTags).toHaveLength(testTags.length);
|
||||
});
|
||||
|
||||
test("Alert item with no options displays 'No options'", async () => {
|
||||
const { getByText } = alertTableRowTest(false, testAlert, ['']);
|
||||
|
||||
const message = await waitFor(() => getByText('No options'));
|
||||
expect(message).toBeInTheDocument();
|
||||
});
|
||||
|
||||
test('Alert item with 2 options displays 2 options', async () => {
|
||||
const testOptions = ['option1', 'option2'];
|
||||
const { getAllByTestId } = alertTableRowTest(false, testAlert, testOptions);
|
||||
|
||||
const options = await waitFor(() => getAllByTestId(`alert-option`));
|
||||
|
||||
expect(options).toHaveLength(testOptions.length);
|
||||
});
|
||||
|
||||
test("Alert item with more than 2 options displays '...' button", async () => {
|
||||
const testOptions = ['option1', 'option2', 'option3'];
|
||||
const { getByTestId } = alertTableRowTest(false, testAlert, testOptions);
|
||||
|
||||
const showMoreButton = await waitFor(() => getByTestId('show-more-options'));
|
||||
|
||||
expect(showMoreButton.textContent).toBe('...');
|
||||
});
|
||||
|
||||
test("Button '...' displays all options for an alert item", async () => {
|
||||
const testOptions = ['option1', 'option2', 'option3'];
|
||||
const { getByTestId, getAllByTestId } = alertTableRowTest(
|
||||
false,
|
||||
testAlert,
|
||||
testOptions,
|
||||
);
|
||||
|
||||
let visibleOptions = await waitFor(() => getAllByTestId(`alert-option`));
|
||||
|
||||
expect(visibleOptions).toHaveLength(2);
|
||||
|
||||
const showMoreButton = await waitFor(() => getByTestId('show-more-options'));
|
||||
|
||||
expect(showMoreButton.textContent).toBe('...');
|
||||
|
||||
fireEvent.click(showMoreButton);
|
||||
|
||||
visibleOptions = await waitFor(() => getAllByTestId(`alert-option`));
|
||||
|
||||
expect(visibleOptions).toHaveLength(testOptions.length);
|
||||
});
|
||||
|
||||
test('Documentation link is available for talos framework', async () => {
|
||||
const { getByTestId } = alertTableRowTest();
|
||||
expect(getByTestId('docs')).toHaveAttribute(
|
||||
|
|
|
@ -592,7 +592,7 @@ test('No tags are displayed if there is no active tag', async () => {
|
|||
// TODO should write tests for alert summary dropdown menu actions performed in StatusDropdown
|
||||
// (adding notes or marking as 'fixed', etc)
|
||||
|
||||
test('table data can be sorted in descending order by test and platform', async () => {
|
||||
test(`table data can be sorted in descending order by 'Test'`, async () => {
|
||||
const {
|
||||
getAllByLabelText,
|
||||
getByTestId,
|
||||
|
@ -613,7 +613,7 @@ test('table data can be sorted in descending order by test and platform', async
|
|||
expect(alertTableRows[2]).toContainElement(alert2);
|
||||
|
||||
const sortByTest = await waitFor(() =>
|
||||
getAllByTitle('Sorted in default order by test and platform'),
|
||||
getAllByTitle('Sorted in default order by test'),
|
||||
);
|
||||
|
||||
// firing the sort button once triggers ascending sort
|
||||
|
@ -628,6 +628,50 @@ test('table data can be sorted in descending order by test and platform', async
|
|||
expect(alertTableRows[2]).toContainElement(alert3);
|
||||
});
|
||||
|
||||
test(`table data can be sorted in ascending order by 'Platform'`, async () => {
|
||||
const {
|
||||
getByTestId,
|
||||
getAllByLabelText,
|
||||
getAllByTitle,
|
||||
} = alertsViewControls();
|
||||
|
||||
let alertTableRows = await waitFor(() =>
|
||||
getAllByLabelText('Alert table row'),
|
||||
);
|
||||
|
||||
const alert1 = await waitFor(() => getByTestId('69344'));
|
||||
const alert2 = await waitFor(() => getByTestId('69345'));
|
||||
const alert3 = await waitFor(() => getByTestId('69346'));
|
||||
|
||||
// alerts are sorted in a default manner without clicking on sort buttons
|
||||
expect(alertTableRows[0]).toContainElement(alert3);
|
||||
expect(alertTableRows[1]).toContainElement(alert1);
|
||||
expect(alertTableRows[2]).toContainElement(alert2);
|
||||
|
||||
const sortByPlatform = await waitFor(() =>
|
||||
getAllByTitle('Sorted in default order by platform'),
|
||||
);
|
||||
|
||||
// firing the sort button once triggers ascending sort
|
||||
fireEvent.click(sortByPlatform[0]);
|
||||
|
||||
alertTableRows = await waitFor(() => getAllByLabelText('Alert table row'));
|
||||
|
||||
expect(alertTableRows[0]).toContainElement(alert1);
|
||||
expect(alertTableRows[1]).toContainElement(alert3);
|
||||
expect(alertTableRows[2]).toContainElement(alert2);
|
||||
});
|
||||
|
||||
test(`table data cannot be sorted by 'Tags & Options'`, async () => {
|
||||
const { getAllByTitle } = alertsViewControls();
|
||||
|
||||
const sortByTags = await waitFor(() =>
|
||||
getAllByTitle('Sorted by tags & options disabled'),
|
||||
);
|
||||
|
||||
expect(sortByTags[0]).toHaveClass('disabled-button');
|
||||
});
|
||||
|
||||
test(`table data can be sorted in ascending order by 'Confidence'`, async () => {
|
||||
const {
|
||||
getAllByLabelText,
|
||||
|
@ -662,40 +706,6 @@ test(`table data can be sorted in ascending order by 'Confidence'`, async () =>
|
|||
expect(alertTableRows[2]).toContainElement(alert3);
|
||||
});
|
||||
|
||||
test(`table data can be sorted in ascending order by 'Tags'`, async () => {
|
||||
const {
|
||||
getAllByLabelText,
|
||||
getByTestId,
|
||||
getAllByTitle,
|
||||
} = alertsViewControls();
|
||||
|
||||
let alertTableRows = await waitFor(() =>
|
||||
getAllByLabelText('Alert table row'),
|
||||
);
|
||||
|
||||
const alert1 = await waitFor(() => getByTestId('69344'));
|
||||
const alert2 = await waitFor(() => getByTestId('69345'));
|
||||
const alert3 = await waitFor(() => getByTestId('69346'));
|
||||
|
||||
// alerts are sorted in a default manner without clicking on sort buttons
|
||||
expect(alertTableRows[0]).toContainElement(alert3);
|
||||
expect(alertTableRows[1]).toContainElement(alert1);
|
||||
expect(alertTableRows[2]).toContainElement(alert2);
|
||||
|
||||
const sortByTags = await waitFor(() =>
|
||||
getAllByTitle('Sorted in default order by tags'),
|
||||
);
|
||||
|
||||
// firing the sort button once triggers ascending sort
|
||||
fireEvent.click(sortByTags[0]);
|
||||
|
||||
alertTableRows = await waitFor(() => getAllByLabelText('Alert table row'));
|
||||
|
||||
expect(alertTableRows[0]).toContainElement(alert2);
|
||||
expect(alertTableRows[1]).toContainElement(alert1);
|
||||
expect(alertTableRows[2]).toContainElement(alert3);
|
||||
});
|
||||
|
||||
test('test data can be sorted only by one column', async () => {
|
||||
const {
|
||||
getAllByLabelText,
|
||||
|
@ -716,12 +726,12 @@ test('test data can be sorted only by one column', async () => {
|
|||
expect(alertTableRows[1]).toContainElement(alert1);
|
||||
expect(alertTableRows[2]).toContainElement(alert2);
|
||||
|
||||
const sortByTags = await waitFor(() =>
|
||||
getAllByTitle('Sorted in default order by tags'),
|
||||
const sortByPlatform = await waitFor(() =>
|
||||
getAllByTitle('Sorted in default order by platform'),
|
||||
);
|
||||
// firing the sort button once triggers ascending sort
|
||||
fireEvent.click(sortByTags[0]);
|
||||
expect(sortByTags[0].title).toBe('Sorted in ascending order by tags');
|
||||
fireEvent.click(sortByPlatform[0]);
|
||||
expect(sortByPlatform[0].title).toBe('Sorted in ascending order by platform');
|
||||
|
||||
const sortByConfidence = await waitFor(() =>
|
||||
getAllByTitle('Sorted in default order by confidence'),
|
||||
|
@ -730,7 +740,7 @@ test('test data can be sorted only by one column', async () => {
|
|||
expect(sortByConfidence[0].title).toBe(
|
||||
'Sorted in ascending order by confidence',
|
||||
);
|
||||
expect(sortByTags[0].title).toBe('Sorted in default order by tags');
|
||||
expect(sortByPlatform[0].title).toBe('Sorted in default order by platform');
|
||||
|
||||
alertTableRows = await waitFor(() => getAllByLabelText('Alert table row'));
|
||||
|
||||
|
|
|
@ -11,13 +11,13 @@ import {
|
|||
import projects from '../../mock/repositories';
|
||||
import CompareTableControls from '../../../../ui/perfherder/compare/CompareTableControls';
|
||||
import CompareTable from '../../../../ui/perfherder/compare/CompareTable';
|
||||
import SortButton from '../../../../ui/perfherder/shared/SortButton';
|
||||
import ComparePageTitle from '../../../../ui/shared/ComparePageTitle';
|
||||
import {
|
||||
compareTableText,
|
||||
filterText,
|
||||
} from '../../../../ui/perfherder/perf-helpers/constants';
|
||||
import JobModel from '../../../../ui/models/job';
|
||||
import TableColumnHeader from '../../../../ui/perfherder/shared/TableColumnHeader';
|
||||
|
||||
// TODO addtional tests:
|
||||
// 1) that the table is receiving the correct data structure after data
|
||||
|
@ -694,7 +694,7 @@ test(`measurement unit is passed in the header name for Base and New`, async ()
|
|||
expect(queryByText('Base (ms)')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
test(`SortButton shows the title as expected`, async () => {
|
||||
test(`TableColumnHeader shows the title as expected`, async () => {
|
||||
const defaultProps = {
|
||||
onChangeSort: jest.fn(),
|
||||
column: {
|
||||
|
@ -702,7 +702,7 @@ test(`SortButton shows the title as expected`, async () => {
|
|||
currentSort: 'default',
|
||||
},
|
||||
};
|
||||
const { queryByText } = render(<SortButton {...defaultProps} />);
|
||||
const { queryByText } = render(<TableColumnHeader {...defaultProps} />);
|
||||
|
||||
expect(queryByText('New (score)')).not.toBeInTheDocument();
|
||||
expect(queryByText('New')).toBeInTheDocument();
|
||||
|
|
|
@ -544,3 +544,10 @@ li.pagination-active.active > button {
|
|||
.legend-docs a:visited {
|
||||
color: #337ab7;
|
||||
}
|
||||
|
||||
.disabled-button {
|
||||
border: 1px solid transparent;
|
||||
box-sizing: border-box;
|
||||
cursor: not-allowed;
|
||||
opacity: 0.65;
|
||||
}
|
||||
|
|
|
@ -16,12 +16,13 @@ import {
|
|||
} from '../perf-helpers/helpers';
|
||||
import TruncatedText from '../../shared/TruncatedText';
|
||||
import ErrorBoundary from '../../shared/ErrorBoundary';
|
||||
import SortButton from '../shared/SortButton';
|
||||
import TableColumnHeader from '../shared/TableColumnHeader';
|
||||
import SortButtonDisabled from '../shared/SortButtonDisabled';
|
||||
import { tableSort, getNextSort, sort, sortTables } from '../perf-helpers/sort';
|
||||
|
||||
import AlertTableRow from './AlertTableRow';
|
||||
import AlertHeader from './AlertHeader';
|
||||
import StatusDropdown from './StatusDropdown';
|
||||
import AlertTableRow from './AlertTableRow';
|
||||
import DownstreamSummary from './DownstreamSummary';
|
||||
import AlertActionPanel from './AlertActionPanel';
|
||||
import SelectAlertsDropdown from './SelectAlertsDropdown';
|
||||
|
@ -37,13 +38,18 @@ export default class AlertTable extends React.Component {
|
|||
allSelected: false,
|
||||
selectedAlerts: [],
|
||||
tableConfig: {
|
||||
TestAndPlatform: {
|
||||
name: 'Test and platform',
|
||||
Test: {
|
||||
name: 'Test',
|
||||
sortValue: 'title',
|
||||
currentSort: tableSort.default,
|
||||
},
|
||||
Tags: {
|
||||
name: 'Tags',
|
||||
Platform: {
|
||||
name: 'Platform',
|
||||
sortValue: 'machine_platform',
|
||||
currentSort: tableSort.default,
|
||||
},
|
||||
TagsOptions: {
|
||||
name: 'Tags & Options',
|
||||
sortValue: 'tags',
|
||||
currentSort: tableSort.default,
|
||||
},
|
||||
|
@ -347,47 +353,48 @@ export default class AlertTable extends React.Component {
|
|||
<th> </th>
|
||||
<th> </th>
|
||||
<th className="align-bottom">
|
||||
<SortButton
|
||||
column={tableConfig.TestAndPlatform}
|
||||
<TableColumnHeader
|
||||
column={tableConfig.Test}
|
||||
onChangeSort={this.onChangeSort}
|
||||
/>
|
||||
</th>
|
||||
<th className="align-bottom">
|
||||
{' '}
|
||||
<SortButton
|
||||
column={tableConfig.Tags}
|
||||
<TableColumnHeader
|
||||
column={tableConfig.Platform}
|
||||
onChangeSort={this.onChangeSort}
|
||||
/>
|
||||
</th>
|
||||
<th className="align-bottom">
|
||||
{' '}
|
||||
<SortButton
|
||||
{tableConfig.TagsOptions.name}
|
||||
<SortButtonDisabled column={tableConfig.TagsOptions} />
|
||||
</th>
|
||||
<th className="align-bottom">
|
||||
<TableColumnHeader
|
||||
column={tableConfig.PreviousValue}
|
||||
onChangeSort={this.onChangeSort}
|
||||
/>
|
||||
</th>
|
||||
<th> </th>
|
||||
<th className="align-bottom">
|
||||
{' '}
|
||||
<SortButton
|
||||
<TableColumnHeader
|
||||
column={tableConfig.NewValue}
|
||||
onChangeSort={this.onChangeSort}
|
||||
/>
|
||||
</th>
|
||||
<th className="align-bottom">
|
||||
<SortButton
|
||||
<TableColumnHeader
|
||||
column={tableConfig.AbsoluteDifference}
|
||||
onChangeSort={this.onChangeSort}
|
||||
/>
|
||||
</th>
|
||||
<th className="align-bottom">
|
||||
<SortButton
|
||||
<TableColumnHeader
|
||||
column={tableConfig.Magnitude}
|
||||
onChangeSort={this.onChangeSort}
|
||||
/>
|
||||
</th>
|
||||
<th className="align-bottom">
|
||||
<SortButton
|
||||
<TableColumnHeader
|
||||
column={tableConfig.Confidence}
|
||||
onChangeSort={this.onChangeSort}
|
||||
/>
|
||||
|
|
|
@ -0,0 +1,19 @@
|
|||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import Badge from 'reactstrap/lib/Badge';
|
||||
|
||||
export default class AlertTablePlatform extends React.PureComponent {
|
||||
render() {
|
||||
const { platform } = this.props;
|
||||
|
||||
return (
|
||||
<Badge color="light" data-testid="alert-platform">
|
||||
{platform}
|
||||
</Badge>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
AlertTablePlatform.propTypes = {
|
||||
platform: PropTypes.string.isRequired,
|
||||
};
|
|
@ -2,14 +2,7 @@
|
|||
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import {
|
||||
Button,
|
||||
FormGroup,
|
||||
Input,
|
||||
Label,
|
||||
Badge,
|
||||
UncontrolledTooltip,
|
||||
} from 'reactstrap';
|
||||
import { Button, FormGroup, Input, Label } from 'reactstrap';
|
||||
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
||||
import {
|
||||
faStar as faStarSolid,
|
||||
|
@ -38,15 +31,15 @@ import {
|
|||
phTimeRanges,
|
||||
} from '../perf-helpers/constants';
|
||||
|
||||
import AlertTablePlatform from './AlertTablePlatform';
|
||||
import AlertTableTagsOptions from './AlertTableTagsOptions';
|
||||
|
||||
export default class AlertTableRow extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
const { tags } = this.props.alert.series_signature;
|
||||
this.state = {
|
||||
starred: this.props.alert.starred,
|
||||
checkboxSelected: false,
|
||||
displayAllTags: false,
|
||||
tags,
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -183,12 +176,8 @@ export default class AlertTableRow extends React.Component {
|
|||
frameworkName,
|
||||
);
|
||||
const { title } = alert;
|
||||
const { suite } = alert.series_signature;
|
||||
const { url, remainingTestName } = getSplitTestTitle(
|
||||
title,
|
||||
suite,
|
||||
frameworkName,
|
||||
);
|
||||
const { suite, test } = alert.series_signature;
|
||||
const { url } = getSplitTestTitle(title, suite, frameworkName);
|
||||
return (
|
||||
<span>
|
||||
<span
|
||||
|
@ -197,14 +186,16 @@ export default class AlertTableRow extends React.Component {
|
|||
title={alert.backfill_record ? backfillRetriggeredTitle : ''}
|
||||
>
|
||||
{hasDocumentation && alert.title ? (
|
||||
<div className="alert-docs">
|
||||
<div className="alert-docs" data-testid={`alert ${alert.id} title`}>
|
||||
<a data-testid="docs" href={url}>
|
||||
{suite}
|
||||
</a>{' '}
|
||||
{remainingTestName}
|
||||
{test}
|
||||
</div>
|
||||
) : (
|
||||
<div>{alert.title}</div>
|
||||
<div data-testid={`alert ${alert.id} title`}>
|
||||
{suite} {test}
|
||||
</div>
|
||||
)}
|
||||
</span>{' '}
|
||||
{this.renderAlertStatus(alert, alertStatus, statusColor)}{' '}
|
||||
|
@ -230,50 +221,6 @@ export default class AlertTableRow extends React.Component {
|
|||
);
|
||||
};
|
||||
|
||||
showTags = (tags) => {
|
||||
return tags.map((item) => (
|
||||
<Badge color="light" key={`${item}`} data-testid="alert-tag">
|
||||
{item}
|
||||
</Badge>
|
||||
));
|
||||
};
|
||||
|
||||
getTags = (alert) => {
|
||||
const { displayAllTags, tags } = this.state;
|
||||
const visibleTags = 2;
|
||||
|
||||
if (tags.length && tags[0] !== '') {
|
||||
return (
|
||||
<React.Fragment>
|
||||
{this.showTags(tags.slice(0, visibleTags))}
|
||||
{!displayAllTags && tags.length > visibleTags && (
|
||||
<Button
|
||||
color="link"
|
||||
size="sm"
|
||||
id={`alert-${alert.id}-tags`}
|
||||
onClick={() =>
|
||||
this.setState((prevState) => ({
|
||||
displayAllTags: !prevState.displayAllTags,
|
||||
}))
|
||||
}
|
||||
>
|
||||
<span>...</span>
|
||||
<UncontrolledTooltip
|
||||
placement="top"
|
||||
target={`alert-${alert.id}-tags`}
|
||||
>
|
||||
Show more tags
|
||||
</UncontrolledTooltip>
|
||||
</Button>
|
||||
)}
|
||||
{displayAllTags && this.showTags(tags.slice(visibleTags))}
|
||||
</React.Fragment>
|
||||
);
|
||||
}
|
||||
|
||||
return <Badge color="light">No tags</Badge>;
|
||||
};
|
||||
|
||||
// arbitrary scale from 0-20% multiplied by 5, capped
|
||||
// at 100 (so 20% regression === 100% bad)
|
||||
getCappedMagnitude = (percent) => Math.min(Math.abs(percent) * 5, 100);
|
||||
|
@ -297,6 +244,9 @@ export default class AlertTableRow extends React.Component {
|
|||
const { user, alert, alertSummary } = this.props;
|
||||
const { starred, checkboxSelected } = this.state;
|
||||
|
||||
const { tags, extra_options: options } = alert.series_signature;
|
||||
const items = { tags, options };
|
||||
|
||||
const alertStatus = getStatus(alert.status, alertStatusMap);
|
||||
const tooltipText = alert.classifier_email
|
||||
? `Classified by ${alert.classifier_email}`
|
||||
|
@ -357,7 +307,14 @@ export default class AlertTableRow extends React.Component {
|
|||
this.getTitleText(alert, alertStatus)
|
||||
)}
|
||||
</td>
|
||||
<td className="table-width-md">{this.getTags(alert)}</td>
|
||||
<td className="table-width-md">
|
||||
<AlertTablePlatform
|
||||
platform={alert.series_signature.machine_platform}
|
||||
/>
|
||||
</td>
|
||||
<td className="table-width-md">
|
||||
<AlertTableTagsOptions alertId={alert.id} items={items} />
|
||||
</td>
|
||||
<td className="table-width-md">{formatNumber(alert.prev_value)}</td>
|
||||
<td className="table-width-sm">
|
||||
<span
|
||||
|
|
|
@ -0,0 +1,97 @@
|
|||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import UncontrolledTooltip from 'reactstrap/lib/UncontrolledTooltip';
|
||||
import Button from 'reactstrap/lib/Button';
|
||||
import Badge from 'reactstrap/lib/Badge';
|
||||
|
||||
export default class AlertTableTagsOptions extends React.Component {
|
||||
itemsType = { tags: 'tags', options: 'options' };
|
||||
|
||||
visibleItems = {
|
||||
tags: 2,
|
||||
options: 2,
|
||||
};
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
const { tags, options } = this.props.items;
|
||||
this.state = {
|
||||
displayAllItems: {
|
||||
tags: false,
|
||||
options: false,
|
||||
},
|
||||
options,
|
||||
tags,
|
||||
};
|
||||
}
|
||||
|
||||
showItems = (items, type) => {
|
||||
const badgeId = {
|
||||
tags: 'alert-tag',
|
||||
options: 'alert-option',
|
||||
};
|
||||
|
||||
return items.map((item) => (
|
||||
<Badge color="light" key={`${item}`} data-testid={badgeId[type]}>
|
||||
{item}
|
||||
</Badge>
|
||||
));
|
||||
};
|
||||
|
||||
displayItems = (items, type) => {
|
||||
const { alertId } = this.props;
|
||||
const { displayAllItems } = this.state;
|
||||
|
||||
return items.length && items[0] !== '' ? (
|
||||
<div>
|
||||
{this.showItems(items.slice(0, this.visibleItems[type]), type)}
|
||||
{!displayAllItems[type] && items.length > this.visibleItems[type] && (
|
||||
<Button
|
||||
color="link"
|
||||
size="sm"
|
||||
id={`alert-${alertId}-${type}`}
|
||||
onClick={() =>
|
||||
this.setState((prevState) => ({
|
||||
displayAllItems: {
|
||||
...prevState.displayAllItems,
|
||||
[type]: !prevState.displayAllItems[type],
|
||||
},
|
||||
}))
|
||||
}
|
||||
>
|
||||
<span data-testid={`show-more-${type}`}>...</span>
|
||||
<UncontrolledTooltip
|
||||
placement="top"
|
||||
target={`alert-${alertId}-${type}`}
|
||||
>
|
||||
Show more {type}
|
||||
</UncontrolledTooltip>
|
||||
</Button>
|
||||
)}
|
||||
{displayAllItems[type] &&
|
||||
this.showItems(items.slice(this.visibleItems[type]), type)}
|
||||
</div>
|
||||
) : (
|
||||
<Badge color="light">No {type}</Badge>
|
||||
);
|
||||
};
|
||||
|
||||
render() {
|
||||
const { options, tags } = this.state;
|
||||
|
||||
return (
|
||||
<React.Fragment>
|
||||
{this.displayItems(tags, this.itemsType.tags)}
|
||||
{this.displayItems(options, this.itemsType.options)}
|
||||
</React.Fragment>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
AlertTableTagsOptions.propTypes = {
|
||||
items: PropTypes.shape({
|
||||
tags: PropTypes.array.isRequired,
|
||||
options: PropTypes.array.isRequired,
|
||||
}).isRequired,
|
||||
alertId: PropTypes.number.isRequired,
|
||||
};
|
|
@ -8,7 +8,7 @@ import { getHashBasedId, getSplitTestTitle } from '../perf-helpers/helpers';
|
|||
import { testDocumentationFrameworks } from '../perf-helpers/constants';
|
||||
import { hashFunction } from '../../helpers/utils';
|
||||
import { tableSort, getNextSort, sort, sortTables } from '../perf-helpers/sort';
|
||||
import SortButton from '../shared/SortButton';
|
||||
import TableColumnHeader from '../shared/TableColumnHeader';
|
||||
|
||||
import RetriggerButton from './RetriggerButton';
|
||||
import CompareTableRow from './CompareTableRow';
|
||||
|
@ -167,14 +167,14 @@ export default class CompareTable extends React.Component {
|
|||
<FontAwesomeIcon icon={faHashtag} />
|
||||
</Button>
|
||||
)}
|
||||
<SortButton
|
||||
<TableColumnHeader
|
||||
column={tableConfig.TestName}
|
||||
onChangeSort={this.onChangeSort}
|
||||
/>
|
||||
</div>
|
||||
</th>
|
||||
<th className="table-width-lg">
|
||||
<SortButton
|
||||
<TableColumnHeader
|
||||
column={tableConfig.Base}
|
||||
onChangeSort={this.onChangeSort}
|
||||
/>
|
||||
|
@ -182,25 +182,25 @@ export default class CompareTable extends React.Component {
|
|||
{/* empty for less than/greater than data */}
|
||||
<th className="table-width-sm" aria-label="Comparison" />
|
||||
<th className="table-width-lg">
|
||||
<SortButton
|
||||
<TableColumnHeader
|
||||
column={tableConfig.New}
|
||||
onChangeSort={this.onChangeSort}
|
||||
/>
|
||||
</th>
|
||||
<th className="table-width-lg">
|
||||
<SortButton
|
||||
<TableColumnHeader
|
||||
column={tableConfig.Delta}
|
||||
onChangeSort={this.onChangeSort}
|
||||
/>
|
||||
</th>
|
||||
<th className="table-width-lg">
|
||||
<SortButton
|
||||
<TableColumnHeader
|
||||
column={tableConfig.Magnitude}
|
||||
onChangeSort={this.onChangeSort}
|
||||
/>
|
||||
</th>
|
||||
<th className="table-width-lg">
|
||||
<SortButton
|
||||
<TableColumnHeader
|
||||
column={tableConfig.Confidence}
|
||||
onChangeSort={this.onChangeSort}
|
||||
/>
|
||||
|
|
|
@ -14,12 +14,16 @@ export const sortTypes = {
|
|||
sortByString: (value) => (a, b) => a[value].localeCompare(b[value]),
|
||||
sortByStrFirstElement: (value) => (a, b) =>
|
||||
a.series_signature[value][0].localeCompare(b.series_signature[value][0]),
|
||||
sortBySeriesSignatureValue: (value) => (a, b) =>
|
||||
a.series_signature[value].localeCompare(b.series_signature[value]),
|
||||
sortByValue: (value) => (a, b) => a[value] - b[value],
|
||||
},
|
||||
[tableSort.descending]: {
|
||||
sortByString: (value) => (a, b) => b[value].localeCompare(a[value]),
|
||||
sortByStrFirstElement: (value) => (a, b) =>
|
||||
b.series_signature[value][0].localeCompare(a.series_signature[value][0]),
|
||||
sortBySeriesSignatureValue: (value) => (a, b) =>
|
||||
b.series_signature[value].localeCompare(a.series_signature[value]),
|
||||
sortByValue: (value) => (a, b) => b[value] - a[value],
|
||||
},
|
||||
};
|
||||
|
@ -49,14 +53,18 @@ export const sort = (sortValue, sortType, data, table) => {
|
|||
} else {
|
||||
validData = data;
|
||||
}
|
||||
const { sortByString, sortByValue, sortByStrFirstElement } = sortTypes[
|
||||
sortType
|
||||
];
|
||||
const {
|
||||
sortByString,
|
||||
sortByValue,
|
||||
sortByStrFirstElement,
|
||||
sortBySeriesSignatureValue,
|
||||
} = sortTypes[sortType];
|
||||
|
||||
const getSortType = {
|
||||
title: sortByString,
|
||||
name: sortByString,
|
||||
tags: sortByStrFirstElement,
|
||||
machine_platform: sortBySeriesSignatureValue,
|
||||
};
|
||||
|
||||
let doSort = getSortType[sortValue];
|
||||
|
|
|
@ -27,17 +27,14 @@ export default class SortButton extends React.Component {
|
|||
const { column, onChangeSort } = this.props;
|
||||
const { name, currentSort } = column;
|
||||
return (
|
||||
<div className="d-flex align-items-end">
|
||||
<div>{name === 'Test name' ? '' : `${name}`}</div>
|
||||
<Badge
|
||||
className="mx-1 btn btn-darker-secondary"
|
||||
role="button"
|
||||
title={`Sorted in ${currentSort} order by ${name.toLowerCase()}`}
|
||||
onClick={() => onChangeSort(column)}
|
||||
>
|
||||
<FontAwesomeIcon icon={this.sortTypes[currentSort].icon} />
|
||||
</Badge>
|
||||
</div>
|
||||
<Badge
|
||||
className="mx-1 btn btn-darker-secondary"
|
||||
role="button"
|
||||
title={`Sorted in ${currentSort} order by ${name.toLowerCase()}`}
|
||||
onClick={() => onChangeSort(column)}
|
||||
>
|
||||
<FontAwesomeIcon icon={this.sortTypes[currentSort].icon} />
|
||||
</Badge>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,24 @@
|
|||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { Badge } from 'reactstrap';
|
||||
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
||||
import { faSort } from '@fortawesome/free-solid-svg-icons';
|
||||
|
||||
export default class SortButtonDisabled extends React.PureComponent {
|
||||
render() {
|
||||
const {
|
||||
column: { name },
|
||||
} = this.props;
|
||||
return (
|
||||
<Badge
|
||||
className="mx-1 disabled-button"
|
||||
aria-disabled="true"
|
||||
title={`Sorted by ${name.toLowerCase()} disabled`}
|
||||
>
|
||||
<FontAwesomeIcon icon={faSort} />
|
||||
</Badge>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
SortButtonDisabled.propTypes = { column: PropTypes.shape({}).isRequired };
|
|
@ -0,0 +1,23 @@
|
|||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
import SortButton from './SortButton';
|
||||
|
||||
// eslint-disable-next-line react/prefer-stateless-function
|
||||
export default class TableColumnHeader extends React.Component {
|
||||
render() {
|
||||
const { column, onChangeSort } = this.props;
|
||||
const { name } = column;
|
||||
return (
|
||||
<div className="d-flex align-items-end">
|
||||
<div>{name === 'Test name' ? '' : `${name}`}</div>
|
||||
<SortButton column={column} onChangeSort={onChangeSort} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
TableColumnHeader.propTypes = {
|
||||
column: PropTypes.shape({}).isRequired,
|
||||
onChangeSort: PropTypes.func.isRequired,
|
||||
};
|
Загрузка…
Ссылка в новой задаче