Browse Source

fix(mep): Use new endpoint and fix search error (#37480)

This uses the new endpoint for search as well as fixing the last remaining major UI error for MEP internal+.
Kev 2 years ago
parent
commit
ae0519bfa8

+ 1 - 1
static/app/utils/performance/contexts/metricsEnhancedPerformanceDataContext.tsx

@@ -66,7 +66,7 @@ export const MEPTag = () => {
     return <span data-test-id="no-metrics-data-tag" />;
   }
 
-  const tagText = isMetricsData ? 'metrics' : 'transactions';
+  const tagText = isMetricsData ? 'processed' : 'indexed';
 
   return <Tag data-test-id="has-metrics-data-tag">{tagText}</Tag>;
 };

+ 18 - 4
static/app/utils/performance/contexts/metricsEnhancedSetting.tsx

@@ -46,7 +46,8 @@ export enum MEPState {
   transactionsOnly = 'transactionsOnly',
 }
 
-const METRIC_SETTING_PARAM = 'metricSetting';
+export const METRIC_SETTING_PARAM = 'metricSetting';
+export const METRIC_SEARCH_SETTING_PARAM = 'metricSearchSetting'; // TODO: Clean this up since we don't need multiple params in practice.
 
 const storageKey = 'performance.metrics-enhanced-setting';
 export class MEPSetting {
@@ -67,11 +68,24 @@ export class MEPSetting {
   }
 }
 
