when viewing specific failure in IFV details, update graph (#7519)

This commit is contained in:
Joel Maher 2022-11-18 08:42:45 -08:00 коммит произвёл GitHub
Родитель 4083943628
Коммит 94718b8448
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: 4AEE18F83AFDEB23
7 изменённых файлов: 180 добавлений и 57 удалений

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

@ -74,7 +74,7 @@ class IntermittentFailuresApp extends React.Component {
render={(props) => <BugDetailsView {...props} />} render={(props) => <BugDetailsView {...props} />}
/> />
<Route <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} />} render={(props) => <BugDetailsView {...props} />}
/> />
<Redirect from={`${path}/`} to={`${path}/main`} /> <Redirect from={`${path}/`} to={`${path}/main`} />

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

@ -29,7 +29,9 @@ const BugDetailsView = (props) => {
initialParamsSet, initialParamsSet,
startday, startday,
endday, endday,
failurehash,
updateState, updateState,
updateHash,
bug, bug,
summary, summary,
errorMessages, errorMessages,
@ -37,19 +39,24 @@ const BugDetailsView = (props) => {
tableFailureStatus, tableFailureStatus,
graphFailureStatus, graphFailureStatus,
uniqueLines, uniqueLines,
uniqueFrequency,
} = props; } = props;
const customFilter = ({ filter, onChange }) => { const customFilter = ({ filter }) => {
if (!tableData || !uniqueLines) return; if (!tableData || !uniqueLines) return;
return ( return (
<select <select
onChange={(event) => onChange(event.target.value)} onChange={(event) => updateHash(event.target.value)}
style={{ width: '100%' }} style={{ width: '100%' }}
value={filter ? filter.value : 'all'} value={filter ? filter.value : failurehash}
> >
<option value="all">All</option> <option value="all">All</option>
{uniqueLines.map((vals) => { {uniqueLines.map((vals) => {
return <option value={vals[1]}>{vals[0]}</option>; return (
<option key={vals[1]} value={vals[1]}>
{vals[0]}
</option>
);
})} })}
</select> </select>
); );
@ -127,20 +134,6 @@ const BugDetailsView = (props) => {
{ {
Header: 'Log', Header: 'Log',
accessor: 'job_id', 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 }), Filter: ({ filter, onChange }) => customFilter({ filter, onChange }),
Cell: (_props) => { Cell: (_props) => {
const { value, original } = _props; const { value, original } = _props;
@ -188,17 +181,45 @@ const BugDetailsView = (props) => {
}, },
]; ];
let gOneData = {};
let graphOneData = null; let graphOneData = null;
let graphTwoData = null; let graphTwoData = null;
let _tableData = null;
if (graphData.length > 0) { if (graphData.length > 0) {
({ graphOneData, graphTwoData } = calculateMetrics(graphData)); ({ 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 ( return (
<Layout <Layout
{...props} {...props}
graphOneData={graphOneData} graphOneData={gOneData}
graphTwoData={graphTwoData} graphTwoData={graphTwoData}
header={ header={
<React.Fragment> <React.Fragment>
@ -261,7 +282,10 @@ const BugDetailsView = (props) => {
<Row> <Row>
<Col xs="12" className="mx-auto"> <Col xs="12" className="mx-auto">
<p className="text-secondary"> <p className="text-secondary">
{tableData.length} total failures {failurehash in gOneData
? gOneData[failurehash][0].count
: 0}{' '}
total failures
</p> </p>
</Col> </Col>
</Row> </Row>
@ -272,9 +296,10 @@ const BugDetailsView = (props) => {
} }
table={ table={
bug && bug &&
initialParamsSet && ( initialParamsSet &&
_tableData && (
<ReactTable <ReactTable
data={tableData} data={_tableData}
filterable filterable
showPageSizeOptions showPageSizeOptions
columns={columns} columns={columns}
@ -295,18 +320,21 @@ BugDetailsView.propTypes = {
tree: PropTypes.string.isRequired, tree: PropTypes.string.isRequired,
updateAppState: PropTypes.func, updateAppState: PropTypes.func,
updateState: PropTypes.func.isRequired, updateState: PropTypes.func.isRequired,
updateHash: PropTypes.func.isRequired,
startday: PropTypes.string.isRequired, startday: PropTypes.string.isRequired,
failurehash: PropTypes.string.isRequired,
endday: PropTypes.string.isRequired, endday: PropTypes.string.isRequired,
tableData: PropTypes.arrayOf(PropTypes.shape({})), tableData: PropTypes.arrayOf(PropTypes.shape({})),
graphData: PropTypes.arrayOf(PropTypes.shape({})), graphData: PropTypes.arrayOf(PropTypes.shape({})),
initialParamsSet: PropTypes.bool.isRequired, initialParamsSet: PropTypes.bool.isRequired,
bug: PropTypes.number.isRequired, bug: PropTypes.string.isRequired,
summary: PropTypes.string.isRequired, summary: PropTypes.string.isRequired,
errorMessages: PropTypes.arrayOf(PropTypes.string), errorMessages: PropTypes.arrayOf(PropTypes.string),
lastLocation: PropTypes.shape({}).isRequired, lastLocation: PropTypes.shape({}).isRequired,
tableFailureStatus: PropTypes.string, tableFailureStatus: PropTypes.string,
graphFailureStatus: PropTypes.string, graphFailureStatus: PropTypes.string,
uniqueLines: PropTypes.arrayOf(PropTypes.array), uniqueLines: PropTypes.arrayOf(PropTypes.array),
uniqueFrequency: PropTypes.shape({}),
}; };
BugDetailsView.defaultProps = { BugDetailsView.defaultProps = {
@ -317,6 +345,7 @@ BugDetailsView.defaultProps = {
graphFailureStatus: null, graphFailureStatus: null,
updateAppState: null, updateAppState: null,
uniqueLines: [], uniqueLines: [],
uniqueFrequency: {},
}; };
const defaultState = { const defaultState = {

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

@ -26,7 +26,8 @@ const Graph = ({ graphData, title, legendData }) => (
data={legendData} data={legendData}
/> />
)} )}
{graphData.length > 0 && {graphData &&
graphData.length > 0 &&
graphData.map((item) => ( graphData.map((item) => (
<VictoryLine <VictoryLine
key={item} key={item}

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

@ -25,7 +25,7 @@ export default class GraphsContainer extends React.Component {
}; };
render() { render() {
const { graphOneData, graphTwoData, children } = this.props; const { graphOneData, graphTwoData, failurehash, children } = this.props;
const { showGraphTwo, showAlternateView } = this.state; const { showGraphTwo, showAlternateView } = this.state;
return ( return (
@ -33,13 +33,24 @@ export default class GraphsContainer extends React.Component {
<Row className="pt-5"> <Row className="pt-5">
{showAlternateView ? ( {showAlternateView ? (
<GraphAlternateView <GraphAlternateView
graphData={graphOneData} graphData={graphOneData[failurehash]}
className="failure-per-count" className="failure-per-count"
colNum={1} 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>
<Row> <Row>
@ -90,21 +101,19 @@ export default class GraphsContainer extends React.Component {
} }
GraphsContainer.propTypes = { GraphsContainer.propTypes = {
graphOneData: PropTypes.arrayOf( graphOneData: PropTypes.shape({
PropTypes.shape({ hash: PropTypes.arrayOf(
data: PropTypes.arrayOf(PropTypes.shape({})), PropTypes.shape({
color: PropTypes.string, data: PropTypes.arrayOf(PropTypes.shape({})),
}), color: PropTypes.string,
), }),
),
}),
graphTwoData: PropTypes.arrayOf( graphTwoData: PropTypes.arrayOf(
PropTypes.shape({ PropTypes.shape({
data: PropTypes.arrayOf(PropTypes.shape({})), data: PropTypes.arrayOf(PropTypes.shape({})),
color: PropTypes.string, color: PropTypes.string,
}), }),
PropTypes.shape({
data: PropTypes.arrayOf(PropTypes.shape({})),
color: PropTypes.string,
}),
), ),
children: PropTypes.element.isRequired, children: PropTypes.element.isRequired,
}; };

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

@ -21,8 +21,10 @@ const Layout = (props) => {
tableFailureStatus, tableFailureStatus,
graphFailureStatus, graphFailureStatus,
updateState, updateState,
updateHash,
graphOneData, graphOneData,
graphTwoData, graphTwoData,
failurehash,
table, table,
datePicker, datePicker,
header, header,
@ -36,7 +38,12 @@ const Layout = (props) => {
} }
return ( return (
<Container fluid className="my-5 max-width-default"> <Container fluid className="my-5 max-width-default">
<Navigation updateState={updateState} tree={tree} {...props} /> <Navigation
updateState={updateState}
updateHash={updateHash}
tree={tree}
{...props}
/>
{(isFetchingGraphs || isFetchingTable) && {(isFetchingGraphs || isFetchingTable) &&
!( !(
tableFailureStatus || tableFailureStatus ||
@ -60,6 +67,7 @@ const Layout = (props) => {
<GraphsContainer <GraphsContainer
graphOneData={graphOneData} graphOneData={graphOneData}
graphTwoData={graphTwoData} graphTwoData={graphTwoData}
failurehash={failurehash}
> >
{datePicker} {datePicker}
</GraphsContainer> </GraphsContainer>
@ -88,8 +96,8 @@ Layout.propTypes = {
datePicker: PropTypes.element.isRequired, datePicker: PropTypes.element.isRequired,
header: PropTypes.oneOfType([PropTypes.shape({}), PropTypes.bool]), header: PropTypes.oneOfType([PropTypes.shape({}), PropTypes.bool]),
table: PropTypes.oneOfType([PropTypes.shape({}), PropTypes.bool]), table: PropTypes.oneOfType([PropTypes.shape({}), PropTypes.bool]),
graphOneData: PropTypes.arrayOf(PropTypes.shape({})),
graphTwoData: PropTypes.arrayOf(PropTypes.shape({})), graphTwoData: PropTypes.arrayOf(PropTypes.shape({})),
failurehash: PropTypes.string,
tableData: PropTypes.arrayOf(PropTypes.shape({})), tableData: PropTypes.arrayOf(PropTypes.shape({})),
graphData: PropTypes.oneOfType([ graphData: PropTypes.oneOfType([
PropTypes.arrayOf(PropTypes.shape({})), PropTypes.arrayOf(PropTypes.shape({})),
@ -105,7 +113,6 @@ Layout.propTypes = {
}; };
Layout.defaultProps = { Layout.defaultProps = {
graphOneData: null,
graphTwoData: null, graphTwoData: null,
tableFailureStatus: null, tableFailureStatus: null,
graphFailureStatus: null, graphFailureStatus: null,
@ -113,6 +120,7 @@ Layout.defaultProps = {
isFetchingGraphs: null, isFetchingGraphs: null,
tableData: null, tableData: null,
graphData: null, graphData: null,
failurehash: 'all',
tree: null, tree: null,
table: null, table: null,
header: null, header: null,

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

@ -24,6 +24,7 @@ const MainView = (props) => {
initialParamsSet, initialParamsSet,
startday, startday,
endday, endday,
failurehash,
updateState, updateState,
tree, tree,
location, location,
@ -52,6 +53,7 @@ const MainView = (props) => {
tree={tree} tree={tree}
startday={startday} startday={startday}
endday={endday} endday={endday}
failurehash={failurehash}
location={location} location={location}
graphData={graphData} graphData={graphData}
tableData={tableData} tableData={tableData}
@ -103,6 +105,8 @@ const MainView = (props) => {
totalFailures, totalFailures,
totalRuns, totalRuns,
} = calculateMetrics(graphData)); } = calculateMetrics(graphData));
graphOneData = { all: graphOneData };
graphOneData.all[0].count = tableData.length;
} }
const getHeaderAriaLabel = (state, bug, data) => { const getHeaderAriaLabel = (state, bug, data) => {
@ -190,6 +194,7 @@ MainView.propTypes = {
updateState: PropTypes.func.isRequired, updateState: PropTypes.func.isRequired,
startday: PropTypes.string.isRequired, startday: PropTypes.string.isRequired,
endday: PropTypes.string.isRequired, endday: PropTypes.string.isRequired,
failurehash: PropTypes.string.isRequired,
tableData: PropTypes.arrayOf(PropTypes.shape({})), tableData: PropTypes.arrayOf(PropTypes.shape({})),
graphData: PropTypes.arrayOf(PropTypes.shape({})), graphData: PropTypes.arrayOf(PropTypes.shape({})),
initialParamsSet: PropTypes.bool.isRequired, initialParamsSet: PropTypes.bool.isRequired,
@ -206,6 +211,7 @@ const defaultState = {
tree: 'all', tree: 'all',
startday: ISODate(moment().utc().subtract(7, 'days')), startday: ISODate(moment().utc().subtract(7, 'days')),
endday: ISODate(moment().utc()), endday: ISODate(moment().utc()),
failurehash: 'all',
endpoint: bugsEndpoint, endpoint: bugsEndpoint,
route: '/main', route: '/main',
}; };

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

@ -1,3 +1,4 @@
import moment from 'moment';
import React from 'react'; import React from 'react';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
@ -11,7 +12,7 @@ import {
} from '../helpers/url'; } from '../helpers/url';
import { getData } from '../helpers/http'; import { getData } from '../helpers/http';
import { validateQueryParams, mergeData, formatBugs } from './helpers'; import { validateQueryParams, mergeData, formatBugs, ISODate } from './helpers';
const withView = (defaultState) => (WrappedComponent) => { const withView = (defaultState) => (WrappedComponent) => {
class View extends React.Component { class View extends React.Component {
@ -25,6 +26,7 @@ const withView = (defaultState) => (WrappedComponent) => {
tree: this.default.tree || null, tree: this.default.tree || null,
startday: this.default.startday || null, startday: this.default.startday || null,
endday: this.default.endday || null, endday: this.default.endday || null,
failurehash: this.default.failurehash || 'all',
bug: this.default.id || null, bug: this.default.id || null,
summary: this.default.summary || null, summary: this.default.summary || null,
tableData: [], tableData: [],
@ -43,8 +45,8 @@ const withView = (defaultState) => (WrappedComponent) => {
setQueryParams = () => { setQueryParams = () => {
const { location, history } = this.props; const { location, history } = this.props;
const { startday, endday, tree, bug } = this.state; const { startday, endday, tree, failurehash, bug } = this.state;
const params = { startday, endday, tree }; const params = { startday, endday, tree, failurehash };
if (bug) { if (bug) {
params.bug = bug; params.bug = bug;
@ -84,9 +86,9 @@ const withView = (defaultState) => (WrappedComponent) => {
}; };
// trim off the timestamp and "TEST-UNEXPECTED-XXX | " // trim off the timestamp and "TEST-UNEXPECTED-XXX | "
lineTrimmer = async (failureLines) => { lineTrimmer = async (failureLines, pushTime) => {
if (failureLines === undefined) { if (failureLines === undefined) {
return ['', '']; return ['', '', pushTime];
} }
if (typeof failureLines === 'string') { if (typeof failureLines === 'string') {
failureLines = failureLines.split('\n'); failureLines = failureLines.split('\n');
@ -102,22 +104,68 @@ const withView = (defaultState) => (WrappedComponent) => {
}); });
const rv = trimmedLines.join('\n'); const rv = trimmedLines.join('\n');
return this.hashMessage(rv).then((hash) => { return this.hashMessage(rv).then((hash) => {
return [rv, hash]; return [rv, hash, pushTime];
}); });
}; };
getUniqueLines = async (tableData) => { getUniqueLines = async (tableData) => {
const { startday, endday } = this.state;
const uniqueLogKeys = []; const uniqueLogKeys = [];
const uniqueLogHashes = []; 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)) { for (const result of await Promise.all(results)) {
if (uniqueLogKeys.indexOf(result[1]) === -1) { const hash = result[1];
uniqueLogKeys.push(result[1]); if (hash === '') {
uniqueLogHashes.push(result); 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) => { getTableData = async (url) => {
@ -137,7 +185,8 @@ const withView = (defaultState) => (WrappedComponent) => {
tableData: mergedData || data, tableData: mergedData || data,
tableFailureStatus: failureStatus, tableFailureStatus: failureStatus,
isFetchingTable: false, isFetchingTable: false,
uniqueLines, uniqueLines: uniqueLines[0],
uniqueFrequency: uniqueLines[1],
}); });
}; };
@ -179,8 +228,8 @@ const withView = (defaultState) => (WrappedComponent) => {
updateState = (updatedObj) => { updateState = (updatedObj) => {
this.setState(updatedObj, () => { this.setState(updatedObj, () => {
const { startday, endday, tree, bug } = this.state; const { startday, endday, tree, failurehash, bug } = this.state;
const params = { startday, endday, tree }; const params = { startday, endday, tree, failurehash };
if (bug) { if (bug) {
params.bug = 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) => { updateData = (params, urlChanged = false) => {
const { mainGraphData, mainTableData } = this.props; const { mainGraphData, mainTableData } = this.props;
@ -241,7 +305,13 @@ const withView = (defaultState) => (WrappedComponent) => {
render() { render() {
const updateState = { updateState: this.updateState }; 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} />; return <WrappedComponent {...newProps} />;
} }
} }