Bug 1602833 - Group failures by platform and config (#5831)

This commit is contained in:
Cameron Dawson 2020-01-17 09:02:42 -08:00 коммит произвёл GitHub
Родитель 9c075730ea
Коммит 3ebf7b7772
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: 4AEE18F83AFDEB23
4 изменённых файлов: 307 добавлений и 114 удалений

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

@ -41,6 +41,7 @@ describe('TestFailure', () => {
user={{ email: 'foo' }}
revision="abc"
currentRepo={{ name: repoName }}
groupedBy="platform"
notify={() => {}}
/>
);

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

@ -16,11 +16,15 @@ import {
DropdownMenu,
DropdownToggle,
DropdownItem,
Navbar,
Nav,
NavItem,
UncontrolledButtonDropdown,
} from 'reactstrap';
import JobModel from '../models/job';
import TestFailure from './TestFailure';
import GroupedTests from './GroupedTests';
class ClassificationGroup extends React.PureComponent {
constructor(props) {
@ -29,6 +33,8 @@ class ClassificationGroup extends React.PureComponent {
this.state = {
detailsShowing: props.expanded,
retriggerDropdownOpen: false,
groupedBy: 'path',
orderedBy: 'count',
};
}
@ -57,8 +63,21 @@ class ClassificationGroup extends React.PureComponent {
JobModel.retrigger(uniqueJobs, currentRepo, notify, times);
};
setGroupedBy = groupedBy => {
this.setState({ groupedBy });
};
setOrderedBy = orderedBy => {
this.setState({ orderedBy });
};
render() {
const { detailsShowing, retriggerDropdownOpen } = this.state;
const {
detailsShowing,
retriggerDropdownOpen,
groupedBy,
orderedBy,
} = this.state;
const {
group,
name,
@ -97,50 +116,113 @@ class ClassificationGroup extends React.PureComponent {
</h4>
<Collapse isOpen={detailsShowing} className="w-100">
{hasRetriggerAll && Object.keys(group).length > 0 && (
<ButtonGroup>
<Button
title="Retrigger all 'Need Investigation' jobs once"
onClick={() => this.retriggerAll(1)}
>
<FontAwesomeIcon
icon={faRedo}
title="Retrigger"
className="mr-2"
/>
Retrigger all
</Button>
<ButtonDropdown
isOpen={retriggerDropdownOpen}
toggle={this.toggleRetrigger}
>
<DropdownToggle caret />
<DropdownMenu>
{[5, 10, 15].map(times => (
<DropdownItem
key={times}
title={`Retrigger all 'Need Investigation' jobs ${times} times`}
onClick={() => this.retriggerAll(times)}
<Navbar className="mb-4">
<Nav>
<NavItem>
<ButtonGroup size="sm">
<Button
title="Retrigger all 'Need Investigation' jobs once"
onClick={() => this.retriggerAll(1)}
size="sm"
>
Retrigger all {times} times
</DropdownItem>
))}
</DropdownMenu>
</ButtonDropdown>
</ButtonGroup>
<FontAwesomeIcon
icon={faRedo}
title="Retrigger"
className="mr-2"
/>
Retrigger all
</Button>
<ButtonDropdown
isOpen={retriggerDropdownOpen}
toggle={this.toggleRetrigger}
size="sm"
>
<DropdownToggle caret />
<DropdownMenu>
{[5, 10, 15].map(times => (
<DropdownItem
key={times}
title={`Retrigger all 'Need Investigation' jobs ${times} times`}
onClick={() => this.retriggerAll(times)}
tag="a"
>
Retrigger all {times} times
</DropdownItem>
))}
</DropdownMenu>
</ButtonDropdown>
</ButtonGroup>
</NavItem>
<NavItem>
<UncontrolledButtonDropdown size="sm" className="ml-1">
<DropdownToggle
className="btn-sm ml-1 text-capitalize"
id="groupTestsDropdown"
caret
>
Group By: {groupedBy}
</DropdownToggle>
<DropdownMenu toggler="groupTestsDropdown">
<DropdownItem
tag="a"
onClick={() => this.setGroupedBy('none')}
>
None
</DropdownItem>
<DropdownItem
tag="a"
onClick={() => this.setGroupedBy('path')}
>
Path
</DropdownItem>
<DropdownItem
tag="a"
onClick={() => this.setGroupedBy('platform')}
>
Platform
</DropdownItem>
</DropdownMenu>
</UncontrolledButtonDropdown>
</NavItem>
<NavItem>
<UncontrolledButtonDropdown size="sm" className="ml-1">
<DropdownToggle
className="btn-sm ml-1 text-capitalize"
id="groupTestsDropdown"
caret
>
Order By: {orderedBy}
</DropdownToggle>
<DropdownMenu toggler="groupTestsDropdown">
<DropdownItem
tag="a"
onClick={() => this.setOrderedBy('count')}
>
Count
</DropdownItem>
<DropdownItem
tag="a"
onClick={() => this.setOrderedBy('text')}
>
Text
</DropdownItem>
</DropdownMenu>
</UncontrolledButtonDropdown>
</NavItem>
</Nav>
</Navbar>
)}
<div>
{group &&
group.map(failure => (
<TestFailure
key={failure.key}
failure={failure}
repo={repo}
currentRepo={currentRepo}
revision={revision}
user={user}
notify={notify}
/>
))}
<GroupedTests
group={group}
repo={repo}
revision={revision}
user={user}
groupedBy={groupedBy}
orderedBy={orderedBy}
currentRepo={currentRepo}
notify={notify}
/>
</div>
</Collapse>
</Row>

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

@ -0,0 +1,103 @@
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import { Button, UncontrolledCollapse } from 'reactstrap';
import groupBy from 'lodash/groupBy';
import orderBy from 'lodash/orderBy';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { faCaretDown } from '@fortawesome/free-solid-svg-icons';
import TestFailure from './TestFailure';
class GroupedTests extends Component {
getGroupedTests = tests => {
const { groupedBy } = this.props;
const grouped = groupBy(tests, test => {
switch (groupedBy) {
case 'none':
return 'none';
case 'path':
return test.testName;
case 'platform':
return `${test.platform} ${test.config}`;
}
});
return grouped;
};
render() {
const {
group,
repo,
revision,
user,
notify,
currentRepo,
orderedBy,
groupedBy,
} = this.props;
const groupedTests = this.getGroupedTests(group);
const groupedArray = Object.entries(groupedTests).map(([key, tests]) => ({
key,
id: key.replace(/[^a-z0-9-]+/gi, ''), // make this a valid selector
tests,
}));
const sortedGroups =
orderedBy === 'count'
? orderBy(groupedArray, ['tests.length'], ['desc'])
: orderBy(groupedArray, ['key'], ['asc']);
return (
<div>
{groupedTests &&
sortedGroups.map(group => (
<div key={group.id}>
<Button
id={`${group.id}-group`}
color="secondary"
outline
className="p-3 bg-light text-center text-monospace border-bottom-0 border-right-0 border-left-0 border-secondary w-100"
title="Click to expand for test detail"
>
{group.key === 'none' ? 'All' : group.key} -
<span className="ml-2 font-italic">
{group.tests.length} test{group.tests.length > 1 && 's'}
</span>
<FontAwesomeIcon icon={faCaretDown} className="ml-1" />
</Button>
<UncontrolledCollapse toggler={`${group.id}-group`}>
{group.tests.map(failure => (
<TestFailure
key={failure.key}
failure={failure}
repo={repo}
currentRepo={currentRepo}
revision={revision}
user={user}
notify={notify}
groupedBy={groupedBy}
className="ml-3"
/>
))}
</UncontrolledCollapse>
</div>
))}
</div>
);
}
}
GroupedTests.propTypes = {
group: PropTypes.array.isRequired,
groupedBy: PropTypes.string.isRequired,
orderedBy: PropTypes.string.isRequired,
revision: PropTypes.string.isRequired,
repo: PropTypes.string.isRequired,
currentRepo: PropTypes.object.isRequired,
user: PropTypes.object.isRequired,
notify: PropTypes.func.isRequired,
};
export default GroupedTests;

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

@ -1,6 +1,13 @@
import React from 'react';
import PropTypes from 'prop-types';
import { Badge, Button, Row, Col, UncontrolledTooltip } from 'reactstrap';
import {
Badge,
Button,
Row,
Col,
UncontrolledTooltip,
UncontrolledCollapse,
} from 'reactstrap';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { faRedo } from '@fortawesome/free-solid-svg-icons';
@ -40,7 +47,7 @@ class TestFailure extends React.PureComponent {
};
render() {
const { failure, repo, revision } = this.props;
const { failure, repo, revision, groupedBy } = this.props;
const {
testName,
action,
@ -62,42 +69,9 @@ class TestFailure extends React.PureComponent {
const { detailsShowing } = this.state;
return (
<Col className="mt-2 mb-3 ml-2" key={key}>
<Row className="border-top border-secondary justify-content-between">
<Row className="ml-1 w-100">
<span
color="secondary"
className="font-weight-bold text-uppercase mr-1"
>
{action} :
</span>
{testName}
{tier > 1 && (
<span className="ml-1 small text-muted">[tier-{tier}]</span>
)}
<span id={key} className="ml-auto mr-3">
<strong>Pass/Fail Ratio:</strong>{' '}
{Math.round(passFailRatio * 100)}%
</span>
<UncontrolledTooltip target={key} placement="left">
Greater than 50% (and/or classification history) will make this an
intermittent
</UncontrolledTooltip>
</Row>
{!!confidence && (
<span title="Best guess at a classification" className="ml-auto">
{classificationMap[suggestedClassification]}
<Badge
color="secondary"
className="ml-2 mr-3"
title="Confidence in this classification guess"
>
{confidence}
</Badge>
</span>
)}
</Row>
<div className="small">
<Row className="border-top m-3" key={key}>
<Col>
<Row>{groupedBy !== 'path' && <span>{testName}</span>}</Row>
<Button
onClick={() => this.retriggerJob(failJobs[0])}
outline
@ -107,8 +81,16 @@ class TestFailure extends React.PureComponent {
>
<FontAwesomeIcon icon={faRedo} title="Retrigger" />
</Button>
<span>
{platform} {config}:
{groupedBy !== 'platform' && (
<span>
{platform} {config}:
</span>
)}
{tier > 1 && (
<span className="ml-1 small text-muted">[tier-{tier}]</span>
)}
<span color="secondary" className="text-uppercase ml-1 mr-1">
{action} :
</span>
{failJobs.map(failJob => (
<Job
@ -158,39 +140,63 @@ class TestFailure extends React.PureComponent {
key={inProgressJob.id}
/>
))}
</div>
{!!logLines.length && (
<div>
<Button
className="border-0 text-info bg-transparent p-1"
onClick={this.toggleDetails}
>
{detailsShowing ? 'less...' : 'more...'}
</Button>
</div>
)}
{logLines.map(logLine => (
<Row
className="small text-monospace mt-2 ml-3"
key={logLine.line_number}
>
{detailsShowing ? (
<div className="pre-wrap text-break">
{logLine.subtest}
<Row className="ml-3">
<div>{logLine.message}</div>
<div>{logLine.signature}</div>
<div>{logLine.stackwalk_stdout}</div>
</Row>
</div>
) : (
<div className="pre-wrap text-break">
{!!logLine.subtest && logLine.subtest.substr(0, 200)}
</div>
{!!logLines.length && (
<span>
<Button
id={key}
className="border-0 text-info btn-sm p-1"
outline
onClick={this.toggleDetails}
>
{detailsShowing ? 'less...' : 'more...'}
</Button>
<UncontrolledCollapse toggler={key}>
{logLines.map(logLine => (
<Row
className="small text-monospace mt-2 ml-3"
key={logLine.line_number}
>
<div className="pre-wrap text-break">
{logLine.subtest}
<Row className="ml-3">
<div>{logLine.message}</div>
<div>{logLine.signature}</div>
<div>{logLine.stackwalk_stdout}</div>
</Row>
</div>
</Row>
))}
</UncontrolledCollapse>
</span>
)}
</Col>
<span className="ml-1">
<Row className="justify-content-between mr-2">
{!!confidence && (
<span title="Best guess at a classification" className="ml-auto">
{classificationMap[suggestedClassification]}
<Badge
color="secondary"
className="ml-2 mr-3"
title="Confidence in this classification guess"
>
{confidence}
</Badge>
</span>
)}
</Row>
))}
</Col>
<Row>
<span id={`${key}-ratio`} className="mr-3">
<strong>Pass/Fail Ratio:</strong>{' '}
{Math.round(passFailRatio * 100)}%
</span>
<UncontrolledTooltip target={`${key}-ratio`} placement="left">
Greater than 50% (and/or classification history) will make this an
intermittent
</UncontrolledTooltip>
</Row>
</span>
</Row>
);
}
}
@ -214,6 +220,7 @@ TestFailure.propTypes = {
user: PropTypes.object.isRequired,
revision: PropTypes.string.isRequired,
notify: PropTypes.func.isRequired,
groupedBy: PropTypes.string.isRequired,
};
export default TestFailure;