Browse Source

feat(performance-metrics): Port vitalInfo - summary to metrics (#30787)

Priscila Oliveira 3 years ago
parent
commit
6d171b3213

+ 0 - 1
static/app/utils/metrics/metricsRequest.tsx

@@ -88,7 +88,6 @@ class MetricsRequest extends React.Component<Props, State> {
 
   get path() {
     const {orgSlug} = this.props;
-
     return `/organizations/${orgSlug}/metrics/data/`;
   }
 

+ 48 - 15
static/app/views/performance/data.tsx

@@ -450,7 +450,12 @@ function generateGenericPerformanceEventView(
   savedQuery.query = conditions.formatString();
 
   const eventView = EventView.fromNewQueryWithLocation(savedQuery, location);
-  eventView.additionalConditions.addFilterValues('event.type', ['transaction']);
+  // event.type is not a valid metric tag, so it will be added to the query only
+  // in case the metric switch is disabled (for now).
+  if (!isMetricsData) {
+    eventView.additionalConditions.addFilterValues('event.type', ['transaction']);
+  }
+
   return eventView;
 }
 
@@ -517,7 +522,13 @@ function generateBackendPerformanceEventView(
   savedQuery.query = conditions.formatString();
 
   const eventView = EventView.fromNewQueryWithLocation(savedQuery, location);
-  eventView.additionalConditions.addFilterValues('event.type', ['transaction']);
+
+  // event.type is not a valid metric tag, so it will be added to the query only
+  // in case the metric switch is disabled (for now).
+  if (!isMetricsData) {
+    eventView.additionalConditions.addFilterValues('event.type', ['transaction']);
+  }
+
   return eventView;
 }
 
@@ -602,7 +613,13 @@ function generateMobilePerformanceEventView(
   savedQuery.query = conditions.formatString();
 
   const eventView = EventView.fromNewQueryWithLocation(savedQuery, location);
-  eventView.additionalConditions.addFilterValues('event.type', ['transaction']);
+
+  // event.type is not a valid metric tag, so it will be added to the query only
+  // in case the metric switch is disabled (for now).
+  if (!isMetricsData) {
+    eventView.additionalConditions.addFilterValues('event.type', ['transaction']);
+  }
+
   return eventView;
 }
 
@@ -667,9 +684,14 @@ function generateFrontendPageloadPerformanceEventView(
   savedQuery.query = conditions.formatString();
 
   const eventView = EventView.fromNewQueryWithLocation(savedQuery, location);
-  eventView.additionalConditions
-    .addFilterValues('event.type', ['transaction'])
-    .addFilterValues('transaction.op', ['pageload']);
+
+  // event.type and transaction.op are not valid metric tags, so they will be added to the query only
+  // in case the metric switch is disabled (for now).
+  if (!isMetricsData) {
+    eventView.additionalConditions.addFilterValues('event.type', ['transaction']);
+    eventView.additionalConditions.addFilterValues('transaction.op', ['pageload']);
+  }
+
   return eventView;
 }
 
@@ -735,12 +757,18 @@ function generateFrontendOtherPerformanceEventView(
   savedQuery.query = conditions.formatString();
 
   const eventView = EventView.fromNewQueryWithLocation(savedQuery, location);
-  eventView.additionalConditions.addFilterValues('event.type', ['transaction']);
 
-  if (!organization.features.includes('organizations:performance-landing-widgets')) {
-    // Original landing page still should use Frontend (other) with pageload excluded.
-    eventView.additionalConditions.addFilterValues('!transaction.op', ['pageload']);
+  // event.type and !transaction.op are not valid metric tags, so they will be added to the query only
+  // in case the metric switch is disabled (for now).
+  if (!isMetricsData) {
+    eventView.additionalConditions.addFilterValues('event.type', ['transaction']);
+
+    if (!organization.features.includes('organizations:performance-landing-widgets')) {
+      // Original landing page still should use Frontend (other) with pageload excluded.
+      eventView.additionalConditions.addFilterValues('!transaction.op', ['pageload']);
+    }
   }
+
   return eventView;
 }
 
@@ -781,8 +809,8 @@ export function generatePerformanceEventView(
 }
 
 export function generatePerformanceVitalDetailView(
-  _organization: Organization,
-  location: Location
+  location: Location,
+  isMetricsData: boolean
 ): EventView {
   const {query} = location;
 
@@ -831,8 +859,13 @@ export function generatePerformanceVitalDetailView(
   savedQuery.query = conditions.formatString();
 
   const eventView = EventView.fromNewQueryWithLocation(savedQuery, location);
-  eventView.additionalConditions
-    .addFilterValues('event.type', ['transaction'])
-    .addFilterValues('has', [vitalName]);
+
+  // event.type and has are not valid metric tags, so they will be added to the query only
+  // in case the metric switch is disabled (for now).
+  if (!isMetricsData) {
+    eventView.additionalConditions.addFilterValues('event.type', ['transaction']);
+    eventView.additionalConditions.addFilterValues('has', [vitalName]);
+  }
+
   return eventView;
 }

+ 4 - 3
static/app/views/performance/landing/widgets/widgets/lineChartListWidgetMetrics.tsx

@@ -51,6 +51,7 @@ export function LineChartListWidgetMetrics(props: PerformanceWidgetProps) {
     chartHeight,
   } = props;
   const field = fields[0];
+  const orgSlug = organization.slug;
 
   if (fields.length !== 1) {
     throw new Error(`Line chart list widget can only accept a single field (${fields})`);
@@ -68,7 +69,7 @@ export function LineChartListWidgetMetrics(props: PerformanceWidgetProps) {
       component: ({start, end, period, project, environment, children, eventView}) => (
         <MetricsRequest
           api={api}
-          orgSlug={organization.slug}
+          orgSlug={orgSlug}
           start={start}
           end={end}
           statsPeriod={period}
@@ -110,7 +111,7 @@ export function LineChartListWidgetMetrics(props: PerformanceWidgetProps) {
         return (
           <MetricsRequest
             api={api}
-            orgSlug={organization.slug}
+            orgSlug={orgSlug}
             start={start}
             end={end}
             statsPeriod={period}
@@ -174,7 +175,7 @@ export function LineChartListWidgetMetrics(props: PerformanceWidgetProps) {
                 }
 
                 const transactionTarget = transactionSummaryRouteWithQuery({
-                  orgSlug: organization.slug,
+                  orgSlug,
                   projectID: decodeList(location.query.project), // TODO(metrics): filter by project once api supports it (listItem['project.id'])
                   transaction,
                   query: props.eventView.getPageFiltersQuery(),

+ 8 - 7
static/app/views/performance/landing/widgets/widgets/vitalWidgetMetrics.tsx

@@ -50,6 +50,7 @@ export function VitalWidgetMetrics(props: PerformanceWidgetProps) {
   const [selectedListIndex, setSelectListIndex] = useState(0);
   const field = props.fields[0];
   const vital = settingToVital[chartSetting];
+  const orgSlug = organization.slug;
 
   const Queries = {
     list: useMemo<QueryDefinition<DataType, WidgetDataResult>>(
@@ -58,7 +59,7 @@ export function VitalWidgetMetrics(props: PerformanceWidgetProps) {
         component: ({start, end, period, project, environment, children, fields}) => (
           <MetricsRequest
             api={api}
-            orgSlug={organization.slug}
+            orgSlug={orgSlug}
             start={start}
             end={end}
             statsPeriod={period}
@@ -97,7 +98,7 @@ export function VitalWidgetMetrics(props: PerformanceWidgetProps) {
         }) => (
           <MetricsRequest
             api={api}
-            orgSlug={organization.slug}
+            orgSlug={orgSlug}
             start={start}
             end={end}
             statsPeriod={period}
@@ -141,8 +142,8 @@ export function VitalWidgetMetrics(props: PerformanceWidgetProps) {
 
         const data = {
           [vital]: getVitalData({
-            field,
             transaction: selectedTransaction,
+            field,
             response: widgetData.chart.response,
           }),
         };
@@ -163,7 +164,7 @@ export function VitalWidgetMetrics(props: PerformanceWidgetProps) {
       EmptyComponent={WidgetEmptyStateWarning}
       HeaderActions={provided => {
         const target = vitalDetailRouteWithQuery({
-          orgSlug: organization.slug,
+          orgSlug,
           query: eventView.generateQueryStringObject(),
           vitalName: vital,
           projectID: decodeList(location.query.project),
@@ -238,7 +239,7 @@ export function VitalWidgetMetrics(props: PerformanceWidgetProps) {
                   _eventView.query = initialConditions.formatString();
 
                   const target = vitalDetailRouteWithQuery({
-                    orgSlug: organization.slug,
+                    orgSlug,
                     query: _eventView.generateQueryStringObject(),
                     vitalName: vital,
                     projectID: decodeList(location.query.project), // TODO(metrics): filter by project once api supports it (listItem['project.id'])
@@ -246,8 +247,8 @@ export function VitalWidgetMetrics(props: PerformanceWidgetProps) {
 
                   const data = {
                     [vital]: getVitalData({
-                      field,
                       transaction,
+                      field,
                       response: widgetData.chart.response,
                     }),
                   };
@@ -309,7 +310,7 @@ export function getVitalData({
     good: 0,
     p75: 0,
     ...groups.reduce((acc, group) => {
-      acc[group.by.measurement_rating] = group.totals[field];
+      acc[group.by.measurement_rating] = group.totals[field] ?? 0;
       return acc;
     }, {}),
     total: groups.reduce((acc, group) => acc + (group.totals[field] ?? 0), 0),

+ 1 - 2
static/app/views/performance/table.tsx

@@ -60,9 +60,8 @@ type Props = {
   organization: Organization;
   location: Location;
   setError: (msg: string | undefined) => void;
-  summaryConditions?: string;
-
   projects: Project[];
+  summaryConditions?: string;
   columnTitles?: string[];
 };
 

+ 14 - 0
static/app/views/performance/transactionSummary/header.tsx

@@ -1,4 +1,5 @@
 import * as React from 'react';
+import {browserHistory} from 'react-router';
 import styled from '@emotion/styled';
 import {Location} from 'history';
 
@@ -19,6 +20,7 @@ import {decodeScalar} from 'sentry/utils/queryString';
 import Breadcrumb from 'sentry/views/performance/breadcrumb';
 
 import {getCurrentLandingDisplay, LandingDisplayField} from '../landing/utils';
+import {MetricsSwitch} from '../metricsSwitch';
 
 import {eventsRouteWithQuery} from './transactionEvents/utils';
 import {spansRouteWithQuery} from './transactionSpans/utils';
@@ -211,6 +213,17 @@ class TransactionHeader extends React.Component<Props> {
     }
   }
 
+  handleSwitchMetrics = () => {
+    const {location} = this.props;
+    browserHistory.push({
+      pathname: location.pathname,
+      query: {
+        ...location.query,
+        query: undefined,
+      },
+    });
+  };
+
   render() {
     const {organization, location, projectId, transactionName, currentTab} = this.props;
 
@@ -242,6 +255,7 @@ class TransactionHeader extends React.Component<Props> {
         </Layout.HeaderContent>
         <Layout.HeaderActions>
           <ButtonBar gap={1}>
+            <MetricsSwitch onSwitch={this.handleSwitchMetrics} />
             <Feature organization={organization} features={['incidents']}>
               {({hasFeature}) => hasFeature && this.renderCreateAlertButton()}
             </Feature>

+ 73 - 57
static/app/views/performance/transactionSummary/pageLayout.tsx

@@ -19,6 +19,7 @@ import EventView from 'sentry/utils/discover/eventView';
 import {PerformanceEventViewProvider} from 'sentry/utils/performance/contexts/performanceEventViewContext';
 import {decodeScalar} from 'sentry/utils/queryString';
 
+import {MetricsSwitchContext} from '../metricsSwitch';
 import {getTransactionName} from '../utils';
 
 import TransactionHeader from './header';
@@ -33,6 +34,7 @@ export type ChildProps = {
   projectId: string;
   transactionName: string;
   setError: React.Dispatch<React.SetStateAction<string | undefined>>;
+  isMetricsData: boolean;
   // These are used to trigger a reload when the threshold/metric changes.
   transactionThreshold?: number;
   transactionThresholdMetric?: TransactionThresholdMetric;
@@ -44,7 +46,11 @@ type Props = {
   projects: Project[];
   tab: Tab;
   getDocumentTitle: (name: string) => string;
-  generateEventView: (location: Location, transactionName: string) => EventView;
+  generateEventView: (props: {
+    location: Location;
+    transactionName: string;
+    isMetricsData: boolean;
+  }) => EventView;
   childComponent: (props: ChildProps) => JSX.Element;
   relativeDateOptions?: Record<string, React.ReactNode>;
   maxPickableDays?: number;
@@ -96,8 +102,6 @@ function PageLayout(props: Props) {
     TransactionThresholdMetric | undefined
   >();
 
-  const eventView = generateEventView(location, transactionName);
-
   return (
     <SentryDocumentTitle
       title={getDocumentTitle(transactionName)}
@@ -109,60 +113,72 @@ function PageLayout(props: Props) {
         organization={organization}
         renderDisabled={NoAccess}
       >
-        <PerformanceEventViewProvider value={{eventView}}>
-          <PageFiltersContainer
-            lockedMessageSubject={t('transaction')}
-            shouldForceProject={defined(project)}
-            forceProject={project}
-            specificProjectSlugs={defined(project) ? [project.slug] : []}
-            disableMultipleProjectSelection
-            showProjectSettingsLink
-            relativeDateOptions={relativeDateOptions}
-            maxPickableDays={maxPickableDays}
-          >
-            <StyledPageContent>
-              <NoProjectMessage organization={organization}>
-                <TransactionHeader
-                  eventView={eventView}
-                  location={location}
-                  organization={organization}
-                  projects={projects}
-                  projectId={projectId}
-                  transactionName={transactionName}
-                  currentTab={tab}
-                  hasWebVitals={tab === Tab.WebVitals ? 'yes' : 'maybe'}
-                  handleIncompatibleQuery={handleIncompatibleQuery}
-                  onChangeThreshold={(threshold, metric) => {
-                    setTransactionThreshold(threshold);
-                    setTransactionThresholdMetric(metric);
-                  }}
-                />
-                <Layout.Body>
-                  <StyledSdkUpdatesAlert />
-                  {defined(error) && (
-                    <StyledAlert type="error" icon={<IconFlag size="md" />}>
-                      {error}
-                    </StyledAlert>
-                  )}
-                  {incompatibleAlertNotice && (
-                    <Layout.Main fullWidth>{incompatibleAlertNotice}</Layout.Main>
-                  )}
-                  <ChildComponent
-                    location={location}
-                    organization={organization}
-                    projects={projects}
-                    eventView={eventView}
-                    projectId={projectId}
-                    transactionName={transactionName}
-                    setError={setError}
-                    transactionThreshold={transactionThreshold}
-                    transactionThresholdMetric={transactionThresholdMetric}
-                  />
-                </Layout.Body>
-              </NoProjectMessage>
-            </StyledPageContent>
-          </PageFiltersContainer>
-        </PerformanceEventViewProvider>
+        <MetricsSwitchContext.Consumer>
+          {({isMetricsData}) => {
+            const eventView = generateEventView({
+              location,
+              transactionName,
+              isMetricsData,
+            });
+            return (
+              <PerformanceEventViewProvider value={{eventView}}>
+                <PageFiltersContainer
+                  lockedMessageSubject={t('transaction')}
+                  shouldForceProject={defined(project)}
+                  forceProject={project}
+                  specificProjectSlugs={defined(project) ? [project.slug] : []}
+                  disableMultipleProjectSelection
+                  showProjectSettingsLink
+                  relativeDateOptions={relativeDateOptions}
+                  maxPickableDays={maxPickableDays}
+                >
+                  <StyledPageContent>
+                    <NoProjectMessage organization={organization}>
+                      <TransactionHeader
+                        eventView={eventView}
+                        location={location}
+                        organization={organization}
+                        projects={projects}
+                        projectId={projectId}
+                        transactionName={transactionName}
+                        currentTab={tab}
+                        hasWebVitals={tab === Tab.WebVitals ? 'yes' : 'maybe'}
+                        handleIncompatibleQuery={handleIncompatibleQuery}
+                        onChangeThreshold={(threshold, metric) => {
+                          setTransactionThreshold(threshold);
+                          setTransactionThresholdMetric(metric);
+                        }}
+                      />
+                      <Layout.Body>
+                        <StyledSdkUpdatesAlert />
+                        {defined(error) && (
+                          <StyledAlert type="error" icon={<IconFlag size="md" />}>
+                            {error}
+                          </StyledAlert>
+                        )}
+                        {incompatibleAlertNotice && (
+                          <Layout.Main fullWidth>{incompatibleAlertNotice}</Layout.Main>
+                        )}
+                        <ChildComponent
+                          location={location}
+                          organization={organization}
+                          projects={projects}
+                          eventView={eventView}
+                          projectId={projectId}
+                          transactionName={transactionName}
+                          setError={setError}
+                          transactionThreshold={transactionThreshold}
+                          transactionThresholdMetric={transactionThresholdMetric}
+                          isMetricsData={isMetricsData}
+                        />
+                      </Layout.Body>
+                    </NoProjectMessage>
+                  </StyledPageContent>
+                </PageFiltersContainer>
+              </PerformanceEventViewProvider>
+            );
+          }}
+        </MetricsSwitchContext.Consumer>
       </Feature>
     </SentryDocumentTitle>
   );

+ 17 - 4
static/app/views/performance/transactionSummary/transactionEvents/index.tsx

@@ -205,12 +205,25 @@ function getWebVital(location: Location): WebVital | undefined {
   return undefined;
 }
 
-function generateEventView(location: Location, transactionName: string): EventView {
+function generateEventView({
+  location,
+  transactionName,
+  isMetricsData,
+}: {
+  location: Location;
+  transactionName: string;
+  isMetricsData: boolean;
+}): EventView {
   const query = decodeScalar(location.query.query, '');
   const conditions = new MutableSearch(query);
-  conditions
-    .setFilterValues('event.type', ['transaction'])
-    .setFilterValues('transaction', [transactionName]);
+
+  // event.type is not a valid metric tag, so it will be added to the query only
+  // in case the metric switch is disabled (for now).
+  if (!isMetricsData) {
+    conditions.setFilterValues('event.type', ['transaction']);
+  }
+
+  conditions.setFilterValues('transaction', [transactionName]);
 
   Object.keys(conditions.filters).forEach(field => {
     if (isAggregateField(field)) {

+ 28 - 10
static/app/views/performance/transactionSummary/transactionOverview/content.tsx

@@ -37,6 +37,7 @@ import {
   VITAL_GROUPS,
 } from 'sentry/views/performance/transactionSummary/transactionVitals/constants';
 
+import MetricsSearchBar from '../../metricsSearchBar';
 import {isSummaryViewFrontend, isSummaryViewFrontendPageLoad} from '../../utils';
 import Filter, {
   decodeFilterFromLocation,
@@ -71,6 +72,7 @@ type Props = {
   projects: Project[];
   onChangeFilter: (newFilter: SpanOperationBreakdownFilter) => void;
   spanOperationBreakdownFilter: SpanOperationBreakdownFilter;
+  isMetricsData?: boolean;
 };
 
 class SummaryContent extends React.Component<Props> {
@@ -192,6 +194,7 @@ class SummaryContent extends React.Component<Props> {
       totalValues,
       onChangeFilter,
       spanOperationBreakdownFilter,
+      isMetricsData,
     } = this.props;
     const hasPerformanceEventsPage = organization.features.includes(
       'performance-events-page'
@@ -302,15 +305,28 @@ class SummaryContent extends React.Component<Props> {
               currentFilter={spanOperationBreakdownFilter}
               onChangeFilter={onChangeFilter}
             />
-            <StyledSearchBar
-              searchSource="transaction_summary"
-              organization={organization}
-              projectIds={eventView.project}
-              query={query}
-              fields={eventView.fields}
-              onSearch={this.handleSearch}
-              maxQueryLength={MAX_QUERY_LENGTH}
-            />
+            <SearchBarContainer>
+              {isMetricsData ? (
+                <MetricsSearchBar
+                  searchSource="transaction_summary_metrics"
+                  orgSlug={organization.slug}
+                  projectIds={eventView.project}
+                  query={query}
+                  onSearch={this.handleSearch}
+                  maxQueryLength={MAX_QUERY_LENGTH}
+                />
+              ) : (
+                <SearchBar
+                  searchSource="transaction_summary"
+                  organization={organization}
+                  projectIds={eventView.project}
+                  query={query}
+                  fields={eventView.fields}
+                  onSearch={this.handleSearch}
+                  maxQueryLength={MAX_QUERY_LENGTH}
+                />
+              )}
+            </SearchBarContainer>
           </Search>
           <TransactionSummaryCharts
             organization={organization}
@@ -384,6 +400,8 @@ class SummaryContent extends React.Component<Props> {
             error={error}
             totals={totalValues}
             transactionName={transactionName}
+            eventView={eventView}
+            isMetricsData={isMetricsData}
           />
           {!isFrontendView && (
             <StatusBreakdown
@@ -494,7 +512,7 @@ const Search = styled('div')`
   margin-bottom: ${space(3)};
 `;
 
-const StyledSearchBar = styled(SearchBar)`
+const SearchBarContainer = styled('div')`
   flex-grow: 1;
 `;
 

+ 19 - 4
static/app/views/performance/transactionSummary/transactionOverview/index.tsx

@@ -81,6 +81,7 @@ function OverviewContentWrapper(props: ChildProps) {
     transactionName,
     transactionThreshold,
     transactionThresholdMetric,
+    isMetricsData,
   } = props;
 
   const spanOperationBreakdownFilter = decodeFilterFromLocation(location);
@@ -132,6 +133,7 @@ function OverviewContentWrapper(props: ChildProps) {
             totalValues={totals}
             onChangeFilter={onChangeFilter}
             spanOperationBreakdownFilter={spanOperationBreakdownFilter}
+            isMetricsData={isMetricsData}
           />
         );
       }}
@@ -150,14 +152,27 @@ function getDocumentTitle(transactionName: string): string {
   return [t('Summary'), t('Performance')].join(' - ');
 }
 
-function generateEventView(location: Location, transactionName: string): EventView {
+function generateEventView({
+  location,
+  transactionName,
+  isMetricsData,
+}: {
+  location: Location;
+  transactionName: string;
+  isMetricsData: boolean;
+}): EventView {
   // Use the user supplied query but overwrite any transaction or event type
   // conditions they applied.
   const query = decodeScalar(location.query.query, '');
   const conditions = new MutableSearch(query);
-  conditions
-    .setFilterValues('event.type', ['transaction'])
-    .setFilterValues('transaction', [transactionName]);
+
+  // event.type is not a valid metric tag, so it will be added to the query only
+  // in case the metric switch is disabled (for now).
+  if (!isMetricsData) {
+    conditions.setFilterValues('event.type', ['transaction']);
+  }
+
+  conditions.setFilterValues('transaction', [transactionName]);
 
   Object.keys(conditions.filters).forEach(field => {
     if (isAggregateField(field)) {

Some files were not shown because too many files changed in this diff