index.tsx 8.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263
  1. import {FC, Fragment, useEffect, useRef} from 'react';
  2. import {browserHistory} from 'react-router';
  3. import styled from '@emotion/styled';
  4. import {Location} from 'history';
  5. import {openModal} from 'sentry/actionCreators/modal';
  6. import Feature from 'sentry/components/acl/feature';
  7. import Button from 'sentry/components/button';
  8. import ButtonBar from 'sentry/components/buttonBar';
  9. import DatePageFilter from 'sentry/components/datePageFilter';
  10. import EnvironmentPageFilter from 'sentry/components/environmentPageFilter';
  11. import SearchBar from 'sentry/components/events/searchBar';
  12. import {GlobalSdkUpdateAlert} from 'sentry/components/globalSdkUpdateAlert';
  13. import * as Layout from 'sentry/components/layouts/thirds';
  14. import LoadingIndicator from 'sentry/components/loadingIndicator';
  15. import PageFilterBar from 'sentry/components/organizations/pageFilterBar';
  16. import PageHeading from 'sentry/components/pageHeading';
  17. import * as TeamKeyTransactionManager from 'sentry/components/performance/teamKeyTransactionsManager';
  18. import ProjectPageFilter from 'sentry/components/projectPageFilter';
  19. import {MAX_QUERY_LENGTH} from 'sentry/constants';
  20. import {IconSettings} from 'sentry/icons';
  21. import {t} from 'sentry/locale';
  22. import {PageContent} from 'sentry/styles/organization';
  23. import space from 'sentry/styles/space';
  24. import {Organization, PageFilters, Project} from 'sentry/types';
  25. import EventView from 'sentry/utils/discover/eventView';
  26. import {generateAggregateFields} from 'sentry/utils/discover/fields';
  27. import {GenericQueryBatcher} from 'sentry/utils/performance/contexts/genericQueryBatcher';
  28. import {
  29. PageErrorAlert,
  30. PageErrorProvider,
  31. } from 'sentry/utils/performance/contexts/pageError';
  32. import useTeams from 'sentry/utils/useTeams';
  33. import Onboarding from '../onboarding';
  34. import {MetricsEventsDropdown} from '../transactionSummary/transactionOverview/metricEvents/metricsEventsDropdown';
  35. import {getTransactionSearchQuery} from '../utils';
  36. import {AllTransactionsView} from './views/allTransactionsView';
  37. import {BackendView} from './views/backendView';
  38. import {FrontendOtherView} from './views/frontendOtherView';
  39. import {FrontendPageloadView} from './views/frontendPageloadView';
  40. import {MobileView} from './views/mobileView';
  41. import SamplingModal, {modalCss} from './samplingModal';
  42. import {
  43. getDefaultDisplayForPlatform,
  44. getLandingDisplayFromParam,
  45. handleLandingDisplayChange,
  46. LANDING_DISPLAYS,
  47. LandingDisplayField,
  48. } from './utils';
  49. type Props = {
  50. eventView: EventView;
  51. handleSearch: (searchQuery: string) => void;
  52. handleTrendsClick: () => void;
  53. location: Location;
  54. onboardingProject: Project | undefined;
  55. organization: Organization;
  56. projects: Project[];
  57. selection: PageFilters;
  58. setError: (msg: string | undefined) => void;
  59. };
  60. const fieldToViewMap: Record<LandingDisplayField, FC<Props>> = {
  61. [LandingDisplayField.ALL]: AllTransactionsView,
  62. [LandingDisplayField.BACKEND]: BackendView,
  63. [LandingDisplayField.FRONTEND_OTHER]: FrontendOtherView,
  64. [LandingDisplayField.FRONTEND_PAGELOAD]: FrontendPageloadView,
  65. [LandingDisplayField.MOBILE]: MobileView,
  66. };
  67. export function PerformanceLanding(props: Props) {
  68. const {
  69. organization,
  70. location,
  71. eventView,
  72. projects,
  73. handleSearch,
  74. handleTrendsClick,
  75. onboardingProject,
  76. } = props;
  77. const {teams, initiallyLoaded} = useTeams({provideUserTeams: true});
  78. const hasMounted = useRef(false);
  79. const paramLandingDisplay = getLandingDisplayFromParam(location);
  80. const defaultLandingDisplayForProjects = getDefaultDisplayForPlatform(
  81. projects,
  82. eventView
  83. );
  84. const landingDisplay = paramLandingDisplay ?? defaultLandingDisplayForProjects;
  85. const showOnboarding = onboardingProject !== undefined;
  86. useEffect(() => {
  87. if (hasMounted.current) {
  88. browserHistory.replace({
  89. pathname: location.pathname,
  90. query: {
  91. ...location.query,
  92. landingDisplay: undefined,
  93. },
  94. });
  95. }
  96. }, [eventView.project.join('.')]);
  97. useEffect(() => {
  98. hasMounted.current = true;
  99. }, []);
  100. const filterString = getTransactionSearchQuery(location, eventView.query);
  101. const ViewComponent = fieldToViewMap[landingDisplay.field];
  102. const fnOpenModal = () => {
  103. openModal(
  104. modalProps => (
  105. <SamplingModal
  106. {...modalProps}
  107. organization={organization}
  108. eventView={eventView}
  109. projects={projects}
  110. onApply={() => {}}
  111. isMEPEnabled
  112. />
  113. ),
  114. {modalCss, backdrop: 'static'}
  115. );
  116. };
  117. let pageFilters: React.ReactNode = (
  118. <PageFilterBar condensed>
  119. <ProjectPageFilter />
  120. <EnvironmentPageFilter />
  121. <DatePageFilter alignDropdown="left" />
  122. </PageFilterBar>
  123. );
  124. if (showOnboarding) {
  125. pageFilters = <SearchContainerWithFilter>{pageFilters}</SearchContainerWithFilter>;
  126. }
  127. return (
  128. <StyledPageContent data-test-id="performance-landing-v3">
  129. <PageErrorProvider>
  130. <Layout.Header>
  131. <Layout.HeaderContent>
  132. <StyledHeading>{t('Performance')}</StyledHeading>
  133. </Layout.HeaderContent>
  134. <Layout.HeaderActions>
  135. {!showOnboarding && (
  136. <ButtonBar gap={3}>
  137. <Button
  138. priority="primary"
  139. data-test-id="landing-header-trends"
  140. onClick={() => handleTrendsClick()}
  141. >
  142. {t('View Trends')}
  143. </Button>
  144. <Feature features={['organizations:performance-use-metrics']}>
  145. <Button
  146. onClick={() => fnOpenModal()}
  147. icon={<IconSettings />}
  148. aria-label={t('Settings')}
  149. data-test-id="open-meps-settings"
  150. />
  151. </Feature>
  152. </ButtonBar>
  153. )}
  154. </Layout.HeaderActions>
  155. <Layout.HeaderNavTabs>
  156. {LANDING_DISPLAYS.map(({label, field}) => (
  157. <li key={label} className={landingDisplay.field === field ? 'active' : ''}>
  158. <a
  159. href="#"
  160. data-test-id={`landing-tab-${field}`}
  161. onClick={() =>
  162. handleLandingDisplayChange(
  163. field,
  164. location,
  165. projects,
  166. organization,
  167. eventView
  168. )
  169. }
  170. >
  171. {t(label)}
  172. </a>
  173. </li>
  174. ))}
  175. </Layout.HeaderNavTabs>
  176. </Layout.Header>
  177. <Layout.Body>
  178. <Layout.Main fullWidth>
  179. <GlobalSdkUpdateAlert />
  180. <PageErrorAlert />
  181. {showOnboarding ? (
  182. <Fragment>
  183. {pageFilters}
  184. <Onboarding organization={organization} project={onboardingProject} />
  185. </Fragment>
  186. ) : (
  187. <Fragment>
  188. <SearchContainerWithFilter>
  189. {pageFilters}
  190. <SearchBar
  191. searchSource="performance_landing"
  192. organization={organization}
  193. projectIds={eventView.project}
  194. query={filterString}
  195. fields={generateAggregateFields(
  196. organization,
  197. [...eventView.fields, {field: 'tps()'}],
  198. ['epm()', 'eps()']
  199. )}
  200. onSearch={handleSearch}
  201. maxQueryLength={MAX_QUERY_LENGTH}
  202. />
  203. <MetricsEventsDropdown />
  204. </SearchContainerWithFilter>
  205. {initiallyLoaded ? (
  206. <TeamKeyTransactionManager.Provider
  207. organization={organization}
  208. teams={teams}
  209. selectedTeams={['myteams']}
  210. selectedProjects={eventView.project.map(String)}
  211. >
  212. <GenericQueryBatcher>
  213. <ViewComponent {...props} />
  214. </GenericQueryBatcher>
  215. </TeamKeyTransactionManager.Provider>
  216. ) : (
  217. <LoadingIndicator />
  218. )}
  219. </Fragment>
  220. )}
  221. </Layout.Main>
  222. </Layout.Body>
  223. </PageErrorProvider>
  224. </StyledPageContent>
  225. );
  226. }
  227. const StyledPageContent = styled(PageContent)`
  228. padding: 0;
  229. `;
  230. const StyledHeading = styled(PageHeading)`
  231. line-height: 40px;
  232. `;
  233. const SearchContainerWithFilter = styled('div')`
  234. display: grid;
  235. grid-template-rows: auto auto;
  236. gap: ${space(2)};
  237. margin-bottom: ${space(2)};
  238. @media (min-width: ${p => p.theme.breakpoints[0]}) {
  239. grid-template-rows: auto;
  240. grid-template-columns: auto 1fr;
  241. }
  242. `;