index.tsx 9.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268
  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. organization: Organization;
  55. projects: Project[];
  56. selection: PageFilters;
  57. setError: (msg: string | undefined) => void;
  58. shouldShowOnboarding: boolean;
  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. shouldShowOnboarding,
  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. useEffect(() => {
  86. if (hasMounted.current) {
  87. browserHistory.replace({
  88. pathname: location.pathname,
  89. query: {
  90. ...location.query,
  91. landingDisplay: undefined,
  92. },
  93. });
  94. }
  95. }, [eventView.project.join('.')]);
  96. useEffect(() => {
  97. hasMounted.current = true;
  98. }, []);
  99. const filterString = getTransactionSearchQuery(location, eventView.query);
  100. const showOnboarding = shouldShowOnboarding;
  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. return (
  118. <StyledPageContent data-test-id="performance-landing-v3">
  119. <PageErrorProvider>
  120. <Layout.Header>
  121. <Layout.HeaderContent>
  122. <StyledHeading>{t('Performance')}</StyledHeading>
  123. </Layout.HeaderContent>
  124. <Layout.HeaderActions>
  125. {!showOnboarding && (
  126. <ButtonBar gap={3}>
  127. <Button
  128. priority="primary"
  129. data-test-id="landing-header-trends"
  130. onClick={() => handleTrendsClick()}
  131. >
  132. {t('View Trends')}
  133. </Button>
  134. <Feature features={['organizations:performance-use-metrics']}>
  135. <Button
  136. onClick={() => fnOpenModal()}
  137. icon={<IconSettings />}
  138. aria-label={t('Settings')}
  139. data-test-id="open-meps-settings"
  140. />
  141. </Feature>
  142. </ButtonBar>
  143. )}
  144. </Layout.HeaderActions>
  145. <Layout.HeaderNavTabs>
  146. {LANDING_DISPLAYS.map(({label, field}) => (
  147. <li key={label} className={landingDisplay.field === field ? 'active' : ''}>
  148. <a
  149. href="#"
  150. data-test-id={`landing-tab-${field}`}
  151. onClick={() =>
  152. handleLandingDisplayChange(
  153. field,
  154. location,
  155. projects,
  156. organization,
  157. eventView
  158. )
  159. }
  160. >
  161. {t(label)}
  162. </a>
  163. </li>
  164. ))}
  165. </Layout.HeaderNavTabs>
  166. </Layout.Header>
  167. <Layout.Body>
  168. <Layout.Main fullWidth>
  169. <GlobalSdkUpdateAlert />
  170. <PageErrorAlert />
  171. {showOnboarding ? (
  172. <Onboarding
  173. organization={organization}
  174. project={
  175. props.selection.projects.length > 0
  176. ? // If some projects selected, use the first selection
  177. projects.find(
  178. project => props.selection.projects[0].toString() === project.id
  179. ) || projects[0]
  180. : // Otherwise, use the first project in the org
  181. projects[0]
  182. }
  183. />
  184. ) : (
  185. <Fragment>
  186. {organization.features.includes('selection-filters-v2') && (
  187. <StyledPageFilterBar condensed>
  188. <ProjectPageFilter />
  189. <EnvironmentPageFilter />
  190. <DatePageFilter alignDropdown="left" />
  191. </StyledPageFilterBar>
  192. )}
  193. <SearchContainerWithFilter>
  194. <SearchBar
  195. searchSource="performance_landing"
  196. organization={organization}
  197. projectIds={eventView.project}
  198. query={filterString}
  199. fields={generateAggregateFields(
  200. organization,
  201. [...eventView.fields, {field: 'tps()'}],
  202. ['epm()', 'eps()']
  203. )}
  204. onSearch={handleSearch}
  205. maxQueryLength={MAX_QUERY_LENGTH}
  206. />
  207. <MetricsEventsDropdown />
  208. </SearchContainerWithFilter>
  209. {initiallyLoaded ? (
  210. <TeamKeyTransactionManager.Provider
  211. organization={organization}
  212. teams={teams}
  213. selectedTeams={['myteams']}
  214. selectedProjects={eventView.project.map(String)}
  215. >
  216. <GenericQueryBatcher>
  217. <ViewComponent {...props} />
  218. </GenericQueryBatcher>
  219. </TeamKeyTransactionManager.Provider>
  220. ) : (
  221. <LoadingIndicator />
  222. )}
  223. </Fragment>
  224. )}
  225. </Layout.Main>
  226. </Layout.Body>
  227. </PageErrorProvider>
  228. </StyledPageContent>
  229. );
  230. }
  231. const StyledPageContent = styled(PageContent)`
  232. padding: 0;
  233. `;
  234. const StyledHeading = styled(PageHeading)`
  235. line-height: 40px;
  236. `;
  237. const SearchContainerWithFilter = styled('div')`
  238. display: grid;
  239. gap: ${space(0)};
  240. margin-bottom: ${space(2)};
  241. @media (min-width: ${p => p.theme.breakpoints[0]}) {
  242. grid-template-columns: 1fr min-content;
  243. }
  244. `;
  245. const StyledPageFilterBar = styled(PageFilterBar)`
  246. margin-bottom: ${space(1)};
  247. `;