index.tsx 6.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195
  1. import {Fragment, useCallback, useEffect, useMemo} from 'react';
  2. import {browserHistory} from 'react-router';
  3. import styled from '@emotion/styled';
  4. import {Location} from 'history';
  5. import DatePageFilter from 'sentry/components/datePageFilter';
  6. import EnvironmentPageFilter from 'sentry/components/environmentPageFilter';
  7. import IdBadge from 'sentry/components/idBadge';
  8. import * as Layout from 'sentry/components/layouts/thirds';
  9. import NoProjectMessage from 'sentry/components/noProjectMessage';
  10. import PageFilterBar from 'sentry/components/organizations/pageFilterBar';
  11. import PageFiltersContainer from 'sentry/components/organizations/pageFilters/container';
  12. import {Breadcrumb} from 'sentry/components/profiling/breadcrumb';
  13. import SentryDocumentTitle from 'sentry/components/sentryDocumentTitle';
  14. import SmartSearchBar, {SmartSearchBarProps} from 'sentry/components/smartSearchBar';
  15. import {MAX_QUERY_LENGTH} from 'sentry/constants';
  16. import {t} from 'sentry/locale';
  17. import space from 'sentry/styles/space';
  18. import {PageFilters, Project} from 'sentry/types';
  19. import {defined} from 'sentry/utils';
  20. import trackAdvancedAnalyticsEvent from 'sentry/utils/analytics/trackAdvancedAnalyticsEvent';
  21. import {useProfileFilters} from 'sentry/utils/profiling/hooks/useProfileFilters';
  22. import {decodeScalar} from 'sentry/utils/queryString';
  23. import {MutableSearch} from 'sentry/utils/tokenizeSearch';
  24. import useOrganization from 'sentry/utils/useOrganization';
  25. import useProjects from 'sentry/utils/useProjects';
  26. import withPageFilters from 'sentry/utils/withPageFilters';
  27. import {ProfileSummaryContent} from './content';
  28. interface ProfileSummaryPageProps {
  29. location: Location;
  30. params: {
  31. projectId?: Project['slug'];
  32. };
  33. selection?: PageFilters;
  34. }
  35. function ProfileSummaryPage(props: ProfileSummaryPageProps) {
  36. const organization = useOrganization();
  37. const {projects} = useProjects({
  38. slugs: defined(props.params.projectId) ? [props.params.projectId] : [],
  39. });
  40. useEffect(() => {
  41. trackAdvancedAnalyticsEvent('profiling_views.profile_summary', {
  42. organization,
  43. });
  44. }, [organization]);
  45. // Extract the project matching the provided project slug,
  46. // if it doesn't exist, set this to null and handle it accordingly.
  47. const project = projects.length === 1 ? projects[0] : null;
  48. const transaction = decodeScalar(props.location.query.transaction);
  49. const rawQuery = useMemo(
  50. () => decodeScalar(props.location.query.query, ''),
  51. [props.location.query.query]
  52. );
  53. const query = useMemo(() => {
  54. const search = new MutableSearch(rawQuery);
  55. if (defined(transaction)) {
  56. search.setFilterValues('transaction_name', [transaction]);
  57. }
  58. return search.formatString();
  59. }, [rawQuery, transaction]);
  60. const filtersQuery = useMemo(() => {
  61. // To avoid querying for the filters each time the query changes,
  62. // do not pass the user query to get the filters.
  63. const search = new MutableSearch('');
  64. if (defined(transaction)) {
  65. search.setFilterValues('transaction_name', [transaction]);
  66. }
  67. return search.formatString();
  68. }, [transaction]);
  69. const profileFilters = useProfileFilters({
  70. query: filtersQuery,
  71. selection: props.selection,
  72. });
  73. const handleSearch: SmartSearchBarProps['onSearch'] = useCallback(
  74. (searchQuery: string) => {
  75. browserHistory.push({
  76. ...props.location,
  77. query: {
  78. ...props.location.query,
  79. query: searchQuery || undefined,
  80. },
  81. });
  82. },
  83. [props.location]
  84. );
  85. return (
  86. <SentryDocumentTitle
  87. title={t('Profiling \u2014 Profile Summary')}
  88. orgSlug={organization.slug}
  89. >
  90. <PageFiltersContainer
  91. shouldForceProject={defined(project)}
  92. forceProject={project}
  93. specificProjectSlugs={defined(project) ? [project.slug] : []}
  94. >
  95. <NoProjectMessage organization={organization}>
  96. {project && transaction && (
  97. <Fragment>
  98. <Layout.Header>
  99. <Layout.HeaderContent>
  100. <Breadcrumb
  101. organization={organization}
  102. trails={[
  103. {
  104. type: 'landing',
  105. payload: {
  106. query: props.location.query,
  107. },
  108. },
  109. {
  110. type: 'profile summary',
  111. payload: {
  112. projectSlug: project.slug,
  113. query: props.location.query,
  114. transaction,
  115. },
  116. },
  117. ]}
  118. />
  119. <Layout.Title>
  120. <Title>
  121. <IdBadge
  122. project={project}
  123. avatarSize={28}
  124. hideName
  125. avatarProps={{hasTooltip: true, tooltip: project.slug}}
  126. />
  127. {transaction}
  128. </Title>
  129. </Layout.Title>
  130. </Layout.HeaderContent>
  131. </Layout.Header>
  132. <Layout.Body>
  133. <Layout.Main fullWidth>
  134. <ActionBar>
  135. <PageFilterBar condensed>
  136. <EnvironmentPageFilter />
  137. <DatePageFilter alignDropdown="left" />
  138. </PageFilterBar>
  139. <SmartSearchBar
  140. organization={organization}
  141. hasRecentSearches
  142. searchSource="profile_summary"
  143. supportedTags={profileFilters}
  144. query={rawQuery}
  145. onSearch={handleSearch}
  146. maxQueryLength={MAX_QUERY_LENGTH}
  147. />
  148. </ActionBar>
  149. <ProfileSummaryContent
  150. location={props.location}
  151. project={project}
  152. selection={props.selection}
  153. transaction={transaction}
  154. query={query}
  155. />
  156. </Layout.Main>
  157. </Layout.Body>
  158. </Fragment>
  159. )}
  160. </NoProjectMessage>
  161. </PageFiltersContainer>
  162. </SentryDocumentTitle>
  163. );
  164. }
  165. const Title = styled('div')`
  166. display: flex;
  167. gap: ${space(1)};
  168. `;
  169. const ActionBar = styled('div')`
  170. display: grid;
  171. gap: ${space(2)};
  172. grid-template-columns: min-content auto;
  173. margin-bottom: ${space(2)};
  174. `;
  175. export default withPageFilters(ProfileSummaryPage);