зеркало из https://github.com/mozilla/treeherder.git
when viewing specific failure in IFV details, update graph (#7519)
This commit is contained in:
Родитель
4083943628
Коммит
94718b8448
|
@ -74,7 +74,7 @@ class IntermittentFailuresApp extends React.Component {
|
|||
render={(props) => <BugDetailsView {...props} />}
|
||||
/>
|
||||
<Route
|
||||
path={`${path}/bugdetails?startday=:startday&endday=:endday&tree=:tree&bug=bug`}
|
||||
path={`${path}/bugdetails?startday=:startday&endday=:endday&tree=:tree&failurehash=:failurehash&bug=bug`}
|
||||
render={(props) => <BugDetailsView {...props} />}
|
||||
/>
|
||||
<Redirect from={`${path}/`} to={`${path}/main`} />
|
||||
|
|
|
@ -29,7 +29,9 @@ const BugDetailsView = (props) => {
|
|||
initialParamsSet,
|
||||
startday,
|
||||
endday,
|
||||
failurehash,
|
||||
updateState,
|
||||
updateHash,
|
||||
bug,
|
||||
summary,
|
||||
errorMessages,
|
||||
|
@ -37,19 +39,24 @@ const BugDetailsView = (props) => {
|
|||
tableFailureStatus,
|
||||
graphFailureStatus,
|
||||
uniqueLines,
|
||||
uniqueFrequency,
|
||||
} = props;
|
||||
|
||||
const customFilter = ({ filter, onChange }) => {
|
||||
const customFilter = ({ filter }) => {
|
||||
if (!tableData || !uniqueLines) return;
|
||||
return (
|
||||
<select
|
||||
onChange={(event) => onChange(event.target.value)}
|
||||
onChange={(event) => updateHash(event.target.value)}
|
||||
style={{ width: '100%' }}
|
||||
value={filter ? filter.value : 'all'}
|
||||
value={filter ? filter.value : failurehash}
|
||||
>
|
||||
<option value="all">All</option>
|
||||
{uniqueLines.map((vals) => {
|
||||
return <option value={vals[1]}>{vals[0]}</option>;
|
||||
return (
|
||||
<option key={vals[1]} value={vals[1]}>
|
||||
{vals[0]}
|
||||
</option>
|
||||
);
|
||||
})}
|
||||
</select>
|
||||
);
|
||||
|
@ -127,20 +134,6 @@ const BugDetailsView = (props) => {
|
|||
{
|
||||
Header: 'Log',
|
||||
accessor: 'job_id',
|
||||
filterMethod: (filter, row) => {
|
||||
if (filter.value === 'all') {
|
||||
return true;
|
||||
}
|
||||
const trimmed = lineTrimmer(row._original.lines);
|
||||
let filterValue = '';
|
||||
const hashIndex = 1;
|
||||
uniqueLines.forEach((uniqueLine) => {
|
||||
if (trimmed === uniqueLine[0]) {
|
||||
filterValue = uniqueLine[hashIndex];
|
||||
}
|
||||
});
|
||||
return filterValue === filter.value;
|
||||
},
|
||||
Filter: ({ filter, onChange }) => customFilter({ filter, onChange }),
|
||||
Cell: (_props) => {
|
||||
const { value, original } = _props;
|
||||
|
@ -188,17 +181,45 @@ const BugDetailsView = (props) => {
|
|||
},
|
||||
];
|
||||
|
||||
let gOneData = {};
|
||||
let graphOneData = null;
|
||||
let graphTwoData = null;
|
||||
let _tableData = null;
|
||||
|
||||
if (graphData.length > 0) {
|
||||
({ graphOneData, graphTwoData } = calculateMetrics(graphData));
|
||||
if (uniqueFrequency) {
|
||||
gOneData = uniqueFrequency;
|
||||
}
|
||||
gOneData.all = graphOneData;
|
||||
gOneData.all[0].count = tableData.length;
|
||||
|
||||
_tableData = tableData;
|
||||
// here we Filter() the tableData.
|
||||
// Since we use urlParams for failurehash, this synchronizes it.
|
||||
if (failurehash !== 'all') {
|
||||
const tData = [];
|
||||
tableData.forEach((row) => {
|
||||
const trimmed = lineTrimmer(row.lines);
|
||||
let filterValue = '';
|
||||
const hashIndex = 1;
|
||||
uniqueLines.forEach((uniqueLine) => {
|
||||
if (trimmed === uniqueLine[0]) {
|
||||
filterValue = uniqueLine[hashIndex];
|
||||
}
|
||||
});
|
||||
if (filterValue === failurehash) {
|
||||
tData.push(row);
|
||||
}
|
||||
});
|
||||
_tableData = tData;
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<Layout
|
||||
{...props}
|
||||
graphOneData={graphOneData}
|
||||
graphOneData={gOneData}
|
||||
graphTwoData={graphTwoData}
|
||||
header={
|
||||
<React.Fragment>
|
||||
|
@ -261,7 +282,10 @@ const BugDetailsView = (props) => {
|
|||
<Row>
|
||||
<Col xs="12" className="mx-auto">
|
||||
<p className="text-secondary">
|
||||
{tableData.length} total failures
|
||||
{failurehash in gOneData
|
||||
? gOneData[failurehash][0].count
|
||||
: 0}{' '}
|
||||
total failures
|
||||
</p>
|
||||
</Col>
|
||||
</Row>
|
||||
|
@ -272,9 +296,10 @@ const BugDetailsView = (props) => {
|
|||
}
|
||||
table={
|
||||
bug &&
|
||||
initialParamsSet && (
|
||||
initialParamsSet &&
|
||||
_tableData && (
|
||||
<ReactTable
|
||||
data={tableData}
|
||||
data={_tableData}
|
||||
filterable
|
||||
showPageSizeOptions
|
||||
columns={columns}
|
||||
|
@ -295,18 +320,21 @@ BugDetailsView.propTypes = {
|
|||
tree: PropTypes.string.isRequired,
|
||||
updateAppState: PropTypes.func,
|
||||
updateState: PropTypes.func.isRequired,
|
||||
updateHash: PropTypes.func.isRequired,
|
||||
startday: PropTypes.string.isRequired,
|
||||
failurehash: PropTypes.string.isRequired,
|
||||
endday: PropTypes.string.isRequired,
|
||||
tableData: PropTypes.arrayOf(PropTypes.shape({})),
|
||||
graphData: PropTypes.arrayOf(PropTypes.shape({})),
|
||||
initialParamsSet: PropTypes.bool.isRequired,
|
||||
bug: PropTypes.number.isRequired,
|
||||
bug: PropTypes.string.isRequired,
|
||||
summary: PropTypes.string.isRequired,
|
||||
errorMessages: PropTypes.arrayOf(PropTypes.string),
|
||||
lastLocation: PropTypes.shape({}).isRequired,
|
||||
tableFailureStatus: PropTypes.string,
|
||||
graphFailureStatus: PropTypes.string,
|
||||
uniqueLines: PropTypes.arrayOf(PropTypes.array),
|
||||
uniqueFrequency: PropTypes.shape({}),
|
||||
};
|
||||
|
||||
BugDetailsView.defaultProps = {
|
||||
|
@ -317,6 +345,7 @@ BugDetailsView.defaultProps = {
|
|||
graphFailureStatus: null,
|
||||
updateAppState: null,
|
||||
uniqueLines: [],
|
||||
uniqueFrequency: {},
|
||||
};
|
||||
|
||||
const defaultState = {
|
||||
|
|
|
@ -26,7 +26,8 @@ const Graph = ({ graphData, title, legendData }) => (
|
|||
data={legendData}
|
||||
/>
|
||||
)}
|
||||
{graphData.length > 0 &&
|
||||
{graphData &&
|
||||
graphData.length > 0 &&
|
||||
graphData.map((item) => (
|
||||
<VictoryLine
|
||||
key={item}
|
||||
|
|
|
@ -25,7 +25,7 @@ export default class GraphsContainer extends React.Component {
|
|||
};
|
||||
|
||||
render() {
|
||||
const { graphOneData, graphTwoData, children } = this.props;
|
||||
const { graphOneData, graphTwoData, failurehash, children } = this.props;
|
||||
const { showGraphTwo, showAlternateView } = this.state;
|
||||
|
||||
return (
|
||||
|
@ -33,13 +33,24 @@ export default class GraphsContainer extends React.Component {
|
|||
<Row className="pt-5">
|
||||
{showAlternateView ? (
|
||||
<GraphAlternateView
|
||||
graphData={graphOneData}
|
||||
graphData={graphOneData[failurehash]}
|
||||
className="failure-per-count"
|
||||
colNum={1}
|
||||
title="Failure Count Per Push"
|
||||
title={
|
||||
failurehash === 'all'
|
||||
? 'Failure Count Per Push'
|
||||
: 'Failure Count Per Day'
|
||||
}
|
||||
/>
|
||||
) : (
|
||||
<Graph graphData={graphOneData} title="Failure Count per Push" />
|
||||
<Graph
|
||||
graphData={graphOneData[failurehash]}
|
||||
title={
|
||||
failurehash === 'all'
|
||||
? 'Failure Count Per Push'
|
||||
: 'Failure Count Per Day'
|
||||
}
|
||||
/>
|
||||
)}
|
||||
</Row>
|
||||
<Row>
|
||||
|
@ -90,21 +101,19 @@ export default class GraphsContainer extends React.Component {
|
|||
}
|
||||
|
||||
GraphsContainer.propTypes = {
|
||||
graphOneData: PropTypes.arrayOf(
|
||||
PropTypes.shape({
|
||||
data: PropTypes.arrayOf(PropTypes.shape({})),
|
||||
color: PropTypes.string,
|
||||
}),
|
||||
),
|
||||
graphOneData: PropTypes.shape({
|
||||
hash: PropTypes.arrayOf(
|
||||
PropTypes.shape({
|
||||
data: PropTypes.arrayOf(PropTypes.shape({})),
|
||||
color: PropTypes.string,
|
||||
}),
|
||||
),
|
||||
}),
|
||||
graphTwoData: PropTypes.arrayOf(
|
||||
PropTypes.shape({
|
||||
data: PropTypes.arrayOf(PropTypes.shape({})),
|
||||
color: PropTypes.string,
|
||||
}),
|
||||
PropTypes.shape({
|
||||
data: PropTypes.arrayOf(PropTypes.shape({})),
|
||||
color: PropTypes.string,
|
||||
}),
|
||||
),
|
||||
children: PropTypes.element.isRequired,
|
||||
};
|
||||
|
|
|
@ -21,8 +21,10 @@ const Layout = (props) => {
|
|||
tableFailureStatus,
|
||||
graphFailureStatus,
|
||||
updateState,
|
||||
updateHash,
|
||||
graphOneData,
|
||||
graphTwoData,
|
||||
failurehash,
|
||||
table,
|
||||
datePicker,
|
||||
header,
|
||||
|
@ -36,7 +38,12 @@ const Layout = (props) => {
|
|||
}
|
||||
return (
|
||||
<Container fluid className="my-5 max-width-default">
|
||||
<Navigation updateState={updateState} tree={tree} {...props} />
|
||||
<Navigation
|
||||
updateState={updateState}
|
||||
updateHash={updateHash}
|
||||
tree={tree}
|
||||
{...props}
|
||||
/>
|
||||
{(isFetchingGraphs || isFetchingTable) &&
|
||||
!(
|
||||
tableFailureStatus ||
|
||||
|
@ -60,6 +67,7 @@ const Layout = (props) => {
|
|||
<GraphsContainer
|
||||
graphOneData={graphOneData}
|
||||
graphTwoData={graphTwoData}
|
||||
failurehash={failurehash}
|
||||
>
|
||||
{datePicker}
|
||||
</GraphsContainer>
|
||||
|
@ -88,8 +96,8 @@ Layout.propTypes = {
|
|||
datePicker: PropTypes.element.isRequired,
|
||||
header: PropTypes.oneOfType([PropTypes.shape({}), PropTypes.bool]),
|
||||
table: PropTypes.oneOfType([PropTypes.shape({}), PropTypes.bool]),
|
||||
graphOneData: PropTypes.arrayOf(PropTypes.shape({})),
|
||||
graphTwoData: PropTypes.arrayOf(PropTypes.shape({})),
|
||||
failurehash: PropTypes.string,
|
||||
tableData: PropTypes.arrayOf(PropTypes.shape({})),
|
||||
graphData: PropTypes.oneOfType([
|
||||
PropTypes.arrayOf(PropTypes.shape({})),
|
||||
|
@ -105,7 +113,6 @@ Layout.propTypes = {
|
|||
};
|
||||
|
||||
Layout.defaultProps = {
|
||||
graphOneData: null,
|
||||
graphTwoData: null,
|
||||
tableFailureStatus: null,
|
||||
graphFailureStatus: null,
|
||||
|
@ -113,6 +120,7 @@ Layout.defaultProps = {
|
|||
isFetchingGraphs: null,
|
||||
tableData: null,
|
||||
graphData: null,
|
||||
failurehash: 'all',
|
||||
tree: null,
|
||||
table: null,
|
||||
header: null,
|
||||
|
|
|
@ -24,6 +24,7 @@ const MainView = (props) => {
|
|||
initialParamsSet,
|
||||
startday,
|
||||
endday,
|
||||
failurehash,
|
||||
updateState,
|
||||
tree,
|
||||
location,
|
||||
|
@ -52,6 +53,7 @@ const MainView = (props) => {
|
|||
tree={tree}
|
||||
startday={startday}
|
||||
endday={endday}
|
||||
failurehash={failurehash}
|
||||
location={location}
|
||||
graphData={graphData}
|
||||
tableData={tableData}
|
||||
|
@ -103,6 +105,8 @@ const MainView = (props) => {
|
|||
totalFailures,
|
||||
totalRuns,
|
||||
} = calculateMetrics(graphData));
|
||||
graphOneData = { all: graphOneData };
|
||||
graphOneData.all[0].count = tableData.length;
|
||||
}
|
||||
|
||||
const getHeaderAriaLabel = (state, bug, data) => {
|
||||
|
@ -190,6 +194,7 @@ MainView.propTypes = {
|
|||
updateState: PropTypes.func.isRequired,
|
||||
startday: PropTypes.string.isRequired,
|
||||
endday: PropTypes.string.isRequired,
|
||||
failurehash: PropTypes.string.isRequired,
|
||||
tableData: PropTypes.arrayOf(PropTypes.shape({})),
|
||||
graphData: PropTypes.arrayOf(PropTypes.shape({})),
|
||||
initialParamsSet: PropTypes.bool.isRequired,
|
||||
|
@ -206,6 +211,7 @@ const defaultState = {
|
|||
tree: 'all',
|
||||
startday: ISODate(moment().utc().subtract(7, 'days')),
|
||||
endday: ISODate(moment().utc()),
|
||||
failurehash: 'all',
|
||||
endpoint: bugsEndpoint,
|
||||
route: '/main',
|
||||
};
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
import moment from 'moment';
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
|
@ -11,7 +12,7 @@ import {
|
|||
} from '../helpers/url';
|
||||
import { getData } from '../helpers/http';
|
||||
|
||||
import { validateQueryParams, mergeData, formatBugs } from './helpers';
|
||||
import { validateQueryParams, mergeData, formatBugs, ISODate } from './helpers';
|
||||
|
||||
const withView = (defaultState) => (WrappedComponent) => {
|
||||
class View extends React.Component {
|
||||
|
@ -25,6 +26,7 @@ const withView = (defaultState) => (WrappedComponent) => {
|
|||
tree: this.default.tree || null,
|
||||
startday: this.default.startday || null,
|
||||
endday: this.default.endday || null,
|
||||
failurehash: this.default.failurehash || 'all',
|
||||
bug: this.default.id || null,
|
||||
summary: this.default.summary || null,
|
||||
tableData: [],
|
||||
|
@ -43,8 +45,8 @@ const withView = (defaultState) => (WrappedComponent) => {
|
|||
|
||||
setQueryParams = () => {
|
||||
const { location, history } = this.props;
|
||||
const { startday, endday, tree, bug } = this.state;
|
||||
const params = { startday, endday, tree };
|
||||
const { startday, endday, tree, failurehash, bug } = this.state;
|
||||
const params = { startday, endday, tree, failurehash };
|
||||
|
||||
if (bug) {
|
||||
params.bug = bug;
|
||||
|
@ -84,9 +86,9 @@ const withView = (defaultState) => (WrappedComponent) => {
|
|||
};
|
||||
|
||||
// trim off the timestamp and "TEST-UNEXPECTED-XXX | "
|
||||
lineTrimmer = async (failureLines) => {
|
||||
lineTrimmer = async (failureLines, pushTime) => {
|
||||
if (failureLines === undefined) {
|
||||
return ['', ''];
|
||||
return ['', '', pushTime];
|
||||
}
|
||||
if (typeof failureLines === 'string') {
|
||||
failureLines = failureLines.split('\n');
|
||||
|
@ -102,22 +104,68 @@ const withView = (defaultState) => (WrappedComponent) => {
|
|||
});
|
||||
const rv = trimmedLines.join('\n');
|
||||
return this.hashMessage(rv).then((hash) => {
|
||||
return [rv, hash];
|
||||
return [rv, hash, pushTime];
|
||||
});
|
||||
};
|
||||
|
||||
getUniqueLines = async (tableData) => {
|
||||
const { startday, endday } = this.state;
|
||||
|
||||
const uniqueLogKeys = [];
|
||||
const uniqueLogHashes = [];
|
||||
const uniqueFrequency = {};
|
||||
|
||||
const results = tableData.map((td) => this.lineTrimmer(td.lines));
|
||||
const results = tableData.map((td) =>
|
||||
this.lineTrimmer(td.lines, td.push_time),
|
||||
);
|
||||
for (const result of await Promise.all(results)) {
|
||||
if (uniqueLogKeys.indexOf(result[1]) === -1) {
|
||||
uniqueLogKeys.push(result[1]);
|
||||
uniqueLogHashes.push(result);
|
||||
const hash = result[1];
|
||||
if (hash === '') {
|
||||
continue;
|
||||
}
|
||||
if (uniqueLogKeys.indexOf(hash) === -1) {
|
||||
uniqueLogKeys.push(hash);
|
||||
uniqueLogHashes.push([result[0], hash]);
|
||||
|
||||
uniqueFrequency[hash] = [
|
||||
{ data: [], color: 'red', dates: {}, datemap: {}, count: 0 },
|
||||
];
|
||||
let start = ISODate(moment(startday).utc());
|
||||
const end = ISODate(moment(endday).utc());
|
||||
// create entry for each date in range so graph looks nice.
|
||||
while (start <= end) {
|
||||
const sdate = moment(start).format('MMM DD');
|
||||
uniqueFrequency[hash][0].dates[sdate] = 0;
|
||||
uniqueFrequency[hash][0].datemap[sdate] = start;
|
||||
start = ISODate(moment(start).utc().add(1, 'days'));
|
||||
}
|
||||
}
|
||||
|
||||
// store frequency data by date to use in graphs, etc.
|
||||
const date = result[2].split(' ')[0];
|
||||
const sdate = moment(date).format('MMM DD');
|
||||
if (!(date in uniqueFrequency[hash][0].dates)) {
|
||||
uniqueFrequency[hash][0].dates[sdate] = 0;
|
||||
}
|
||||
uniqueFrequency[hash][0].dates[sdate] += 1;
|
||||
uniqueFrequency[hash][0].count += 1;
|
||||
}
|
||||
return uniqueLogHashes;
|
||||
|
||||
// convert data to a graph friendly format
|
||||
uniqueLogKeys.forEach((hval) => {
|
||||
const dates = Object.keys(uniqueFrequency[hval][0].dates);
|
||||
dates.sort();
|
||||
dates.forEach((date) => {
|
||||
const counter = uniqueFrequency[hval][0].dates[date];
|
||||
uniqueFrequency[hval][0].data.push({
|
||||
date,
|
||||
failurePerPush: counter,
|
||||
x: uniqueFrequency[hval][0].datemap[date],
|
||||
y: counter,
|
||||
});
|
||||
});
|
||||
});
|
||||
return [uniqueLogHashes, uniqueFrequency];
|
||||
};
|
||||
|
||||
getTableData = async (url) => {
|
||||
|
@ -137,7 +185,8 @@ const withView = (defaultState) => (WrappedComponent) => {
|
|||
tableData: mergedData || data,
|
||||
tableFailureStatus: failureStatus,
|
||||
isFetchingTable: false,
|
||||
uniqueLines,
|
||||
uniqueLines: uniqueLines[0],
|
||||
uniqueFrequency: uniqueLines[1],
|
||||
});
|
||||
};
|
||||
|
||||
|
@ -179,8 +228,8 @@ const withView = (defaultState) => (WrappedComponent) => {
|
|||
|
||||
updateState = (updatedObj) => {
|
||||
this.setState(updatedObj, () => {
|
||||
const { startday, endday, tree, bug } = this.state;
|
||||
const params = { startday, endday, tree };
|
||||
const { startday, endday, tree, failurehash, bug } = this.state;
|
||||
const params = { startday, endday, tree, failurehash };
|
||||
|
||||
if (bug) {
|
||||
params.bug = bug;
|
||||
|
@ -195,6 +244,21 @@ const withView = (defaultState) => (WrappedComponent) => {
|
|||
});
|
||||
};
|
||||
|
||||
updateHash = (hashVal) => {
|
||||
this.setState({ failurehash: hashVal }, () => {
|
||||
const { startday, endday, tree, failurehash, bug } = this.state;
|
||||
const params = { startday, endday, tree, failurehash };
|
||||
|
||||
if (bug) {
|
||||
params.bug = bug;
|
||||
}
|
||||
|
||||
// update query params if dates or tree are updated
|
||||
const queryString = createQueryParams(params);
|
||||
updateQueryParams(queryString, this.props.history, this.props.location);
|
||||
});
|
||||
};
|
||||
|
||||
updateData = (params, urlChanged = false) => {
|
||||
const { mainGraphData, mainTableData } = this.props;
|
||||
|
||||
|
@ -241,7 +305,13 @@ const withView = (defaultState) => (WrappedComponent) => {
|
|||
|
||||
render() {
|
||||
const updateState = { updateState: this.updateState };
|
||||
const newProps = { ...this.props, ...this.state, ...updateState };
|
||||
const updateHash = { updateHash: this.updateHash };
|
||||
const newProps = {
|
||||
...this.props,
|
||||
...this.state,
|
||||
...updateState,
|
||||
...updateHash,
|
||||
};
|
||||
return <WrappedComponent {...newProps} />;
|
||||
}
|
||||
}
|
||||
|
|
Загрузка…
Ссылка в новой задаче