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:
parent
1f4febbd3c
commit
e10ad7257f
@ -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
|
||||
|
@ -116,6 +116,7 @@ const LineChartComponent: VFC<{
|
||||
return (
|
||||
<StyledContainer>
|
||||
<Line
|
||||
key={cover ? 'cover' : 'chart'}
|
||||
options={options}
|
||||
data={data}
|
||||
plugins={[customHighlightPlugin]}
|
||||
|
@ -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(
|
||||
|
@ -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}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
@ -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}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
@ -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}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
@ -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}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
@ -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(() => {
|
||||
|
@ -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(
|
||||
() => ({
|
||||
|
@ -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}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
@ -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>
|
||||
|
||||
|
@ -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'>
|
||||
|
||||
</StyledLoadingSkeleton>
|
||||
}
|
||||
/>
|
||||
</StyledUserCount>
|
||||
</StyledUserBox>
|
||||
<StyledCustomShadow />
|
||||
|
Loading…
Reference in New Issue
Block a user