1
0
mirror of https://github.com/Unleash/unleash.git synced 2025-03-23 00:16:25 +01:00

Fix: insights loading (#6834)

Loading state for
- charts (placeholder data, animation)
- user stats - loading skeleton animation
- empty flags stats
- kept other "stat" widgets as-is, usually not visible
This commit is contained in:
Tymoteusz Czech 2024-04-15 09:46:56 +02:00 committed by GitHub
parent 1f4febbd3c
commit e10ad7257f
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
12 changed files with 97 additions and 33 deletions

View File

@ -1,4 +1,6 @@
import { ConditionallyRender } from '../common/ConditionallyRender/ConditionallyRender';
import type { VFC } from 'react';
import { Box, styled } from '@mui/material';
import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender';
import { Widget } from './components/Widget/Widget';
import { UserStats } from './componentsStat/UserStats/UserStats';
import { UsersChart } from './componentsChart/UsersChart/UsersChart';
@ -18,9 +20,7 @@ import type {
InstanceInsightsSchemaUsers,
} from 'openapi';
import type { GroupedDataByProject } from './hooks/useGroupedProjectTrends';
import { Box, styled } from '@mui/material';
import { allOption } from '../common/ProjectSelect/ProjectSelect';
import type { VFC } from 'react';
import { allOption } from 'component/common/ProjectSelect/ProjectSelect';
import { chartInfo } from './chart-info';
interface IChartsProps {
@ -107,6 +107,7 @@ export const InsightsCharts: VFC<IChartsProps> = ({
count={users.total}
active={users.active}
inactive={users.inactive}
isLoading={loading}
/>
</Widget>
}
@ -116,7 +117,10 @@ export const InsightsCharts: VFC<IChartsProps> = ({
? chartInfo.usersInProject
: chartInfo.avgUsersPerProject)}
>
<UserStats count={summary.averageUsers} />
<UserStats
count={summary.averageUsers}
isLoading={loading}
/>
</Widget>
}
/>
@ -134,6 +138,7 @@ export const InsightsCharts: VFC<IChartsProps> = ({
<ChartWidget {...chartInfo.usersPerProject}>
<UsersPerProjectChart
projectFlagTrends={groupedProjectsData}
isLoading={loading}
/>
</ChartWidget>
}
@ -144,6 +149,7 @@ export const InsightsCharts: VFC<IChartsProps> = ({
flagsPerUser={
showAllProjects ? getFlagsPerUser(flags, users) : ''
}
isLoading={loading}
/>
</Widget>
<ConditionallyRender
@ -160,6 +166,7 @@ export const InsightsCharts: VFC<IChartsProps> = ({
<ChartWidget {...chartInfo.flagsPerProject}>
<FlagsProjectChart
projectFlagTrends={groupedProjectsData}
isLoading={loading}
/>
</ChartWidget>
}
@ -180,6 +187,7 @@ export const InsightsCharts: VFC<IChartsProps> = ({
<ProjectHealthChart
projectFlagTrends={groupedProjectsData}
isAggregate={showAllProjects}
isLoading={loading}
/>
</ChartWidget>
<Widget {...chartInfo.medianTimeToProduction}>
@ -195,6 +203,7 @@ export const InsightsCharts: VFC<IChartsProps> = ({
<TimeToProductionChart
projectFlagTrends={groupedProjectsData}
isAggregate={showAllProjects}
isLoading={loading}
/>
</ChartWidget>
</StyledGrid>
@ -207,6 +216,7 @@ export const InsightsCharts: VFC<IChartsProps> = ({
metricsSummaryTrends={groupedMetricsData}
allDatapointsSorted={allMetricsDatapoints}
isAggregate={showAllProjects}
isLoading={loading}
/>
</Widget>
<Widget

View File

@ -116,6 +116,7 @@ const LineChartComponent: VFC<{
return (
<StyledContainer>
<Line
key={cover ? 'cover' : 'chart'}
options={options}
data={data}
plugins={[customHighlightPlugin]}

View File

@ -18,7 +18,7 @@ export const FlagsChart: VFC<IFlagsChartProps> = ({
isLoading,
}) => {
const theme = useTheme();
const notEnoughData = flagTrends.length < 2;
const notEnoughData = !isLoading && flagTrends.length < 2;
const placeholderData = usePlaceholderData({ fill: true, type: 'double' });
const data = useMemo(

View File

@ -13,10 +13,12 @@ interface IFlagsProjectChartProps {
projectFlagTrends: GroupedDataByProject<
InstanceInsightsSchema['projectFlagTrends']
>;
isLoading?: boolean;
}
export const FlagsProjectChart: VFC<IFlagsProjectChartProps> = ({
projectFlagTrends,
isLoading,
}) => {
const placeholderData = usePlaceholderData({
type: 'constant',
@ -24,20 +26,22 @@ export const FlagsProjectChart: VFC<IFlagsProjectChartProps> = ({
const data = useProjectChartData(projectFlagTrends);
const notEnoughData = useMemo(
() => (data.datasets.some((d) => d.data.length > 1) ? false : true),
[data],
() =>
!isLoading &&
(data.datasets.some((d) => d.data.length > 1) ? false : true),
[data, isLoading],
);
return (
<LineChart
data={notEnoughData ? placeholderData : data}
data={notEnoughData || isLoading ? placeholderData : data}
overrideOptions={{
parsing: {
yAxisKey: 'total',
xAxisKey: 'date',
},
}}
cover={notEnoughData ? <NotEnoughData /> : false}
cover={notEnoughData ? <NotEnoughData /> : isLoading}
/>
);
};

View File

@ -20,12 +20,14 @@ interface IMetricsSummaryChartProps {
>;
isAggregate?: boolean;
allDatapointsSorted: string[];
isLoading?: boolean;
}
export const MetricsSummaryChart: VFC<IMetricsSummaryChartProps> = ({
metricsSummaryTrends,
isAggregate,
allDatapointsSorted,
isLoading,
}) => {
const theme = useTheme();
const metricsSummary = useFilledMetricsSummary(
@ -33,8 +35,10 @@ export const MetricsSummaryChart: VFC<IMetricsSummaryChartProps> = ({
allDatapointsSorted,
);
const notEnoughData = useMemo(
() => !metricsSummary.datasets.some((d) => d.data.length > 1),
[metricsSummary],
() =>
!isLoading &&
!metricsSummary.datasets.some((d) => d.data.length > 1),
[metricsSummary, isLoading],
);
const placeholderData = usePlaceholderData();
@ -67,7 +71,7 @@ export const MetricsSummaryChart: VFC<IMetricsSummaryChartProps> = ({
return (
<LineChart
data={notEnoughData ? placeholderData : data}
data={notEnoughData || isLoading ? placeholderData : data}
TooltipComponent={MetricsSummaryTooltip}
overrideOptions={
notEnoughData
@ -79,7 +83,7 @@ export const MetricsSummaryChart: VFC<IMetricsSummaryChartProps> = ({
},
}
}
cover={notEnoughData ? <NotEnoughData /> : false}
cover={notEnoughData ? <NotEnoughData /> : isLoading}
/>
);
};

View File

@ -10,20 +10,24 @@ import {
} from 'component/insights/components/LineChart/LineChart';
import { useTheme } from '@mui/material';
import type { GroupedDataByProject } from 'component/insights/hooks/useGroupedProjectTrends';
import { usePlaceholderData } from 'component/insights/hooks/usePlaceholderData';
interface IProjectHealthChartProps {
projectFlagTrends: GroupedDataByProject<
InstanceInsightsSchema['projectFlagTrends']
>;
isAggregate?: boolean;
isLoading?: boolean;
}
export const ProjectHealthChart: VFC<IProjectHealthChartProps> = ({
projectFlagTrends,
isAggregate,
isLoading,
}) => {
const projectsData = useProjectChartData(projectFlagTrends);
const theme = useTheme();
const placeholderData = usePlaceholderData();
const aggregateHealthData = useMemo(() => {
const labels = Array.from(
@ -85,12 +89,19 @@ export const ProjectHealthChart: VFC<IProjectHealthChartProps> = ({
};
}, [projectsData, theme]);
const data = isAggregate ? aggregateHealthData : projectsData;
const aggregateOrProjectData = isAggregate
? aggregateHealthData
: projectsData;
const notEnoughData = useMemo(
() =>
projectsData.datasets.some((d) => d.data.length > 1) ? false : true,
[projectsData],
!isLoading &&
(projectsData.datasets.some((d) => d.data.length > 1)
? false
: true),
[projectsData, isLoading],
);
const data =
notEnoughData || isLoading ? placeholderData : aggregateOrProjectData;
return (
<LineChart
@ -104,7 +115,7 @@ export const ProjectHealthChart: VFC<IProjectHealthChartProps> = ({
parsing: { yAxisKey: 'health', xAxisKey: 'date' },
}
}
cover={notEnoughData ? <NotEnoughData /> : false}
cover={notEnoughData ? <NotEnoughData /> : isLoading}
/>
);
};

View File

@ -18,17 +18,21 @@ interface ITimeToProductionChartProps {
InstanceInsightsSchema['projectFlagTrends']
>;
isAggregate?: boolean;
isLoading?: boolean;
}
export const TimeToProductionChart: VFC<ITimeToProductionChartProps> = ({
projectFlagTrends,
isAggregate,
isLoading,
}) => {
const theme = useTheme();
const projectsDatasets = useProjectChartData(projectFlagTrends);
const notEnoughData = useMemo(
() => !projectsDatasets.datasets.some((d) => d.data.length > 1),
[projectsDatasets],
() =>
!isLoading &&
!projectsDatasets.datasets.some((d) => d.data.length > 1),
[projectsDatasets, isLoading],
);
const aggregatedPerDay = useMemo(() => {
@ -62,7 +66,7 @@ export const TimeToProductionChart: VFC<ITimeToProductionChartProps> = ({
const placeholderData = usePlaceholderData();
return (
<LineChart
data={notEnoughData ? placeholderData : data}
data={notEnoughData || isLoading ? placeholderData : data}
TooltipComponent={TimeToProductionTooltip}
overrideOptions={
notEnoughData
@ -74,7 +78,7 @@ export const TimeToProductionChart: VFC<ITimeToProductionChartProps> = ({
},
}
}
cover={notEnoughData ? <NotEnoughData /> : false}
cover={notEnoughData ? <NotEnoughData /> : isLoading}
/>
);
};

View File

@ -92,7 +92,7 @@ export const UpdatesPerEnvironmentTypeChart: VFC<
> = ({ environmentTypeTrends, isLoading }) => {
const theme = useTheme();
const getEnvironmentTypeColor = useEnvironmentTypeColor();
const notEnoughData = environmentTypeTrends?.length < 2;
const notEnoughData = !isLoading && environmentTypeTrends?.length < 2;
const placeholderData = usePlaceholderData({ fill: true, type: 'double' });
const data = useMemo(() => {

View File

@ -21,7 +21,7 @@ export const UsersChart: VFC<IUsersChartProps> = ({
}) => {
const showInactiveUsers = useUiFlag('showInactiveUsers');
const theme = useTheme();
const notEnoughData = userTrends.length < 2;
const notEnoughData = !isLoading && userTrends.length < 2;
const placeholderData = usePlaceholderData({ fill: true, type: 'rising' });
const data = useMemo(
() => ({

View File

@ -13,10 +13,12 @@ interface IUsersPerProjectChartProps {
projectFlagTrends: GroupedDataByProject<
InstanceInsightsSchema['projectFlagTrends']
>;
isLoading?: boolean;
}
export const UsersPerProjectChart: VFC<IUsersPerProjectChartProps> = ({
projectFlagTrends,
isLoading,
}) => {
const placeholderData = usePlaceholderData({
type: 'constant',
@ -24,20 +26,22 @@ export const UsersPerProjectChart: VFC<IUsersPerProjectChartProps> = ({
const data = useProjectChartData(projectFlagTrends);
const notEnoughData = useMemo(
() => (data.datasets.some((d) => d.data.length > 1) ? false : true),
[data],
() =>
!isLoading &&
(data.datasets.some((d) => d.data.length > 1) ? false : true),
[data, isLoading],
);
return (
<LineChart
data={notEnoughData ? placeholderData : data}
data={notEnoughData || isLoading ? placeholderData : data}
overrideOptions={{
parsing: {
yAxisKey: 'users',
xAxisKey: 'date',
},
}}
cover={notEnoughData ? <NotEnoughData /> : false}
cover={notEnoughData ? <NotEnoughData /> : isLoading}
/>
);
};

View File

@ -68,17 +68,21 @@ const StyledIcon = styled(Icon)(({ theme }) => ({
interface IFlagStatsProps {
count: number;
flagsPerUser?: string;
isLoading?: boolean;
}
export const FlagStats: React.FC<IFlagStatsProps> = ({
count,
flagsPerUser,
isLoading,
}) => {
return (
<>
<StyledRingContainer>
<StyledRing>
<StyledRingContent>{count}</StyledRingContent>
<StyledRingContent>
{isLoading ? '' : count}
</StyledRingContent>
</StyledRing>
</StyledRingContainer>

View File

@ -70,9 +70,21 @@ interface IUserStatsProps {
count: number;
active?: number;
inactive?: number;
isLoading?: boolean;
}
export const UserStats: FC<IUserStatsProps> = ({ count, active, inactive }) => {
const StyledLoadingSkeleton = styled(Box)(() => ({
'&:before': {
background: 'transparent',
},
}));
export const UserStats: FC<IUserStatsProps> = ({
count,
active,
inactive,
isLoading,
}) => {
const showInactiveUsers = useUiFlag('showInactiveUsers');
const showDistribution =
showInactiveUsers && active !== undefined && inactive !== undefined;
@ -83,9 +95,19 @@ export const UserStats: FC<IUserStatsProps> = ({ count, active, inactive }) => {
<StyledUserContainer>
<StyledUserBox>
<StyledUserCount variant='h2'>
{Number.parseInt(`${count}`, 10) === count
? count
: count.toFixed(2)}
<ConditionallyRender
condition={isLoading !== true}
show={
Number.parseInt(`${count}`, 10) === count
? count
: count.toFixed(2)
}
elseShow={
<StyledLoadingSkeleton className='skeleton'>
&nbsp;
</StyledLoadingSkeleton>
}
/>
</StyledUserCount>
</StyledUserBox>
<StyledCustomShadow />