Repository: getsentry/sentry
Pull request #89612: ref(insights): Refactor Resource Summary + Landing charts into separate modules
Author: login: billyvg
Labels: name: Scope: Frontend
description: Automatically applied to PRs that change frontend components
Created: 2025-04-15T15:16:32Z | Merged: 2025-04-22T17:47:21Z

Part of https://github.com/getsentry/sentry/issues/88560



Diff:
diff --git a/static/app/views/insights/browser/resources/components/charts/resourceLandingPageCharts.tsx b/static/app/views/insights/browser/resources/components/charts/resourceLandingPageCharts.tsx
index 437901eff9a0fc..cb186b085e06d2 100644
--- a/static/app/views/insights/browser/resources/components/charts/resourceLandingPageCharts.tsx
+++ b/static/app/views/insights/browser/resources/components/charts/resourceLandingPageCharts.tsx
@@ -1,83 +1,23 @@
 import styled from '@emotion/styled';
 
 import {space} from 'sentry/styles/space';
-import {EMPTY_OPTION_VALUE, MutableSearch} from 'sentry/utils/tokenizeSearch';
-import {InsightsLineChartWidget} from 'sentry/views/insights/common/components/insightsLineChartWidget';
-import {useSpanMetricsSeries} from 'sentry/views/insights/common/queries/useDiscoverSeries';
-import type {ModuleFilters} from 'sentry/views/insights/common/views/spans/types';
-import {
-  getDurationChartTitle,
-  getThroughputChartTitle,
-} from 'sentry/views/insights/common/views/spans/types';
-import {SpanMetricsField} from 'sentry/views/insights/types';
-
-const {SPAN_SELF_TIME, NORMALIZED_DESCRIPTION, SPAN_DOMAIN} = SpanMetricsField;
-
-type Props = {
-  appliedFilters: ModuleFilters;
-  extraQuery?: string[];
-};
-
-export function ResourceLandingPageCharts({appliedFilters, extraQuery}: Props) {
-  let query: string = buildDiscoverQueryConditions(appliedFilters);
-
-  if (extraQuery) {
-    query += ` ${extraQuery.join(' ')}`;
-  }
-
-  const {data, isPending, error} = useSpanMetricsSeries(
-    {
-      search: new MutableSearch(query),
-      yAxis: ['epm()', `avg(${SPAN_SELF_TIME})`],
-      transformAliasToInputFormat: true,
-    },
-    'api.starfish.span-time-charts'
-  );
+import {ResourceLandingDurationChartWidget} from 'sentry/views/insights/common/components/widgets/resourceLandingDurationChartWidget';
+import {ResourceLandingThroughputChartWidget} from 'sentry/views/insights/common/components/widgets/resourceLandingThroughputChartWidget';
 
+export function ResourceLandingPageCharts() {
   return (
     <ChartsContainer>
       <ChartsContainerItem>
-        <InsightsLineChartWidget
-          title={getThroughputChartTitle('resource')}
-          series={[data['epm()']]}
-          isLoading={isPending}
-          error={error}
-        />
+        <ResourceLandingThroughputChartWidget />
       </ChartsContainerItem>
 
       <ChartsContainerItem>
-        <InsightsLineChartWidget
-          title={getDurationChartTitle('resource')}
-          series={[data[`avg(${SPAN_SELF_TIME})`]]}
-          isLoading={isPending}
-          error={error}
-        />
+        <ResourceLandingDurationChartWidget />
       </ChartsContainerItem>
     </ChartsContainer>
   );
 }
 
-const SPAN_FILTER_KEYS = ['span_operation', SPAN_DOMAIN, 'action'];
-
-const buildDiscoverQueryConditions = (appliedFilters: ModuleFilters) => {
-  const result = Object.keys(appliedFilters)
-    .filter(key => SPAN_FILTER_KEYS.includes(key))
-    // @ts-expect-error TS(7053): Element implicitly has an 'any' type because expre... Remove this comment to see the full error message
-    .filter(key => Boolean(appliedFilters[key]))
-    .map(key => {
-      // @ts-expect-error TS(7053): Element implicitly has an 'any' type because expre... Remove this comment to see the full error message
-      const value = appliedFilters[key];
-      if (key === SPAN_DOMAIN && value === EMPTY_OPTION_VALUE) {
-        return [`!has:${SPAN_DOMAIN}`];
-      }
-      return `${key}:${value}`;
-    });
-
-  result.push(`has:${NORMALIZED_DESCRIPTION}`);
-
-  return result.join(' ');
-};
-
 const ChartsContainer = styled('div')`
   display: flex;
   flex-direction: row;
diff --git a/static/app/views/insights/browser/resources/components/charts/resourceSummaryCharts.tsx b/static/app/views/insights/browser/resources/components/charts/resourceSummaryCharts.tsx
index 18726cb67f0db9..8ca6b7a8a04a7d 100644
--- a/static/app/views/insights/browser/resources/components/charts/resourceSummaryCharts.tsx
+++ b/static/app/views/insights/browser/resources/components/charts/resourceSummaryCharts.tsx
@@ -1,105 +1,23 @@
 import {Fragment} from 'react';
 
-import {t} from 'sentry/locale';
-import {MutableSearch} from 'sentry/utils/tokenizeSearch';
-import {Referrer} from 'sentry/views/insights/browser/resources/referrer';
-import {DATA_TYPE, FIELD_ALIASES} from 'sentry/views/insights/browser/resources/settings';
-import {useResourceModuleFilters} from 'sentry/views/insights/browser/resources/utils/useResourceFilters';
-import {InsightsLineChartWidget} from 'sentry/views/insights/common/components/insightsLineChartWidget';
 import * as ModuleLayout from 'sentry/views/insights/common/components/moduleLayout';
-import {useSpanMetricsSeries} from 'sentry/views/insights/common/queries/useDiscoverSeries';
-import {
-  getDurationChartTitle,
-  getThroughputChartTitle,
-} from 'sentry/views/insights/common/views/spans/types';
-import {SpanMetricsField} from 'sentry/views/insights/types';
-
-const {
-  SPAN_SELF_TIME,
-  HTTP_RESPONSE_CONTENT_LENGTH,
-  HTTP_DECODED_RESPONSE_CONTENT_LENGTH,
-  HTTP_RESPONSE_TRANSFER_SIZE,
-  RESOURCE_RENDER_BLOCKING_STATUS,
-} = SpanMetricsField;
-
-function ResourceSummaryCharts(props: {groupId: string}) {
-  const filters = useResourceModuleFilters();
-
-  const mutableSearch = MutableSearch.fromQueryObject({
-    'span.group': props.groupId,
-    ...(filters[RESOURCE_RENDER_BLOCKING_STATUS]
-      ? {
-          [RESOURCE_RENDER_BLOCKING_STATUS]: filters[RESOURCE_RENDER_BLOCKING_STATUS],
-        }
-      : {}),
-    ...(filters[SpanMetricsField.USER_GEO_SUBREGION]
-      ? {
-          [SpanMetricsField.USER_GEO_SUBREGION]: `[${filters[SpanMetricsField.USER_GEO_SUBREGION].join(',')}]`,
-        }
-      : {}),
-  });
-
-  const {
-    data: spanMetricsSeriesData,
-    isPending: areSpanMetricsSeriesLoading,
-    error: spanMetricsSeriesError,
-  } = useSpanMetricsSeries(
-    {
-      search: mutableSearch,
-      yAxis: [
-        `epm()`,
-        `avg(${SPAN_SELF_TIME})`,
-        `avg(${HTTP_RESPONSE_CONTENT_LENGTH})`,
-        `avg(${HTTP_DECODED_RESPONSE_CONTENT_LENGTH})`,
-        `avg(${HTTP_RESPONSE_TRANSFER_SIZE})`,
-      ],
-      enabled: Boolean(props.groupId),
-      transformAliasToInputFormat: true,
-    },
-    Referrer.RESOURCE_SUMMARY_CHARTS
-  );
-
-  if (spanMetricsSeriesData) {
-    spanMetricsSeriesData[`avg(${HTTP_RESPONSE_TRANSFER_SIZE})`].lineStyle = {
-      type: 'dashed',
-    };
-    spanMetricsSeriesData[`avg(${HTTP_DECODED_RESPONSE_CONTENT_LENGTH})`].lineStyle = {
-      type: 'dashed',
-    };
-  }
+import {ResourceSummaryAverageSizeChartWidget} from 'sentry/views/insights/common/components/widgets/resourceSummaryAverageSizeChartWidget';
+import {ResourceSummaryDurationChartWidget} from 'sentry/views/insights/common/components/widgets/resourceSummaryDurationChartWidget';
+import {ResourceSummaryThroughputChartWidget} from 'sentry/views/insights/common/components/widgets/resourceSummaryThroughputChartWidget';
 
+function ResourceSummaryCharts() {
   return (
     <Fragment>
       <ModuleLayout.Third>
-        <InsightsLineChartWidget
-          title={getThroughputChartTitle('resource')}
-          series={[spanMetricsSeriesData?.[`epm()`]]}
-          isLoading={areSpanMetricsSeriesLoading}
-          error={spanMetricsSeriesError}
-        />
+        <ResourceSummaryThroughputChartWidget />
       </ModuleLayout.Third>
 
       <ModuleLayout.Third>
-        <InsightsLineChartWidget
-          title={getDurationChartTitle('resource')}
-          series={[spanMetricsSeriesData?.[`avg(${SPAN_SELF_TIME})`]]}
-          isLoading={areSpanMetricsSeriesLoading}
-          error={spanMetricsSeriesError}
-        />
+        <ResourceSummaryDurationChartWidget />
       </ModuleLayout.Third>
 
       <ModuleLayout.Third>
-        <InsightsLineChartWidget
-          title={t('Average %s Size', DATA_TYPE)}
-          series={[
-            spanMetricsSeriesData?.[`avg(${HTTP_DECODED_RESPONSE_CONTENT_LENGTH})`],
-            spanMetricsSeriesData?.[`avg(${HTTP_RESPONSE_TRANSFER_SIZE})`],
-            spanMetricsSeriesData?.[`avg(${HTTP_RESPONSE_CONTENT_LENGTH})`],
-          ]}
-          aliases={FIELD_ALIASES}
-          isLoading={areSpanMetricsSeriesLoading}
-          error={spanMetricsSeriesError}
-        />
+        <ResourceSummaryAverageSizeChartWidget />
       </ModuleLayout.Third>
     </Fragment>
   );
diff --git a/static/app/views/insights/browser/resources/components/resourceView.tsx b/static/app/views/insights/browser/resources/components/resourceView.tsx
index ddec211538d010..0abf456916a7bc 100644
--- a/static/app/views/insights/browser/resources/components/resourceView.tsx
+++ b/static/app/views/insights/browser/resources/components/resourceView.tsx
@@ -8,7 +8,6 @@ import {trackAnalytics} from 'sentry/utils/analytics';
 import {useLocation} from 'sentry/utils/useLocation';
 import {useNavigate} from 'sentry/utils/useNavigate';
 import useOrganization from 'sentry/utils/useOrganization';
-import {getResourceTypeFilter} from 'sentry/views/insights/browser/common/queries/useResourcesQuery';
 import RenderBlockingSelector from 'sentry/views/insights/browser/resources/components/renderBlockingSelector';
 import ResourceTable from 'sentry/views/insights/browser/resources/components/tables/resourceTable';
 import {
@@ -24,16 +23,13 @@ import {
 import {useResourceSort} from 'sentry/views/insights/browser/resources/utils/useResourceSort';
 import {QueryParameterNames} from 'sentry/views/insights/common/views/queryParameters';
 import {TransactionSelector} from 'sentry/views/insights/common/views/spans/selectors/transactionSelector';
-import type {ModuleFilters} from 'sentry/views/insights/common/views/spans/types';
 
 import {ResourceLandingPageCharts} from './charts/resourceLandingPageCharts';
 
 const {
   SPAN_OP: RESOURCE_TYPE,
-  SPAN_DOMAIN,
   TRANSACTION,
   RESOURCE_RENDER_BLOCKING_STATUS,
-  USER_GEO_SUBREGION,
 } = BrowserStarfishFields;
 
 type Option = {
@@ -45,25 +41,10 @@ function ResourceView() {
   const filters = useResourceModuleFilters();
   const sort = useResourceSort();
 
-  const spanTimeChartsFilters: ModuleFilters = {
-    'span.op': `[${DEFAULT_RESOURCE_TYPES.join(',')}]`,
-    ...(filters[SPAN_DOMAIN] ? {[SPAN_DOMAIN]: filters[SPAN_DOMAIN]} : {}),
-  };
-
-  const extraQuery = [
-    ...getResourceTypeFilter(undefined, DEFAULT_RESOURCE_TYPES),
-    ...(filters[USER_GEO_SUBREGION]
-      ? [`user.geo.subregion:[${filters[USER_GEO_SUBREGION].join(',')}]`]
-      : []),
-  ];
-
   return (
     <Fragment>
       <SpanTimeChartsContainer>
-        <ResourceLandingPageCharts
-          appliedFilters={spanTimeChartsFilters}
-          extraQuery={extraQuery}
-        />
+        <ResourceLandingPageCharts />
       </SpanTimeChartsContainer>
 
       <DropdownContainer>
diff --git a/static/app/views/insights/browser/resources/views/resourceSummaryPage.tsx b/static/app/views/insights/browser/resources/views/resourceSummaryPage.tsx
index 921d252fb2cf42..d8390dc864510e 100644
--- a/static/app/views/insights/browser/resources/views/resourceSummaryPage.tsx
+++ b/static/app/views/insights/browser/resources/views/resourceSummaryPage.tsx
@@ -155,7 +155,7 @@ function ResourceSummary() {
                 </ModuleLayout.Full>
               )}
 
-              <ResourceSummaryCharts groupId={groupId!} />
+              <ResourceSummaryCharts />
 
               <ModuleLayout.Full>
                 <ResourceSummaryTable />
diff --git a/static/app/views/insights/common/components/widgets/hooks/useResourceLandingSeries.tsx b/static/app/views/insights/common/components/widgets/hooks/useResourceLandingSeries.tsx
new file mode 100644
index 00000000000000..7392b688d115a5
--- /dev/null
+++ b/static/app/views/insights/common/components/widgets/hooks/useResourceLandingSeries.tsx
@@ -0,0 +1,68 @@
+import type {PageFilters} from 'sentry/types/core';
+import {EMPTY_OPTION_VALUE, MutableSearch} from 'sentry/utils/tokenizeSearch';
+import {getResourceTypeFilter} from 'sentry/views/insights/browser/common/queries/useResourcesQuery';
+import {DEFAULT_RESOURCE_TYPES} from 'sentry/views/insights/browser/resources/settings';
+import {useResourceModuleFilters} from 'sentry/views/insights/browser/resources/utils/useResourceFilters';
+import {useSpanMetricsSeries} from 'sentry/views/insights/common/queries/useDiscoverSeries';
+import type {ModuleFilters} from 'sentry/views/insights/common/views/spans/types';
+import {SpanMetricsField} from 'sentry/views/insights/types';
+
+const {NORMALIZED_DESCRIPTION, SPAN_DOMAIN, SPAN_SELF_TIME, USER_GEO_SUBREGION} =
+  SpanMetricsField;
+
+const SPAN_FILTER_KEYS = ['span_operation', SPAN_DOMAIN, 'action'];
+
+const buildDiscoverQueryConditions = (appliedFilters: ModuleFilters) => {
+  const result = Object.keys(appliedFilters)
+    .filter(key => SPAN_FILTER_KEYS.includes(key))
+    // @ts-expect-error TS(7053): Element implicitly has an 'any' type because expre... Remove this comment to see the full error message
+    .filter(key => Boolean(appliedFilters[key]))
+    .map(key => {
+      // @ts-expect-error TS(7053): Element implicitly has an 'any' type because expre... Remove this comment to see the full error message
+      const value = appliedFilters[key];
+      if (key === SPAN_DOMAIN && value === EMPTY_OPTION_VALUE) {
+        return [`!has:${SPAN_DOMAIN}`];
+      }
+      return `${key}:${value}`;
+    });
+
+  result.push(`has:${NORMALIZED_DESCRIPTION}`);
+
+  return result.join(' ');
+};
+
+interface Props {
+  pageFilters?: PageFilters;
+}
+
+export function useResourceLandingSeries(props: Props = {}) {
+  const filters = useResourceModuleFilters();
+
+  const spanTimeChartsFilters: ModuleFilters = {
+    'span.op': `[${DEFAULT_RESOURCE_TYPES.join(',')}]`,
+    ...(filters[SPAN_DOMAIN] ? {[SPAN_DOMAIN]: filters[SPAN_DOMAIN]} : {}),
+  };
+
+  const extraQuery = [
+    ...getResourceTypeFilter(undefined, DEFAULT_RESOURCE_TYPES),
+    ...(filters[USER_GEO_SUBREGION]
+      ? [`user.geo.subregion:[${filters[USER_GEO_SUBREGION].join(',')}]`]
+      : []),
+  ];
+
+  let query: string = buildDiscoverQueryConditions(spanTimeChartsFilters);
+
+  if (extraQuery.length) {
+    query += ` ${extraQuery.join(' ')}`;
+  }
+
+  return useSpanMetricsSeries(
+    {
+      search: new MutableSearch(query),
+      yAxis: ['epm()', `avg(${SPAN_SELF_TIME})`],
+      transformAliasToInputFormat: true,
+    },
+    'api.starfish.span-time-charts',
+    props.pageFilters
+  );
+}
diff --git a/static/app/views/insights/common/components/widgets/hooks/useResourceSummarySeries.tsx b/static/app/views/insights/common/components/widgets/hooks/useResourceSummarySeries.tsx
new file mode 100644
index 00000000000000..f8ad5a17334e6f
--- /dev/null
+++ b/static/app/views/insights/common/components/widgets/hooks/useResourceSummarySeries.tsx
@@ -0,0 +1,54 @@
+import type {PageFilters} from 'sentry/types/core';
+import {MutableSearch} from 'sentry/utils/tokenizeSearch';
+import {Referrer} from 'sentry/views/insights/browser/resources/referrer';
+import {useResourceModuleFilters} from 'sentry/views/insights/browser/resources/utils/useResourceFilters';
+import {useSpanMetricsSeries} from 'sentry/views/insights/common/queries/useDiscoverSeries';
+import {SpanMetricsField} from 'sentry/views/insights/types';
+
+const {
+  SPAN_SELF_TIME,
+  HTTP_RESPONSE_CONTENT_LENGTH,
+  HTTP_DECODED_RESPONSE_CONTENT_LENGTH,
+  HTTP_RESPONSE_TRANSFER_SIZE,
+  RESOURCE_RENDER_BLOCKING_STATUS,
+} = SpanMetricsField;
+
+interface Props {
+  groupId?: string;
+  pageFilters?: PageFilters;
+}
+
+export function useResourceSummarySeries({pageFilters, groupId}: Props = {}) {
+  const filters = useResourceModuleFilters();
+
+  const mutableSearch = MutableSearch.fromQueryObject({
+    'span.group': groupId,
+    ...(filters[RESOURCE_RENDER_BLOCKING_STATUS]
+      ? {
+          [RESOURCE_RENDER_BLOCKING_STATUS]: filters[RESOURCE_RENDER_BLOCKING_STATUS],
+        }
+      : {}),
+    ...(filters[SpanMetricsField.USER_GEO_SUBREGION]
+      ? {
+          [SpanMetricsField.USER_GEO_SUBREGION]: `[${filters[SpanMetricsField.USER_GEO_SUBREGION].join(',')}]`,
+        }
+      : {}),
+  });
+
+  return useSpanMetricsSeries(
+    {
+      search: mutableSearch,
+      yAxis: [
+        `epm()`,
+        `avg(${SPAN_SELF_TIME})`,
+        `avg(${HTTP_RESPONSE_CONTENT_LENGTH})`,
+        `avg(${HTTP_DECODED_RESPONSE_CONTENT_LENGTH})`,
+        `avg(${HTTP_RESPONSE_TRANSFER_SIZE})`,
+      ],
+      enabled: Boolean(groupId),
+      transformAliasToInputFormat: true,
+    },
+    Referrer.RESOURCE_SUMMARY_CHARTS,
+    pageFilters
+  );
+}
diff --git a/static/app/views/insights/common/components/widgets/resourceLandingDurationChartWidget.tsx b/static/app/views/insights/common/components/widgets/resourceLandingDurationChartWidget.tsx
new file mode 100644
index 00000000000000..9435c251386350
--- /dev/null
+++ b/static/app/views/insights/common/components/widgets/resourceLandingDurationChartWidget.tsx
@@ -0,0 +1,24 @@
+import {InsightsLineChartWidget} from 'sentry/views/insights/common/components/insightsLineChartWidget';
+import {useResourceLandingSeries} from 'sentry/views/insights/common/components/widgets/hooks/useResourceLandingSeries';
+import type {LoadableChartWidgetProps} from 'sentry/views/insights/common/components/widgets/types';
+import {getDurationChartTitle} from 'sentry/views/insights/common/views/spans/types';
+import {SpanMetricsField} from 'sentry/views/insights/types';
+
+const {SPAN_SELF_TIME} = SpanMetricsField;
+
+export function ResourceLandingDurationChartWidget(props: LoadableChartWidgetProps) {
+  const {data, isPending, error} = useResourceLandingSeries({
+    pageFilters: props.pageFilters,
+  });
+
+  return (
+    <InsightsLineChartWidget
+      {...props}
+      id="resourceLandingDurationChartWidget"
+      title={getDurationChartTitle('resource')}
+      series={[data[`avg(${SPAN_SELF_TIME})`]]}
+      isLoading={isPending}
+      error={error}
+    />
+  );
+}
diff --git a/static/app/views/insights/common/components/widgets/resourceLandingThroughputChartWidget.tsx b/static/app/views/insights/common/components/widgets/resourceLandingThroughputChartWidget.tsx
new file mode 100644
index 00000000000000..861a0e906820a2
--- /dev/null
+++ b/static/app/views/insights/common/components/widgets/resourceLandingThroughputChartWidget.tsx
@@ -0,0 +1,21 @@
+import {InsightsLineChartWidget} from 'sentry/views/insights/common/components/insightsLineChartWidget';
+import {useResourceLandingSeries} from 'sentry/views/insights/common/components/widgets/hooks/useResourceLandingSeries';
+import type {LoadableChartWidgetProps} from 'sentry/views/insights/common/components/widgets/types';
+import {getThroughputChartTitle} from 'sentry/views/insights/common/views/spans/types';
+
+export function ResourceLandingThroughputChartWidget(props: LoadableChartWidgetProps) {
+  const {data, isPending, error} = useResourceLandingSeries({
+    pageFilters: props.pageFilters,
+  });
+
+  return (
+    <InsightsLineChartWidget
+      {...props}
+      id="resourceLandingThroughputChartWidget"
+      title={getThroughputChartTitle('resource')}
+      series={[data['epm()']]}
+      isLoading={isPending}
+      error={error}
+    />
+  );
+}
diff --git a/static/app/views/insights/common/components/widgets/resourceSummaryAverageSizeChartWidget.tsx b/static/app/views/insights/common/components/widgets/resourceSummaryAverageSizeChartWidget.tsx
new file mode 100644
index 00000000000000..4162e78f081da3
--- /dev/null
+++ b/static/app/views/insights/common/components/widgets/resourceSummaryAverageSizeChartWidget.tsx
@@ -0,0 +1,46 @@
+import {t} from 'sentry/locale';
+import {useParams} from 'sentry/utils/useParams';
+import {DATA_TYPE, FIELD_ALIASES} from 'sentry/views/insights/browser/resources/settings';
+import {InsightsLineChartWidget} from 'sentry/views/insights/common/components/insightsLineChartWidget';
+import {useResourceSummarySeries} from 'sentry/views/insights/common/components/widgets/hooks/useResourceSummarySeries';
+import type {LoadableChartWidgetProps} from 'sentry/views/insights/common/components/widgets/types';
+import {SpanMetricsField} from 'sentry/views/insights/types';
+
+const {
+  HTTP_RESPONSE_CONTENT_LENGTH,
+  HTTP_DECODED_RESPONSE_CONTENT_LENGTH,
+  HTTP_RESPONSE_TRANSFER_SIZE,
+} = SpanMetricsField;
+
+export function ResourceSummaryAverageSizeChartWidget(props: LoadableChartWidgetProps) {
+  const {groupId} = useParams();
+
+  const {data, isPending, error} = useResourceSummarySeries({
+    groupId,
+    pageFilters: props.pageFilters,
+  });
+
+  if (data) {
+    data[`avg(${HTTP_RESPONSE_TRANSFER_SIZE})`].lineStyle = {
+      type: 'dashed',
+    };
+    data[`avg(${HTTP_DECODED_RESPONSE_CONTENT_LENGTH})`].lineStyle = {
+      type: 'dashed',
+    };
+  }
+  return (
+    <InsightsLineChartWidget
+      {...props}
+      id="resourceSummaryAverageSizeChartWidget"
+      title={t('Average %s Size', DATA_TYPE)}
+      series={[
+        data?.[`avg(${HTTP_DECODED_RESPONSE_CONTENT_LENGTH})`],
+        data?.[`avg(${HTTP_RESPONSE_TRANSFER_SIZE})`],
+        data?.[`avg(${HTTP_RESPONSE_CONTENT_LENGTH})`],
+      ]}
+      aliases={FIELD_ALIASES}
+      isLoading={isPending}
+      error={error}
+    />
+  );
+}
diff --git a/static/app/views/insights/common/components/widgets/resourceSummaryDurationChartWidget.tsx b/static/app/views/insights/common/components/widgets/resourceSummaryDurationChartWidget.tsx
new file mode 100644
index 00000000000000..ade1896b5ac4d5
--- /dev/null
+++ b/static/app/views/insights/common/components/widgets/resourceSummaryDurationChartWidget.tsx
@@ -0,0 +1,28 @@
+import {useParams} from 'sentry/utils/useParams';
+import {InsightsLineChartWidget} from 'sentry/views/insights/common/components/insightsLineChartWidget';
+import {useResourceSummarySeries} from 'sentry/views/insights/common/components/widgets/hooks/useResourceSummarySeries';
+import type {LoadableChartWidgetProps} from 'sentry/views/insights/common/components/widgets/types';
+import {getDurationChartTitle} from 'sentry/views/insights/common/views/spans/types';
+import {SpanMetricsField} from 'sentry/views/insights/types';
+
+const {SPAN_SELF_TIME} = SpanMetricsField;
+
+export function ResourceSummaryDurationChartWidget(props: LoadableChartWidgetProps) {
+  const {groupId} = useParams();
+
+  const {data, isPending, error} = useResourceSummarySeries({
+    groupId,
+    pageFilters: props.pageFilters,
+  });
+
+  return (
+    <InsightsLineChartWidget
+      {...props}
+      id="resourceSummaryDurationChartWidget"
+      title={getDurationChartTitle('resource')}
+      series={[data?.[`avg(${SPAN_SELF_TIME})`]]}
+      isLoading={isPending}
+      error={error}
+    />
+  );
+}
diff --git a/static/app/views/insights/common/components/widgets/resourceSummaryThroughputChartWidget.tsx b/static/app/views/insights/common/components/widgets/resourceSummaryThroughputChartWidget.tsx
new file mode 100644
index 00000000000000..ab49e3723eeca7
--- /dev/null
+++ b/static/app/views/insights/common/components/widgets/resourceSummaryThroughputChartWidget.tsx
@@ -0,0 +1,25 @@
+import {useParams} from 'sentry/utils/useParams';
+import {InsightsLineChartWidget} from 'sentry/views/insights/common/components/insightsLineChartWidget';
+import {useResourceSummarySeries} from 'sentry/views/insights/common/components/widgets/hooks/useResourceSummarySeries';
+import type {LoadableChartWidgetProps} from 'sentry/views/insights/common/components/widgets/types';
+import {getThroughputChartTitle} from 'sentry/views/insights/common/views/spans/types';
+
+export function ResourceSummaryThroughputChartWidget(props: LoadableChartWidgetProps) {
+  const {groupId} = useParams();
+
+  const {data, isPending, error} = useResourceSummarySeries({
+    groupId,
+    pageFilters: props.pageFilters,
+  });
+
+  return (
+    <InsightsLineChartWidget
+      {...props}
+      id="resourceSummaryThroughputChartWidget"
+      title={getThroughputChartTitle('resource')}
+      series={[data?.[`epm()`]]}
+      isLoading={isPending}
+      error={error}
+    />
+  );
+}


Reviews:
state: COMMENTED
author_association: NONE
submitted_at: 2025-04-15T19:41:10Z
state: COMMENTED
author_association: NONE
submitted_at: 2025-04-15T19:41:12Z
state: COMMENTED
author_association: NONE
submitted_at: 2025-04-15T19:41:14Z
state: COMMENTED
author_association: NONE
submitted_at: 2025-04-15T19:41:16Z
state: COMMENTED
author_association: NONE
submitted_at: 2025-04-15T19:41:18Z
user: login: billyvg
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-04-15T19:45:02Z
user: login: DominikB2014
state: APPROVED
author_association: CONTRIBUTOR
submitted_at: 2025-04-17T19:43:32Z
user: login: billyvg
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-04-22T15:00:17Z
user: login: billyvg
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-04-22T15:55:38Z

Review comments:
diff_hunk: @@ -45,25 +41,10 @@ function ResourceView() {
   const filters = useResourceModuleFilters();
   const sort = useResourceSort();
 
-  const spanTimeChartsFilters: ModuleFilters = {
-    'span.op': `[${DEFAULT_RESOURCE_TYPES.join(',')}]`,
-    ...(filters[SPAN_DOMAIN] ? {[SPAN_DOMAIN]: filters[SPAN_DOMAIN]} : {}),
-  };
-
-  const extraQuery = [
-    ...getResourceTypeFilter(undefined, DEFAULT_RESOURCE_TYPES),
-    ...(filters[USER_GEO_SUBREGION]
-      ? [`user.geo.subregion:[${filters[USER_GEO_SUBREGION].join(',')}]`]
-      : []),
-  ];
-
   return (
     <Fragment>
       <SpanTimeChartsContainer>
-        <ResourceLandingPageCharts
-          appliedFilters={spanTimeChartsFilters}
-          extraQuery={extraQuery}
path: static/app/views/insights/browser/resources/components/resourceView.tsx
user: login: billyvg
body: Moved these into the new widget
created_at: 2025-04-15T19:30:25Z
author_association: MEMBER
diff_hunk: @@ -155,7 +155,7 @@ function ResourceSummary() {
                 </ModuleLayout.Full>
               )}
 
-              <ResourceSummaryCharts groupId={groupId!} />
path: static/app/views/insights/browser/resources/views/resourceSummaryPage.tsx
user: login: billyvg
body: Moved into widget, `groupId` comes from URL params - currently drawer is directly tied to the chart/page where it was opened, so i think it's fine to have the widget grab this from the URL/router. if there was some world where we would render these charts separate independently, then we'd need to refactor to allow a way to specify the required properties that we'd need to add to the URL for linking and for rendering.
created_at: 2025-04-15T19:33:15Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,73 @@
+import type {PageFilters} from 'sentry/types/core';
+import {EMPTY_OPTION_VALUE, MutableSearch} from 'sentry/utils/tokenizeSearch';
+import {getResourceTypeFilter} from 'sentry/views/insights/browser/common/queries/useResourcesQuery';
+import {DEFAULT_RESOURCE_TYPES} from 'sentry/views/insights/browser/resources/settings';
+import {
+  BrowserStarfishFields,
+  useResourceModuleFilters,
+} from 'sentry/views/insights/browser/resources/utils/useResourceFilters';
+import {useSpanMetricsSeries} from 'sentry/views/insights/common/queries/useDiscoverSeries';
+import type {ModuleFilters} from 'sentry/views/insights/common/views/spans/types';
+import {SpanMetricsField} from 'sentry/views/insights/types';
+
+const {SPAN_SELF_TIME, NORMALIZED_DESCRIPTION, SPAN_DOMAIN} = SpanMetricsField;
+const {SPAN_DOMAIN: STARFISH_SPAN_DOMAIN, USER_GEO_SUBREGION} = BrowserStarfishFields;
path: static/app/views/insights/common/components/widgets/hooks/useResourceLandingSeries.tsx
user: login: billyvg
body: had to alias `BrowserStarfishFields.SPAN_DOMAIN` since we moved these down into the new hook and now there are 2 `SPAN_DOMAIN`s in scope.
created_at: 2025-04-15T19:34:08Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,73 @@
+import type {PageFilters} from 'sentry/types/core';
+import {EMPTY_OPTION_VALUE, MutableSearch} from 'sentry/utils/tokenizeSearch';
+import {getResourceTypeFilter} from 'sentry/views/insights/browser/common/queries/useResourcesQuery';
+import {DEFAULT_RESOURCE_TYPES} from 'sentry/views/insights/browser/resources/settings';
+import {
+  BrowserStarfishFields,
+  useResourceModuleFilters,
+} from 'sentry/views/insights/browser/resources/utils/useResourceFilters';
+import {useSpanMetricsSeries} from 'sentry/views/insights/common/queries/useDiscoverSeries';
+import type {ModuleFilters} from 'sentry/views/insights/common/views/spans/types';
+import {SpanMetricsField} from 'sentry/views/insights/types';
+
+const {SPAN_SELF_TIME, NORMALIZED_DESCRIPTION, SPAN_DOMAIN} = SpanMetricsField;
+const {SPAN_DOMAIN: STARFISH_SPAN_DOMAIN, USER_GEO_SUBREGION} = BrowserStarfishFields;
+
+const SPAN_FILTER_KEYS = ['span_operation', SPAN_DOMAIN, 'action'];
+
+const buildDiscoverQueryConditions = (appliedFilters: ModuleFilters) => {
+  const result = Object.keys(appliedFilters)
+    .filter(key => SPAN_FILTER_KEYS.includes(key))
+    // @ts-expect-error TS(7053): Element implicitly has an 'any' type because expre... Remove this comment to see the full error message
+    .filter(key => Boolean(appliedFilters[key]))
+    .map(key => {
+      // @ts-expect-error TS(7053): Element implicitly has an 'any' type because expre... Remove this comment to see the full error message
+      const value = appliedFilters[key];
+      if (key === SPAN_DOMAIN && value === EMPTY_OPTION_VALUE) {
+        return [`!has:${SPAN_DOMAIN}`];
+      }
+      return `${key}:${value}`;
+    });
+
+  result.push(`has:${NORMALIZED_DESCRIPTION}`);
+
+  return result.join(' ');
+};
+
+interface Props {
+  pageFilters?: PageFilters;
+}
+
+export function useResourceLandingSeries(props: Props = {}) {
+  const filters = useResourceModuleFilters();
+
+  const spanTimeChartsFilters: ModuleFilters = {
+    'span.op': `[${DEFAULT_RESOURCE_TYPES.join(',')}]`,
+    ...(filters[STARFISH_SPAN_DOMAIN]
+      ? {[STARFISH_SPAN_DOMAIN]: filters[STARFISH_SPAN_DOMAIN]}
+      : {}),
+  };
+
+  const extraQuery = [
+    ...getResourceTypeFilter(undefined, DEFAULT_RESOURCE_TYPES),
+    ...(filters[USER_GEO_SUBREGION]
+      ? [`user.geo.subregion:[${filters[USER_GEO_SUBREGION].join(',')}]`]
path: static/app/views/insights/common/components/widgets/hooks/useResourceLandingSeries.tsx
body: Consider adding error handling for the case where filters[USER_GEO_SUBREGION] exists but is empty. The current code assumes it has values when joining with commas which could lead to invalid query construction.
created_at: 2025-04-15T19:41:10Z
author_association: NONE
diff_hunk: @@ -0,0 +1,73 @@
+import type {PageFilters} from 'sentry/types/core';
+import {EMPTY_OPTION_VALUE, MutableSearch} from 'sentry/utils/tokenizeSearch';
+import {getResourceTypeFilter} from 'sentry/views/insights/browser/common/queries/useResourcesQuery';
+import {DEFAULT_RESOURCE_TYPES} from 'sentry/views/insights/browser/resources/settings';
+import {
+  BrowserStarfishFields,
+  useResourceModuleFilters,
+} from 'sentry/views/insights/browser/resources/utils/useResourceFilters';
+import {useSpanMetricsSeries} from 'sentry/views/insights/common/queries/useDiscoverSeries';
+import type {ModuleFilters} from 'sentry/views/insights/common/views/spans/types';
+import {SpanMetricsField} from 'sentry/views/insights/types';
+
+const {SPAN_SELF_TIME, NORMALIZED_DESCRIPTION, SPAN_DOMAIN} = SpanMetricsField;
+const {SPAN_DOMAIN: STARFISH_SPAN_DOMAIN, USER_GEO_SUBREGION} = BrowserStarfishFields;
+
+const SPAN_FILTER_KEYS = ['span_operation', SPAN_DOMAIN, 'action'];
+
+const buildDiscoverQueryConditions = (appliedFilters: ModuleFilters) => {
+  const result = Object.keys(appliedFilters)
+    .filter(key => SPAN_FILTER_KEYS.includes(key))
+    // @ts-expect-error TS(7053): Element implicitly has an 'any' type because expre... Remove this comment to see the full error message
+    .filter(key => Boolean(appliedFilters[key]))
+    .map(key => {
+      // @ts-expect-error TS(7053): Element implicitly has an 'any' type because expre... Remove this comment to see the full error message
+      const value = appliedFilters[key];
+      if (key === SPAN_DOMAIN && value === EMPTY_OPTION_VALUE) {
+        return [`!has:${SPAN_DOMAIN}`];
+      }
+      return `${key}:${value}`;
+    });
+
+  result.push(`has:${NORMALIZED_DESCRIPTION}`);
+
+  return result.join(' ');
path: static/app/views/insights/common/components/widgets/hooks/useResourceLandingSeries.tsx
body: The buildDiscoverQueryConditions function has unsafe type assertions with @ts-expect-error. Consider adding proper type guards or interfaces to handle the ModuleFilters type safely.
created_at: 2025-04-15T19:41:12Z
author_association: NONE
diff_hunk: @@ -0,0 +1,46 @@
+import {t} from 'sentry/locale';
+import {useParams} from 'sentry/utils/useParams';
+import {DATA_TYPE, FIELD_ALIASES} from 'sentry/views/insights/browser/resources/settings';
+import {InsightsLineChartWidget} from 'sentry/views/insights/common/components/insightsLineChartWidget';
+import {useResourceSummarySeries} from 'sentry/views/insights/common/components/widgets/hooks/useResourceSummarySeries';
+import type {LoadableChartWidgetProps} from 'sentry/views/insights/common/components/widgets/types';
+import {SpanMetricsField} from 'sentry/views/insights/types';
+
+const {
+  HTTP_RESPONSE_CONTENT_LENGTH,
+  HTTP_DECODED_RESPONSE_CONTENT_LENGTH,
+  HTTP_RESPONSE_TRANSFER_SIZE,
+} = SpanMetricsField;
+
+export function ResourceSummaryAverageSizeChartWidget(props: LoadableChartWidgetProps) {
+  const {groupId} = useParams();
+
+  const {data, isPending, error} = useResourceSummarySeries({
+    groupId,
+    pageFilters: props.pageFilters,
+  });
+
+  if (data) {
+    data[`avg(${HTTP_RESPONSE_TRANSFER_SIZE})`].lineStyle = {
+      type: 'dashed',
+    };
+    data[`avg(${HTTP_DECODED_RESPONSE_CONTENT_LENGTH})`].lineStyle = {
+      type: 'dashed',
+    };
path: static/app/views/insights/common/components/widgets/resourceSummaryAverageSizeChartWidget.tsx
body: Line style modifications directly mutate the data object. It's better to create a new transformed series object to maintain data immutability.
created_at: 2025-04-15T19:41:14Z
author_association: NONE
diff_hunk: @@ -0,0 +1,54 @@
+import type {PageFilters} from 'sentry/types/core';
+import {MutableSearch} from 'sentry/utils/tokenizeSearch';
+import {Referrer} from 'sentry/views/insights/browser/resources/referrer';
+import {useResourceModuleFilters} from 'sentry/views/insights/browser/resources/utils/useResourceFilters';
+import {useSpanMetricsSeries} from 'sentry/views/insights/common/queries/useDiscoverSeries';
+import {SpanMetricsField} from 'sentry/views/insights/types';
+
+const {
+  SPAN_SELF_TIME,
+  HTTP_RESPONSE_CONTENT_LENGTH,
+  HTTP_DECODED_RESPONSE_CONTENT_LENGTH,
+  HTTP_RESPONSE_TRANSFER_SIZE,
+  RESOURCE_RENDER_BLOCKING_STATUS,
+} = SpanMetricsField;
+
+interface Props {
+  groupId?: string;
+  pageFilters?: PageFilters;
+}
+
+export function useResourceSummarySeries({pageFilters, groupId}: Props = {}) {
+  const filters = useResourceModuleFilters();
+
+  const mutableSearch = MutableSearch.fromQueryObject({
+    'span.group': groupId,
+    ...(filters[RESOURCE_RENDER_BLOCKING_STATUS]
+      ? {
+          [RESOURCE_RENDER_BLOCKING_STATUS]: filters[RESOURCE_RENDER_BLOCKING_STATUS],
+        }
+      : {}),
+    ...(filters[SpanMetricsField.USER_GEO_SUBREGION]
+      ? {
+          [SpanMetricsField.USER_GEO_SUBREGION]: `[${filters[SpanMetricsField.USER_GEO_SUBREGION].join(',')}]`,
+        }
+      : {}),
+  });
+
+  return useSpanMetricsSeries(
+    {
+      search: mutableSearch,
+      yAxis: [
+        `epm()`,
+        `avg(${SPAN_SELF_TIME})`,
+        `avg(${HTTP_RESPONSE_CONTENT_LENGTH})`,
+        `avg(${HTTP_DECODED_RESPONSE_CONTENT_LENGTH})`,
+        `avg(${HTTP_RESPONSE_TRANSFER_SIZE})`,
path: static/app/views/insights/common/components/widgets/hooks/useResourceSummarySeries.tsx
body: The hook's enabled condition only depends on groupId. Consider making this more flexible by allowing the consumer to provide an enabled flag or additional conditions.
created_at: 2025-04-15T19:41:16Z
author_association: NONE
diff_hunk: @@ -0,0 +1,24 @@
+import {InsightsLineChartWidget} from 'sentry/views/insights/common/components/insightsLineChartWidget';
+import {useResourceLandingSeries} from 'sentry/views/insights/common/components/widgets/hooks/useResourceLandingSeries';
+import type {LoadableChartWidgetProps} from 'sentry/views/insights/common/components/widgets/types';
+import {getDurationChartTitle} from 'sentry/views/insights/common/views/spans/types';
+import {SpanMetricsField} from 'sentry/views/insights/types';
+
+const {SPAN_SELF_TIME} = SpanMetricsField;
+
+export function ResourceLandingDurationChartWidget(props: LoadableChartWidgetProps) {
+  const {data, isPending, error} = useResourceLandingSeries({
+    pageFilters: props.pageFilters,
+  });
+
+  return (
+    <InsightsLineChartWidget
+      {...props}
+      id="resourceLandingDurationChartWidget"
+      title={getDurationChartTitle('resource')}
+      series={[data[`avg(${SPAN_SELF_TIME})`]]}
+      isLoading={isPending}
+      error={error}
path: static/app/views/insights/common/components/widgets/resourceLandingDurationChartWidget.tsx
body: Add a useCallback for the series array construction to prevent unnecessary re-renders in the InsightsLineChartWidget component.
created_at: 2025-04-15T19:41:18Z
author_association: NONE
diff_hunk: @@ -0,0 +1,73 @@
+import type {PageFilters} from 'sentry/types/core';
+import {EMPTY_OPTION_VALUE, MutableSearch} from 'sentry/utils/tokenizeSearch';
+import {getResourceTypeFilter} from 'sentry/views/insights/browser/common/queries/useResourcesQuery';
+import {DEFAULT_RESOURCE_TYPES} from 'sentry/views/insights/browser/resources/settings';
+import {
+  BrowserStarfishFields,
+  useResourceModuleFilters,
+} from 'sentry/views/insights/browser/resources/utils/useResourceFilters';
+import {useSpanMetricsSeries} from 'sentry/views/insights/common/queries/useDiscoverSeries';
+import type {ModuleFilters} from 'sentry/views/insights/common/views/spans/types';
+import {SpanMetricsField} from 'sentry/views/insights/types';
+
+const {SPAN_SELF_TIME, NORMALIZED_DESCRIPTION, SPAN_DOMAIN} = SpanMetricsField;
+const {SPAN_DOMAIN: STARFISH_SPAN_DOMAIN, USER_GEO_SUBREGION} = BrowserStarfishFields;
+
+const SPAN_FILTER_KEYS = ['span_operation', SPAN_DOMAIN, 'action'];
+
+const buildDiscoverQueryConditions = (appliedFilters: ModuleFilters) => {
+  const result = Object.keys(appliedFilters)
+    .filter(key => SPAN_FILTER_KEYS.includes(key))
+    // @ts-expect-error TS(7053): Element implicitly has an 'any' type because expre... Remove this comment to see the full error message
+    .filter(key => Boolean(appliedFilters[key]))
+    .map(key => {
+      // @ts-expect-error TS(7053): Element implicitly has an 'any' type because expre... Remove this comment to see the full error message
+      const value = appliedFilters[key];
+      if (key === SPAN_DOMAIN && value === EMPTY_OPTION_VALUE) {
+        return [`!has:${SPAN_DOMAIN}`];
+      }
+      return `${key}:${value}`;
+    });
+
+  result.push(`has:${NORMALIZED_DESCRIPTION}`);
+
+  return result.join(' ');
+};
+
+interface Props {
+  pageFilters?: PageFilters;
+}
+
+export function useResourceLandingSeries(props: Props = {}) {
+  const filters = useResourceModuleFilters();
+
+  const spanTimeChartsFilters: ModuleFilters = {
+    'span.op': `[${DEFAULT_RESOURCE_TYPES.join(',')}]`,
+    ...(filters[STARFISH_SPAN_DOMAIN]
+      ? {[STARFISH_SPAN_DOMAIN]: filters[STARFISH_SPAN_DOMAIN]}
+      : {}),
+  };
+
+  const extraQuery = [
+    ...getResourceTypeFilter(undefined, DEFAULT_RESOURCE_TYPES),
+    ...(filters[USER_GEO_SUBREGION]
+      ? [`user.geo.subregion:[${filters[USER_GEO_SUBREGION].join(',')}]`]
path: static/app/views/insights/common/components/widgets/hooks/useResourceLandingSeries.tsx
user: login: billyvg
body: We check that it's not empty in the ternary
created_at: 2025-04-15T19:42:05Z
author_association: MEMBER
diff_hunk: @@ -1,105 +1,23 @@
 import {Fragment} from 'react';
 
-import {t} from 'sentry/locale';
-import {MutableSearch} from 'sentry/utils/tokenizeSearch';
-import {Referrer} from 'sentry/views/insights/browser/resources/referrer';
-import {DATA_TYPE, FIELD_ALIASES} from 'sentry/views/insights/browser/resources/settings';
-import {useResourceModuleFilters} from 'sentry/views/insights/browser/resources/utils/useResourceFilters';
-import {InsightsLineChartWidget} from 'sentry/views/insights/common/components/insightsLineChartWidget';
 import * as ModuleLayout from 'sentry/views/insights/common/components/moduleLayout';
-import {useSpanMetricsSeries} from 'sentry/views/insights/common/queries/useDiscoverSeries';
-import {
-  getDurationChartTitle,
-  getThroughputChartTitle,
-} from 'sentry/views/insights/common/views/spans/types';
-import {SpanMetricsField} from 'sentry/views/insights/types';
-
-const {
-  SPAN_SELF_TIME,
-  HTTP_RESPONSE_CONTENT_LENGTH,
-  HTTP_DECODED_RESPONSE_CONTENT_LENGTH,
-  HTTP_RESPONSE_TRANSFER_SIZE,
-  RESOURCE_RENDER_BLOCKING_STATUS,
-} = SpanMetricsField;
-
-function ResourceSummaryCharts(props: {groupId: string}) {
-  const filters = useResourceModuleFilters();
-
-  const mutableSearch = MutableSearch.fromQueryObject({
-    'span.group': props.groupId,
-    ...(filters[RESOURCE_RENDER_BLOCKING_STATUS]
-      ? {
-          [RESOURCE_RENDER_BLOCKING_STATUS]: filters[RESOURCE_RENDER_BLOCKING_STATUS],
-        }
-      : {}),
-    ...(filters[SpanMetricsField.USER_GEO_SUBREGION]
-      ? {
-          [SpanMetricsField.USER_GEO_SUBREGION]: `[${filters[SpanMetricsField.USER_GEO_SUBREGION].join(',')}]`,
-        }
-      : {}),
-  });
-
-  const {
-    data: spanMetricsSeriesData,
-    isPending: areSpanMetricsSeriesLoading,
-    error: spanMetricsSeriesError,
-  } = useSpanMetricsSeries(
-    {
-      search: mutableSearch,
-      yAxis: [
-        `epm()`,
-        `avg(${SPAN_SELF_TIME})`,
-        `avg(${HTTP_RESPONSE_CONTENT_LENGTH})`,
-        `avg(${HTTP_DECODED_RESPONSE_CONTENT_LENGTH})`,
-        `avg(${HTTP_RESPONSE_TRANSFER_SIZE})`,
-      ],
-      enabled: Boolean(props.groupId),
-      transformAliasToInputFormat: true,
-    },
-    Referrer.RESOURCE_SUMMARY_CHARTS
-  );
-
-  if (spanMetricsSeriesData) {
-    spanMetricsSeriesData[`avg(${HTTP_RESPONSE_TRANSFER_SIZE})`].lineStyle = {
-      type: 'dashed',
-    };
-    spanMetricsSeriesData[`avg(${HTTP_DECODED_RESPONSE_CONTENT_LENGTH})`].lineStyle = {
-      type: 'dashed',
-    };
-  }
path: static/app/views/insights/browser/resources/components/charts/resourceSummaryCharts.tsx
user: login: billyvg
body: All 3 charts shared the data fetching code, but in [the new charts](https://github.com/getsentry/sentry/pull/89612/files#diff-a09e77212e8b79b7832e5832a92dd583e35a329a8a4ff6203137f7f2cd5ff381R23-R30) only the AverageSeries chart has this code since it is the only chart that uses these series (IIUC).
created_at: 2025-04-15T19:44:45Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,73 @@
+import type {PageFilters} from 'sentry/types/core';
+import {EMPTY_OPTION_VALUE, MutableSearch} from 'sentry/utils/tokenizeSearch';
+import {getResourceTypeFilter} from 'sentry/views/insights/browser/common/queries/useResourcesQuery';
+import {DEFAULT_RESOURCE_TYPES} from 'sentry/views/insights/browser/resources/settings';
+import {
+  BrowserStarfishFields,
+  useResourceModuleFilters,
+} from 'sentry/views/insights/browser/resources/utils/useResourceFilters';
+import {useSpanMetricsSeries} from 'sentry/views/insights/common/queries/useDiscoverSeries';
+import type {ModuleFilters} from 'sentry/views/insights/common/views/spans/types';
+import {SpanMetricsField} from 'sentry/views/insights/types';
+
+const {SPAN_SELF_TIME, NORMALIZED_DESCRIPTION, SPAN_DOMAIN} = SpanMetricsField;
+const {SPAN_DOMAIN: STARFISH_SPAN_DOMAIN, USER_GEO_SUBREGION} = BrowserStarfishFields;
path: static/app/views/insights/common/components/widgets/hooks/useResourceLandingSeries.tsx
user: login: DominikB2014
body: `BrowserStarfishFields` is in the process of being deprecated, could we just grab all of these fields from `SpanMetricsFeild` ?
created_at: 2025-04-17T19:35:11Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,73 @@
+import type {PageFilters} from 'sentry/types/core';
+import {EMPTY_OPTION_VALUE, MutableSearch} from 'sentry/utils/tokenizeSearch';
+import {getResourceTypeFilter} from 'sentry/views/insights/browser/common/queries/useResourcesQuery';
+import {DEFAULT_RESOURCE_TYPES} from 'sentry/views/insights/browser/resources/settings';
+import {
+  BrowserStarfishFields,
+  useResourceModuleFilters,
+} from 'sentry/views/insights/browser/resources/utils/useResourceFilters';
+import {useSpanMetricsSeries} from 'sentry/views/insights/common/queries/useDiscoverSeries';
+import type {ModuleFilters} from 'sentry/views/insights/common/views/spans/types';
+import {SpanMetricsField} from 'sentry/views/insights/types';
+
+const {SPAN_SELF_TIME, NORMALIZED_DESCRIPTION, SPAN_DOMAIN} = SpanMetricsField;
+const {SPAN_DOMAIN: STARFISH_SPAN_DOMAIN, USER_GEO_SUBREGION} = BrowserStarfishFields;
path: static/app/views/insights/common/components/widgets/hooks/useResourceLandingSeries.tsx
user: login: billyvg
body: @DominikB2014 Oops, should have looked into the enums... updated!
created_at: 2025-04-22T15:00:17Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,68 @@
+import type {PageFilters} from 'sentry/types/core';
+import {EMPTY_OPTION_VALUE, MutableSearch} from 'sentry/utils/tokenizeSearch';
+import {getResourceTypeFilter} from 'sentry/views/insights/browser/common/queries/useResourcesQuery';
+import {DEFAULT_RESOURCE_TYPES} from 'sentry/views/insights/browser/resources/settings';
+import {useResourceModuleFilters} from 'sentry/views/insights/browser/resources/utils/useResourceFilters';
+import {useSpanMetricsSeries} from 'sentry/views/insights/common/queries/useDiscoverSeries';
+import type {ModuleFilters} from 'sentry/views/insights/common/views/spans/types';
+import {SpanMetricsField} from 'sentry/views/insights/types';
+
+const {NORMALIZED_DESCRIPTION, SPAN_DOMAIN, SPAN_SELF_TIME, USER_GEO_SUBREGION} =
+  SpanMetricsField;
+
+const SPAN_FILTER_KEYS = ['span_operation', SPAN_DOMAIN, 'action'];
+
+const buildDiscoverQueryConditions = (appliedFilters: ModuleFilters) => {
+  const result = Object.keys(appliedFilters)
+    .filter(key => SPAN_FILTER_KEYS.includes(key))
+    // @ts-expect-error TS(7053): Element implicitly has an 'any' type because expre... Remove this comment to see the full error message
+    .filter(key => Boolean(appliedFilters[key]))
+    .map(key => {
+      // @ts-expect-error TS(7053): Element implicitly has an 'any' type because expre... Remove this comment to see the full error message
+      const value = appliedFilters[key];
+      if (key === SPAN_DOMAIN && value === EMPTY_OPTION_VALUE) {
+        return [`!has:${SPAN_DOMAIN}`];
+      }
+      return `${key}:${value}`;
+    });
+
+  result.push(`has:${NORMALIZED_DESCRIPTION}`);
+
+  return result.join(' ');
+};
+
+interface Props {
+  pageFilters?: PageFilters;
+}
+
+export function useResourceLandingSeries(props: Props = {}) {
+  const filters = useResourceModuleFilters();
+
+  const spanTimeChartsFilters: ModuleFilters = {
+    'span.op': `[${DEFAULT_RESOURCE_TYPES.join(',')}]`,
+    ...(filters[SPAN_DOMAIN] ? {[SPAN_DOMAIN]: filters[SPAN_DOMAIN]} : {}),
+  };
+
+  const extraQuery = [
+    ...getResourceTypeFilter(undefined, DEFAULT_RESOURCE_TYPES),
+    ...(filters[USER_GEO_SUBREGION]
+      ? [`user.geo.subregion:[${filters[USER_GEO_SUBREGION].join(',')}]`]
+      : []),
+  ];
+
+  let query: string = buildDiscoverQueryConditions(spanTimeChartsFilters);
+
+  if (extraQuery) {
+    query += ` ${extraQuery.join(' ')}`;
+  }
path: static/app/views/insights/common/components/widgets/hooks/useResourceLandingSeries.tsx
user: login: billyvg
body: Since `extraQuery` was moved into this hook, it will always be defined... instead, let's check for length.

```suggestion
  if (extraQuery.length) {
    query += ` ${extraQuery.join(' ')}`;
  }
```
created_at: 2025-04-22T15:55:38Z
author_association: MEMBER

PR comments:
user: login: billyvg
created_at: 2025-04-15T19:40:32Z
body: @codecov-ai-reviewer review
author_association: MEMBER
user: login: ghost
created_at: 2025-04-15T19:40:38Z
body: On it! We are reviewing the PR and will provide feedback shortly.
author_association: NONE