Skip to content

Commit

Permalink
Add modern webui Report
Browse files Browse the repository at this point in the history
  • Loading branch information
Andrew Baldwin committed Oct 11, 2023
1 parent 6e25463 commit 8ac81a6
Show file tree
Hide file tree
Showing 24 changed files with 542 additions and 283 deletions.
4 changes: 4 additions & 0 deletions locust/webui/.eslintrc
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,10 @@
"pattern": "App",
"group": "internal"
},
{
"pattern": "Report",
"group": "internal"
},
{
"pattern": "{api,components,constants,hooks,redux,styles,types,utils}/**",
"group": "internal"
Expand Down
7 changes: 0 additions & 7 deletions locust/webui/dist/assets/index-0c9ff576.js

This file was deleted.

236 changes: 236 additions & 0 deletions locust/webui/dist/assets/index-f6041128.js

Large diffs are not rendered by default.

230 changes: 0 additions & 230 deletions locust/webui/dist/assets/vendor-87854ba9.js

This file was deleted.

21 changes: 14 additions & 7 deletions locust/webui/dist/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,7 @@
<meta name="theme-color" content="#000000" />

<title>Locust</title>
<script type="module" crossorigin src="/assets/index-0c9ff576.js"></script>
<link rel="modulepreload" crossorigin href="/assets/vendor-87854ba9.js">
<script type="module" crossorigin src="/assets/index-f6041128.js"></script>
</head>
<body>
<div id="root"></div>
Expand All @@ -18,11 +17,19 @@
// The server sets timestamps in UTC
// We need to convert these timestamps to locale time
window.templateArgs.history =
window.templateArgs.history.length ? window.templateArgs.history.map(({ time: serverTime, ...history }) => ({
...history,
time: new Date(new Date().setUTCHours(...(serverTime.split(":")))).toLocaleTimeString()
})) : []
window.templateArgs.history.length ? window.templateArgs.history.map(({ time: serverTime, ...history }) => ({
...history,
time: new Date(new Date().setUTCHours(...(serverTime.split(":")))).toLocaleTimeString()
})) : []
</script>



{% if static_js %}
<script>
{{ static_js|safe }}
</script>
{% else %}

{% endif %}
</body>
</html>
23 changes: 23 additions & 0 deletions locust/webui/dist/report.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<link rel="icon" href="/assets/favicon.ico" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<meta name="theme-color" content="#000000" />

<title>Locust</title>
</head>
<body>
<div id="root"></div>

<script>
window.templateArgs = {{ template_args|tojson }}
window.theme = "{{theme}}"
</script>

<script>
{{ static_js|safe }}
</script>
</body>
</html>
18 changes: 13 additions & 5 deletions locust/webui/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -16,11 +16,19 @@
// The server sets timestamps in UTC
// We need to convert these timestamps to locale time
window.templateArgs.history =
window.templateArgs.history.length ? window.templateArgs.history.map(({ time: serverTime, ...history }) => ({
...history,
time: new Date(new Date().setUTCHours(...(serverTime.split(":")))).toLocaleTimeString()
})) : []
window.templateArgs.history.length ? window.templateArgs.history.map(({ time: serverTime, ...history }) => ({
...history,
time: new Date(new Date().setUTCHours(...(serverTime.split(":")))).toLocaleTimeString()
})) : []
</script>
<script type="module" src="./src/index.tsx"></script>


{% if static_js %}
<script>
{{ static_js|safe }}
</script>
{% else %}
<script type="module" src="./src/index.tsx"></script>
{% endif %}
</body>
</html>
23 changes: 23 additions & 0 deletions locust/webui/public/report.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<link rel="icon" href="/assets/favicon.ico" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<meta name="theme-color" content="#000000" />

<title>Locust</title>
</head>
<body>
<div id="root"></div>

<script>
window.templateArgs = {{ template_args|tojson }}
window.theme = "{{theme}}"
</script>

<script>
{{ static_js|safe }}
</script>
</body>
</html>
112 changes: 112 additions & 0 deletions locust/webui/src/Report.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
import { Box, Typography, Container, Link } from '@mui/material';
import CssBaseline from '@mui/material/CssBaseline';
import { ThemeProvider } from '@mui/material/styles';

import { ExceptionsTable } from 'components/ExceptionsTable/ExceptionsTable';
import { FailuresTable } from 'components/FailuresTable/FailuresTable';
import ResponseTimeTable from 'components/ResponseTimeTable/ResponseTimeTable';
import { StatsTable } from 'components/StatsTable/StatsTable';
import { SwarmCharts } from 'components/SwarmCharts/SwarmCharts';
import { SwarmRatios } from 'components/SwarnRatios/SwarmRatios';
import createTheme, { THEME_MODE } from 'styles/theme';
import { IReport } from 'types/swarm.types';

const theme = createTheme(window.theme || THEME_MODE.LIGHT);

export default function Report({
locustfile,
showDownloadLink,
startTime,
endTime,
charts,
host,
exceptionsStatistics,
requestsStatistics,
failuresStatistics,
responseTimeStatistics,
tasks,
}: IReport) {
return (
<ThemeProvider theme={theme}>
<CssBaseline />

<Container maxWidth='lg' sx={{ my: 4 }}>
<Box sx={{ display: 'flex', justifyContent: 'space-between', alignItems: 'flex-end' }}>
<Typography
component='h1'
noWrap
sx={{
fontWeight: 700,
}}
variant='h3'
>
Locust Test Report
</Typography>
{showDownloadLink && (
<Link href={`?download=1&theme=${window.theme}`}>Download the Report</Link>
)}
</Box>
<Box sx={{ my: 2 }}>
<Box sx={{ display: 'flex', columnGap: 0.5 }}>
<Typography fontWeight={600}>During:</Typography>
<Typography>
{startTime} - {endTime}
</Typography>
</Box>

<Box sx={{ display: 'flex', columnGap: 0.5 }}>
<Typography fontWeight={600}>Target Host:</Typography>
<Typography>{host || 'None'}</Typography>
</Box>

<Box sx={{ display: 'flex', columnGap: 0.5 }}>
<Typography fontWeight={600}>Script:</Typography>
<Typography>{locustfile}</Typography>
</Box>
</Box>

<Box sx={{ display: 'flex', flexDirection: 'column', rowGap: 4 }}>
<Box>
<Typography component='h2' mb={1} noWrap variant='h4'>
Request Statistics
</Typography>
<StatsTable stats={requestsStatistics} />
</Box>
<Box>
<Typography component='h2' mb={1} noWrap variant='h4'>
Response Time Statistics
</Typography>
<ResponseTimeTable responseTimes={responseTimeStatistics} />
</Box>
<Box>
<Typography component='h2' mb={1} noWrap variant='h4'>
Failures Statistics
</Typography>
<FailuresTable errors={failuresStatistics} />
</Box>
{!!exceptionsStatistics.length && (
<Box>
<Typography component='h2' mb={1} noWrap variant='h4'>
Exceptions Statistics
</Typography>
<ExceptionsTable exceptions={exceptionsStatistics} />
</Box>
)}

<Box>
<Typography component='h2' mb={1} noWrap variant='h4'>
Charts
</Typography>
<SwarmCharts charts={charts} />
</Box>
<Box>
<Typography component='h2' mb={1} noWrap variant='h4'>
Final ratio
</Typography>
<SwarmRatios ratios={tasks} />
</Box>
</Box>
</Container>
</ThemeProvider>
);
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,10 @@ const tableStructure = [
{ key: 'traceback', title: 'Traceback' },
];

function FailuresTable({ exceptions }: { exceptions: ISwarmException[] }) {
export function ExceptionsTable({ exceptions }: { exceptions: ISwarmException[] }) {
return <Table<ISwarmException> rows={exceptions} structure={tableStructure} />;
}

const storeConnector = ({ ui: { exceptions } }: IRootState) => ({ exceptions });

export default connect(storeConnector)(FailuresTable);
export default connect(storeConnector)(ExceptionsTable);
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ const tableStructure = [
{ key: 'error', title: 'Message', markdown: true },
];

function FailuresTable({ errors }: { errors: ISwarmError[] }) {
export function FailuresTable({ errors }: { errors: ISwarmError[] }) {
return <Table<ISwarmError> rows={errors} structure={tableStructure} />;
}

Expand Down
2 changes: 1 addition & 1 deletion locust/webui/src/components/Layout/Navbar/Navbar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ export default function Navbar() {
<Toolbar sx={{ display: 'flex', justifyContent: 'space-between' }}>
<Link
color='inherit'
href='#'
href='/'
sx={{ display: 'flex', alignItems: 'center', columnGap: 2 }}
underline='none'
>
Expand Down
8 changes: 1 addition & 7 deletions locust/webui/src/components/LineChart/LineChart.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,8 @@ import {
TooltipComponentFormatterCallbackParams,
DefaultLabelFormatterCallbackParams,
} from 'echarts';
import { connect } from 'react-redux';

import { IUiState } from 'redux/slice/ui.slice';
import { IRootState } from 'redux/store';
import { ICharts } from 'types/ui.types';

interface ILine {
Expand Down Expand Up @@ -151,7 +149,7 @@ registerTheme('locust', {
},
});

function LineChart({ charts, title, lines }: ILineChart) {
export default function LineChart({ charts, title, lines }: ILineChart) {
const [chart, setChart] = useState<ECharts | null>(null);

const chartContainer = useRef<HTMLDivElement | null>(null);
Expand Down Expand Up @@ -188,7 +186,3 @@ function LineChart({ charts, title, lines }: ILineChart) {

return <div ref={chartContainer} style={{ width: '100%', height: '300px' }}></div>;
}

const storeConnector = ({ ui: { charts } }: IRootState) => ({ charts });

export default connect(storeConnector)(LineChart);
9 changes: 8 additions & 1 deletion locust/webui/src/components/Reports/Reports.tsx
Original file line number Diff line number Diff line change
@@ -1,13 +1,17 @@
import { Link, List, ListItem } from '@mui/material';
import { connect } from 'react-redux';

import { useSelector } from 'redux/hooks';
import { ISwarmState } from 'redux/slice/swarm.slice';
import { IRootState } from 'redux/store';
import { THEME_MODE } from 'styles/theme';

function Reports({
extendedCsvFiles,
statsHistoryEnabled,
}: Pick<ISwarmState, 'extendedCsvFiles' | 'statsHistoryEnabled'>) {
const isDarkMode = useSelector(({ theme: { isDarkMode } }) => isDarkMode);

return (
<List sx={{ display: 'flex', flexDirection: 'column' }}>
<ListItem>
Expand All @@ -27,7 +31,10 @@ function Reports({
<Link href='/exceptions/csv'>Download exceptions CSV</Link>
</ListItem>
<ListItem>
<Link href='/stats/report' target='_blank'>
<Link
href={`/stats/report?theme=${isDarkMode ? THEME_MODE.DARK : THEME_MODE.LIGHT}`}
target='_blank'
>
Download Report
</Link>
</ListItem>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import { useMemo } from 'react';

import Table from 'components/Table/Table';
import { IResponseTime } from 'types/ui.types';

const tableStructure = [
{ key: 'method', title: 'Method' },
{ key: 'name', title: 'Name' },
];

interface IResponseTimeTable {
responseTimes: IResponseTime[];
}

export default function ResponseTimeTable({ responseTimes }: IResponseTimeTable) {
const percentileColumns = useMemo(
() =>
Object.keys(responseTimes[0])
.filter(value => Boolean(Number(value)))
.map(percentile => ({ key: percentile, title: `${Number(percentile) * 100}%ile (ms)` })),
[responseTimes],
);

return <Table rows={responseTimes} structure={[...tableStructure, ...percentileColumns]} />;
}
2 changes: 1 addition & 1 deletion locust/webui/src/components/StatsTable/StatsTable.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ const tableStructure = [
{ key: 'currentFailPerSec', title: 'Current Failures/s', round: 2 },
];

function StatsTable({ stats }: { stats: ISwarmStat[] }) {
export function StatsTable({ stats }: { stats: ISwarmStat[] }) {
return <Table<ISwarmStat> rows={stats} structure={tableStructure} />;
}

Expand Down
12 changes: 10 additions & 2 deletions locust/webui/src/components/SwarmCharts/SwarmCharts.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,8 @@
import { connect } from 'react-redux';

import LineChart, { ILineChartProps } from 'components/LineChart/LineChart';
import { IRootState } from 'redux/store';
import { ICharts } from 'types/ui.types';

const availableSwarmCharts: ILineChartProps[] = [
{
Expand All @@ -21,12 +25,16 @@ const availableSwarmCharts: ILineChartProps[] = [
},
];

export default function SwarmCharts() {
export function SwarmCharts({ charts }: { charts: ICharts }) {
return (
<div>
{availableSwarmCharts.map((lineChartProps, index) => (
<LineChart key={`swarm-chart-${index}`} {...lineChartProps} />
<LineChart key={`swarm-chart-${index}`} {...lineChartProps} charts={charts} />
))}
</div>
);
}

const storeConnector = ({ ui: { charts } }: IRootState) => ({ charts });

export default connect(storeConnector)(SwarmCharts);
2 changes: 1 addition & 1 deletion locust/webui/src/components/SwarnRatios/SwarmRatios.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ function NestedRatioList({ classRatio }: { classRatio: IClassRatio }) {
);
}

function SwarmRatios({ ratios: { perClass, total } }: { ratios: IUiState['ratios'] }) {
export function SwarmRatios({ ratios: { perClass, total } }: { ratios: IUiState['ratios'] }) {
if (!perClass && !total) {
return null;
}
Expand Down
Loading

0 comments on commit 8ac81a6

Please sign in to comment.