index.tsx 6.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190
  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. location={props.location}
  102. organization={organization}
  103. trails={[
  104. {type: 'landing'},
  105. {
  106. type: 'profile summary',
  107. payload: {
  108. projectSlug: project.slug,
  109. transaction,
  110. },
  111. },
  112. ]}
  113. />
  114. <Layout.Title>
  115. <Title>
  116. <IdBadge
  117. project={project}
  118. avatarSize={28}
  119. hideName
  120. avatarProps={{hasTooltip: true, tooltip: project.slug}}
  121. />
  122. {transaction}
  123. </Title>
  124. </Layout.Title>
  125. </Layout.HeaderContent>
  126. </Layout.Header>
  127. <Layout.Body>
  128. <Layout.Main fullWidth>
  129. <ActionBar>
  130. <PageFilterBar condensed>
  131. <EnvironmentPageFilter />
  132. <DatePageFilter alignDropdown="left" />
  133. </PageFilterBar>
  134. <SmartSearchBar
  135. organization={organization}
  136. hasRecentSearches
  137. searchSource="profile_summary"
  138. supportedTags={profileFilters}
  139. query={rawQuery}
  140. onSearch={handleSearch}
  141. maxQueryLength={MAX_QUERY_LENGTH}
  142. />
  143. </ActionBar>
  144. <ProfileSummaryContent
  145. location={props.location}
  146. project={project}
  147. selection={props.selection}
  148. transaction={transaction}
  149. query={query}
  150. />
  151. </Layout.Main>
  152. </Layout.Body>
  153. </Fragment>
  154. )}
  155. </NoProjectMessage>
  156. </PageFiltersContainer>
  157. </SentryDocumentTitle>
  158. );
  159. }
  160. const Title = styled('div')`
  161. display: flex;
  162. gap: ${space(1)};
  163. `;
  164. const ActionBar = styled('div')`
  165. display: grid;
  166. gap: ${space(2)};
  167. grid-template-columns: min-content auto;
  168. margin-bottom: ${space(2)};
  169. `;
  170. export default withPageFilters(ProfileSummaryPage);