pageLayout.tsx 5.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168
  1. import {useState} from 'react';
  2. import {browserHistory} from 'react-router';
  3. import styled from '@emotion/styled';
  4. import {Location} from 'history';
  5. import Feature from 'sentry/components/acl/feature';
  6. import Alert from 'sentry/components/alert';
  7. import * as Layout from 'sentry/components/layouts/thirds';
  8. import NoProjectMessage from 'sentry/components/noProjectMessage';
  9. import PageFiltersContainer from 'sentry/components/organizations/pageFilters/container';
  10. import SentryDocumentTitle from 'sentry/components/sentryDocumentTitle';
  11. import {t} from 'sentry/locale';
  12. import {PageContent} from 'sentry/styles/organization';
  13. import {Organization, Project} from 'sentry/types';
  14. import {defined} from 'sentry/utils';
  15. import EventView from 'sentry/utils/discover/eventView';
  16. import {useMetricsCardinalityContext} from 'sentry/utils/performance/contexts/metricsCardinality';
  17. import {PerformanceEventViewProvider} from 'sentry/utils/performance/contexts/performanceEventViewContext';
  18. import {decodeScalar} from 'sentry/utils/queryString';
  19. import {getTransactionName} from '../utils';
  20. import TransactionHeader from './header';
  21. import Tab from './tabs';
  22. import {TransactionThresholdMetric} from './transactionThresholdModal';
  23. export type ChildProps = {
  24. eventView: EventView;
  25. location: Location;
  26. organization: Organization;
  27. projectId: string;
  28. projects: Project[];
  29. setError: React.Dispatch<React.SetStateAction<string | undefined>>;
  30. transactionName: string;
  31. // These are used to trigger a reload when the threshold/metric changes.
  32. transactionThreshold?: number;
  33. transactionThresholdMetric?: TransactionThresholdMetric;
  34. };
  35. type Props = {
  36. childComponent: (props: ChildProps) => JSX.Element;
  37. generateEventView: (props: {location: Location; transactionName: string}) => EventView;
  38. getDocumentTitle: (name: string) => string;
  39. location: Location;
  40. organization: Organization;
  41. projects: Project[];
  42. tab: Tab;
  43. features?: string[];
  44. };
  45. function PageLayout(props: Props) {
  46. const {
  47. location,
  48. organization,
  49. projects,
  50. tab,
  51. getDocumentTitle,
  52. generateEventView,
  53. childComponent: ChildComponent,
  54. features = [],
  55. } = props;
  56. const projectId = decodeScalar(location.query.project);
  57. const transactionName = getTransactionName(location);
  58. const [error, setError] = useState<string | undefined>();
  59. const metricsCardinality = useMetricsCardinalityContext();
  60. const [transactionThreshold, setTransactionThreshold] = useState<number | undefined>();
  61. const [transactionThresholdMetric, setTransactionThresholdMetric] = useState<
  62. TransactionThresholdMetric | undefined
  63. >();
  64. if (!defined(projectId) || !defined(transactionName)) {
  65. redirectToPerformanceHomepage(organization, location);
  66. return null;
  67. }
  68. const project = projects.find(p => p.id === projectId);
  69. const eventView = generateEventView({location, transactionName});
  70. return (
  71. <SentryDocumentTitle
  72. title={getDocumentTitle(transactionName)}
  73. orgSlug={organization.slug}
  74. projectSlug={project?.slug}
  75. >
  76. <Feature
  77. features={['performance-view', ...features]}
  78. organization={organization}
  79. renderDisabled={NoAccess}
  80. >
  81. <PerformanceEventViewProvider value={{eventView}}>
  82. <PageFiltersContainer
  83. shouldForceProject={defined(project)}
  84. forceProject={project}
  85. specificProjectSlugs={defined(project) ? [project.slug] : []}
  86. >
  87. <StyledPageContent>
  88. <NoProjectMessage organization={organization}>
  89. <TransactionHeader
  90. eventView={eventView}
  91. location={location}
  92. organization={organization}
  93. projects={projects}
  94. projectId={projectId}
  95. transactionName={transactionName}
  96. currentTab={tab}
  97. hasWebVitals={tab === Tab.WebVitals ? 'yes' : 'maybe'}
  98. onChangeThreshold={(threshold, metric) => {
  99. setTransactionThreshold(threshold);
  100. setTransactionThresholdMetric(metric);
  101. }}
  102. metricsCardinality={metricsCardinality}
  103. />
  104. <Layout.Body>
  105. {defined(error) && (
  106. <StyledAlert type="error" showIcon>
  107. {error}
  108. </StyledAlert>
  109. )}
  110. <ChildComponent
  111. location={location}
  112. organization={organization}
  113. projects={projects}
  114. eventView={eventView}
  115. projectId={projectId}
  116. transactionName={transactionName}
  117. setError={setError}
  118. transactionThreshold={transactionThreshold}
  119. transactionThresholdMetric={transactionThresholdMetric}
  120. />
  121. </Layout.Body>
  122. </NoProjectMessage>
  123. </StyledPageContent>
  124. </PageFiltersContainer>
  125. </PerformanceEventViewProvider>
  126. </Feature>
  127. </SentryDocumentTitle>
  128. );
  129. }
  130. export function NoAccess() {
  131. return <Alert type="warning">{t("You don't have access to this feature")}</Alert>;
  132. }
  133. const StyledPageContent = styled(PageContent)`
  134. padding: 0;
  135. `;
  136. const StyledAlert = styled(Alert)`
  137. grid-column: 1/3;
  138. margin: 0;
  139. `;
  140. export function redirectToPerformanceHomepage(
  141. organization: Organization,
  142. location: Location
  143. ) {
  144. // If there is no transaction name, redirect to the Performance landing page
  145. browserHistory.replace({
  146. pathname: `/organizations/${organization.slug}/performance/`,
  147. query: {
  148. ...location.query,
  149. },
  150. });
  151. }
  152. export default PageLayout;