content.tsx 6.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198
  1. import {useEffect, useRef, useState} from 'react';
  2. import {browserHistory, InjectedRouter} from 'react-router';
  3. import * as Sentry from '@sentry/react';
  4. import {Location} from 'history';
  5. import isEqual from 'lodash/isEqual';
  6. import {loadOrganizationTags} from 'sentry/actionCreators/tags';
  7. import PageFiltersContainer from 'sentry/components/organizations/pageFilters/container';
  8. import SentryDocumentTitle from 'sentry/components/sentryDocumentTitle';
  9. import {ALL_ACCESS_PROJECTS} from 'sentry/constants/pageFilters';
  10. import {t} from 'sentry/locale';
  11. import {PageFilters, Project} from 'sentry/types';
  12. import {trackAnalytics} from 'sentry/utils/analytics';
  13. import {
  14. canUseMetricsData,
  15. MEPState,
  16. METRIC_SEARCH_SETTING_PARAM,
  17. } from 'sentry/utils/performance/contexts/metricsEnhancedSetting';
  18. import {PerformanceEventViewProvider} from 'sentry/utils/performance/contexts/performanceEventViewContext';
  19. import useRouteAnalyticsEventNames from 'sentry/utils/routeAnalytics/useRouteAnalyticsEventNames';
  20. import useRouteAnalyticsParams from 'sentry/utils/routeAnalytics/useRouteAnalyticsParams';
  21. import useApi from 'sentry/utils/useApi';
  22. import useOrganization from 'sentry/utils/useOrganization';
  23. import usePrevious from 'sentry/utils/usePrevious';
  24. import useProjects from 'sentry/utils/useProjects';
  25. import withPageFilters from 'sentry/utils/withPageFilters';
  26. import {getLandingDisplayFromParam} from './landing/utils';
  27. import {generatePerformanceEventView, getDefaultStatsPeriod} from './data';
  28. import {PerformanceLanding} from './landing';
  29. import {
  30. addRoutePerformanceContext,
  31. getSelectedProjectPlatforms,
  32. handleTrendsClick,
  33. } from './utils';
  34. type Props = {
  35. location: Location;
  36. router: InjectedRouter;
  37. selection: PageFilters;
  38. demoMode?: boolean;
  39. };
  40. type State = {
  41. error?: string;
  42. };
  43. function PerformanceContent({selection, location, demoMode, router}: Props) {
  44. const api = useApi();
  45. const organization = useOrganization();
  46. const {projects} = useProjects();
  47. const mounted = useRef(false);
  48. const previousDateTime = usePrevious(selection.datetime);
  49. const [state, setState] = useState<State>({error: undefined});
  50. const withStaticFilters = canUseMetricsData(organization);
  51. const eventView = generatePerformanceEventView(
  52. location,
  53. projects,
  54. {
  55. withStaticFilters,
  56. },
  57. organization
  58. );
  59. function getOnboardingProject(): Project | undefined {
  60. // XXX used by getsentry to bypass onboarding for the upsell demo state.
  61. if (demoMode) {
  62. return undefined;
  63. }
  64. if (projects.length === 0) {
  65. return undefined;
  66. }
  67. // Current selection is 'my projects' or 'all projects'
  68. if (eventView.project.length === 0 || eventView.project[0] === ALL_ACCESS_PROJECTS) {
  69. const filtered = projects.filter(p => p.firstTransactionEvent === false);
  70. if (filtered.length === projects.length) {
  71. return filtered[0];
  72. }
  73. }
  74. // Any other subset of projects.
  75. const filtered = projects.filter(
  76. p =>
  77. eventView.project.includes(parseInt(p.id, 10)) &&
  78. p.firstTransactionEvent === false
  79. );
  80. if (filtered.length === eventView.project.length) {
  81. return filtered[0];
  82. }
  83. return undefined;
  84. }
  85. const onboardingProject = getOnboardingProject();
  86. useRouteAnalyticsEventNames(
  87. 'performance_views.overview.view',
  88. 'Performance Views: Transaction overview view'
  89. );
  90. useRouteAnalyticsParams({
  91. project_platforms: getSelectedProjectPlatforms(location, projects),
  92. show_onboarding: onboardingProject !== undefined,
  93. tab: getLandingDisplayFromParam(location)?.field,
  94. });
  95. useEffect(() => {
  96. if (!mounted.current) {
  97. loadOrganizationTags(api, organization.slug, selection);
  98. addRoutePerformanceContext(selection);
  99. mounted.current = true;
  100. return;
  101. }
  102. if (!isEqual(previousDateTime, selection.datetime)) {
  103. loadOrganizationTags(api, organization.slug, selection);
  104. addRoutePerformanceContext(selection);
  105. }
  106. }, [
  107. selection.datetime,
  108. previousDateTime,
  109. selection,
  110. api,
  111. organization,
  112. onboardingProject,
  113. location,
  114. projects,
  115. ]);
  116. function setError(newError?: string) {
  117. if (
  118. typeof newError === 'object' ||
  119. (Array.isArray(newError) && typeof newError[0] === 'object')
  120. ) {
  121. Sentry.withScope(scope => {
  122. scope.setExtra('error', newError);
  123. Sentry.captureException(new Error('setError failed with error type.'));
  124. });
  125. return;
  126. }
  127. setState({...state, error: newError});
  128. }
  129. function handleSearch(searchQuery: string, currentMEPState?: MEPState) {
  130. trackAnalytics('performance_views.overview.search', {organization});
  131. browserHistory.push({
  132. pathname: location.pathname,
  133. query: {
  134. ...location.query,
  135. cursor: undefined,
  136. query: String(searchQuery).trim() || undefined,
  137. [METRIC_SEARCH_SETTING_PARAM]: currentMEPState,
  138. isDefaultQuery: false,
  139. },
  140. });
  141. }
  142. return (
  143. <SentryDocumentTitle title={t('Performance')} orgSlug={organization.slug}>
  144. <PerformanceEventViewProvider value={{eventView}}>
  145. <PageFiltersContainer
  146. defaultSelection={{
  147. datetime: {
  148. start: null,
  149. end: null,
  150. utc: false,
  151. period: getDefaultStatsPeriod(organization),
  152. },
  153. }}
  154. >
  155. <PerformanceLanding
  156. router={router}
  157. eventView={eventView}
  158. setError={setError}
  159. handleSearch={handleSearch}
  160. handleTrendsClick={() =>
  161. handleTrendsClick({
  162. location,
  163. organization,
  164. projectPlatforms: getSelectedProjectPlatforms(location, projects),
  165. })
  166. }
  167. onboardingProject={onboardingProject}
  168. organization={organization}
  169. location={location}
  170. projects={projects}
  171. selection={selection}
  172. withStaticFilters={withStaticFilters}
  173. />
  174. </PageFiltersContainer>
  175. </PerformanceEventViewProvider>
  176. </SentryDocumentTitle>
  177. );
  178. }
  179. export default withPageFilters(PerformanceContent);