Browse Source

feat(insights): add search bar to cache and queue sample panels (#71415)

Adds a span search bar to cache and queue sample panels.
Kevin Liu 9 months ago
parent
commit
456ad17160

+ 39 - 1
static/app/views/performance/cache/samplePanel/samplePanel.tsx

@@ -3,20 +3,24 @@ import styled from '@emotion/styled';
 import keyBy from 'lodash/keyBy';
 import keyBy from 'lodash/keyBy';
 import * as qs from 'query-string';
 import * as qs from 'query-string';
 
 
+import Feature from 'sentry/components/acl/feature';
 import ProjectAvatar from 'sentry/components/avatar/projectAvatar';
 import ProjectAvatar from 'sentry/components/avatar/projectAvatar';
 import {Button} from 'sentry/components/button';
 import {Button} from 'sentry/components/button';
 import {CompactSelect} from 'sentry/components/compactSelect';
 import {CompactSelect} from 'sentry/components/compactSelect';
+import SearchBar from 'sentry/components/events/searchBar';
 import Link from 'sentry/components/links/link';
 import Link from 'sentry/components/links/link';
 import {t} from 'sentry/locale';
 import {t} from 'sentry/locale';
 import {space} from 'sentry/styles/space';
 import {space} from 'sentry/styles/space';
 import {trackAnalytics} from 'sentry/utils/analytics';
 import {trackAnalytics} from 'sentry/utils/analytics';
 import {DurationUnit, RateUnit, SizeUnit} from 'sentry/utils/discover/fields';
 import {DurationUnit, RateUnit, SizeUnit} from 'sentry/utils/discover/fields';
+import {DiscoverDatasets} from 'sentry/utils/discover/types';
 import {PageAlertProvider} from 'sentry/utils/performance/contexts/pageAlert';
 import {PageAlertProvider} from 'sentry/utils/performance/contexts/pageAlert';
 import {decodeScalar} from 'sentry/utils/queryString';
 import {decodeScalar} from 'sentry/utils/queryString';
 import {MutableSearch} from 'sentry/utils/tokenizeSearch';
 import {MutableSearch} from 'sentry/utils/tokenizeSearch';
 import useLocationQuery from 'sentry/utils/url/useLocationQuery';
 import useLocationQuery from 'sentry/utils/url/useLocationQuery';
 import {useLocation} from 'sentry/utils/useLocation';
 import {useLocation} from 'sentry/utils/useLocation';
 import useOrganization from 'sentry/utils/useOrganization';
 import useOrganization from 'sentry/utils/useOrganization';
+import usePageFilters from 'sentry/utils/usePageFilters';
 import useProjects from 'sentry/utils/useProjects';
 import useProjects from 'sentry/utils/useProjects';
 import useRouter from 'sentry/utils/useRouter';
 import useRouter from 'sentry/utils/useRouter';
 import {normalizeUrl} from 'sentry/utils/withDomainRequired';
 import {normalizeUrl} from 'sentry/utils/withDomainRequired';
@@ -28,6 +32,7 @@ import {SpanSamplesTable} from 'sentry/views/performance/cache/tables/spanSample
 import {useDebouncedState} from 'sentry/views/performance/http/useDebouncedState';
 import {useDebouncedState} from 'sentry/views/performance/http/useDebouncedState';
 import {MetricReadout} from 'sentry/views/performance/metricReadout';
 import {MetricReadout} from 'sentry/views/performance/metricReadout';
 import * as ModuleLayout from 'sentry/views/performance/moduleLayout';
 import * as ModuleLayout from 'sentry/views/performance/moduleLayout';
+import {useSpanFieldSupportedTags} from 'sentry/views/performance/utils/useSpanFieldSupportedTags';
 import DetailPanel from 'sentry/views/starfish/components/detailPanel';
 import DetailPanel from 'sentry/views/starfish/components/detailPanel';
 import {getTimeSpentExplanation} from 'sentry/views/starfish/components/tableCells/timeSpentCell';
 import {getTimeSpentExplanation} from 'sentry/views/starfish/components/tableCells/timeSpentCell';
 import {useMetrics, useSpanMetrics} from 'sentry/views/starfish/queries/useDiscover';
 import {useMetrics, useSpanMetrics} from 'sentry/views/starfish/queries/useDiscover';
@@ -52,12 +57,15 @@ export function CacheSamplePanel() {
   const router = useRouter();
   const router = useRouter();
   const location = useLocation();
   const location = useLocation();
   const organization = useOrganization();
   const organization = useOrganization();
+  const {selection} = usePageFilters();
+  const supportedTags = useSpanFieldSupportedTags();
 
 
   const query = useLocationQuery({
   const query = useLocationQuery({
     fields: {
     fields: {
       project: decodeScalar,
       project: decodeScalar,
       transaction: decodeScalar,
       transaction: decodeScalar,
       statusClass: decodeScalar,
       statusClass: decodeScalar,
+      spanSearchQuery: decodeScalar,
     },
     },
   });
   });
 
 
@@ -140,7 +148,11 @@ export function CacheSamplePanel() {
 
 
   const useIndexedCacheSpans = (isCacheHit, limit) =>
   const useIndexedCacheSpans = (isCacheHit, limit) =>
     useIndexedSpans({
     useIndexedSpans({
-      search: MutableSearch.fromQueryObject({...sampleFilters, 'cache.hit': isCacheHit}),
+      search: MutableSearch.fromQueryObject({
+        ...sampleFilters,
+        ...new MutableSearch(query.spanSearchQuery).filters,
+        'cache.hit': isCacheHit,
+      }),
       fields: [
       fields: [
         SpanIndexedField.PROJECT,
         SpanIndexedField.PROJECT,
         SpanIndexedField.TRACE,
         SpanIndexedField.TRACE,
@@ -205,6 +217,16 @@ export function CacheSamplePanel() {
   const {projects} = useProjects();
   const {projects} = useProjects();
   const project = projects.find(p => query.project === p.id);
   const project = projects.find(p => query.project === p.id);
 
 
+  const handleSearch = (newSpanSearchQuery: string) => {
+    router.replace({
+      pathname: location.pathname,
+      query: {
+        ...query,
+        spanSearchQuery: newSpanSearchQuery,
+      },
+    });
+  };
+
   const handleClose = () => {
   const handleClose = () => {
     router.replace({
     router.replace({
       pathname: router.location.pathname,
       pathname: router.location.pathname,
@@ -362,6 +384,22 @@ export function CacheSamplePanel() {
             />
             />
           </ModuleLayout.Half>
           </ModuleLayout.Half>
 
 
+          <Feature features="performance-sample-panel-search">
+            <ModuleLayout.Full>
+              <SearchBar
+                searchSource={`${ModuleName.CACHE}-sample-panel`}
+                query={query.spanSearchQuery}
+                onSearch={handleSearch}
+                placeholder={t('Search for span attributes')}
+                organization={organization}
+                metricAlert={false}
+                supportedTags={supportedTags}
+                dataset={DiscoverDatasets.SPANS_INDEXED}
+                projectIds={selection.projects}
+              />
+            </ModuleLayout.Full>
+          </Feature>
+
           <Fragment>
           <Fragment>
             <ModuleLayout.Full>
             <ModuleLayout.Full>
               <SpanSamplesTable
               <SpanSamplesTable

+ 27 - 1
static/app/views/performance/queues/destinationSummary/destinationSummaryPage.spec.tsx

@@ -51,7 +51,7 @@ describe('destinationSummaryPage', () => {
     initiallyLoaded: false,
     initiallyLoaded: false,
   });
   });
 
 
-  let eventsMock, eventsStatsMock;
+  let eventsMock, eventsStatsMock, spanFieldTagsMock;
 
 
   beforeEach(() => {
   beforeEach(() => {
     eventsMock = MockApiClient.addMockResponse({
     eventsMock = MockApiClient.addMockResponse({
@@ -65,6 +65,21 @@ describe('destinationSummaryPage', () => {
       method: 'GET',
       method: 'GET',
       body: {data: []},
       body: {data: []},
     });
     });
+
+    spanFieldTagsMock = MockApiClient.addMockResponse({
+      url: `/organizations/${organization.slug}/spans/fields/`,
+      method: 'GET',
+      body: [
+        {
+          key: 'api_key',
+          name: 'Api Key',
+        },
+        {
+          key: 'bytes.size',
+          name: 'Bytes.Size',
+        },
+      ],
+    });
   });
   });
 
 
   it('renders', async () => {
   it('renders', async () => {
@@ -75,5 +90,16 @@ describe('destinationSummaryPage', () => {
     screen.getByText('Published vs Processed');
     screen.getByText('Published vs Processed');
     expect(eventsStatsMock).toHaveBeenCalled();
     expect(eventsStatsMock).toHaveBeenCalled();
     expect(eventsMock).toHaveBeenCalled();
     expect(eventsMock).toHaveBeenCalled();
+    expect(spanFieldTagsMock).toHaveBeenCalledWith(
+      `/organizations/${organization.slug}/spans/fields/`,
+      expect.objectContaining({
+        method: 'GET',
+        query: {
+          project: [],
+          environment: [],
+          statsPeriod: '1h',
+        },
+      })
+    );
   });
   });
 });
 });

+ 39 - 1
static/app/views/performance/queues/destinationSummary/messageSpanSamplesPanel.spec.tsx

@@ -12,7 +12,7 @@ jest.mock('sentry/utils/usePageFilters');
 describe('messageSpanSamplesPanel', () => {
 describe('messageSpanSamplesPanel', () => {
   const organization = OrganizationFixture();
   const organization = OrganizationFixture();
 
 
-  let eventsRequestMock, eventsStatsRequestMock, samplesRequestMock;
+  let eventsRequestMock, eventsStatsRequestMock, samplesRequestMock, spanFieldTagsMock;
 
 
   jest.mocked(usePageFilters).mockReturnValue({
   jest.mocked(usePageFilters).mockReturnValue({
     isReady: true,
     isReady: true,
@@ -110,6 +110,21 @@ describe('messageSpanSamplesPanel', () => {
         ],
         ],
       },
       },
     });
     });
+
+    spanFieldTagsMock = MockApiClient.addMockResponse({
+      url: `/organizations/${organization.slug}/spans/fields/`,
+      method: 'GET',
+      body: [
+        {
+          key: 'api_key',
+          name: 'Api Key',
+        },
+        {
+          key: 'bytes.size',
+          name: 'Bytes.Size',
+        },
+      ],
+    });
   });
   });
 
 
   afterAll(() => {
   afterAll(() => {
@@ -187,6 +202,18 @@ describe('messageSpanSamplesPanel', () => {
         }),
         }),
       })
       })
     );
     );
+    expect(spanFieldTagsMock).toHaveBeenNthCalledWith(
+      1,
+      `/organizations/${organization.slug}/spans/fields/`,
+      expect.objectContaining({
+        method: 'GET',
+        query: {
+          project: [],
+          environment: [],
+          statsPeriod: '1h',
+        },
+      })
+    );
     expect(screen.getByRole('table', {name: 'Span Samples'})).toBeInTheDocument();
     expect(screen.getByRole('table', {name: 'Span Samples'})).toBeInTheDocument();
     expect(screen.getByText('Consumer')).toBeInTheDocument();
     expect(screen.getByText('Consumer')).toBeInTheDocument();
     // Metrics Ribbon
     // Metrics Ribbon
@@ -271,6 +298,17 @@ describe('messageSpanSamplesPanel', () => {
         }),
         }),
       })
       })
     );
     );
+    expect(spanFieldTagsMock).toHaveBeenCalledWith(
+      `/organizations/${organization.slug}/spans/fields/`,
+      expect.objectContaining({
+        method: 'GET',
+        query: {
+          project: [],
+          environment: [],
+          statsPeriod: '1h',
+        },
+      })
+    );
     expect(screen.getByRole('table', {name: 'Span Samples'})).toBeInTheDocument();
     expect(screen.getByRole('table', {name: 'Span Samples'})).toBeInTheDocument();
     expect(screen.getByText('Producer')).toBeInTheDocument();
     expect(screen.getByText('Producer')).toBeInTheDocument();
     // Metrics Ribbon
     // Metrics Ribbon

+ 42 - 0
static/app/views/performance/queues/destinationSummary/messageSpanSamplesPanel.tsx

@@ -2,20 +2,24 @@ import {Fragment, useCallback} from 'react';
 import styled from '@emotion/styled';
 import styled from '@emotion/styled';
 import * as qs from 'query-string';
 import * as qs from 'query-string';
 
 
+import Feature from 'sentry/components/acl/feature';
 import ProjectAvatar from 'sentry/components/avatar/projectAvatar';
 import ProjectAvatar from 'sentry/components/avatar/projectAvatar';
 import {Button} from 'sentry/components/button';
 import {Button} from 'sentry/components/button';
 import {CompactSelect, type SelectOption} from 'sentry/components/compactSelect';
 import {CompactSelect, type SelectOption} from 'sentry/components/compactSelect';
+import SearchBar from 'sentry/components/events/searchBar';
 import Link from 'sentry/components/links/link';
 import Link from 'sentry/components/links/link';
 import {t} from 'sentry/locale';
 import {t} from 'sentry/locale';
 import {space} from 'sentry/styles/space';
 import {space} from 'sentry/styles/space';
 import {trackAnalytics} from 'sentry/utils/analytics';
 import {trackAnalytics} from 'sentry/utils/analytics';
 import {DurationUnit, SizeUnit} from 'sentry/utils/discover/fields';
 import {DurationUnit, SizeUnit} from 'sentry/utils/discover/fields';
+import {DiscoverDatasets} from 'sentry/utils/discover/types';
 import {PageAlertProvider} from 'sentry/utils/performance/contexts/pageAlert';
 import {PageAlertProvider} from 'sentry/utils/performance/contexts/pageAlert';
 import {decodeScalar} from 'sentry/utils/queryString';
 import {decodeScalar} from 'sentry/utils/queryString';
 import {MutableSearch} from 'sentry/utils/tokenizeSearch';
 import {MutableSearch} from 'sentry/utils/tokenizeSearch';
 import useLocationQuery from 'sentry/utils/url/useLocationQuery';
 import useLocationQuery from 'sentry/utils/url/useLocationQuery';
 import {useLocation} from 'sentry/utils/useLocation';
 import {useLocation} from 'sentry/utils/useLocation';
 import useOrganization from 'sentry/utils/useOrganization';
 import useOrganization from 'sentry/utils/useOrganization';
+import usePageFilters from 'sentry/utils/usePageFilters';
 import useProjects from 'sentry/utils/useProjects';
 import useProjects from 'sentry/utils/useProjects';
 import useRouter from 'sentry/utils/useRouter';
 import useRouter from 'sentry/utils/useRouter';
 import {normalizeUrl} from 'sentry/utils/withDomainRequired';
 import {normalizeUrl} from 'sentry/utils/withDomainRequired';
@@ -37,6 +41,7 @@ import {
   RETRY_COUNT_OPTIONS,
   RETRY_COUNT_OPTIONS,
   TRACE_STATUS_OPTIONS,
   TRACE_STATUS_OPTIONS,
 } from 'sentry/views/performance/queues/settings';
 } from 'sentry/views/performance/queues/settings';
