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 * as Layout from 'sentry/components/layouts/thirds'; import LoadingIndicator from 'sentry/components/loadingIndicator'; import PageFilterBar from 'sentry/components/organizations/pageFilterBar'; import {PageHeadingQuestionTooltip} from 'sentry/components/pageHeadingQuestionTooltip'; import TransactionNameSearchBar from 'sentry/components/performance/searchBar'; import * as TeamKeyTransactionManager from 'sentry/components/performance/teamKeyTransactionsManager'; import ProjectPageFilter from 'sentry/components/projectPageFilter'; import {TabList, TabPanels, Tabs} from 'sentry/components/tabs'; import {t} from 'sentry/locale'; import {space} from 'sentry/styles/space'; import {Organization, PageFilters, Project} from 'sentry/types'; import EventView from 'sentry/utils/discover/eventView'; import {GenericQueryBatcher} from 'sentry/utils/performance/contexts/genericQueryBatcher'; import {MetricsCardinalityProvider} from 'sentry/utils/performance/contexts/metricsCardinality'; import { 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 {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]; } if (conditions.freeText.length > 0) { // raw text query will be wrapped in wildcards in generatePerformanceEventView // so no need to wrap it here return conditions.freeText.join(' '); } 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; return ( handleLandingDisplayChange(field, location, projects, organization, eventView) } > {t('Performance')} {!showOnboarding && ( )} {LANDING_DISPLAYS.map(({label, field}) => ( {label} ))} {metricsDataSide => { return ( {showOnboarding ? ( {pageFilters} ) : ( {pageFilters} {({metricSettingState}) => ( // TODO replace `handleSearch prop` with transaction name search once // transaction name search becomes the default search bar { handleSearch( query, metricSettingState ?? undefined ); }} query={getFreeTextFromQuery(derivedQuery)} /> )} {initiallyLoaded ? ( ) : ( )} )} ); }} ); } 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; } `;