content.tsx 4.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156
  1. import {useEffect, 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} from 'sentry/types';
  12. import trackAdvancedAnalyticsEvent from 'sentry/utils/analytics/trackAdvancedAnalyticsEvent';
  13. import {PerformanceEventViewProvider} from 'sentry/utils/performance/contexts/performanceEventViewContext';
  14. import useApi from 'sentry/utils/useApi';
  15. import useOrganization from 'sentry/utils/useOrganization';
  16. import usePrevious from 'sentry/utils/usePrevious';
  17. import useProjects from 'sentry/utils/useProjects';
  18. import withPageFilters from 'sentry/utils/withPageFilters';
  19. import {DEFAULT_STATS_PERIOD, generatePerformanceEventView} from './data';
  20. import {PerformanceLanding} from './landing';
  21. import {useMetricsSwitch} from './metricsSwitch';
  22. import {addRoutePerformanceContext, handleTrendsClick} from './utils';
  23. type Props = {
  24. location: Location;
  25. router: InjectedRouter;
  26. selection: PageFilters;
  27. demoMode?: boolean;
  28. };
  29. type State = {
  30. error?: string;
  31. };
  32. function PerformanceContent({selection, location, demoMode}: Props) {
  33. const api = useApi();
  34. const organization = useOrganization();
  35. const {projects} = useProjects();
  36. const {isMetricsData} = useMetricsSwitch();
  37. const previousDateTime = usePrevious(selection.datetime);
  38. const [state, setState] = useState<State>({error: undefined});
  39. useEffect(() => {
  40. loadOrganizationTags(api, organization.slug, selection);
  41. addRoutePerformanceContext(selection);
  42. trackAdvancedAnalyticsEvent('performance_views.overview.view', {
  43. organization,
  44. show_onboarding: shouldShowOnboarding(),
  45. });
  46. }, []);
  47. useEffect(() => {
  48. loadOrganizationTags(api, organization.slug, selection);
  49. addRoutePerformanceContext(selection);
  50. }, [selection.projects]);
  51. useEffect(() => {
  52. if (!isEqual(previousDateTime, selection.datetime)) {
  53. loadOrganizationTags(api, organization.slug, selection);
  54. addRoutePerformanceContext(selection);
  55. }
  56. }, [selection.datetime]);
  57. function setError(newError?: string) {
  58. if (
  59. typeof newError === 'object' ||
  60. (Array.isArray(newError) && typeof newError[0] === 'object')
  61. ) {
  62. Sentry.withScope(scope => {
  63. scope.setExtra('error', newError);
  64. Sentry.captureException(new Error('setError failed with error type.'));
  65. });
  66. return;
  67. }
  68. setState({...state, error: newError});
  69. }
  70. function handleSearch(searchQuery: string) {
  71. trackAdvancedAnalyticsEvent('performance_views.overview.search', {organization});
  72. browserHistory.push({
  73. pathname: location.pathname,
  74. query: {
  75. ...location.query,
  76. cursor: undefined,
  77. query: String(searchQuery).trim() || undefined,
  78. isDefaultQuery: false,
  79. },
  80. });
  81. }
  82. const eventView = generatePerformanceEventView(location, projects, {
  83. isMetricsData,
  84. });
  85. function shouldShowOnboarding() {
  86. // XXX used by getsentry to bypass onboarding for the upsell demo state.
  87. if (demoMode) {
  88. return false;
  89. }
  90. if (projects.length === 0) {
  91. return false;
  92. }
  93. // Current selection is 'my projects' or 'all projects'
  94. if (eventView.project.length === 0 || eventView.project === [ALL_ACCESS_PROJECTS]) {
  95. return (
  96. projects.filter(p => p.firstTransactionEvent === false).length === projects.length
  97. );
  98. }
  99. // Any other subset of projects.
  100. return (
  101. projects.filter(
  102. p =>
  103. eventView.project.includes(parseInt(p.id, 10)) &&
  104. p.firstTransactionEvent === false
  105. ).length === eventView.project.length
  106. );
  107. }
  108. return (
  109. <SentryDocumentTitle title={t('Performance')} orgSlug={organization.slug}>
  110. <PerformanceEventViewProvider value={{eventView}}>
  111. <PageFiltersContainer
  112. defaultSelection={{
  113. datetime: {
  114. start: null,
  115. end: null,
  116. utc: false,
  117. period: DEFAULT_STATS_PERIOD,
  118. },
  119. }}
  120. >
  121. <PerformanceLanding
  122. eventView={eventView}
  123. setError={setError}
  124. handleSearch={handleSearch}
  125. handleTrendsClick={() => handleTrendsClick({location, organization})}
  126. shouldShowOnboarding={shouldShowOnboarding()}
  127. organization={organization}
  128. location={location}
  129. projects={projects}
  130. selection={selection}
  131. />
  132. </PageFiltersContainer>
  133. </PerformanceEventViewProvider>
  134. </SentryDocumentTitle>
  135. );
  136. }
  137. export default withPageFilters(PerformanceContent);