From 45d48d12a939232123c1c8bdd14de4f3695b82b2 Mon Sep 17 00:00:00 2001 From: Thomas Heartman Date: Tue, 10 Jun 2025 11:35:32 +0200 Subject: [PATCH] Chore: add skeleton loaders for lifecycle trend numbers (#10103) Adds skeleton loading indicators for the lifecycle trend tile numbers: - total flag count - median stats In doing so, I have added the `data-loading` attribute to the PrettifyLargeNumber component (to avoid having to wrap it in a separate element for that alone), and have added refs to the InsightsSection component. The loading indicators look better in dark mode than in light mode, because they use the same background color as the text box in light mode, so only the big number is visible. There is a task in Linear to look into fixing this. image image --------- Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- .../PrettifyLargeNumber/PrettifyLargeNumber.tsx | 9 ++++++++- .../insights/sections/InsightsSection.tsx | 11 ++++++----- .../insights/sections/LifecycleInsights.tsx | 14 ++++++++------ 3 files changed, 22 insertions(+), 12 deletions(-) diff --git a/frontend/src/component/common/PrettifyLargeNumber/PrettifyLargeNumber.tsx b/frontend/src/component/common/PrettifyLargeNumber/PrettifyLargeNumber.tsx index 69e44ed196..200ef8d87d 100644 --- a/frontend/src/component/common/PrettifyLargeNumber/PrettifyLargeNumber.tsx +++ b/frontend/src/component/common/PrettifyLargeNumber/PrettifyLargeNumber.tsx @@ -19,6 +19,10 @@ interface IPrettifyLargeNumberProps { * @default 2 */ precision?: number; + /** + * Data attribute for loading state + */ + 'data-loading'?: string; } export const prettifyLargeNumber = @@ -34,12 +38,15 @@ export const PrettifyLargeNumber: FC = ({ value, threshold = 1_000_000, precision = 2, + 'data-loading': dataLoading, }) => { const prettyValue = prettifyLargeNumber(threshold, precision)(value); const showTooltip = value > threshold; const valueSpan = ( - {prettyValue} + + {prettyValue} + ); return ( diff --git a/frontend/src/component/insights/sections/InsightsSection.tsx b/frontend/src/component/insights/sections/InsightsSection.tsx index 392144284c..32d0e3da39 100644 --- a/frontend/src/component/insights/sections/InsightsSection.tsx +++ b/frontend/src/component/insights/sections/InsightsSection.tsx @@ -1,5 +1,5 @@ import { styled } from '@mui/material'; -import type { FC, PropsWithChildren, ReactNode } from 'react'; +import { forwardRef, type PropsWithChildren, type ReactNode } from 'react'; const StyledSection = styled('section')(({ theme }) => ({ display: 'flex', @@ -21,14 +21,15 @@ const SectionTitleRow = styled('div')(({ theme }) => ({ rowGap: theme.spacing(2), })); -export const InsightsSection: FC< +export const InsightsSection = forwardRef< + HTMLElement, PropsWithChildren<{ title: string; filters?: ReactNode }> -> = ({ title, children, filters: HeaderActions }) => ( - +>(({ title, children, filters: HeaderActions }, ref) => ( +

{title}

{HeaderActions}
{children}
-); +)); diff --git a/frontend/src/component/insights/sections/LifecycleInsights.tsx b/frontend/src/component/insights/sections/LifecycleInsights.tsx index 29244dd51a..ee9b5f1c33 100644 --- a/frontend/src/component/insights/sections/LifecycleInsights.tsx +++ b/frontend/src/component/insights/sections/LifecycleInsights.tsx @@ -13,6 +13,7 @@ import { } from 'component/common/PrettifyLargeNumber/PrettifyLargeNumber.tsx'; import { FeatureLifecycleStageIcon } from 'component/common/FeatureLifecycle/FeatureLifecycleStageIcon.tsx'; import { normalizeDays } from './normalize-days.ts'; +import useLoading from 'hooks/useLoading.ts'; type LifecycleTrend = { totalFlags: number; @@ -128,14 +129,17 @@ export const LifecycleInsights: FC = () => { stateConfig, ); - // todo: use data from the actual endpoint when we have something useful to return + const loadingLabel = 'lifecycle-trend-charts'; + // todo (lifecycleMetrics): use data from the actual endpoint when we have something useful to return const projects = state[`${statePrefix}project`]?.values ?? [allOption.id]; const { insights, loading } = useInsights(); + const loadingRef = useLoading(loading, `[data-loading="${loadingLabel}"]`); const { lifecycleTrends } = insights; return ( { {
Median time for flags currently in stage
-
+
{normalizeDays( data.averageTimeInStageDays, )} @@ -186,7 +191,7 @@ export const LifecycleInsights: FC = () => { Historical median time for flags in stage -
+
{normalizeDays( data.averageTimeInStageDays, )} @@ -247,9 +252,6 @@ const Chart: React.FC<{ stage: string; data: LifecycleTrend }> = ({ labels: { value: { formatter: (value, context) => { - // todo (lifecycleMetrics): use a nice - // formatter here, so that 1,000,000 - // flags are instead formatted as 1M if ( context.chart.legend ?.legendItems?.[1].hidden