import {FC, Fragment, useEffect, useRef} from 'react'; import {browserHistory, InjectedRouter} from 'react-router'; import styled from '@emotion/styled'; import {Location} from 'history'; import Button from 'sentry/components/button'; import ButtonBar from 'sentry/components/buttonBar'; import DatePageFilter from 'sentry/components/datePageFilter'; import EnvironmentPageFilter from 'sentry/components/environmentPageFilter'; import SearchBar from 'sentry/components/events/searchBar'; import * as Layout from 'sentry/components/layouts/thirds'; import LoadingIndicator from 'sentry/components/loadingIndicator'; import PageFilterBar from 'sentry/components/organizations/pageFilterBar'; import PageHeading from 'sentry/components/pageHeading'; import TransactionNameSearchBar from 'sentry/components/performance/searchBar'; import * as TeamKeyTransactionManager from 'sentry/components/performance/teamKeyTransactionsManager'; import ProjectPageFilter from 'sentry/components/projectPageFilter'; import {Item, TabList, TabPanels, Tabs} from 'sentry/components/tabs'; import {MAX_QUERY_LENGTH} from 'sentry/constants'; import {t} from 'sentry/locale'; import {PageContent} from 'sentry/styles/organization'; import space from 'sentry/styles/space'; import {Organization, PageFilters, Project} from 'sentry/types'; import EventView from 'sentry/utils/discover/eventView'; import {generateAggregateFields} from 'sentry/utils/discover/fields'; import {GenericQueryBatcher} from 'sentry/utils/performance/contexts/genericQueryBatcher'; import {MetricsCardinalityProvider} from 'sentry/utils/performance/contexts/metricsCardinality'; import { canUseMetricsData, MEPConsumer, MEPSettingProvider, MEPState, } from 'sentry/utils/performance/contexts/metricsEnhancedSetting'; import { PageErrorAlert, PageErrorProvider, } from 'sentry/utils/performance/contexts/pageError'; import {MutableSearch} from 'sentry/utils/tokenizeSearch'; import useTeams from 'sentry/utils/useTeams'; import Onboarding from '../onboarding'; import {MetricsEventsDropdown} from '../transactionSummary/transactionOverview/metricEvents/metricsEventsDropdown'; import {getTransactionSearchQuery} from '../utils'; import {AllTransactionsView} from './views/allTransactionsView'; import {BackendView} from './views/backendView'; import {FrontendOtherView} from './views/frontendOtherView'; import {FrontendPageloadView} from './views/frontendPageloadView'; import {MobileView} from './views/mobileView'; import {DynamicSamplingMetricsAccuracyAlert} from './dynamicSamplingMetricsAccuracyAlert'; import {MetricsDataSwitcher} from './metricsDataSwitcher'; import {MetricsDataSwitcherAlert} from './metricsDataSwitcherAlert'; import { getDefaultDisplayForPlatform, getLandingDisplayFromParam, handleLandingDisplayChange, LANDING_DISPLAYS, LandingDisplayField, } from './utils'; type Props = { eventView: EventView; handleSearch: (searchQuery: string, currentMEPState?: MEPState) => void; handleTrendsClick: () => void; location: Location; onboardingProject: Project | undefined; organization: Organization; projects: Project[]; router: InjectedRouter; selection: PageFilters; setError: (msg: string | undefined) => void; withStaticFilters: boolean; }; const fieldToViewMap: Record> = { [LandingDisplayField.ALL]: AllTransactionsView, [LandingDisplayField.BACKEND]: BackendView, [LandingDisplayField.FRONTEND_OTHER]: FrontendOtherView, [LandingDisplayField.FRONTEND_PAGELOAD]: FrontendPageloadView, [LandingDisplayField.MOBILE]: MobileView, }; export function PerformanceLanding(props: Props) { const { organization, location, eventView, projects, handleSearch, handleTrendsClick, onboardingProject, } = props; const {teams, initiallyLoaded} = useTeams({provideUserTeams: true}); const hasMounted = useRef(false); const paramLandingDisplay = getLandingDisplayFromParam(location); const defaultLandingDisplayForProjects = getDefaultDisplayForPlatform( projects, eventView ); const landingDisplay = paramLandingDisplay ?? defaultLandingDisplayForProjects; const showOnboarding = onboardingProject !== undefined; useEffect(() => { if (hasMounted.current) { browserHistory.replace({ pathname: location.pathname, query: { ...location.query, landingDisplay: undefined, }, }); } // eslint-disable-next-line react-hooks/exhaustive-deps }, [eventView.project.join('.')]); useEffect(() => { hasMounted.current = true; }, []); const getFreeTextFromQuery = (query: string) => { const conditions = new MutableSearch(query); const transactionValues = conditions.getFilterValues('transaction'); if (transactionValues.length) { return transactionValues[0]; } return ''; }; const derivedQuery = getTransactionSearchQuery(location, eventView.query); const ViewComponent = fieldToViewMap[landingDisplay.field]; let pageFilters: React.ReactNode = ( ); if (showOnboarding) { pageFilters = {pageFilters}; } const SearchFilterContainer = organization.features.includes('performance-use-metrics') ? SearchContainerWithFilterAndMetrics : SearchContainerWithFilter; const shouldShowTransactionNameOnlySearch = canUseMetricsData(organization); const fullSelectedProjects = eventView.getFullSelectedProjects(projects); return ( handleLandingDisplayChange(field, location, projects, organization, eventView) } > {t('Performance')} {!showOnboarding && ( )} {LANDING_DISPLAYS.map(({label, field}) => ( {t(label)} ))} {metricsDataSide => { return ( {!metricsDataSide.shouldWarnIncompatibleSDK && !metricsDataSide.shouldNotifyUnnamedTransactions && ( )} {showOnboarding ? ( {pageFilters} ) : ( {pageFilters} {({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 handleSearch(query, metricSettingState) } query={searchQuery} /> ) : ( handleSearch( query, metricSettingState ?? undefined ) } maxQueryLength={MAX_QUERY_LENGTH} /> ); }} {initiallyLoaded ? ( ) : ( )} )} ); }} ); } const StyledPageContent = styled(PageContent)` padding: 0; `; const StyledHeading = styled(PageHeading)` line-height: 40px; `; const SearchContainerWithFilter = styled('div')` display: grid; grid-template-rows: auto auto; gap: ${space(2)}; margin-bottom: ${space(2)}; @media (min-width: ${p => p.theme.breakpoints.small}) { grid-template-rows: auto; grid-template-columns: auto 1fr; } `; const SearchContainerWithFilterAndMetrics = styled('div')` display: grid; grid-template-rows: auto auto auto; gap: ${space(2)}; margin-bottom: ${space(2)}; @media (min-width: ${p => p.theme.breakpoints.small}) { grid-template-rows: auto; grid-template-columns: auto 1fr auto; } `;