+import {useSpanFieldSupportedTags} from 'sentry/views/performance/utils/useSpanFieldSupportedTags';
 import {Subtitle} from 'sentry/views/profiling/landing/styles';
 import {Subtitle} from 'sentry/views/profiling/landing/styles';
 import {computeAxisMax} from 'sentry/views/starfish/components/chart';
 import {computeAxisMax} from 'sentry/views/starfish/components/chart';
 import DetailPanel from 'sentry/views/starfish/components/detailPanel';
 import DetailPanel from 'sentry/views/starfish/components/detailPanel';
@@ -58,10 +63,14 @@ export function MessageSpanSamplesPanel() {
       transaction: decodeScalar,
       transaction: decodeScalar,
       retryCount: decodeRetryCount,
       retryCount: decodeRetryCount,
       traceStatus: decodeTraceStatus,
       traceStatus: decodeTraceStatus,
+      spanSearchQuery: decodeScalar,
       'span.op': decodeScalar,
       'span.op': decodeScalar,
     },
     },
   });
   });
   const {projects} = useProjects();
   const {projects} = useProjects();
+  const {selection} = usePageFilters();
+  const supportedTags = useSpanFieldSupportedTags();
+
   const project = projects.find(p => query.project === p.id);
   const project = projects.find(p => query.project === p.id);
 
 
   const organization = useOrganization();
   const organization = useOrganization();
