mirror of
https://github.com/Unleash/unleash.git
synced 2025-05-31 01:16:01 +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,
|
||||
userTrendsOrder: 3,
|
||||
flagStatsOrder: 2,
|
||||
largeChartSpan: 1,
|
||||
};
|
||||
}
|
||||
|
||||
@ -42,6 +43,7 @@ const useDashboardGrid = () => {
|
||||
chartSpan: 2,
|
||||
userTrendsOrder: 3,
|
||||
flagStatsOrder: 2,
|
||||
largeChartSpan: 2,
|
||||
};
|
||||
}
|
||||
|
||||
@ -50,6 +52,7 @@ const useDashboardGrid = () => {
|
||||
chartSpan: 1,
|
||||
userTrendsOrder: 2,
|
||||
flagStatsOrder: 3,
|
||||
largeChartSpan: 2,
|
||||
};
|
||||
};
|
||||
|
||||
@ -69,8 +72,13 @@ export const ExecutiveDashboard: VFC = () => {
|
||||
).toFixed(1);
|
||||
}, [executiveDashboardData]);
|
||||
|
||||
const { gridTemplateColumns, chartSpan, userTrendsOrder, flagStatsOrder } =
|
||||
useDashboardGrid();
|
||||
const {
|
||||
gridTemplateColumns,
|
||||
chartSpan,
|
||||
userTrendsOrder,
|
||||
flagStatsOrder,
|
||||
largeChartSpan,
|
||||
} = useDashboardGrid();
|
||||
|
||||
return (
|
||||
<>
|
||||
@ -107,11 +115,18 @@ export const ExecutiveDashboard: VFC = () => {
|
||||
flagTrends={executiveDashboardData.flagTrends}
|
||||
/>
|
||||
</Widget>
|
||||
<Widget
|
||||
title='Number of flags per project'
|
||||
order={5}
|
||||
span={largeChartSpan}
|
||||
>
|
||||
<FlagsProjectChart
|
||||
projectFlagTrends={
|
||||
executiveDashboardData.projectFlagTrends
|
||||
}
|
||||
/>
|
||||
</Widget>
|
||||
</StyledGrid>
|
||||
|
||||
<FlagsProjectChart
|
||||
projectFlagTrends={executiveDashboardData.projectFlagTrends}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
@ -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(
|
||||
() => import('./FlagsProjectChartComponent'),
|
||||
);
|
||||
interface IFlagsProjectChartProps {
|
||||
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