1
0
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:
Thomas Heartman 2025-09-03 09:02:10 +02:00 committed by GitHub
parent 9a7f2c520a
commit 778eaa9873
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 154 additions and 67 deletions

View 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>
);
};

View File

@ -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>

View File

@ -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}
</>
);
};

View File

@ -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,
},
],
};