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} />}
/>
<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,17 +101,15 @@ export default class GraphsContainer extends React.Component {
}
GraphsContainer.propTypes = {
graphOneData: PropTypes.arrayOf(
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,
}),
graphTwoData: PropTypes.arrayOf(
PropTypes.shape({
data: PropTypes.arrayOf(PropTypes.shape({})),
color: PropTypes.string,

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

@ -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'));
}
}
return uniqueLogHashes;
// 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;
}
// 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} />;
}
}