Browse Source

ref(metrics): Loading states and request de-duplication (#74007)

Add proper loading states for virtual metrics in metrics explorer and
dashboard widgets.
Prevent requesting metrics meta and extraction rules before page filters
are initialized.
ArthurKnaus 8 months ago
parent
commit
fa610eed4f

+ 6 - 1
static/app/components/modals/metricWidgetViewerModal.tsx

@@ -4,6 +4,7 @@ import {css} from '@emotion/react';
 import type {ModalRenderProps} from 'sentry/actionCreators/modal';
 import {Button, LinkButton} from 'sentry/components/button';
 import ButtonBar from 'sentry/components/buttonBar';
+import LoadingIndicator from 'sentry/components/loadingIndicator';
 import {
   MetricWidgetTitle,
   type MetricWidgetTitleState,
@@ -55,7 +56,7 @@ function MetricWidgetViewerModal({
   dashboardFilters,
 }: Props) {
   const {selection} = usePageFilters();
-  const {resolveVirtualMRI, getVirtualMRIQuery} = useVirtualMetricsContext();
+  const {resolveVirtualMRI, getVirtualMRIQuery, isLoading} = useVirtualMetricsContext();
   const [userHasModified, setUserHasModified] = useState(false);
   const [displayType, setDisplayType] = useState(widget.displayType);
   const [metricQueries, setMetricQueries] = useState<DashboardMetricsQuery[]>(() =>
@@ -282,6 +283,10 @@ function MetricWidgetViewerModal({
 
   const {mri, aggregation, query, condition} = metricQueries[0];
 
+  if (isLoading) {
+    return <LoadingIndicator />;
+  }
+
   return (
     <Fragment>
       <OrganizationContext.Provider value={organization}>

+ 2 - 1
static/app/utils/metrics/useMetricsQuery.tsx

@@ -192,7 +192,7 @@ export function useMetricsQuery(
   enableRefetch = true
 ) {
   const organization = useOrganization();
-  const {resolveVirtualMRI} = useVirtualMetricsContext();
+  const {resolveVirtualMRI, isLoading} = useVirtualMetricsContext();
 
   const resolvedQueries = useMemo(
     () =>
@@ -236,6 +236,7 @@ export function useMetricsQuery(
       refetchOnReconnect: enableRefetch,
       refetchOnWindowFocus: enableRefetch,
       refetchInterval: false,
+      enabled: !isLoading,
     }
   );
 }

+ 3 - 7
static/app/utils/metrics/virtualMetricsContext.tsx

@@ -100,11 +100,11 @@ const EMPTY_ARRAY: never[] = [];
 
 export function VirtualMetricsContextProvider({children}: Props) {
   const organization = useOrganization();
-  const {selection} = usePageFilters();
+  const {selection, isReady} = usePageFilters();
 
   const {isLoading, data = EMPTY_ARRAY} = useApiQuery<MetricsExtractionRule[]>(
     getMetricsExtractionRulesApiKey(organization.slug, selection.projects),
-    {staleTime: 0}
+    {staleTime: 0, enabled: isReady}
   );
 
   const mriToVirtualMap = useMemo(() => createMRIToVirtualMap(data), [data]);
@@ -262,9 +262,5 @@ export function VirtualMetricsContextProvider({children}: Props) {
     ]
   );
 
-  return (
-    <Context.Provider value={contextValue}>
-      {isLoading ? null : children}
-    </Context.Provider>
-  );
+  return <Context.Provider value={contextValue}>{children}</Context.Provider>;
 }

+ 6 - 2
static/app/views/dashboards/metrics/widgetCard.tsx

@@ -65,7 +65,8 @@ export function MetricWidgetCard({
   renderErrorMessage,
   showContextMenu = true,
 }: Props) {
-  const {getVirtualMRIQuery} = useVirtualMetricsContext();
+  const {getVirtualMRIQuery, isLoading: isLoadingVirtualMetrics} =
+    useVirtualMetricsContext();
 
   const metricQueries = useMemo(
     () =>
@@ -84,7 +85,10 @@ export function MetricWidgetCard({
     [widget, dashboardFilters, getVirtualMRIQuery]
   );
 
-  const widgetMQL = useMemo(() => getWidgetTitle(metricQueries), [metricQueries]);
+  const widgetMQL = useMemo(
+    () => (isLoadingVirtualMetrics ? '' : getWidgetTitle(metricQueries)),
+    [isLoadingVirtualMetrics, metricQueries]
+  );
 
   const {interval: validatedInterval} = useMetricsIntervalOptions({
     // TODO: Figure out why this can be undefined

+ 9 - 2
static/app/views/metrics/context.tsx

@@ -239,10 +239,17 @@ export function MetricsContextProvider({children}: {children: React.ReactNode})
   const pageFilters = usePageFilters();
   const {data: metaCustom, isLoading: isMetaCustomLoading} = useVirtualizedMetricsMeta(
     pageFilters.selection,
-    ['custom']
+    ['custom'],
+    true,
+    pageFilters.isReady
   );
   const {data: metaPerformance, isLoading: isMetaPerformanceLoading} =
-    useVirtualizedMetricsMeta(pageFilters.selection, ['transactions', 'spans']);
+    useVirtualizedMetricsMeta(
+      pageFilters.selection,
+      ['transactions', 'spans'],
+      true,
+      pageFilters.isReady
+    );
   const isMultiChartMode = multiChartMode === 1;
   const firstCustomMetric = metaCustom[0]?.mri;
 

+ 8 - 3
static/app/views/metrics/layout.tsx

@@ -23,13 +23,14 @@ import {ProjectPageFilter} from 'sentry/components/organizations/projectPageFilt
 import {PageHeadingQuestionTooltip} from 'sentry/components/pageHeadingQuestionTooltip';
 import {t, tct} from 'sentry/locale';
 import {space} from 'sentry/styles/space';
-import type {Organization} from 'sentry/types';
+import type {Organization} from 'sentry/types/organization';
 import {trackAnalytics} from 'sentry/utils/analytics';
 import {METRICS_DOCS_URL} from 'sentry/utils/metrics/constants';
 import {
   hasCustomMetrics,
   hasCustomMetricsExtractionRules,
 } from 'sentry/utils/metrics/features';
+import {useVirtualMetricsContext} from 'sentry/utils/metrics/virtualMetricsContext';
 import useDismissAlert from 'sentry/utils/useDismissAlert';
 import {useLocalStorageState} from 'sentry/utils/useLocalStorageState';
 import useMedia from 'sentry/utils/useMedia';
@@ -73,6 +74,10 @@ export const MetricsLayout = memo(() => {
     hasPerformanceMetrics,
     isHasMetricsLoading,
   } = useMetricsContext();
+  const virtualMetrics = useVirtualMetricsContext();
+
+  const isLoading = isHasMetricsLoading || virtualMetrics.isLoading;
+
   const {activateSidebar} = useMetricsOnboardingSidebar();
   const {dismiss: emptyStateDismiss, isDismissed: isEmptyStateDismissed} =
     useDismissAlert({
@@ -174,7 +179,7 @@ export const MetricsLayout = memo(() => {
             )}
 
           {hasCustomMetricsExtractionRules(organization) ? (
-            !isHasMetricsLoading && hasSentCustomMetrics ? (
+            !isLoading && hasSentCustomMetrics ? (
               <MetricsStopIngestionAlert />
             ) : null
           ) : (
@@ -190,7 +195,7 @@ export const MetricsLayout = memo(() => {
             <IntervalSelect />
           </FilterContainer>
 
-          {isHasMetricsLoading ? (
+          {isLoading ? (
             <LoadingIndicator />
           ) : !showOnboardingPanel ? (
             <Fragment>