@@ -128,6 +137,13 @@ export function MessageSpanSamplesPanel() {
   sampleFilters.addFilterValue('transaction', query.transaction);
   sampleFilters.addFilterValue('transaction', query.transaction);
   sampleFilters.addFilterValue('messaging.destination.name', query.destination);
   sampleFilters.addFilterValue('messaging.destination.name', query.destination);
 
 
+  // filter by key-value filters specified in the search bar query
+  Object.entries(new MutableSearch(query.spanSearchQuery).filters).forEach(
+    ([key, value]) => {
+      sampleFilters.addFilterValues(key, value);
+    }
+  );
+
   if (query.traceStatus.length > 0) {
   if (query.traceStatus.length > 0) {
     sampleFilters.addFilterValue('trace.status', query.traceStatus);
     sampleFilters.addFilterValue('trace.status', query.traceStatus);
   }
   }
@@ -207,6 +223,16 @@ export function MessageSpanSamplesPanel() {
     );
     );
   };
   };
 
 
+  const handleSearch = (newSpanSearchQuery: string) => {
+    router.replace({
+      pathname: location.pathname,
+      query: {
+        ...query,
+        spanSearchQuery: newSpanSearchQuery,
+      },
+    });
+  };
+
   const handleClose = () => {
   const handleClose = () => {
     router.replace({
     router.replace({
       pathname: router.location.pathname,
       pathname: router.location.pathname,
@@ -333,6 +359,22 @@ export function MessageSpanSamplesPanel() {
             />
             />
           </ModuleLayout.Full>
           </ModuleLayout.Full>
 
 
+          <Feature features="performance-sample-panel-search">
+            <ModuleLayout.Full>
+              <SearchBar
+                searchSource={`${ModuleName.QUEUE}-sample-panel`}
+                query={query.spanSearchQuery}
+                onSearch={handleSearch}
+                placeholder={t('Search for span attributes')}
+                organization={organization}
+                metricAlert={false}
+                supportedTags={supportedTags}
+                dataset={DiscoverDatasets.SPANS_INDEXED}
+                projectIds={selection.projects}
+              />
+            </ModuleLayout.Full>
+          </Feature>
+
           <ModuleLayout.Full>
           <ModuleLayout.Full>
             <MessageSpanSamplesTable
             <MessageSpanSamplesTable
               data={durationSamplesData}
               data={durationSamplesData}

+ 1 - 1
static/app/views/starfish/views/spanSummaryPage/sampleList/index.tsx

@@ -211,7 +211,7 @@ export function SampleList({
 
 
         <Feature features="performance-sample-panel-search">
         <Feature features="performance-sample-panel-search">
           <StyledSearchBar
           <StyledSearchBar
-            searchSource="queries-sample-panel"
+            searchSource={`${moduleName}-sample-panel`}
             query={spanSearchQuery}
             query={spanSearchQuery}
             onSearch={handleSearch}
             onSearch={handleSearch}
             placeholder={t('Search for span attributes')}
             placeholder={t('Search for span attributes')}