Browse Source

fix(webvitals): fixes webvitals landing page search not working (#68535)

Fixes webvitals landing page search incorrectly escaping wildcard
asterisks, which broke searching. Also adds a test case for this
scenario.
edwardgou-sentry 11 months ago
parent
commit
fdab09719d

+ 81 - 0
static/app/views/performance/browser/webVitals/pagePerformanceTable.spec.tsx

@@ -0,0 +1,81 @@
+import {OrganizationFixture} from 'sentry-fixture/organization';
+
+import {render, waitFor} from 'sentry-test/reactTestingLibrary';
+
+import {useLocation} from 'sentry/utils/useLocation';
+import useOrganization from 'sentry/utils/useOrganization';
+import usePageFilters from 'sentry/utils/usePageFilters';
+import {PagePerformanceTable} from 'sentry/views/performance/browser/webVitals/pagePerformanceTable';
+
+jest.mock('sentry/utils/useLocation');
+jest.mock('sentry/utils/usePageFilters');
+jest.mock('sentry/utils/useOrganization');
+
+describe('PagePerformanceTable', function () {
+  const organization = OrganizationFixture();
+
+  let eventsMock;
+
+  beforeEach(function () {
+    jest.mocked(useLocation).mockReturnValue({
+      pathname: '',
+      search: '',
+      query: {},
+      hash: '',
+      state: undefined,
+      action: 'PUSH',
+      key: '',
+    });
+    jest.mocked(usePageFilters).mockReturnValue({
+      isReady: true,
+      desyncedFilters: new Set(),
+      pinnedFilters: new Set(),
+      shouldPersist: true,
+      selection: {
+        datetime: {
+          period: '10d',
+          start: null,
+          end: null,
+          utc: false,
+        },
+        environments: [],
+        projects: [],
+      },
+    });
+    jest.mocked(useOrganization).mockReturnValue(organization);
+    eventsMock = MockApiClient.addMockResponse({
+      url: `/organizations/${organization.slug}/events/`,
+      body: {
+        data: [],
+      },
+    });
+  });
+
+  afterEach(function () {
+    jest.clearAllMocks();
+  });
+
+  it('escapes user input search filter', async () => {
+    jest.mocked(useLocation).mockReturnValue({
+      pathname: '',
+      search: '',
+      query: {query: '/issues/*'},
+      hash: '',
+      state: undefined,
+      action: 'PUSH',
+      key: '',
+    });
+    render(<PagePerformanceTable />);
+    await waitFor(() => {
+      expect(eventsMock).toHaveBeenCalledTimes(2);
+      expect(eventsMock).toHaveBeenLastCalledWith(
+        '/organizations/org-slug/events/',
+        expect.objectContaining({
+          query: expect.objectContaining({
+            query: expect.stringContaining('transaction:"*/issues/\\**"'),
+          }),
+        })
+      );
+    });
+  });
+});

+ 6 - 5
static/app/views/performance/browser/webVitals/pagePerformanceTable.tsx

@@ -19,6 +19,7 @@ import type {Sort} from 'sentry/utils/discover/fields';
 import {parseFunction} from 'sentry/utils/discover/fields';
 import {formatAbbreviatedNumber, getDuration} from 'sentry/utils/formatters';
 import {decodeScalar} from 'sentry/utils/queryString';
+import {escapeFilterValue} from 'sentry/utils/tokenizeSearch';
 import {useLocation} from 'sentry/utils/useLocation';
 import useProjects from 'sentry/utils/useProjects';
 import {PerformanceBadge} from 'sentry/views/performance/browser/webVitals/components/performanceBadge';
@@ -72,9 +73,7 @@ export function PagePerformanceTable() {
     sort = {...sort, field: 'p75(measurements.inp)'};
   }
   const {data: projectScoresData, isLoading: isProjectScoresLoading} =
-    useProjectWebVitalsScoresQuery({
-      transaction: query,
-    });
+    useProjectWebVitalsScoresQuery();
 
   const {
     data,
@@ -82,8 +81,9 @@ export function PagePerformanceTable() {
     isLoading: isTransactionWebVitalsQueryLoading,
   } = useTransactionWebVitalsQuery({
     limit: MAX_ROWS,
-    transaction: query,
+    transaction: `*${escapeFilterValue(query)}*`,
     defaultSort: DEFAULT_SORT,
+    shouldEscapeFilters: false,
   });
 
   const scoreCount = projectScoresData?.data?.[0]?.[
@@ -284,7 +284,7 @@ export function PagePerformanceTable() {
       ...location,
       query: {
         ...location.query,
-        query: newQuery === '' ? undefined : `*${newQuery}*`,
+        query: newQuery === '' ? undefined : newQuery,
         cursor: undefined,
       },
     });
@@ -296,6 +296,7 @@ export function PagePerformanceTable() {
         <StyledSearchBar
           placeholder={t('Search for more Pages')}
           onSearch={handleSearch}
+          defaultQuery={query}
         />
         <StyledPagination
           pageLinks={pageLinks}

+ 3 - 1
static/app/views/performance/browser/webVitals/utils/queries/storedScoreQueries/useTransactionWebVitalsScoresQuery.tsx

@@ -18,6 +18,7 @@ type Props = {
   enabled?: boolean;
   limit?: number;
   query?: string;
+  shouldEscapeFilters?: boolean;
   sortName?: string;
   transaction?: string | null;
   webVital?: WebVitals | 'total';
@@ -31,6 +32,7 @@ export const useTransactionWebVitalsScoresQuery = ({
   enabled = true,
   webVital = 'total',
   query,
+  shouldEscapeFilters = true,
 }: Props) => {
   const organization = useOrganization();
   const pageFilters = usePageFilters();
@@ -43,7 +45,7 @@ export const useTransactionWebVitalsScoresQuery = ({
     ...(query ? [query] : []),
   ]);
   if (transaction) {
-    search.addFilterValue('transaction', transaction);
+    search.addFilterValue('transaction', transaction, shouldEscapeFilters);
   }
   const eventView = EventView.fromNewQueryWithPageFilters(
     {

+ 3 - 0
static/app/views/performance/browser/webVitals/utils/queries/useTransactionWebVitalsQuery.tsx

@@ -7,6 +7,7 @@ type Props = {
   enabled?: boolean;
   limit?: number;
   query?: string;
+  shouldEscapeFilters?: boolean;
   sortName?: string;
   transaction?: string | null;
   webVital?: WebVitals | 'total';
@@ -20,6 +21,7 @@ export const useTransactionWebVitalsQuery = ({
   webVital,
   enabled,
   query,
+  shouldEscapeFilters = true,
 }: Props) => {
   const storedScoresResult = useTransactionWebVitalsScoresQuery({
     limit,
@@ -29,6 +31,7 @@ export const useTransactionWebVitalsQuery = ({
     enabled,
     webVital,
     query,
+    shouldEscapeFilters,
   });
   return storedScoresResult;
 };