Browse Source

ref(insights): Pull URL parameter parsing into samples panels (#83201)

All our samples panels are a little bit different in how they get
parameters from the URL. Some of them get the parameters from
`useLocation()` directly. Some of them take them as props from the
parents. This PR makes a change in which all URL parameters in span
sample panels come from `useLocation()` or from `useLocationQuery` and
are never passed in as props.

This is an important step to allow using the new fancy panels, but also
is a bit of cleanup and consistency.
George Gritsouk 2 months ago
parent
commit
4e2c6dc129

+ 18 - 0
static/app/views/insights/browser/resources/utils/queryParameterDecoders/subregions.tsx

@@ -0,0 +1,18 @@
+import {decodeList} from 'sentry/utils/queryString';
+import {type SubregionCode, subregionCodeToName} from 'sentry/views/insights/types';
+
+const OPTIONS = Object.keys(subregionCodeToName) as SubregionCode[];
+
+export default function decode(
+  value: string | string[] | undefined | null
+): SubregionCode[] | undefined {
+  const decodedValue = decodeList(value);
+
+  const validSubregions = decodedValue.filter(isAValidOption);
+  return validSubregions.length > 0 ? validSubregions : undefined;
+}
+
+function isAValidOption(maybeOption: string): maybeOption is SubregionCode {
+  // Manually widen to allow the comparison to string
+  return (OPTIONS as unknown as string[]).includes(maybeOption as SubregionCode);
+}

+ 1 - 7
static/app/views/insights/browser/resources/views/resourceSummaryPage.tsx

@@ -3,7 +3,6 @@ import React from 'react';
 import * as Layout from 'sentry/components/layouts/thirds';
 import {t, tct} from 'sentry/locale';
 import {MutableSearch} from 'sentry/utils/tokenizeSearch';
-import {useLocation} from 'sentry/utils/useLocation';
 import {useParams} from 'sentry/utils/useParams';
 import ResourceSummaryCharts from 'sentry/views/insights/browser/resources/components/charts/resourceSummaryCharts';
 import RenderBlockingSelector from 'sentry/views/insights/browser/resources/components/renderBlockingSelector';
@@ -44,9 +43,6 @@ function ResourceSummary() {
   const {groupId} = useParams();
   const filters = useResourceModuleFilters();
   const selectedSpanOp = filters[SPAN_OP];
-  const {
-    query: {transaction},
-  } = useLocation();
   const {data, isPending} = useSpanMetrics(
     {
       search: MutableSearch.fromQueryObject({
@@ -142,11 +138,9 @@ function ResourceSummary() {
 
               <ModuleLayout.Full>
                 <SampleList
-                  transactionRoute={webVitalsModuleURL}
-                  subregions={filters[SpanMetricsField.USER_GEO_SUBREGION]}
                   groupId={groupId!}
                   moduleName={ModuleName.RESOURCE}
-                  transactionName={transaction as string}
+                  transactionRoute={webVitalsModuleURL}
                   referrer={TraceViewSources.ASSETS_MODULE}
                 />
               </ModuleLayout.Full>

+ 15 - 7
static/app/views/insights/common/views/spanSummaryPage/sampleList/index.tsx

@@ -16,12 +16,14 @@ import {PageAlert, PageAlertProvider} from 'sentry/utils/performance/contexts/pa
 import {decodeScalar} from 'sentry/utils/queryString';
 import {MutableSearch} from 'sentry/utils/tokenizeSearch';
 import normalizeUrl from 'sentry/utils/url/normalizeUrl';
+import useLocationQuery from 'sentry/utils/url/useLocationQuery';
 import {useLocation} from 'sentry/utils/useLocation';
 import useOrganization from 'sentry/utils/useOrganization';
 import usePageFilters from 'sentry/utils/usePageFilters';
 import useProjects from 'sentry/utils/useProjects';
 import useRouter from 'sentry/utils/useRouter';
 import {DATA_TYPE} from 'sentry/views/insights/browser/resources/settings';
+import decodeSubregions from 'sentry/views/insights/browser/resources/utils/queryParameterDecoders/subregions';
 import DetailPanel from 'sentry/views/insights/common/components/detailPanel';
 import {DEFAULT_COLUMN_ORDER} from 'sentry/views/insights/common/components/samplesTable/spanSamplesTable';
 import DurationChart from 'sentry/views/insights/common/views/spanSummaryPage/sampleList/durationChart';
@@ -32,7 +34,6 @@ import {
   ModuleName,
   SpanIndexedField,
   SpanMetricsField,
-  type SubregionCode,
 } from 'sentry/views/insights/types';
 import {getTransactionSummaryBaseUrl} from 'sentry/views/performance/transactionSummary/utils';
 
@@ -41,26 +42,33 @@ const {HTTP_RESPONSE_CONTENT_LENGTH, SPAN_DESCRIPTION} = SpanMetricsField;
 type Props = {
   groupId: string;
   moduleName: ModuleName;
-  transactionName: string;
   onClose?: () => void;
   referrer?: string;
-  subregions?: SubregionCode[];
-  transactionMethod?: string;
   transactionRoute?: string;
 };
 
 export function SampleList({
   groupId,
   moduleName,
-  transactionName,
-  transactionMethod,
-  subregions,
   onClose,
   transactionRoute,
   referrer,
 }: Props) {
   const organization = useOrganization();
   const {view} = useDomainViewFilters();
+
+  const {
+    transaction: transactionName,
+    transactionMethod,
+    [SpanMetricsField.USER_GEO_SUBREGION]: subregions,
+  } = useLocationQuery({
+    fields: {
+      transaction: decodeScalar,
+      transactionMethod: decodeScalar,
+      [SpanMetricsField.USER_GEO_SUBREGION]: decodeSubregions,
+    },
+  });
+
   const router = useRouter();
   const [highlightedSpanId, setHighlightedSpanId] = useState<string | undefined>(
     undefined

+ 0 - 3
static/app/views/insights/database/views/databaseSpanSummaryPage.tsx

@@ -62,7 +62,6 @@ export function DatabaseSpanSummaryPage({params}: Props) {
   const selectedAggregate = DEFAULT_DURATION_AGGREGATE;
 
   const {groupId} = params;
-  const {transaction, transactionMethod} = location.query;
 
   const filters: SpanMetricsQueryFilters = {
     'span.group': groupId,
@@ -290,8 +289,6 @@ export function DatabaseSpanSummaryPage({params}: Props) {
             <SampleList
               groupId={span[SpanMetricsField.SPAN_GROUP]}
               moduleName={ModuleName.DB}
-              transactionName={transaction}
-              transactionMethod={transactionMethod}
               referrer={TraceViewSources.QUERIES_MODULE}
             />
           </Layout.Main>

+ 0 - 9
static/app/views/insights/mobile/appStarts/views/screenSummaryPage.tsx

@@ -88,10 +88,8 @@ export function ScreenSummaryContentPage() {
     secondaryRelease,
     transaction: transactionName,
     spanGroup,
-    spanDescription,
     spanOp,
     [SpanMetricsField.APP_START_TYPE]: appStartType,
-    'device.class': deviceClass,
   } = location.query;
 
   useEffect(() => {
@@ -183,15 +181,8 @@ export function ScreenSummaryContentPage() {
       </SamplesContainer>
       {spanGroup && spanOp && appStartType && (
         <SpanSamplesPanel
-          additionalFilters={{
-            [SpanMetricsField.APP_START_TYPE]: appStartType,
-            ...(deviceClass ? {[SpanMetricsField.DEVICE_CLASS]: deviceClass} : {}),
-          }}
           groupId={spanGroup}
           moduleName={ModuleName.APP_START}
-          transactionName={transactionName}
-          spanDescription={spanDescription}
-          spanOp={spanOp}
           onClose={() => {
             navigate(
               {

+ 26 - 11
static/app/views/insights/mobile/common/components/spanSamplesPanel.tsx

@@ -9,7 +9,9 @@ import {t} from 'sentry/locale';
 import {space} from 'sentry/styles/space';
 import {trackAnalytics} from 'sentry/utils/analytics';
 import {PageAlert, PageAlertProvider} from 'sentry/utils/performance/contexts/pageAlert';
+import {decodeScalar} from 'sentry/utils/queryString';
 import normalizeUrl from 'sentry/utils/url/normalizeUrl';
+import useLocationQuery from 'sentry/utils/url/useLocationQuery';
 import {useLocation} from 'sentry/utils/useLocation';
 import useOrganization from 'sentry/utils/useOrganization';
 import useRouter from 'sentry/utils/useRouter';
@@ -18,18 +20,13 @@ import {useReleaseSelection} from 'sentry/views/insights/common/queries/useRelea
 import {SpanSamplesContainer} from 'sentry/views/insights/mobile/common/components/spanSamplesPanelContainer';
 import useCrossPlatformProject from 'sentry/views/insights/mobile/common/queries/useCrossPlatformProject';
 import {useDomainViewFilters} from 'sentry/views/insights/pages/useFilters';
-import type {ModuleName} from 'sentry/views/insights/types';
+import {type ModuleName, SpanMetricsField} from 'sentry/views/insights/types';
 import {getTransactionSummaryBaseUrl} from 'sentry/views/performance/transactionSummary/utils';
 
 type Props = {
   groupId: string;
   moduleName: ModuleName;
-  transactionName: string;
-  additionalFilters?: Record<string, string>;
   onClose?: () => void;
-  spanDescription?: string;
-  spanOp?: string;
-  transactionMethod?: string;
   transactionRoute?: string;
 };
 
@@ -39,18 +36,36 @@ const SECONDARY_SPAN_QUERY_KEY = 'secondarySpanSearchQuery';
 export function SpanSamplesPanel({
   groupId,
   moduleName,
-  transactionName,
-  transactionMethod,
-  spanDescription,
   onClose,
   transactionRoute,
-  spanOp,
-  additionalFilters,
 }: Props) {
   const router = useRouter();
   const organization = useOrganization();
   const {view} = useDomainViewFilters();
 
+  const {
+    [SpanMetricsField.APP_START_TYPE]: appStartType,
+    [SpanMetricsField.DEVICE_CLASS]: deviceClass,
+    transaction: transactionName,
+    transactionMethod,
+    spanOp,
+    spanDescription,
+  } = useLocationQuery({
+    fields: {
+      [SpanMetricsField.APP_START_TYPE]: decodeScalar,
+      [SpanMetricsField.DEVICE_CLASS]: decodeScalar,
+      transaction: decodeScalar,
+      transactionMethod: decodeScalar,
+      spanOp: decodeScalar,
+      spanDescription: decodeScalar,
+    },
+  });
+
+  const additionalFilters = {
+    ...(appStartType ? {[SpanMetricsField.APP_START_TYPE]: appStartType} : {}),
+    ...(deviceClass ? {[SpanMetricsField.DEVICE_CLASS]: deviceClass} : {}),
+  };
+
   transactionRoute ??= getTransactionSummaryBaseUrl(organization.slug, view);
 
   const {primaryRelease, secondaryRelease} = useReleaseSelection();

+ 0 - 3
static/app/views/insights/mobile/screenload/views/screenLoadSpansPage.tsx

@@ -86,7 +86,6 @@ export function ScreenLoadSpansContent() {
     primaryRelease,
     secondaryRelease,
     transaction: transactionName,
-    spanDescription,
   } = location.query;
 
   return (
@@ -184,8 +183,6 @@ export function ScreenLoadSpansContent() {
           <SpanSamplesPanel
             groupId={spanGroup}
             moduleName={ModuleName.SCREEN_LOAD}
-            transactionName={transactionName}
-            spanDescription={spanDescription}
             onClose={() => {
               router.replace({
                 pathname: router.location.pathname,

+ 2 - 14
static/app/views/insights/mobile/ui/views/screenSummaryPage.tsx

@@ -19,7 +19,7 @@ import {SamplesTables} from 'sentry/views/insights/mobile/common/components/tabl
 import {SpanOperationTable} from 'sentry/views/insights/mobile/ui/components/tables/spanOperationTable';
 import {MobileHeader} from 'sentry/views/insights/pages/mobile/mobilePageHeader';
 import {isModuleEnabled} from 'sentry/views/insights/pages/utils';
-import {ModuleName, SpanMetricsField} from 'sentry/views/insights/types';
+import {ModuleName} from 'sentry/views/insights/types';
 
 type Query = {
   'device.class': string;
@@ -67,13 +67,7 @@ export function ScreenSummaryContent() {
   const location = useLocation<Query>();
   const router = useRouter();
 
-  const {
-    transaction: transactionName,
-    spanGroup,
-    spanDescription,
-    spanOp,
-    'device.class': deviceClass,
-  } = location.query;
+  const {transaction: transactionName, spanGroup, spanOp} = location.query;
 
   return (
     <Fragment>
@@ -98,14 +92,8 @@ export function ScreenSummaryContent() {
 
       {spanGroup && spanOp && (
         <SpanSamplesPanel
-          additionalFilters={{
-            ...(deviceClass ? {[SpanMetricsField.DEVICE_CLASS]: deviceClass} : {}),
-          }}
           groupId={spanGroup}
           moduleName={ModuleName.OTHER}
-          transactionName={transactionName}
-          spanDescription={spanDescription}
-          spanOp={spanOp}
           onClose={() => {
             router.replace({
               pathname: router.location.pathname,