Просмотр исходного кода

feat(perf): Track user updates to the search query across performance and discover (#34644)

* track dirty and pristine states

* send user_modified param to eventsv2

* fix hooks error

* track events-stats queries

* fix

* remove changes

* track single field area widget user query

* track landing page widgets

* track transaction summary and vital details page

* style(lint): Auto commit lint changes

* style(lint): Auto commit lint changes

* style(lint): Auto commit lint changes

* style(lint): Auto commit lint changes

* track discover search

* update tests

* rename isDirty to userModified

Co-authored-by: getsantry[bot] <66042841+getsantry[bot]@users.noreply.github.com>
Dameli Ushbayeva 2 лет назад
Родитель
Сommit
ccf0622f71

+ 7 - 4
static/app/actionCreators/events.tsx

@@ -36,6 +36,7 @@ type Options = {
   start?: DateString;
   team?: Readonly<string | string[]>;
   topEvents?: number;
+  userModified?: string;
   withoutZerofill?: boolean;
   yAxis?: string | string[];
 };
@@ -85,8 +86,13 @@ export const doEventsRequest = (
     generatePathname,
     queryExtras,
     excludeOther,
+    userModified,
   }: Options
 ): Promise<EventsStats | MultiSeriesEventsStats> => {
+  const pathname =
+    generatePathname?.(organization) ??
+    `/organizations/${organization.slug}/events-stats/`;
+
   const shouldDoublePeriod = canIncludePreviousPeriod(includePrevious, period);
   const urlQuery = Object.fromEntries(
     Object.entries({
@@ -104,6 +110,7 @@ export const doEventsRequest = (
       withoutZerofill: withoutZerofill ? '1' : undefined,
       referrer: referrer ? referrer : 'api.organization-event-stats',
       excludeOther: excludeOther ? '1' : undefined,
+      user_modified: pathname.includes('events-stats') ? userModified : undefined,
     }).filter(([, value]) => typeof value !== 'undefined')
   );
 
@@ -120,10 +127,6 @@ export const doEventsRequest = (
     },
   };
 
-  const pathname =
-    generatePathname?.(organization) ??
-    `/organizations/${organization.slug}/events-stats/`;
-
   if (queryBatching?.batchRequest) {
     return queryBatching.batchRequest(api, pathname, queryObject);
   }

+ 2 - 1
static/app/components/charts/eventsChart.tsx

@@ -37,7 +37,7 @@ import {
   getEquation,
   isEquation,
 } from 'sentry/utils/discover/fields';
-import {decodeList} from 'sentry/utils/queryString';
+import {decodeList, decodeScalar} from 'sentry/utils/queryString';
 import {Theme} from 'sentry/utils/theme';
 
 import EventsGeoRequest from './eventsGeoRequest';
@@ -667,6 +667,7 @@ class EventsChart extends React.Component<EventsChartProps> {
               partial
               // Cannot do interpolation when stacking series
               withoutZerofill={withoutZerofill && !this.isStacked()}
+              userModified={decodeScalar(router.location.query.userModified)}
             >
               {eventData => {
                 return chartImplementation({

+ 4 - 0
static/app/components/charts/eventsRequest.tsx

@@ -193,6 +193,10 @@ type EventsRequestPartialProps = {
    * in the `results` child render function.
    */
   topEvents?: number;
+  /**
+   * Tracks whether the query was modified by a user in the search bar
+   */
+  userModified?: string;
   /**
    * Whether or not to zerofill results
    */

+ 10 - 1
static/app/utils/discover/genericDiscoverQuery.tsx

@@ -12,6 +12,8 @@ import EventView, {
 import {PerformanceEventViewContext} from 'sentry/utils/performance/contexts/performanceEventViewContext';
 import {OrganizationContext} from 'sentry/views/organizationContext';
 
+import {decodeScalar} from '../queryString';
+
 export class QueryError {
   message: string;
   private originalError: any; // For debugging in case parseError picks a value that doesn't make sense.
@@ -170,7 +172,7 @@ class _GenericDiscoverQuery<T, P> extends Component<Props<T, P>, State<T>> {
   }
 
   getPayload(props: Props<T, P>) {
-    const {cursor, limit, noPagination, referrer} = props;
+    const {cursor, limit, noPagination, referrer, location} = props;
     const payload = this.props.getRequestPayload
       ? this.props.getRequestPayload(props)
       : props.eventView.getEventsAPIPayload(props.location);
@@ -188,6 +190,13 @@ class _GenericDiscoverQuery<T, P> extends Component<Props<T, P>, State<T>> {
       payload.referrer = referrer;
     }
 
+    if (props.route === 'eventsv2') {
+      const queryUserModified = decodeScalar(location.query?.userModified);
+      if (queryUserModified !== undefined) {
+        payload.user_modified = queryUserModified;
+      }
+    }
+
     Object.assign(payload, props.queryExtras ?? {});
 
     return payload;

+ 24 - 1
static/app/views/eventsV2/index.tsx

@@ -1,3 +1,6 @@
+import {useEffect, useRef} from 'react';
+import {browserHistory} from 'react-router';
+
 import Feature from 'sentry/components/acl/feature';
 import Alert from 'sentry/components/alert';
 import {t} from 'sentry/locale';
@@ -7,10 +10,30 @@ import withOrganization from 'sentry/utils/withOrganization';
 
 type Props = {
   children: React.ReactChildren;
+  location: any;
   organization: Organization;
 };
 
-function DiscoverContainer({organization, children}: Props) {
+function DiscoverContainer({organization, children, location}: Props) {
+  const prevLocationPathname = useRef('');
+
+  useEffect(
+    // when new discover page loads, query is pristine
+    function () {
+      if (location.pathname !== prevLocationPathname.current) {
+        prevLocationPathname.current = location.pathname;
+        browserHistory.push({
+          pathname: location.pathname,
+          query: {
+            ...location.query,
+            userModified: undefined,
+          },
+        });
+      }
+    },
+    [location]
+  );
+
   function renderNoAccess() {
     return (
       <PageContent>

+ 4 - 1
static/app/views/eventsV2/results.tsx

@@ -314,7 +314,10 @@ class Results extends Component<Props, State> {
 
     router.push({
       pathname: location.pathname,
-      query: searchQueryParams,
+      query: {
+        ...searchQueryParams,
+        userModified: true,
+      },
     });
   };
 

+ 15 - 2
static/app/views/eventsV2/table/index.tsx

@@ -2,16 +2,21 @@ import {PureComponent} from 'react';
 import styled from '@emotion/styled';
 import {Location} from 'history';
 
+import {EventQuery} from 'sentry/actionCreators/events';
 import {Client} from 'sentry/api';
 import Pagination from 'sentry/components/pagination';
 import {t} from 'sentry/locale';
 import {Organization} from 'sentry/types';
 import {metric, trackAnalyticsEvent} from 'sentry/utils/analytics';
 import {TableData} from 'sentry/utils/discover/discoverQuery';
-import EventView, {isAPIPayloadSimilar} from 'sentry/utils/discover/eventView';
+import EventView, {
+  isAPIPayloadSimilar,
+  LocationQuery,
+} from 'sentry/utils/discover/eventView';
 import Measurements from 'sentry/utils/measurements/measurements';
 import parseLinkHeader from 'sentry/utils/parseLinkHeader';
 import {SPAN_OP_BREAKDOWN_FIELDS} from 'sentry/utils/performance/spanOperationBreakdowns/constants';
+import {decodeScalar} from 'sentry/utils/queryString';
 import withApi from 'sentry/utils/withApi';
 
 import TableView from './tableView';
@@ -89,9 +94,17 @@ class Table extends PureComponent<TableProps, TableState> {
 
     const url = `/organizations/${organization.slug}/eventsv2/`;
     const tableFetchID = Symbol('tableFetchID');
-    const apiPayload = eventView.getEventsAPIPayload(location);
+
+    // adding user_modified property. this property will be removed once search bar experiment is complete
+    const apiPayload = eventView.getEventsAPIPayload(location) as LocationQuery &
+      EventQuery & {user_modified?: string};
     apiPayload.referrer = 'api.discover.query-table';
 
+    const queryUserModified = decodeScalar(location.query.userModified);
+    if (queryUserModified !== undefined) {
+      apiPayload.user_modified = queryUserModified;
+    }
+
     setError('', 200);
 
     this.setState({isLoading: true, tableFetchID});

+ 9 - 1
static/app/views/performance/content.tsx

@@ -93,7 +93,14 @@ function PerformanceContent({selection, location, demoMode}: Props) {
       loadOrganizationTags(api, organization.slug, selection);
       addRoutePerformanceContext(selection);
     }
-  }, [selection.datetime]);
+  }, [
+    selection.datetime,
+    previousDateTime,
+    selection,
+    api,
+    organization,
+    onboardingProject,
+  ]);
 
   function setError(newError?: string) {
     if (
@@ -119,6 +126,7 @@ function PerformanceContent({selection, location, demoMode}: Props) {
         cursor: undefined,
         query: String(searchQuery).trim() || undefined,
         isDefaultQuery: false,
+        userModified: true,
       },
     });
   }

+ 25 - 1
static/app/views/performance/index.tsx

@@ -1,3 +1,7 @@
+import {useEffect, useRef} from 'react';
+import {browserHistory} from 'react-router';
+import {Location} from 'history';
+
 import Feature from 'sentry/components/acl/feature';
 import Alert from 'sentry/components/alert';
 import {t} from 'sentry/locale';
@@ -8,10 +12,30 @@ import withOrganization from 'sentry/utils/withOrganization';
 
 type Props = {
   children: React.ReactChildren;
+  location: Location;
   organization: Organization;
 };
 
-function PerformanceContainer({organization, children}: Props) {
+function PerformanceContainer({organization, children, location}: Props) {
+  const prevLocationPathname = useRef('');
+
+  useEffect(
+    function () {
+      // when new perf page loads, query is pristine
+      if (location.pathname !== prevLocationPathname.current) {
+        prevLocationPathname.current = location.pathname;
+        browserHistory.push({
+          pathname: location.pathname,
+          query: {
+            ...location.query,
+            userModified: undefined,
+          },
+        });
+      }
+    },
+    [location]
+  );
+
   function renderNoAccess() {
     return (
       <PageContent>

+ 8 - 1
static/app/views/performance/landing/widgets/widgets/lineChartListWidget.tsx

@@ -13,6 +13,7 @@ import DiscoverQuery from 'sentry/utils/discover/discoverQuery';
 import {getAggregateAlias} from 'sentry/utils/discover/fields';
 import {useMEPSettingContext} from 'sentry/utils/performance/contexts/metricsEnhancedSetting';
 import {usePageError} from 'sentry/utils/performance/contexts/pageError';
+import {decodeScalar} from 'sentry/utils/queryString';
 import {MutableSearch} from 'sentry/utils/tokenizeSearch';
 import withApi from 'sentry/utils/withApi';
 import _DurationChart from 'sentry/views/performance/charts/chart';
@@ -179,12 +180,18 @@ export function LineChartListWidget(props: PerformanceWidgetProps) {
             hideError
             onError={pageError.setPageError}
             queryExtras={getMEPParamsIfApplicable(mepSetting, props.chartSetting)}
+            userModified={decodeScalar(props.location.query.userModified)}
           />
         );
       },
       transform: transformEventsRequestToArea,
     };
-  }, [props.chartSetting, selectedListIndex, mepSetting.memoizationKey]);
+  }, [
+    props.chartSetting,
+    selectedListIndex,
+    mepSetting.memoizationKey,
+    props.location.query.userModified,
+  ]);
 
   const Queries = {
     list: listQuery,

Некоторые файлы не были показаны из-за большого количества измененных файлов