+export function canUseMetricsDevUI(organization: Organization) {
+  return organization.features.includes('performance-use-metrics');
+}
+
 export function canUseMetricsData(organization: Organization) {
-  return (
-    organization.features.includes('performance-use-metrics') ||
-    organization.features.includes('performance-transaction-name-only-search')
+  const isDevFlagOn = canUseMetricsDevUI(organization); // Forces metrics data on as well.
+  const isInternalViewOn = organization.features.includes(
+    'performance-transaction-name-only-search'
+  ); // TODO: Swap this flag out.
+
+  const samplingRolloutFlag = organization.features.includes(
+    'organizations:server-side-sampling'
   );
+  const isRollingOut =
+    samplingRolloutFlag &&
+    organization.features.includes('organizations:mep-rollout-flag');
+
+  return isDevFlagOn || isInternalViewOn || isRollingOut;
 }
 
 export const MEPSettingProvider = ({

+ 54 - 0
static/app/utils/performance/metricsEnhanced/metricsCompatibilityQuery.tsx

@@ -0,0 +1,54 @@
+import omit from 'lodash/omit';
+
+import EventView from 'sentry/utils/discover/eventView';
+import GenericDiscoverQuery, {
+  DiscoverQueryProps,
+  GenericChildrenProps,
+} from 'sentry/utils/discover/genericDiscoverQuery';
+import useApi from 'sentry/utils/useApi';
+
+export interface MetricsCompatibilityData {
+  sum: {
+    metrics?: number;
+    metrics_null?: number;
+    metrics_unparam?: number;
+  };
+  compatible_projects?: number[];
+  dynamic_sampling_projects?: number[];
+}
+
+type QueryProps = Omit<DiscoverQueryProps, 'eventView' | 'api'> & {
+  children: (props: GenericChildrenProps<MetricsCompatibilityData>) => React.ReactNode;
+  eventView: EventView;
+};
+
+function getRequestPayload({
+  eventView,
+  location,
+}: Pick<DiscoverQueryProps, 'eventView' | 'location'>) {
+  return omit(eventView.getEventsAPIPayload(location), [
+    'field',
+    'sort',
+    'per_page',
+    'query',
+  ]);
+}
+
+export default function MetricsCompatibilityQuery({children, ...props}: QueryProps) {
+  const api = useApi();
+  return (
+    <GenericDiscoverQuery<MetricsCompatibilityData, {}>
+      route="events-metrics-compatibility"
+      getRequestPayload={getRequestPayload}
+      {...props}
+      api={api}
+    >
+      {({tableData, ...rest}) => {
+        return children({
+          tableData,
+          ...rest,
+        });
+      }}
+    </GenericDiscoverQuery>
+  );
+}

+ 8 - 5
static/app/views/performance/content.tsx

@@ -11,6 +11,11 @@ import {ALL_ACCESS_PROJECTS} from 'sentry/constants/pageFilters';
 import {t} from 'sentry/locale';
 import {PageFilters, Project} from 'sentry/types';
 import trackAdvancedAnalyticsEvent from 'sentry/utils/analytics/trackAdvancedAnalyticsEvent';
+import {
+  canUseMetricsData,
+  MEPState,
+  METRIC_SEARCH_SETTING_PARAM,
+} from 'sentry/utils/performance/contexts/metricsEnhancedSetting';
 import {PerformanceEventViewProvider} from 'sentry/utils/performance/contexts/performanceEventViewContext';
 import useApi from 'sentry/utils/useApi';
 import useOrganization from 'sentry/utils/useOrganization';
@@ -45,10 +50,7 @@ function PerformanceContent({selection, location, demoMode, router}: Props) {
   const previousDateTime = usePrevious(selection.datetime);
 
   const [state, setState] = useState<State>({error: undefined});
-  const withStaticFilters = organization.features.includes(
-    'performance-transaction-name-only-search'
-  );
-
+  const withStaticFilters = canUseMetricsData(organization);
   const eventView = generatePerformanceEventView(location, projects, {
     withStaticFilters,
   });
@@ -130,7 +132,7 @@ function PerformanceContent({selection, location, demoMode, router}: Props) {
     setState({...state, error: newError});
   }
 
-  function handleSearch(searchQuery: string) {
+  function handleSearch(searchQuery: string, currentMEPState?: MEPState) {
     trackAdvancedAnalyticsEvent('performance_views.overview.search', {organization});
 
     browserHistory.push({
@@ -139,6 +141,7 @@ function PerformanceContent({selection, location, demoMode, router}: Props) {
         ...location.query,
         cursor: undefined,
         query: String(searchQuery).trim() || undefined,
+        [METRIC_SEARCH_SETTING_PARAM]: currentMEPState,
         isDefaultQuery: false,
       },
     });

+ 31 - 11
static/app/views/performance/data.tsx

@@ -5,6 +5,11 @@ import {ALL_ACCESS_PROJECTS} from 'sentry/constants/pageFilters';
 import {t} from 'sentry/locale';
 import {NewQuery, Organization, Project, SelectValue} from 'sentry/types';
 import EventView from 'sentry/utils/discover/eventView';
+import {
+  MEPState,
+  METRIC_SEARCH_SETTING_PARAM,
+  METRIC_SETTING_PARAM,
+} from 'sentry/utils/performance/contexts/metricsEnhancedSetting';
 import {WEB_VITAL_DETAILS} from 'sentry/utils/performance/vitals/constants';
 import {decodeScalar} from 'sentry/utils/queryString';
 import {MutableSearch} from 'sentry/utils/tokenizeSearch';
@@ -389,6 +394,16 @@ function shouldAddDefaultConditions(location: Location) {
   return !searchQuery && isDefaultQuery !== 'false';
 }
 
+function isUsingLimitedSearch(location: Location, withStaticFilters: boolean) {
+  const {query} = location;
+  const mepSearchState = decodeScalar(query[METRIC_SEARCH_SETTING_PARAM], '');
+  const mepSettingState = decodeScalar(query[METRIC_SETTING_PARAM], ''); // TODO: Can be removed since it's for dev ui only.
+  return (
+    withStaticFilters &&
+    (mepSearchState === MEPState.metricsOnly || mepSettingState === MEPState.metricsOnly)
+  );
+}
+
 function generateGenericPerformanceEventView(
   location: Location,
   withStaticFilters: boolean
@@ -430,6 +445,7 @@ function generateGenericPerformanceEventView(
 
   const searchQuery = decodeScalar(query.query, '');
   const conditions = new MutableSearch(searchQuery);
+  const isLimitedSearch = isUsingLimitedSearch(location, withStaticFilters);
 
   // This is not an override condition since we want the duration to appear in the search bar as a default.
   if (shouldAddDefaultConditions(location) && !withStaticFilters) {
@@ -439,11 +455,11 @@ function generateGenericPerformanceEventView(
   // If there is a bare text search, we want to treat it as a search
   // on the transaction name.
   if (conditions.freeText.length > 0) {
-    const parsedFreeText = withStaticFilters
+    const parsedFreeText = isLimitedSearch
       ? decodeScalar(conditions.freeText, '')
       : conditions.freeText.join(' ');
 
-    if (withStaticFilters) {
+    if (isLimitedSearch) {
       // the query here is a user entered condition, no need to escape it
       conditions.setFilterValues('transaction', [`${parsedFreeText}`], false);
     } else {
@@ -512,6 +528,7 @@ function generateBackendPerformanceEventView(
 
   const searchQuery = decodeScalar(query.query, '');
   const conditions = new MutableSearch(searchQuery);
+  const isLimitedSearch = isUsingLimitedSearch(location, withStaticFilters);
 
   // This is not an override condition since we want the duration to appear in the search bar as a default.
   if (shouldAddDefaultConditions(location) && !withStaticFilters) {
@@ -521,11 +538,11 @@ function generateBackendPerformanceEventView(
   // If there is a bare text search, we want to treat it as a search
   // on the transaction name.
   if (conditions.freeText.length > 0) {
-    const parsedFreeText = withStaticFilters
+    const parsedFreeText = isLimitedSearch
       ? decodeScalar(conditions.freeText, '')
       : conditions.freeText.join(' ');
 
-    if (withStaticFilters) {
+    if (isLimitedSearch) {
       // the query here is a user entered condition, no need to escape it
       conditions.setFilterValues('transaction', [`${parsedFreeText}`], false);
     } else {
@@ -598,6 +615,7 @@ function generateMobilePerformanceEventView(
 
   const searchQuery = decodeScalar(query.query, '');
   const conditions = new MutableSearch(searchQuery);
+  const isLimitedSearch = isUsingLimitedSearch(location, withStaticFilters);
 
   // This is not an override condition since we want the duration to appear in the search bar as a default.
   if (shouldAddDefaultConditions(location) && !withStaticFilters) {
@@ -607,12 +625,12 @@ function generateMobilePerformanceEventView(
   // If there is a bare text search, we want to treat it as a search
   // on the transaction name.
   if (conditions.freeText.length > 0) {
-    const parsedFreeText = withStaticFilters
+    const parsedFreeText = isLimitedSearch
       ? // pick first element to search transactions by name
         decodeScalar(conditions.freeText, '')
       : conditions.freeText.join(' ');
 
-    if (withStaticFilters) {
+    if (isLimitedSearch) {
       // the query here is a user entered condition, no need to escape it
       conditions.setFilterValues('transaction', [`${parsedFreeText}`], false);
     } else {
@@ -671,6 +689,7 @@ function generateFrontendPageloadPerformanceEventView(
 
   const searchQuery = decodeScalar(query.query, '');
   const conditions = new MutableSearch(searchQuery);
+  const isLimitedSearch = isUsingLimitedSearch(location, withStaticFilters);
 
   // This is not an override condition since we want the duration to appear in the search bar as a default.
   if (shouldAddDefaultConditions(location) && !withStaticFilters) {
@@ -680,12 +699,12 @@ function generateFrontendPageloadPerformanceEventView(
   // If there is a bare text search, we want to treat it as a search
   // on the transaction name.
   if (conditions.freeText.length > 0) {
-    const parsedFreeText = withStaticFilters
+    const parsedFreeText = isLimitedSearch
       ? // pick first element to search transactions by name
         decodeScalar(conditions.freeText, '')
       : conditions.freeText.join(' ');
 
-    if (withStaticFilters) {
+    if (isLimitedSearch) {
       // the query here is a user entered condition, no need to escape it
       conditions.setFilterValues('transaction', [`${parsedFreeText}`], false);
     } else {
@@ -745,6 +764,7 @@ function generateFrontendOtherPerformanceEventView(
 
   const searchQuery = decodeScalar(query.query, '');
   const conditions = new MutableSearch(searchQuery);
+  const isLimitedSearch = isUsingLimitedSearch(location, withStaticFilters);
 
   // This is not an override condition since we want the duration to appear in the search bar as a default.
   if (shouldAddDefaultConditions(location) && !withStaticFilters) {
@@ -753,13 +773,13 @@ function generateFrontendOtherPerformanceEventView(
 
   // If there is a bare text search, we want to treat it as a search
   // on the transaction name.
-  if (conditions.freeText.length > 0 && !withStaticFilters) {
-    const parsedFreeText = withStaticFilters
+  if (conditions.freeText.length > 0 && !isLimitedSearch) {
+    const parsedFreeText = isLimitedSearch
       ? // pick first element to search transactions by name
         decodeScalar(conditions.freeText, '')
       : conditions.freeText.join(' ');
 
-    if (withStaticFilters) {
+    if (isLimitedSearch) {
       // the query here is a user entered condition, no need to escape it
       conditions.setFilterValues('transaction', [`${parsedFreeText}`], false);
     } else {

+ 19 - 43
static/app/views/performance/landing/index.tsx

@@ -3,8 +3,6 @@ import {browserHistory, InjectedRouter} from 'react-router';
 import styled from '@emotion/styled';
 import {Location} from 'history';
 
-import {openModal} from 'sentry/actionCreators/modal';
-import Feature from 'sentry/components/acl/feature';
 import Button from 'sentry/components/button';
 import ButtonBar from 'sentry/components/buttonBar';
 import DatePageFilter from 'sentry/components/datePageFilter';
@@ -18,7 +16,6 @@ import TransactionNameSearchBar from 'sentry/components/performance/searchBar';
 import * as TeamKeyTransactionManager from 'sentry/components/performance/teamKeyTransactionsManager';
 import ProjectPageFilter from 'sentry/components/projectPageFilter';
 import {MAX_QUERY_LENGTH} from 'sentry/constants';
-import {IconSettings} from 'sentry/icons';
 import {t} from 'sentry/locale';
 import {PageContent} from 'sentry/styles/organization';
 import space from 'sentry/styles/space';
@@ -27,6 +24,7 @@ import EventView from 'sentry/utils/discover/eventView';
 import {generateAggregateFields} from 'sentry/utils/discover/fields';
 import {GenericQueryBatcher} from 'sentry/utils/performance/contexts/genericQueryBatcher';
 import {
+  canUseMetricsData,
   MEPConsumer,
   MEPSettingProvider,
   MEPState,
@@ -49,7 +47,6 @@ import {FrontendPageloadView} from './views/frontendPageloadView';
 import {MobileView} from './views/mobileView';
 import {MetricsDataSwitcher} from './metricsDataSwitcher';
 import {MetricsDataSwitcherAlert} from './metricsDataSwitcherAlert';
-import SamplingModal, {modalCss} from './samplingModal';
 import {
   getDefaultDisplayForPlatform,
   getLandingDisplayFromParam,
@@ -60,7 +57,7 @@ import {
 
 type Props = {
   eventView: EventView;
-  handleSearch: (searchQuery: string) => void;
+  handleSearch: (searchQuery: string, currentMEPState?: MEPState) => void;
   handleTrendsClick: () => void;
   location: Location;
   onboardingProject: Project | undefined;
@@ -89,7 +86,6 @@ export function PerformanceLanding(props: Props) {
     handleSearch,
     handleTrendsClick,
     onboardingProject,
-    withStaticFilters,
   } = props;
 
   const {teams, initiallyLoaded} = useTeams({provideUserTeams: true});
@@ -130,28 +126,9 @@ export function PerformanceLanding(props: Props) {
   };
 
   const derivedQuery = getTransactionSearchQuery(location, eventView.query);
-  const searchQuery = withStaticFilters
-    ? getFreeTextFromQuery(derivedQuery)
-    : derivedQuery;
 
   const ViewComponent = fieldToViewMap[landingDisplay.field];
 
-  const fnOpenModal = () => {
-    openModal(
-      modalProps => (
-        <SamplingModal
-          {...modalProps}
-          organization={organization}
-          eventView={eventView}
-          projects={projects}
-          onApply={() => {}}
-          isMEPEnabled
-        />
-      ),
-      {modalCss, backdrop: 'static'}
-    );
-  };
-
   let pageFilters: React.ReactNode = (
     <PageFilterBar condensed>
       <ProjectPageFilter />
@@ -168,9 +145,7 @@ export function PerformanceLanding(props: Props) {
     ? SearchContainerWithFilterAndMetrics
     : SearchContainerWithFilter;
 
-  const shouldShowTransactionNameOnlySearch = organization.features.includes(
-    'performance-transaction-name-only-search'
-  );
+  const shouldShowTransactionNameOnlySearch = canUseMetricsData(organization);
 
   return (
     <StyledPageContent data-test-id="performance-landing-v3">
@@ -189,14 +164,6 @@ export function PerformanceLanding(props: Props) {
                 >
                   {t('View Trends')}
                 </Button>
-                <Feature features={['organizations:performance-use-metrics']}>
-                  <Button
-                    onClick={() => fnOpenModal()}
-                    icon={<IconSettings />}
-                    aria-label={t('Settings')}
-                    data-test-id="open-meps-settings"
-                  />
-                </Feature>
               </ButtonBar>
             )}
           </Layout.HeaderActions>
@@ -257,16 +224,23 @@ export function PerformanceLanding(props: Props) {
                       <SearchFilterContainer>
                         {pageFilters}
                         <MEPConsumer>
-                          {({metricSettingState}) =>
-                            metricSettingState === MEPState.metricsOnly &&
-                            shouldShowTransactionNameOnlySearch ? (
+                          {({metricSettingState}) => {
+                            const searchQuery =
+                              metricSettingState === MEPState.metricsOnly
+                                ? getFreeTextFromQuery(derivedQuery)
+                                : derivedQuery;
+
+                            return metricSettingState === MEPState.metricsOnly &&
+                              shouldShowTransactionNameOnlySearch ? (
                               // TODO replace `handleSearch prop` with transaction name search once
                               // transaction name search becomes the default search bar
                               <TransactionNameSearchBar
                                 organization={organization}
                                 location={location}
                                 eventView={eventView}
-                                onSearch={handleSearch}
+                                onSearch={(query: string) =>
+                                  handleSearch(query, metricSettingState)
+                                }
                                 query={searchQuery}
                               />
                             ) : (
@@ -280,11 +254,13 @@ export function PerformanceLanding(props: Props) {
                                   [...eventView.fields, {field: 'tps()'}],
                                   ['epm()', 'eps()']
                                 )}
-                                onSearch={handleSearch}
+                                onSearch={(query: string) =>
+                                  handleSearch(query, metricSettingState ?? undefined)
+                                }
                                 maxQueryLength={MAX_QUERY_LENGTH}
                               />
-                            )
-                          }
+                            );
+                          }}
                         </MEPConsumer>
                         <MetricsEventsDropdown />
                       </SearchFilterContainer>

+ 190 - 207
static/app/views/performance/landing/metricsDataSwitcher.tsx

@@ -1,15 +1,23 @@
-import {Fragment} from 'react';
+import {Fragment, useEffect} from 'react';
+import {browserHistory} from 'react-router';
+import styled from '@emotion/styled';
 import {Location} from 'history';
 
+import LoadingIndicator from 'sentry/components/loadingIndicator';
 import {Organization} from 'sentry/types';
-import DiscoverQuery, {TableData} from 'sentry/utils/discover/discoverQuery';
+import {parsePeriodToHours} from 'sentry/utils/dates';
 import EventView from 'sentry/utils/discover/eventView';
-import {GenericChildrenProps} from 'sentry/utils/discover/genericDiscoverQuery';
-import {canUseMetricsData} from 'sentry/utils/performance/contexts/metricsEnhancedSetting';
-
-import {getMetricOnlyQueryParams} from './widgets/utils';
-
-export interface MetricDataSwitcherChildrenProps {
+import {
+  canUseMetricsData,
+  MEPState,
+  METRIC_SEARCH_SETTING_PARAM,
+} from 'sentry/utils/performance/contexts/metricsEnhancedSetting';
+import MetricsCompatibilityQuery, {
+  MetricsCompatibilityData,
+} from 'sentry/utils/performance/metricsEnhanced/metricsCompatibilityQuery';
+import {decodeScalar} from 'sentry/utils/queryString';
+
+export interface MetricDataSwitcherOutcome {
   forceTransactionsOnly: boolean;
   compatibleProjects?: number[];
   shouldNotifyUnnamedTransactions?: boolean;
@@ -17,23 +25,12 @@ export interface MetricDataSwitcherChildrenProps {
 }
 
 interface MetricDataSwitchProps {
-  children: (props: MetricDataSwitcherChildrenProps) => React.ReactNode;
+  children: (props: MetricDataSwitcherOutcome) => React.ReactNode;
   eventView: EventView;
   location: Location;
   organization: Organization;
 }
 
-export enum LandingPageMEPDecision {
-  fallbackToTransactions = 'fallbackToTransactions',
-}
-
-interface DataCounts {
-  metricsCountData: GenericChildrenProps<TableData>;
-  nullData: GenericChildrenProps<TableData>;
-  transactionCountData: GenericChildrenProps<TableData>;
-  unparamData: GenericChildrenProps<TableData>;
-}
-
 /**
  * This component decides based on some stats about current projects whether to show certain views of the landing page.
  * It is primarily needed for the rollout during which time users, despite having the flag enabled,
@@ -52,252 +49,238 @@ export function MetricsDataSwitcher(props: MetricDataSwitchProps) {
     );
   }
 
-  const countView = props.eventView.withColumns([{kind: 'field', field: 'count()'}]);
-  countView.statsPeriod = '15m';
-  countView.start = undefined;
-  countView.end = undefined;
-  const unparamView = countView.clone();
-  unparamView.additionalConditions.setFilterValues('transaction', [
-    '<< unparameterized >>',
-  ]);
-  const nullView = countView.clone();
-  nullView.additionalConditions.setFilterValues('transaction', ['']);
-
-  const projectCompatibleView = countView.withColumns([
-    {kind: 'field', field: 'project.id'},
-    {kind: 'field', field: 'count()'},
-  ]);
-
-  const projectIncompatibleView = projectCompatibleView.clone();
-  projectIncompatibleView.additionalConditions.setFilterValues('transaction', ['']);
-
   const baseDiscoverProps = {
     location: props.location,
     orgSlug: props.organization.slug,
     cursor: '0:0:0',
   };
-
-  const metricsDiscoverProps = {
-    ...baseDiscoverProps,
-    queryExtras: getMetricOnlyQueryParams(),
-  };
+  const _eventView = adjustEventViewTime(props.eventView);
 
   return (
     <Fragment>
-      <DiscoverQuery eventView={countView} {...baseDiscoverProps}>
-        {transactionCountData => (
-          <DiscoverQuery eventView={countView} {...metricsDiscoverProps}>
-            {metricsCountData => (
-              <DiscoverQuery eventView={nullView} {...metricsDiscoverProps}>
-                {nullData => (
-                  <DiscoverQuery eventView={unparamView} {...metricsDiscoverProps}>
-                    {unparamData => (
-                      <DiscoverQuery
-                        eventView={projectCompatibleView}
-                        {...metricsDiscoverProps}
-                      >
-                        {projectsCompatData => (
-                          <DiscoverQuery
-                            eventView={projectIncompatibleView}
-                            {...metricsDiscoverProps}
-                          >
-                            {projectsIncompatData => {
-                              if (
-                                transactionCountData.isLoading ||
-                                unparamData.isLoading ||
-                                metricsCountData.isLoading ||
-                                projectsIncompatData.isLoading ||
-                                nullData.isLoading ||
-                                projectsCompatData.isLoading
-                              ) {
-                                return null;
-                              }
-
-                              const dataCounts: DataCounts = {
-                                transactionCountData,
-                                metricsCountData,
-                                nullData,
-                                unparamData,
-                              };
-
-                              const compatibleProjects = getCompatibleProjects({
-                                projectsCompatData,
-                                projectsIncompatData,
-                              });
-
-                              if (checkIfNotEffectivelySampling(dataCounts)) {
-                                return (
-                                  <Fragment>
-                                    {props.children({
-                                      forceTransactionsOnly: true,
-                                    })}
-                                  </Fragment>
-                                );
-                              }
-
-                              if (checkNoDataFallback(dataCounts)) {
-                                return (
-                                  <Fragment>
-                                    {props.children({
-                                      forceTransactionsOnly: true,
-                                    })}
-                                  </Fragment>
-                                );
-                              }
-
-                              if (checkIncompatibleData(dataCounts)) {
-                                return (
-                                  <Fragment>
-                                    {props.children({
-                                      shouldWarnIncompatibleSDK: true,
-                                      forceTransactionsOnly: true,
-                                      compatibleProjects,
-                                    })}
-                                  </Fragment>
-                                );
-                              }
-
-                              if (checkIfAllOtherData(dataCounts)) {
-                                return (
-                                  <Fragment>
-                                    {props.children({
-                                      shouldNotifyUnnamedTransactions: true,
-                                      forceTransactionsOnly: true,
-                                      compatibleProjects,
-                                    })}
-                                  </Fragment>
-                                );
-                              }
-
-                              if (checkIfPartialOtherData(dataCounts)) {
-                                return (
-                                  <Fragment>
-                                    {props.children({
-                                      shouldNotifyUnnamedTransactions: true,
-                                      compatibleProjects,
-
-                                      forceTransactionsOnly: false,
-                                    })}
-                                  </Fragment>
-                                );
-                              }
-
-                              return (
-                                <Fragment>
-                                  {props.children({
-                                    forceTransactionsOnly: false,
-                                  })}
-                                </Fragment>
-                              );
-                            }}
-                          </DiscoverQuery>
-                        )}
-                      </DiscoverQuery>
-                    )}
-                  </DiscoverQuery>
-                )}
-              </DiscoverQuery>
-            )}
-          </DiscoverQuery>
-        )}
-      </DiscoverQuery>
+      <MetricsCompatibilityQuery eventView={_eventView} {...baseDiscoverProps}>
+        {data => {
+          if (data.isLoading) {
+            return (
+              <Fragment>
+                <LoadingContainer>
+                  <LoadingIndicator />
+                </LoadingContainer>
+              </Fragment>
+            );
+          }
+
+          const outcome = getMetricsOutcome(data.tableData, !!data.error);
+          return (
+            <MetricsSwitchHandler
+              eventView={props.eventView}
+              location={props.location}
+              outcome={outcome}
+              switcherChildren={props.children}
+            />
+          );
+        }}
+      </MetricsCompatibilityQuery>
     </Fragment>
   );
 }
 
+/**
+ * Performance optimization to limit the amount of rows scanned before showing the landing page.
+ */
+function adjustEventViewTime(eventView: EventView) {
+  const _eventView = eventView.clone();
+
+  if (!_eventView.start && !_eventView.end) {
+    if (!_eventView.statsPeriod) {
+      _eventView.statsPeriod = '1h';
+      _eventView.start = undefined;
+      _eventView.end = undefined;
+    } else {
+      const periodHours = parsePeriodToHours(_eventView.statsPeriod);
+      if (periodHours > 1) {
+        _eventView.statsPeriod = '1h';
+        _eventView.start = undefined;
+        _eventView.end = undefined;
+      }
+    }
+  }
+  return _eventView;
+}
+
+interface SwitcherHandlerProps {
+  eventView: EventView;
+  location: Location;
+  outcome: MetricDataSwitcherOutcome;
+  switcherChildren: MetricDataSwitchProps['children'];
+}
+
+function MetricsSwitchHandler({
+  switcherChildren,
+  outcome,
+  location,
+  eventView,
+}: SwitcherHandlerProps) {
+  const {query} = location;
+  const mepSearchState = decodeScalar(query[METRIC_SEARCH_SETTING_PARAM], '');
+  const hasQuery = decodeScalar(query.query, '');
+  const queryIsTransactionsBased = mepSearchState === MEPState.transactionsOnly;
+
+  const shouldAdjustQuery =
+    hasQuery && queryIsTransactionsBased && !outcome.forceTransactionsOnly;
+
+  useEffect(() => {
+    if (shouldAdjustQuery) {
+      browserHistory.push({
+        pathname: location.pathname,
+        query: {
+          ...location.query,
+          cursor: undefined,
+          query: undefined,
+          [METRIC_SEARCH_SETTING_PARAM]: undefined,
+        },
+      });
+    }
+  }, [shouldAdjustQuery, location]);
+
+  if (hasQuery && queryIsTransactionsBased && !outcome.forceTransactionsOnly) {
+    eventView.query = ''; // TODO: Create switcher provider and move it to the route level to remove the need for this.
+  }
+
+  return <Fragment>{switcherChildren(outcome)}</Fragment>;
+}
+
+/**
+ * Logic for picking sides of metrics vs. transactions along with the associated warnings.
+ */
+function getMetricsOutcome(
+  dataCounts: MetricsCompatibilityData | null,
+  hasOtherFallbackCondition: boolean
+) {
+  const fallbackOutcome: MetricDataSwitcherOutcome = {
+    forceTransactionsOnly: true,
+  };
+  const successOutcome: MetricDataSwitcherOutcome = {
+    forceTransactionsOnly: false,
+  };
+  if (!dataCounts) {
+    return fallbackOutcome;
+  }
+  const compatibleProjects = dataCounts.compatible_projects;
+
+  if (hasOtherFallbackCondition) {
+    return fallbackOutcome;
+  }
+
+  if (!dataCounts) {
+    return fallbackOutcome;
+  }
+
+  if (checkForSamplingRules(dataCounts)) {
+    return fallbackOutcome;
+  }
+
+  if (checkNoDataFallback(dataCounts)) {
+    return fallbackOutcome;
+  }
+
+  if (checkIncompatibleData(dataCounts)) {
+    return {
+      shouldWarnIncompatibleSDK: true,
+      forceTransactionsOnly: true,
+      compatibleProjects,
+    };
+  }
+
+  if (checkIfAllOtherData(dataCounts)) {
+    return {
+      shouldNotifyUnnamedTransactions: true,
+      forceTransactionsOnly: true,
+      compatibleProjects,
+    };
+  }
+
+  if (checkIfPartialOtherData(dataCounts)) {
+    return {
+      shouldNotifyUnnamedTransactions: true,
+      compatibleProjects,
+      forceTransactionsOnly: false,
+    };
+  }
+
+  return successOutcome;
+}
+
 /**
  * Fallback if very similar amounts of metrics and transactions are found.
- * Only used to rollout sampling before rules are selected. Could be replaced with project dynamic sampling check directly.
+ * No projects with dynamic sampling means no rules have been enabled yet.
  */
-function checkIfNotEffectivelySampling(dataCounts: DataCounts) {
-  const counts = extractCounts(dataCounts);
+function checkForSamplingRules(dataCounts: MetricsCompatibilityData) {
+  const counts = normalizeCounts(dataCounts);
+  if (!dataCounts.dynamic_sampling_projects?.length) {
+    return true;
+  }
   if (counts.metricsCount === 0) {
     return true;
   }
-  return (
-    counts.transactionsCount > 0 &&
-    counts.metricsCount > counts.transactionsCount &&
-    counts.transactionsCount >= counts.metricsCount * 0.95
-  );
+  return false;
 }
 
 /**
  * Fallback if no metrics found.
  */
-function checkNoDataFallback(dataCounts: DataCounts) {
-  const counts = extractCounts(dataCounts);
+function checkNoDataFallback(dataCounts: MetricsCompatibilityData) {
+  const counts = normalizeCounts(dataCounts);
   return !counts.metricsCount;
 }
 
 /**
  * Fallback and warn if incompatible data found (old specific SDKs).
  */
-function checkIncompatibleData(dataCounts: DataCounts) {
-  const counts = extractCounts(dataCounts);
+function checkIncompatibleData(dataCounts: MetricsCompatibilityData) {
+  const counts = normalizeCounts(dataCounts);
   return counts.nullCount > 0;
 }
 
 /**
  * Fallback and warn about unnamed transactions (specific SDKs).
  */
-function checkIfAllOtherData(dataCounts: DataCounts) {
-  const counts = extractCounts(dataCounts);
+function checkIfAllOtherData(dataCounts: MetricsCompatibilityData) {
+  const counts = normalizeCounts(dataCounts);
   return counts.unparamCount >= counts.metricsCount;
 }
 
 /**
  * Show metrics but warn about unnamed transactions.
  */
-function checkIfPartialOtherData(dataCounts: DataCounts) {
-  const counts = extractCounts(dataCounts);
+function checkIfPartialOtherData(dataCounts: MetricsCompatibilityData) {
+  const counts = normalizeCounts(dataCounts);
   return counts.unparamCount > 0;
 }
 
 /**
  * Temporary function, can be removed after API changes.
  */
-function extractCounts({
-  metricsCountData,
-  transactionCountData,
-  unparamData,
-  nullData,
-}: DataCounts) {
+function normalizeCounts({sum}: MetricsCompatibilityData) {
   try {
-    const metricsCount = Number(metricsCountData.tableData?.data?.[0].count);
-    const transactionsCount = Number(transactionCountData.tableData?.data?.[0].count);
-    const unparamCount = Number(unparamData.tableData?.data?.[0].count);
-    const nullCount = Number(nullData.tableData?.data?.[0].count);
+    const metricsCount = Number(sum.metrics);
+    const unparamCount = Number(sum.metrics_unparam);
+    const nullCount = Number(sum.metrics_null);
     return {
       metricsCount,
-      transactionsCount,
       unparamCount,
       nullCount,
     };
   } catch (_) {
     return {
       metricsCount: 0,
-      transactionsCount: 0,
       unparamCount: 0,
       nullCount: 0,
     };
   }
 }
 
-/**
- * Temporary function, can be removed after API changes.
- */
-function getCompatibleProjects({
-  projectsCompatData,
-  projectsIncompatData,
-}: {
-  projectsCompatData: GenericChildrenProps<TableData>;
-  projectsIncompatData: GenericChildrenProps<TableData>;
-}) {
-  const baseProjectRows = projectsCompatData.tableData?.data || [];
-  const projectIdsPage = baseProjectRows.map(row => Number(row['project.id']));
-
-  const incompatProjectsRows = projectsIncompatData.tableData?.data || [];
-  const incompatProjectIds = incompatProjectsRows.map(row => Number(row['project.id']));
-
-  return projectIdsPage.filter(projectId => !incompatProjectIds.includes(projectId));
-}
+const LoadingContainer = styled('div')`
+  display: flex;
+  justify-content: center;
+`;

+ 2 - 2
static/app/views/performance/landing/metricsDataSwitcherAlert.tsx

@@ -15,9 +15,9 @@ import EventView from 'sentry/utils/discover/eventView';
 
 import {areMultipleProjectsSelected, getSelectedProjectPlatformsArray} from '../utils';
 
-import {MetricDataSwitcherChildrenProps} from './metricsDataSwitcher';
+import {MetricDataSwitcherOutcome} from './metricsDataSwitcher';
 
-interface MetricEnhancedDataAlertProps extends MetricDataSwitcherChildrenProps {
+interface MetricEnhancedDataAlertProps extends MetricDataSwitcherOutcome {
   eventView: EventView;
   location: Location;
   organization: Organization;

+ 2 - 2
static/app/views/performance/transactionSummary/transactionOverview/metricEvents/metricsEventsDropdown.tsx

@@ -36,12 +36,12 @@ function getOptions(mepContext: MetricsEnhancedSettingContext): MetricsEventsOpt
     {
       value: MEPState.metricsOnly,
       prefix,
-      label: t('Metrics'),
+      label: t('Processed'),
     },
     {
       value: MEPState.transactionsOnly,
       prefix,
-      label: t('Transactions'),
+      label: t('Indexed'),
     },
   ];
 }

+ 4 - 23
tests/js/spec/views/performance/landing/index.spec.tsx

@@ -4,7 +4,6 @@ import {mountWithTheme} from 'sentry-test/enzyme';
 import {initializeData} from 'sentry-test/performance/initializePerformanceData';
 import {act} from 'sentry-test/reactTestingLibrary';
 
-import ModalStore from 'sentry/stores/modalStore';
 import TeamStore from 'sentry/stores/teamStore';
 import EventView from 'sentry/utils/discover/eventView';
 import {OrganizationContext} from 'sentry/views/organizationContext';
@@ -108,28 +107,6 @@ describe('Performance > Landing > Index', function () {
     );
   });
 
-  it('renders settings button for MEPS', async function () {
-    const data = initializeData({
-      features: ['performance-use-metrics'],
-    });
-
-    const spy = jest.spyOn(ModalStore, 'openModal');
-
-    wrapper = mountWithTheme(<WrappedComponent data={data} />, data.routerContext);
-    await tick();
-    wrapper.update();
-
-    expect(wrapper.find('div[data-test-id="performance-landing-v3"]').exists()).toBe(
-      true
-    );
-    wrapper.find('button[data-test-id="open-meps-settings"]').simulate('click');
-
-    await tick();
-    wrapper.update();
-
-    expect(spy).toHaveBeenCalledTimes(1);
-  });
-
   it('renders frontend pageload view', async function () {
     const data = initializeData({
       query: {landingDisplay: LandingDisplayField.FRONTEND_PAGELOAD},
@@ -319,8 +296,12 @@ describe('Performance > Landing > Index', function () {
   describe('with transaction search feature', function () {
     it('renders the search bar', async function () {
       addMetricsDataMock();
+
       const data = initializeData({
         features: ['performance-transaction-name-only-search'],
+        query: {
+          field: 'test',
+        },
       });
 
       wrapper = mountWithTheme(

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