mirror of
https://github.com/Unleash/unleash.git
synced 2025-09-05 17:53:12 +02:00
chore: Add a cover for when we don't have enough data to show the chart (#10599)
Adds a cover to the archived vs created flag chart if we don't have enough data or if the data is loading. Follows the pattern established in the other analytics charts. Because the values for a placeholder bar chart are different than for a placeholder line chart, I've added in actual placeholder values. This also makes the gives the chart in question two bars per category, even in the placeholder data, which matches nicely with the actual graph. Before: <img width="2272" height="974" alt="image" src="https://github.com/user-attachments/assets/3336717f-acc8-4d23-a208-138259d6d3c7" /> After: <img width="1131" height="487" alt="image" src="https://github.com/user-attachments/assets/5189e323-c636-4089-b4eb-30b231b09b8b" /> In context with the other charts: <img width="1392" height="1744" alt="image" src="https://github.com/user-attachments/assets/7809669c-dae8-496b-b89b-0913fab85c17" />
This commit is contained in:
parent
9a7f2c520a
commit
778eaa9873
32
frontend/src/component/insights/GraphCover.tsx
Normal file
32
frontend/src/component/insights/GraphCover.tsx
Normal file
@ -0,0 +1,32 @@
|
||||
import { styled } from '@mui/material';
|
||||
import type { FC, PropsWithChildren } from 'react';
|
||||
|
||||
const StyledCover = styled('div')(({ theme }) => ({
|
||||
position: 'absolute',
|
||||
inset: 0,
|
||||
display: 'flex',
|
||||
zIndex: theme.zIndex.appBar,
|
||||
'&::before': {
|
||||
zIndex: theme.zIndex.fab,
|
||||
content: '""',
|
||||
position: 'absolute',
|
||||
inset: 0,
|
||||
backgroundColor: theme.palette.background.paper,
|
||||
opacity: 0.8,
|
||||
},
|
||||
}));
|
||||
|
||||
const StyledCoverContent = styled('div')(({ theme }) => ({
|
||||
zIndex: theme.zIndex.modal,
|
||||
margin: 'auto',
|
||||
color: theme.palette.text.secondary,
|
||||
textAlign: 'center',
|
||||
}));
|
||||
|
||||
export const GraphCover: FC<PropsWithChildren> = ({ children }) => {
|
||||
return (
|
||||
<StyledCover>
|
||||
<StyledCoverContent>{children}</StyledCoverContent>
|
||||
</StyledCover>
|
||||
);
|
||||
};
|
@ -26,33 +26,12 @@ import { styled } from '@mui/material';
|
||||
import { createOptions } from './createChartOptions.ts';
|
||||
import merge from 'deepmerge';
|
||||
import { customHighlightPlugin } from 'component/common/Chart/customHighlightPlugin.ts';
|
||||
import { GraphCover } from 'component/insights/GraphCover.tsx';
|
||||
|
||||
const StyledContainer = styled('div')(({ theme }) => ({
|
||||
position: 'relative',
|
||||
}));
|
||||
|
||||
const StyledCover = styled('div')(({ theme }) => ({
|
||||
position: 'absolute',
|
||||
inset: 0,
|
||||
display: 'flex',
|
||||
zIndex: theme.zIndex.appBar,
|
||||
'&::before': {
|
||||
zIndex: theme.zIndex.fab,
|
||||
content: '""',
|
||||
position: 'absolute',
|
||||
inset: 0,
|
||||
backgroundColor: theme.palette.background.paper,
|
||||
opacity: 0.8,
|
||||
},
|
||||
}));
|
||||
|
||||
const StyledCoverContent = styled('div')(({ theme }) => ({
|
||||
zIndex: theme.zIndex.modal,
|
||||
margin: 'auto',
|
||||
color: theme.palette.text.secondary,
|
||||
textAlign: 'center',
|
||||
}));
|
||||
|
||||
function mergeAll<T>(objects: Partial<T>[]): T {
|
||||
return merge.all<T>(objects.filter((i) => i));
|
||||
}
|
||||
@ -113,11 +92,7 @@ const LineChartComponent: FC<{
|
||||
)
|
||||
}
|
||||
elseShow={
|
||||
<StyledCover>
|
||||
<StyledCoverContent>
|
||||
{cover !== true ? cover : ' '}
|
||||
</StyledCoverContent>
|
||||
</StyledCover>
|
||||
<GraphCover>{cover !== true ? cover : ' '}</GraphCover>
|
||||
}
|
||||
/>
|
||||
</StyledContainer>
|
||||
|
@ -4,7 +4,6 @@ import type { InstanceInsightsSchema } from 'openapi';
|
||||
import { useProjectChartData } from 'component/insights/hooks/useProjectChartData';
|
||||
import { useTheme } from '@mui/material';
|
||||
import type { GroupedDataByProject } from 'component/insights/hooks/useGroupedProjectTrends';
|
||||
import { usePlaceholderData } from 'component/insights/hooks/usePlaceholderData';
|
||||
import {
|
||||
CategoryScale,
|
||||
LinearScale,
|
||||
@ -22,9 +21,12 @@ import type { TooltipState } from 'component/insights/components/LineChart/Chart
|
||||
import type { WeekData, RawWeekData } from './types.ts';
|
||||
import { createTooltip } from 'component/insights/components/LineChart/createTooltip.ts';
|
||||
import { CreationArchiveRatioTooltip } from './CreationArchiveRatioTooltip.tsx';
|
||||
import { Chart } from 'react-chartjs-2';
|
||||
import { getDateFnsLocale } from '../../getDateFnsLocale.ts';
|
||||
import { customHighlightPlugin } from 'component/common/Chart/customHighlightPlugin.ts';
|
||||
import { NotEnoughData } from 'component/insights/components/LineChart/LineChart.tsx';
|
||||
import { placeholderData } from './placeholderData.ts';
|
||||
import { Bar } from 'react-chartjs-2';
|
||||
import { GraphCover } from 'component/insights/GraphCover.tsx';
|
||||
|
||||
ChartJS.register(
|
||||
CategoryScale,
|
||||
@ -51,11 +53,10 @@ export const CreationArchiveChart: FC<ICreationArchiveChartProps> = ({
|
||||
}) => {
|
||||
const creationVsArchivedChart = useProjectChartData(creationArchiveTrends);
|
||||
const theme = useTheme();
|
||||
const placeholderData = usePlaceholderData();
|
||||
const { locationSettings } = useLocationSettings();
|
||||
const [tooltip, setTooltip] = useState<null | TooltipState>(null);
|
||||
|
||||
const aggregateOrProjectData = useMemo(() => {
|
||||
const { notEnoughData, aggregateOrProjectData } = useMemo(() => {
|
||||
const labels: string[] = Array.from(
|
||||
new Set(
|
||||
creationVsArchivedChart.datasets.flatMap((d) =>
|
||||
@ -103,46 +104,53 @@ export const CreationArchiveChart: FC<ICreationArchiveChartProps> = ({
|
||||
.sort((a, b) => (a.week > b.week ? 1 : -1));
|
||||
|
||||
return {
|
||||
datasets: [
|
||||
{
|
||||
label: 'Flags archived',
|
||||
data: weeks,
|
||||
backgroundColor: theme.palette.charts.A2,
|
||||
borderColor: theme.palette.charts.A2,
|
||||
hoverBackgroundColor: theme.palette.charts.A2,
|
||||
hoverBorderColor: theme.palette.charts.A2,
|
||||
parsing: { yAxisKey: 'archivedFlags', xAxisKey: 'date' },
|
||||
order: 1,
|
||||
},
|
||||
{
|
||||
label: 'Flags created',
|
||||
data: weeks,
|
||||
backgroundColor: theme.palette.charts.A1,
|
||||
borderColor: theme.palette.charts.A1,
|
||||
hoverBackgroundColor: theme.palette.charts.A1,
|
||||
hoverBorderColor: theme.palette.charts.A1,
|
||||
parsing: {
|
||||
yAxisKey: 'totalCreatedFlags',
|
||||
xAxisKey: 'date',
|
||||
notEnoughData: weeks.length < 2,
|
||||
aggregateOrProjectData: {
|
||||
datasets: [
|
||||
{
|
||||
label: 'Flags archived',
|
||||
data: weeks,
|
||||
backgroundColor: theme.palette.charts.A2,
|
||||
borderColor: theme.palette.charts.A2,
|
||||
hoverBackgroundColor: theme.palette.charts.A2,
|
||||
hoverBorderColor: theme.palette.charts.A2,
|
||||
parsing: {
|
||||
yAxisKey: 'archivedFlags',
|
||||
xAxisKey: 'date',
|
||||
},
|
||||
order: 1,
|
||||
},
|
||||
order: 2,
|
||||
},
|
||||
],
|
||||
{
|
||||
label: 'Flags created',
|
||||
data: weeks,
|
||||
backgroundColor: theme.palette.charts.A1,
|
||||
borderColor: theme.palette.charts.A1,
|
||||
hoverBackgroundColor: theme.palette.charts.A1,
|
||||
hoverBorderColor: theme.palette.charts.A1,
|
||||
parsing: {
|
||||
yAxisKey: 'totalCreatedFlags',
|
||||
xAxisKey: 'date',
|
||||
},
|
||||
order: 2,
|
||||
},
|
||||
],
|
||||
},
|
||||
};
|
||||
}, [creationVsArchivedChart, theme]);
|
||||
|
||||
const notEnoughData = useMemo(
|
||||
() =>
|
||||
!isLoading &&
|
||||
!creationVsArchivedChart.datasets.some((d) => d.data.length > 1),
|
||||
[creationVsArchivedChart, isLoading],
|
||||
);
|
||||
const data =
|
||||
notEnoughData || isLoading ? placeholderData : aggregateOrProjectData;
|
||||
const useGraphCover = notEnoughData || isLoading;
|
||||
const data = useGraphCover ? placeholderData : aggregateOrProjectData;
|
||||
|
||||
const options = useMemo(
|
||||
() => ({
|
||||
responsive: true,
|
||||
...(useGraphCover
|
||||
? {
|
||||
animation: {
|
||||
duration: 0,
|
||||
},
|
||||
}
|
||||
: {}),
|
||||
interaction: {
|
||||
mode: 'index' as const,
|
||||
intersect: false,
|
||||
@ -157,6 +165,7 @@ export const CreationArchiveChart: FC<ICreationArchiveChartProps> = ({
|
||||
padding: 21,
|
||||
boxHeight: 8,
|
||||
},
|
||||
display: !useGraphCover,
|
||||
},
|
||||
tooltip: {
|
||||
enabled: false,
|
||||
@ -181,7 +190,10 @@ export const CreationArchiveChart: FC<ICreationArchiveChartProps> = ({
|
||||
grid: {
|
||||
display: false,
|
||||
},
|
||||
ticks: { source: 'data' },
|
||||
ticks: {
|
||||
source: 'data' as const,
|
||||
display: !useGraphCover,
|
||||
},
|
||||
},
|
||||
y: {
|
||||
type: 'linear' as const,
|
||||
@ -193,17 +205,17 @@ export const CreationArchiveChart: FC<ICreationArchiveChartProps> = ({
|
||||
},
|
||||
ticks: {
|
||||
stepSize: 1,
|
||||
display: !useGraphCover,
|
||||
},
|
||||
},
|
||||
},
|
||||
}),
|
||||
[theme, locationSettings, setTooltip],
|
||||
[theme, locationSettings, setTooltip, useGraphCover],
|
||||
);
|
||||
|
||||
return (
|
||||
<>
|
||||
<Chart
|
||||
type='bar'
|
||||
<Bar
|
||||
data={data}
|
||||
options={options}
|
||||
height={100}
|
||||
@ -215,6 +227,11 @@ export const CreationArchiveChart: FC<ICreationArchiveChartProps> = ({
|
||||
]}
|
||||
/>
|
||||
<CreationArchiveRatioTooltip tooltip={tooltip} />
|
||||
{useGraphCover ? (
|
||||
<GraphCover>
|
||||
{notEnoughData ? <NotEnoughData /> : isLoading}
|
||||
</GraphCover>
|
||||
) : null}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
@ -0,0 +1,63 @@
|
||||
import type { ChartData } from 'chart.js';
|
||||
import type { WeekData } from './types.ts';
|
||||
|
||||
const data = [
|
||||
{
|
||||
archivedFlags: 3,
|
||||
totalCreatedFlags: 4,
|
||||
archivePercentage: 75,
|
||||
week: '2025-30',
|
||||
date: '2025-07-27T01:00:00.000Z',
|
||||
},
|
||||
{
|
||||
archivedFlags: 7,
|
||||
totalCreatedFlags: 3,
|
||||
archivePercentage: 140,
|
||||
week: '2025-31',
|
||||
date: '2025-08-03T01:00:00.000Z',
|
||||
},
|
||||
{
|
||||
archivedFlags: 2,
|
||||
totalCreatedFlags: 3,
|
||||
archivePercentage: 50,
|
||||
week: '2025-32',
|
||||
date: '2025-08-10T01:00:00.000Z',
|
||||
},
|
||||
{
|
||||
archivedFlags: 2,
|
||||
totalCreatedFlags: 6,
|
||||
archivePercentage: 25,
|
||||
week: '2025-33',
|
||||
date: '2025-08-17T00:40:40.606Z',
|
||||
},
|
||||
{
|
||||
archivedFlags: 4,
|
||||
totalCreatedFlags: 9,
|
||||
archivePercentage: 66.66666666666666,
|
||||
week: '2025-34',
|
||||
date: '2025-08-24T00:29:19.583Z',
|
||||
},
|
||||
];
|
||||
|
||||
export const placeholderData: ChartData<any, WeekData[]> = {
|
||||
datasets: [
|
||||
{
|
||||
label: 'Flags archived',
|
||||
data,
|
||||
parsing: {
|
||||
yAxisKey: 'archivedFlags',
|
||||
xAxisKey: 'date',
|
||||
},
|
||||
order: 1,
|
||||
},
|
||||
{
|
||||
label: 'Flags created',
|
||||
data,
|
||||
parsing: {
|
||||
yAxisKey: 'totalCreatedFlags',
|
||||
xAxisKey: 'date',
|
||||
},
|
||||
order: 2,
|
||||
},
|
||||
],
|
||||
};
|
Loading…
Reference in New Issue
Block a user