mirror of
https://github.com/Unleash/unleash.git
synced 2025-06-09 01:17:06 +02:00
refactor: FlagsChart and FlagsProjectChart components (#6087)
Co-authored-by: Fredrik Strand Oseberg <fredrik.no@gmail.com>
This commit is contained in:
parent
bc95ed654f
commit
d77e5391ed
@ -33,6 +33,7 @@ const useDashboardGrid = () => {
|
|||||||
chartSpan: 1,
|
chartSpan: 1,
|
||||||
userTrendsOrder: 3,
|
userTrendsOrder: 3,
|
||||||
flagStatsOrder: 2,
|
flagStatsOrder: 2,
|
||||||
|
largeChartSpan: 1,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -42,6 +43,7 @@ const useDashboardGrid = () => {
|
|||||||
chartSpan: 2,
|
chartSpan: 2,
|
||||||
userTrendsOrder: 3,
|
userTrendsOrder: 3,
|
||||||
flagStatsOrder: 2,
|
flagStatsOrder: 2,
|
||||||
|
largeChartSpan: 2,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -50,6 +52,7 @@ const useDashboardGrid = () => {
|
|||||||
chartSpan: 1,
|
chartSpan: 1,
|
||||||
userTrendsOrder: 2,
|
userTrendsOrder: 2,
|
||||||
flagStatsOrder: 3,
|
flagStatsOrder: 3,
|
||||||
|
largeChartSpan: 2,
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -69,8 +72,13 @@ export const ExecutiveDashboard: VFC = () => {
|
|||||||
).toFixed(1);
|
).toFixed(1);
|
||||||
}, [executiveDashboardData]);
|
}, [executiveDashboardData]);
|
||||||
|
|
||||||
const { gridTemplateColumns, chartSpan, userTrendsOrder, flagStatsOrder } =
|
const {
|
||||||
useDashboardGrid();
|
gridTemplateColumns,
|
||||||
|
chartSpan,
|
||||||
|
userTrendsOrder,
|
||||||
|
flagStatsOrder,
|
||||||
|
largeChartSpan,
|
||||||
|
} = useDashboardGrid();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
@ -107,11 +115,18 @@ export const ExecutiveDashboard: VFC = () => {
|
|||||||
flagTrends={executiveDashboardData.flagTrends}
|
flagTrends={executiveDashboardData.flagTrends}
|
||||||
/>
|
/>
|
||||||
</Widget>
|
</Widget>
|
||||||
</StyledGrid>
|
<Widget
|
||||||
|
title='Number of flags per project'
|
||||||
|
order={5}
|
||||||
|
span={largeChartSpan}
|
||||||
|
>
|
||||||
<FlagsProjectChart
|
<FlagsProjectChart
|
||||||
projectFlagTrends={executiveDashboardData.projectFlagTrends}
|
projectFlagTrends={
|
||||||
|
executiveDashboardData.projectFlagTrends
|
||||||
|
}
|
||||||
/>
|
/>
|
||||||
|
</Widget>
|
||||||
|
</StyledGrid>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
@ -1,3 +1,37 @@
|
|||||||
import { lazy } from 'react';
|
import { useMemo, type VFC } from 'react';
|
||||||
|
import 'chartjs-adapter-date-fns';
|
||||||
|
import { useTheme } from '@mui/material';
|
||||||
|
import { ExecutiveSummarySchema } from 'openapi';
|
||||||
|
import { LineChart } from '../LineChart/LineChart';
|
||||||
|
|
||||||
export const FlagsChart = lazy(() => import('./FlagsChartComponent'));
|
interface IFlagsChartProps {
|
||||||
|
flagTrends: ExecutiveSummarySchema['flagTrends'];
|
||||||
|
}
|
||||||
|
|
||||||
|
export const FlagsChart: VFC<IFlagsChartProps> = ({ flagTrends }) => {
|
||||||
|
const theme = useTheme();
|
||||||
|
const data = useMemo(
|
||||||
|
() => ({
|
||||||
|
labels: flagTrends.map((item) => item.date),
|
||||||
|
datasets: [
|
||||||
|
{
|
||||||
|
label: 'Total flags',
|
||||||
|
data: flagTrends.map((item) => item.total),
|
||||||
|
borderColor: theme.palette.primary.light,
|
||||||
|
backgroundColor: theme.palette.primary.light,
|
||||||
|
fill: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Stale',
|
||||||
|
data: flagTrends.map((item) => item.stale),
|
||||||
|
borderColor: theme.palette.warning.border,
|
||||||
|
backgroundColor: theme.palette.warning.border,
|
||||||
|
fill: true,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
}),
|
||||||
|
[theme, flagTrends],
|
||||||
|
);
|
||||||
|
|
||||||
|
return <LineChart data={data} />;
|
||||||
|
};
|
||||||
|
@ -1,137 +0,0 @@
|
|||||||
import { useMemo, type VFC } from 'react';
|
|
||||||
import {
|
|
||||||
Chart as ChartJS,
|
|
||||||
CategoryScale,
|
|
||||||
LinearScale,
|
|
||||||
PointElement,
|
|
||||||
LineElement,
|
|
||||||
Title,
|
|
||||||
Tooltip,
|
|
||||||
Legend,
|
|
||||||
TimeScale,
|
|
||||||
} from 'chart.js';
|
|
||||||
import { Line } from 'react-chartjs-2';
|
|
||||||
import 'chartjs-adapter-date-fns';
|
|
||||||
import { Theme, useTheme } from '@mui/material';
|
|
||||||
import {
|
|
||||||
useLocationSettings,
|
|
||||||
type ILocationSettings,
|
|
||||||
} from 'hooks/useLocationSettings';
|
|
||||||
import { formatDateYMD } from 'utils/formatDate';
|
|
||||||
import { ExecutiveSummarySchema } from 'openapi';
|
|
||||||
|
|
||||||
const createData = (
|
|
||||||
theme: Theme,
|
|
||||||
flagTrends: ExecutiveSummarySchema['flagTrends'] = [],
|
|
||||||
) => ({
|
|
||||||
labels: flagTrends.map((item) => item.date),
|
|
||||||
datasets: [
|
|
||||||
{
|
|
||||||
label: 'Total flags',
|
|
||||||
data: flagTrends.map((item) => item.total),
|
|
||||||
borderColor: theme.palette.primary.main,
|
|
||||||
backgroundColor: theme.palette.primary.main,
|
|
||||||
fill: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: 'Stale',
|
|
||||||
data: flagTrends.map((item) => item.stale),
|
|
||||||
borderColor: theme.palette.warning.main,
|
|
||||||
backgroundColor: theme.palette.warning.main,
|
|
||||||
fill: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: 'Active flags',
|
|
||||||
data: flagTrends.map((item) => item.active),
|
|
||||||
borderColor: theme.palette.success.main,
|
|
||||||
backgroundColor: theme.palette.success.main,
|
|
||||||
fill: true,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
});
|
|
||||||
|
|
||||||
const createOptions = (theme: Theme, locationSettings: ILocationSettings) =>
|
|
||||||
({
|
|
||||||
responsive: true,
|
|
||||||
plugins: {
|
|
||||||
legend: {
|
|
||||||
position: 'bottom',
|
|
||||||
},
|
|
||||||
tooltip: {
|
|
||||||
callbacks: {
|
|
||||||
title: (tooltipItems: any) => {
|
|
||||||
const item = tooltipItems?.[0];
|
|
||||||
const date =
|
|
||||||
item?.chart?.data?.labels?.[item.dataIndex];
|
|
||||||
return date
|
|
||||||
? formatDateYMD(
|
|
||||||
date,
|
|
||||||
locationSettings.locale,
|
|
||||||
'UTC',
|
|
||||||
)
|
|
||||||
: '';
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
locale: locationSettings.locale,
|
|
||||||
interaction: {
|
|
||||||
intersect: false,
|
|
||||||
axis: 'x',
|
|
||||||
},
|
|
||||||
color: theme.palette.text.secondary,
|
|
||||||
scales: {
|
|
||||||
y: {
|
|
||||||
type: 'linear',
|
|
||||||
grid: {
|
|
||||||
color: theme.palette.divider,
|
|
||||||
borderColor: theme.palette.divider,
|
|
||||||
},
|
|
||||||
ticks: { color: theme.palette.text.secondary },
|
|
||||||
},
|
|
||||||
x: {
|
|
||||||
type: 'time',
|
|
||||||
time: {
|
|
||||||
unit: 'month',
|
|
||||||
},
|
|
||||||
grid: {
|
|
||||||
color: theme.palette.divider,
|
|
||||||
borderColor: theme.palette.divider,
|
|
||||||
},
|
|
||||||
ticks: {
|
|
||||||
color: theme.palette.text.secondary,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}) as const;
|
|
||||||
|
|
||||||
interface IFlagsChartComponentProps {
|
|
||||||
flagTrends: ExecutiveSummarySchema['flagTrends'];
|
|
||||||
}
|
|
||||||
|
|
||||||
const FlagsChartComponent: VFC<IFlagsChartComponentProps> = ({
|
|
||||||
flagTrends,
|
|
||||||
}) => {
|
|
||||||
const theme = useTheme();
|
|
||||||
const { locationSettings } = useLocationSettings();
|
|
||||||
const data = useMemo(
|
|
||||||
() => createData(theme, flagTrends),
|
|
||||||
[theme, flagTrends],
|
|
||||||
);
|
|
||||||
const options = createOptions(theme, locationSettings);
|
|
||||||
|
|
||||||
return <Line options={options} data={data} />;
|
|
||||||
};
|
|
||||||
|
|
||||||
ChartJS.register(
|
|
||||||
CategoryScale,
|
|
||||||
LinearScale,
|
|
||||||
PointElement,
|
|
||||||
LineElement,
|
|
||||||
TimeScale,
|
|
||||||
Title,
|
|
||||||
Tooltip,
|
|
||||||
Legend,
|
|
||||||
);
|
|
||||||
|
|
||||||
export default FlagsChartComponent;
|
|
@ -1,5 +1,58 @@
|
|||||||
import { lazy } from 'react';
|
import { useMemo, type VFC } from 'react';
|
||||||
|
import 'chartjs-adapter-date-fns';
|
||||||
|
import { useTheme } from '@mui/material';
|
||||||
|
import {
|
||||||
|
ExecutiveSummarySchema,
|
||||||
|
ExecutiveSummarySchemaProjectFlagTrendsItem,
|
||||||
|
} from 'openapi';
|
||||||
|
import { LineChart } from '../LineChart/LineChart';
|
||||||
|
|
||||||
export const FlagsProjectChart = lazy(
|
interface IFlagsProjectChartProps {
|
||||||
() => import('./FlagsProjectChartComponent'),
|
projectFlagTrends: ExecutiveSummarySchema['projectFlagTrends'];
|
||||||
|
}
|
||||||
|
|
||||||
|
const getRandomColor = () => {
|
||||||
|
const letters = '0123456789ABCDEF';
|
||||||
|
let color = '#';
|
||||||
|
for (let i = 0; i < 6; i++) {
|
||||||
|
color += letters[Math.floor(Math.random() * 16)];
|
||||||
|
}
|
||||||
|
return color;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const FlagsProjectChart: VFC<IFlagsProjectChartProps> = ({
|
||||||
|
projectFlagTrends,
|
||||||
|
}) => {
|
||||||
|
const theme = useTheme();
|
||||||
|
const data = useMemo(() => {
|
||||||
|
const groupedFlagTrends = projectFlagTrends.reduce<
|
||||||
|
Record<string, ExecutiveSummarySchemaProjectFlagTrendsItem[]>
|
||||||
|
>((groups, item) => {
|
||||||
|
if (!groups[item.project]) {
|
||||||
|
groups[item.project] = [];
|
||||||
|
}
|
||||||
|
groups[item.project].push(item);
|
||||||
|
return groups;
|
||||||
|
}, {});
|
||||||
|
|
||||||
|
const datasets = Object.entries(groupedFlagTrends).map(
|
||||||
|
([project, trends]) => {
|
||||||
|
const color = getRandomColor();
|
||||||
|
return {
|
||||||
|
label: project,
|
||||||
|
data: trends.map((item) => item.total),
|
||||||
|
borderColor: color,
|
||||||
|
backgroundColor: color,
|
||||||
|
fill: true,
|
||||||
|
};
|
||||||
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
|
return {
|
||||||
|
labels: projectFlagTrends.map((item) => item.date),
|
||||||
|
datasets,
|
||||||
|
};
|
||||||
|
}, [theme, projectFlagTrends]);
|
||||||
|
|
||||||
|
return <LineChart data={data} />;
|
||||||
|
};
|
||||||
|
@ -1,162 +0,0 @@
|
|||||||
import { useMemo, type VFC } from 'react';
|
|
||||||
import {
|
|
||||||
Chart as ChartJS,
|
|
||||||
CategoryScale,
|
|
||||||
LinearScale,
|
|
||||||
PointElement,
|
|
||||||
LineElement,
|
|
||||||
Title,
|
|
||||||
Tooltip,
|
|
||||||
Legend,
|
|
||||||
TimeScale,
|
|
||||||
} from 'chart.js';
|
|
||||||
import { Line } from 'react-chartjs-2';
|
|
||||||
import 'chartjs-adapter-date-fns';
|
|
||||||
import { Paper, Theme, Typography, useTheme } from '@mui/material';
|
|
||||||
import {
|
|
||||||
useLocationSettings,
|
|
||||||
type ILocationSettings,
|
|
||||||
} from 'hooks/useLocationSettings';
|
|
||||||
import { formatDateYMD } from 'utils/formatDate';
|
|
||||||
import {
|
|
||||||
ExecutiveSummarySchema,
|
|
||||||
ExecutiveSummarySchemaProjectFlagTrendsItem,
|
|
||||||
} from 'openapi';
|
|
||||||
|
|
||||||
const getRandomColor = () => {
|
|
||||||
const letters = '0123456789ABCDEF';
|
|
||||||
let color = '#';
|
|
||||||
for (let i = 0; i < 6; i++) {
|
|
||||||
color += letters[Math.floor(Math.random() * 16)];
|
|
||||||
}
|
|
||||||
return color;
|
|
||||||
};
|
|
||||||
|
|
||||||
const createData = (
|
|
||||||
theme: Theme,
|
|
||||||
flagTrends: ExecutiveSummarySchema['projectFlagTrends'] = [],
|
|
||||||
) => {
|
|
||||||
const groupedFlagTrends = flagTrends.reduce<
|
|
||||||
Record<string, ExecutiveSummarySchemaProjectFlagTrendsItem[]>
|
|
||||||
>((groups, item) => {
|
|
||||||
if (!groups[item.project]) {
|
|
||||||
groups[item.project] = [];
|
|
||||||
}
|
|
||||||
groups[item.project].push(item);
|
|
||||||
return groups;
|
|
||||||
}, {});
|
|
||||||
|
|
||||||
const datasets = Object.entries(groupedFlagTrends).map(
|
|
||||||
([project, trends]) => {
|
|
||||||
const color = getRandomColor();
|
|
||||||
return {
|
|
||||||
label: project,
|
|
||||||
data: trends.map((item) => item.total),
|
|
||||||
borderColor: color,
|
|
||||||
backgroundColor: color,
|
|
||||||
fill: true,
|
|
||||||
};
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
return {
|
|
||||||
labels: flagTrends.map((item) => item.date),
|
|
||||||
datasets,
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
const createOptions = (theme: Theme, locationSettings: ILocationSettings) =>
|
|
||||||
({
|
|
||||||
responsive: true,
|
|
||||||
plugins: {
|
|
||||||
legend: {
|
|
||||||
position: 'bottom',
|
|
||||||
},
|
|
||||||
tooltip: {
|
|
||||||
callbacks: {
|
|
||||||
title: (tooltipItems: any) => {
|
|
||||||
const item = tooltipItems?.[0];
|
|
||||||
const date =
|
|
||||||
item?.chart?.data?.labels?.[item.dataIndex];
|
|
||||||
return date
|
|
||||||
? formatDateYMD(
|
|
||||||
date,
|
|
||||||
locationSettings.locale,
|
|
||||||
'UTC',
|
|
||||||
)
|
|
||||||
: '';
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
locale: locationSettings.locale,
|
|
||||||
interaction: {
|
|
||||||
intersect: false,
|
|
||||||
axis: 'x',
|
|
||||||
},
|
|
||||||
color: theme.palette.text.secondary,
|
|
||||||
scales: {
|
|
||||||
y: {
|
|
||||||
type: 'linear',
|
|
||||||
grid: {
|
|
||||||
color: theme.palette.divider,
|
|
||||||
borderColor: theme.palette.divider,
|
|
||||||
},
|
|
||||||
ticks: { color: theme.palette.text.secondary },
|
|
||||||
},
|
|
||||||
x: {
|
|
||||||
type: 'time',
|
|
||||||
time: {
|
|
||||||
unit: 'month',
|
|
||||||
},
|
|
||||||
grid: {
|
|
||||||
color: theme.palette.divider,
|
|
||||||
borderColor: theme.palette.divider,
|
|
||||||
},
|
|
||||||
ticks: {
|
|
||||||
color: theme.palette.text.secondary,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}) as const;
|
|
||||||
|
|
||||||
interface IFlagsChartComponentProps {
|
|
||||||
projectFlagTrends: ExecutiveSummarySchema['projectFlagTrends'];
|
|
||||||
}
|
|
||||||
|
|
||||||
const FlagsProjectChart: VFC<IFlagsChartComponentProps> = ({
|
|
||||||
projectFlagTrends,
|
|
||||||
}) => {
|
|
||||||
const theme = useTheme();
|
|
||||||
const { locationSettings } = useLocationSettings();
|
|
||||||
const data = useMemo(
|
|
||||||
() => createData(theme, projectFlagTrends),
|
|
||||||
[theme, projectFlagTrends],
|
|
||||||
);
|
|
||||||
const options = createOptions(theme, locationSettings);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Paper sx={(theme) => ({ padding: theme.spacing(4) })}>
|
|
||||||
<Typography
|
|
||||||
variant='h3'
|
|
||||||
sx={(theme) => ({ marginBottom: theme.spacing(3) })}
|
|
||||||
>
|
|
||||||
Number of flags per project
|
|
||||||
</Typography>
|
|
||||||
<Line options={options} data={data} />
|
|
||||||
</Paper>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
ChartJS.register(
|
|
||||||
CategoryScale,
|
|
||||||
LinearScale,
|
|
||||||
PointElement,
|
|
||||||
LineElement,
|
|
||||||
TimeScale,
|
|
||||||
Title,
|
|
||||||
Tooltip,
|
|
||||||
Legend,
|
|
||||||
);
|
|
||||||
|
|
||||||
export default FlagsProjectChart;
|
|
Loading…
Reference in New Issue
Block a user