index.tsx 6.8 KB

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