зеркало из https://github.com/mozilla/treeherder.git
332 строки
11 KiB
JavaScript
332 строки
11 KiB
JavaScript
import React from 'react';
|
|
import PropTypes from 'prop-types';
|
|
import { Container, Spinner, Navbar, Nav } from 'reactstrap';
|
|
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
|
import camelCase from 'lodash/camelCase';
|
|
import { Helmet } from 'react-helmet';
|
|
import { Tab, TabList, TabPanel, Tabs } from 'react-tabs';
|
|
|
|
import faviconBroken from '../img/push-health-broken.png';
|
|
import faviconOk from '../img/push-health-ok.png';
|
|
import ErrorMessages from '../shared/ErrorMessages';
|
|
import PushModel from '../models/push';
|
|
import RepositoryModel from '../models/repository';
|
|
import StatusProgress from '../shared/StatusProgress';
|
|
import { scrollToLine } from '../helpers/utils';
|
|
import { resultColorMap, getIcon } from '../helpers/display';
|
|
import {
|
|
createQueryParams,
|
|
parseQueryParams,
|
|
updateQueryParams,
|
|
} from '../helpers/url';
|
|
import InputFilter from '../shared/InputFilter';
|
|
|
|
import TestMetric from './TestMetric';
|
|
import JobListMetric from './JobListMetric';
|
|
import CommitHistory from './CommitHistory';
|
|
|
|
export default class Health extends React.PureComponent {
|
|
constructor(props) {
|
|
super(props);
|
|
|
|
const params = new URLSearchParams(props.location.search);
|
|
|
|
this.state = {
|
|
revision: params.get('revision'),
|
|
repo: params.get('repo'),
|
|
currentRepo: null,
|
|
metrics: {},
|
|
jobs: null,
|
|
result: null,
|
|
failureMessage: null,
|
|
defaultTabIndex: 0,
|
|
testGroup: params.get('testGroup') || '',
|
|
selectedTest: params.get('selectedTest') || '',
|
|
selectedTaskId: params.get('selectedTaskId') || '',
|
|
selectedJobName: params.get('selectedJobName') || '',
|
|
searchStr: params.get('searchStr') || '',
|
|
regressionsOrderBy: params.get('regressionsOrderBy') || 'count',
|
|
regressionsGroupBy: params.get('regressionsGroupBy') || 'path',
|
|
knownIssuesOrderBy: params.get('knownIssuesOrderBy') || 'count',
|
|
knownIssuesGroupBy: params.get('knownIssuesGroupBy') || 'path',
|
|
};
|
|
}
|
|
|
|
async componentDidMount() {
|
|
const { repo, testGroup } = this.state;
|
|
const { location } = this.props;
|
|
const {
|
|
metrics: { linting, builds, tests },
|
|
} = await this.updatePushHealth();
|
|
|
|
const params = parseQueryParams(location.search);
|
|
let defaultTabIndex;
|
|
|
|
if (params.tab !== undefined) {
|
|
defaultTabIndex = ['linting', 'builds', 'tests'].findIndex(
|
|
(metric) => metric === params.tab,
|
|
);
|
|
} else if (testGroup) {
|
|
defaultTabIndex = 2;
|
|
} else {
|
|
defaultTabIndex = [linting, builds, tests].findIndex(
|
|
(metric) => metric.result === 'fail',
|
|
);
|
|
}
|
|
|
|
const repos = await RepositoryModel.getList();
|
|
const currentRepo = repos.find((repoObj) => repoObj.name === repo);
|
|
|
|
this.setState({ defaultTabIndex, currentRepo });
|
|
|
|
// Update the tests every two minutes.
|
|
this.testTimerId = setInterval(() => this.updatePushHealth(), 120000);
|
|
this.notificationsId = setInterval(() => {
|
|
this.props.clearNotification();
|
|
}, 4000);
|
|
}
|
|
|
|
componentWillUnmount() {
|
|
clearInterval(this.testTimerId);
|
|
}
|
|
|
|
updateParamsAndState = (stateObj) => {
|
|
const { location, history } = this.props;
|
|
const newParams = {
|
|
...parseQueryParams(location.search),
|
|
...stateObj,
|
|
};
|
|
const queryString = createQueryParams(newParams);
|
|
|
|
updateQueryParams(queryString, history, location);
|
|
this.setState(stateObj);
|
|
};
|
|
|
|
updatePushHealth = async () => {
|
|
const { repo, revision, status } = this.state;
|
|
|
|
if (status) {
|
|
const { running, pending, completed } = status;
|
|
|
|
if (completed > 0 && pending === 0 && running === 0) {
|
|
clearInterval(this.testTimerId);
|
|
return;
|
|
}
|
|
}
|
|
|
|
const { data, failureStatus } = await PushModel.getHealth(repo, revision);
|
|
const newState = !failureStatus ? data : { failureMessage: data };
|
|
|
|
this.setState(newState);
|
|
return newState;
|
|
};
|
|
|
|
setExpanded = (metricName, expanded) => {
|
|
const root = camelCase(metricName);
|
|
const key = `${root}Expanded`;
|
|
const { [key]: oldExpanded } = this.state;
|
|
|
|
if (oldExpanded !== expanded) {
|
|
this.setState({
|
|
[key]: expanded,
|
|
});
|
|
} else if (expanded) {
|
|
scrollToLine(`#${root}Metric`, 0, 0, {
|
|
behavior: 'smooth',
|
|
block: 'center',
|
|
});
|
|
}
|
|
};
|
|
|
|
filter = (searchStr) => {
|
|
const { location, history } = this.props;
|
|
const newParams = { ...parseQueryParams(location.search), searchStr };
|
|
|
|
if (!searchStr.length) {
|
|
delete newParams.searchStr;
|
|
}
|
|
|
|
const queryString = createQueryParams(newParams);
|
|
|
|
updateQueryParams(queryString, history, location);
|
|
|
|
this.setState({ searchStr });
|
|
};
|
|
|
|
render() {
|
|
const {
|
|
metrics,
|
|
result,
|
|
repo,
|
|
revision,
|
|
jobs,
|
|
failureMessage,
|
|
status,
|
|
searchStr,
|
|
currentRepo,
|
|
testGroup,
|
|
selectedTest,
|
|
defaultTabIndex,
|
|
selectedTaskId,
|
|
selectedJobName,
|
|
regressionsOrderBy,
|
|
regressionsGroupBy,
|
|
knownIssuesOrderBy,
|
|
knownIssuesGroupBy,
|
|
} = this.state;
|
|
const { tests, commitHistory, linting, builds } = metrics;
|
|
|
|
const { notify } = this.props;
|
|
return (
|
|
<React.Fragment>
|
|
<Navbar color="light" light expand="sm" className="w-100">
|
|
{!!tests && (
|
|
<Nav className="mb-2 pt-2 pl-3 justify-content-between w-100">
|
|
<span />
|
|
<span className="mr-2 d-flex">
|
|
<InputFilter
|
|
updateFilterText={this.filter}
|
|
placeholder="filter path or platform"
|
|
/>
|
|
</span>
|
|
</Nav>
|
|
)}
|
|
</Navbar>
|
|
<Helmet>
|
|
<link
|
|
rel="shortcut icon"
|
|
href={result === 'fail' ? faviconBroken : faviconOk}
|
|
/>
|
|
<title>{`[${
|
|
(status && status.testfailed) || 0
|
|
} failures] Push Health`}</title>
|
|
</Helmet>
|
|
<Container fluid className="mt-2 mb-5 max-width-default">
|
|
{!!tests && !!currentRepo && (
|
|
<React.Fragment>
|
|
<div className="d-flex my-5">
|
|
<StatusProgress
|
|
counts={status}
|
|
customStyle="progress-relative"
|
|
/>
|
|
<div className="mt-4 ml-2">
|
|
{commitHistory.details && (
|
|
<CommitHistory
|
|
history={commitHistory.details}
|
|
revision={revision}
|
|
currentRepo={currentRepo}
|
|
compareWithParent={this.compareWithParent}
|
|
/>
|
|
)}
|
|
</div>
|
|
</div>
|
|
<div className="mb-3" />
|
|
<Tabs
|
|
className="w-100 h-100 mr-5 mt-2"
|
|
selectedTabClassName="selected-detail-tab"
|
|
defaultIndex={defaultTabIndex}
|
|
>
|
|
<TabList className="font-weight-500 text-secondary d-flex justify-content-end border-bottom font-size-18">
|
|
{linting.result !== 'none' && (
|
|
<Tab className="pb-2 list-inline-item ml-4 pointable">
|
|
<span className="text-success">
|
|
<FontAwesomeIcon
|
|
icon={getIcon(linting.result)}
|
|
className={`mr-1 text-${
|
|
resultColorMap[linting.result]
|
|
}`}
|
|
/>
|
|
</span>
|
|
Linting
|
|
</Tab>
|
|
)}
|
|
{builds.result !== 'none' && (
|
|
<Tab className="list-inline-item ml-4 pointable">
|
|
<FontAwesomeIcon
|
|
icon={getIcon(builds.result)}
|
|
className={`mr-1 text-${resultColorMap[builds.result]}`}
|
|
/>
|
|
Builds
|
|
</Tab>
|
|
)}
|
|
{tests.result !== 'none' && (
|
|
<Tab className="list-inline-item ml-4 pointable">
|
|
<FontAwesomeIcon
|
|
fill={resultColorMap[tests.result]}
|
|
icon={getIcon(tests.result)}
|
|
className={`mr-1 text-${resultColorMap[tests.result]}`}
|
|
/>
|
|
Tests
|
|
</Tab>
|
|
)}
|
|
</TabList>
|
|
<div>
|
|
<TabPanel>
|
|
<JobListMetric
|
|
data={linting}
|
|
currentRepo={currentRepo}
|
|
revision={revision}
|
|
setExpanded={this.setExpanded}
|
|
updateParamsAndState={this.updateParamsAndState}
|
|
notify={notify}
|
|
selectedTaskId={selectedTaskId}
|
|
selectedJobName={selectedJobName}
|
|
/>
|
|
</TabPanel>
|
|
<TabPanel>
|
|
<JobListMetric
|
|
data={builds}
|
|
currentRepo={currentRepo}
|
|
revision={revision}
|
|
setExpanded={this.setExpanded}
|
|
updateParamsAndState={this.updateParamsAndState}
|
|
notify={notify}
|
|
selectedTaskId={selectedTaskId}
|
|
selectedJobName={selectedJobName}
|
|
/>
|
|
</TabPanel>
|
|
<TabPanel>
|
|
<TestMetric
|
|
jobs={jobs}
|
|
data={tests}
|
|
repo={repo}
|
|
currentRepo={currentRepo}
|
|
revision={revision}
|
|
notify={notify}
|
|
setExpanded={this.setExpanded}
|
|
searchStr={searchStr}
|
|
testGroup={testGroup}
|
|
selectedTest={selectedTest}
|
|
regressionsOrderBy={regressionsOrderBy}
|
|
regressionsGroupBy={regressionsGroupBy}
|
|
knownIssuesOrderBy={knownIssuesOrderBy}
|
|
knownIssuesGroupBy={knownIssuesGroupBy}
|
|
selectedTaskId={selectedTaskId}
|
|
selectedJobName={selectedJobName}
|
|
updateParamsAndState={this.updateParamsAndState}
|
|
investigateTest={this.investigateTest}
|
|
unInvestigateTest={this.unInvestigateTest}
|
|
updatePushHealth={this.updatePushHealth}
|
|
/>
|
|
</TabPanel>
|
|
</div>
|
|
</Tabs>
|
|
</React.Fragment>
|
|
)}
|
|
{failureMessage && <ErrorMessages failureMessage={failureMessage} />}
|
|
{!failureMessage && !tests && (
|
|
<h4>
|
|
<Spinner />
|
|
<span className="ml-2 pb-1">Gathering health data...</span>
|
|
</h4>
|
|
)}
|
|
</Container>
|
|
</React.Fragment>
|
|
);
|
|
}
|
|
}
|
|
|
|
Health.propTypes = {
|
|
location: PropTypes.shape({}).isRequired,
|
|
};
|