Skip to content

Commit

Permalink
Added view for test cases
Browse files Browse the repository at this point in the history
  • Loading branch information
Fgerthoffert committed Nov 25, 2024
1 parent 9790e87 commit adacfe8
Show file tree
Hide file tree
Showing 25 changed files with 1,323 additions and 2 deletions.
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -186,8 +186,8 @@ cd zqueue; yarn run start:dev

Without Keycloak:
cd zapi; KEYCLOAK_DISABLED=true yarn run start:dev
cd zui; KEYCLOAK_DISABLED=true yarn run start:dev
cd zqueue; yarn run start:dev
cd zui; NODE_OPTIONS=--openssl-legacy-provider KEYCLOAK_DISABLED=true yarn run start:dev
cd zqueue; NODE_OPTIONS=--openssl-legacy-provider yarn run start:dev

```

Expand Down
2 changes: 2 additions & 0 deletions src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import CircleciPipelines from './views/circleciPipelines';
import CircleciInsights from './views/circleciInsights';
import TestingStates from './views/testingStates';
import TestingRuns from './views/testingRuns';
import TestingCases from './views/testingCases';
import TestingPerfs from './views/testingPerfs';
import BambooRuns from './views/bambooRuns';
import Login from './views/login';
Expand Down Expand Up @@ -64,6 +65,7 @@ const App: React.FC<connectedProps> = (props: connectedProps) => {
<Route exact path="/circleciInsights" render={() => <CircleciInsights />} />
<Route exact path="/testingStates" render={() => <TestingStates />} />
<Route exact path="/testingRuns" render={() => <TestingRuns />} />
<Route exact path="/testingCases" render={() => <TestingCases />} />
<Route exact path="/testingPerfs" render={() => <TestingPerfs />} />
<Route exact path="/bambooRuns" render={() => <BambooRuns />} />
</Switch>
Expand Down
3 changes: 3 additions & 0 deletions src/models/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import { circleciPipelines } from './circleciPipelines';
import { circleciInsights } from './circleciInsights';
import { testingStates } from './testingStates';
import { testingRuns } from './testingRuns';
import { testingCases } from './testingCases';
import { testingPerfs } from './testingPerfs';
import { bambooRuns } from './bambooRuns';

Expand All @@ -40,6 +41,7 @@ export interface RootModel {
circleciInsights: typeof circleciInsights;
testingStates: typeof testingStates;
testingRuns: typeof testingRuns;
testingCases: typeof testingCases;
testingPerfs: typeof testingPerfs;
bambooRuns: typeof bambooRuns;
}
Expand All @@ -64,6 +66,7 @@ export const models: RootModel = {
circleciInsights,
testingStates,
testingRuns,
testingCases,
testingPerfs,
bambooRuns,
};
111 changes: 111 additions & 0 deletions src/models/testingCases.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
// https://github.com/pimterry/loglevel
import * as log from 'loglevel';

import { Dispatch } from '../store';

declare global {
interface Window {
_env_: any;
}
}

interface TestingStates {
state: any;
reducers: any;
effects: any;
}

export const testingCases: TestingStates = {
state: {
log: {},
loading: false,
selectedTab: 'explore',
dataset: 'testingCases',

query: {},
queries: [],

tablePaginationRowsPerPage: 25,
tablePaginationCurrentPage: 0,
tablePaginationOffset: 0,
tablePaginationLimit: 25,
defaultPoints: false,
},
reducers: {
setLog(state: any, payload: any) {
return { ...state, log: payload };
},
setLoading(state: any, payload: any) {
return { ...state, loading: payload };
},
setSelectedTab(state: any, payload: any) {
return { ...state, selectedTab: payload };
},
setTablePaginationRowsPerPage(state: any, payload: any) {
// Whenever we change the number of rows per page, we also reset all to default
return {
...state,
tablePaginationRowsPerPage: payload,
tablePaginationLimit: payload,
tablePaginationOffset: 0,
tablePaginationCurrentPage: 0,
};
},
setTablePaginationCurrentPage(state: any, newPageNb: number) {
// const updatedOffset = (newPageNb + 1) * state.tablePaginationLimit;
const updatedOffset = newPageNb * state.tablePaginationLimit;
return { ...state, tablePaginationCurrentPage: newPageNb, tablePaginationOffset: updatedOffset };
},
setTablePaginationOffset(state: any, payload: any) {
return { ...state, tablePaginationOffset: payload };
},
setTablePaginationLimit(state: any, payload: any) {
return { ...state, tablePaginationLimit: payload, tablePaginationCurrentPage: 0, tablePaginationOffset: 0 };
},
setQuery(state: any, payload: any) {
return { ...state, query: payload };
},
setQueries(state: any, payload: any) {
return { ...state, queries: payload };
},
},
effects: (dispatch: Dispatch) => ({
async initView() {
const logger = log.noConflict();
if (process.env.NODE_ENV !== 'production') {
logger.enableAll();
} else {
logger.disableAll();
}
logger.info('testingCases Logger initialized');
dispatch.testingCases.setLog(logger);
},

async saveQuery() {
console.log('MODEL SAVE QUERY');
},
async deleteQuery() {
console.log('MODEL DELETE QUERY');
},

async updateQueryIfDifferent(newQuery: any, rootState: any) {
const originalQuery = rootState.testingCases.query;
// Only update the store if the query is different than store
// Might need to replace stringify by Lodash isEqual
if (JSON.stringify(originalQuery) !== JSON.stringify(newQuery)) {
if (newQuery === null) {
dispatch.testingCases.setQuery({});
} else {
dispatch.testingCases.setQuery(newQuery);
}
}
},

async updateTabIfDifferent(newTab: any, rootState: any) {
const originalTab = rootState.testingCases.selectedTab;
if (originalTab !== newTab) {
dispatch.testingCases.setSelectedTab(newTab);
}
},
}),
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
query($query: String, $interval: String) {
testingCases {
data(query: $query) {
failurerate(interval: $interval, buckets: 50) {
field
fromDateStart
toDateStart
buckets {
key
docCount
caseFailureRate
caseTotal
caseTotalAvg
buckets {
dateStart
docCount
caseFailureRate
caseTotal
caseTotalAvg
}
}
}
}
}
}
175 changes: 175 additions & 0 deletions src/views/testingCases/content/explore/failureEvolution/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,175 @@
import React from 'react';
import { loader } from 'graphql.macro';
import { useQuery } from '@apollo/client';
import add from 'date-fns/add';
import Typography from '@material-ui/core/Typography';
import Grid from '@material-ui/core/Grid';
import CheckBoxOutlineBlankIcon from '@material-ui/icons/CheckBoxOutlineBlank';
import AddBoxIcon from '@material-ui/icons/AddBox';
import IndeterminateCheckBoxIcon from '@material-ui/icons/IndeterminateCheckBox';
import { startOfWeek, startOfMonth } from 'date-fns';

import CustomCard from '../../../../../components/customCard';
import FailureChart from '../../../../../components/charts/nivo/failureChart';

const GQL_QUERY = loader('./getFailure.graphql');

interface Props {
query: any;
timeWindowPrior: string;
interval: string;
headerTitle: string;
}

const buildQuery = (sourceQuery: any, additionalData: any) => {
let updatedQuery: any = {};

if (Object.keys(sourceQuery).length === 0) {
updatedQuery = {
op: 'and',
content: [],
};
} else {
updatedQuery = { ...sourceQuery };
}

return {
...updatedQuery,
content: [...updatedQuery.content, ...additionalData],
};
};

const getEmptyWeekCalendar = (firstWeek: Date, lastWeek: Date) => {
const weekCalendar: Array<string> = [];
const currentDate = firstWeek;
lastWeek = add(lastWeek, { days: 1 });
while (currentDate <= lastWeek) {
const currentWeekU = startOfWeek(currentDate, { weekStartsOn: 1 });
currentWeekU.setUTCHours(0, 0, 0, 0); // Needed not to take local browser timezone in consideration.
const currentWeek = currentWeekU.toISOString();
if (!weekCalendar.includes(currentWeek)) {
weekCalendar.push(currentWeek);
}
currentDate.setDate(currentDate.getDate() + 1);
}
return weekCalendar;
};

const getEmptyDayCalendar = (firstDay: Date, lastDay: Date) => {
const dayCalendar: Array<string> = [];
const currentDate = firstDay;
lastDay = add(lastDay, { days: 1 });
while (currentDate <= lastDay) {
currentDate.setUTCHours(0, 0, 0, 0); // Needed not to take local browser timezone in consideration.
dayCalendar.push(currentDate.toISOString());
currentDate.setDate(currentDate.getDate() + 1);
}
return dayCalendar;
};

const getEmptyMonthCalendar = (firstMonth: Date, lastMonth: Date) => {
const weekCalendar: Array<string> = [];
const currentDate = firstMonth;
lastMonth = add(lastMonth, { days: 1 });
while (currentDate <= lastMonth) {
const currentMonthU = startOfMonth(currentDate);
currentMonthU.setUTCHours(0, 0, 0, 0); // Needed not to take local browser timezone in consideration.
const currentMonth = currentMonthU.toISOString();
if (!weekCalendar.includes(currentMonth)) {
weekCalendar.push(currentMonth);
}
currentDate.setDate(currentDate.getDate() + 1);
}
return weekCalendar;
};

const buildDataset = (data: any, emptyCalendar: Array<string>) => {
const dataset = [];
for (const bucket of data.buckets.filter((b: any) => b.caseTotal > 0)) {
const formattedBucket: any = {};
formattedBucket['serie'] = bucket.key;
formattedBucket['avg'] = bucket.caseFailureRate;
for (const week of emptyCalendar) {
const dateExists = bucket.buckets.find((w: any) => w.dateStart === week);
if (week !== 'avg') {
if (dateExists === undefined || dateExists.docCount === 0) {
formattedBucket[week] = -1;
} else {
formattedBucket[week] = dateExists.caseFailureRate;
}
}
}
dataset.push(formattedBucket);
}
return dataset;
};

const FailureEvolution: React.FC<Props> = (props: Props) => {
const { query, timeWindowPrior, headerTitle, interval } = props;

const timeWindow = [{ op: '>=', content: { field: 'createdAt', value: timeWindowPrior } }];

const { data } = useQuery(GQL_QUERY, {
variables: {
query: JSON.stringify(buildQuery(query, timeWindow)),
interval: interval,
},
fetchPolicy: 'network-only',
});
if (data !== undefined) {
const dataset = data.testingCases.data.failurerate;
let emptyCalendar: Array<string> = getEmptyWeekCalendar(
new Date(dataset.fromDateStart),
new Date(dataset.toDateStart),
);
if (interval === 'day') {
emptyCalendar = getEmptyDayCalendar(new Date(dataset.fromDateStart), new Date(dataset.toDateStart));
} else if (interval === 'month') {
emptyCalendar = getEmptyMonthCalendar(new Date(dataset.fromDateStart), new Date(dataset.toDateStart));
}

emptyCalendar.unshift('avg');

const updatedDataset = buildDataset(dataset, emptyCalendar);
return (
<CustomCard headerTitle={headerTitle}>
<FailureChart dataset={updatedDataset} weeks={emptyCalendar} interval={interval} buckets={dataset.buckets} />
<Grid container spacing={1} direction="row" justify="center" alignItems="center">
<Grid item>
<Typography variant="caption" display="block" gutterBottom>
From period average:
</Typography>
</Grid>
<Grid item>
<CheckBoxOutlineBlankIcon />
<Typography variant="caption" display="block" gutterBottom>
Less than 2% variance
</Typography>
</Grid>
<Grid item>
<IndeterminateCheckBoxIcon style={{ color: 'rgb(244, 117, 96)' }} />
<Typography variant="caption" display="block" gutterBottom>
Degradation (&gt; 2%)
</Typography>
</Grid>
<Grid item>
<AddBoxIcon style={{ color: 'rgb(97, 205, 187)' }} />
<Typography variant="caption" display="block" gutterBottom>
Improvement (&gt; 2%)
</Typography>
</Grid>
</Grid>
<Grid container spacing={1} direction="row" justify="center" alignItems="center">
<Grid item>
<Typography variant="caption" display="block" gutterBottom>
Displays the top 50 test cases with most failures, use facets on the left to filter down.
</Typography>
</Grid>
</Grid>
</CustomCard>
);
}
return <span>Loading data</span>;
};

export default FailureEvolution;
Loading

0 comments on commit adacfe8

Please sign in to comment.