content.tsx 4.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178
  1. import {Fragment, useCallback, useMemo} from 'react';
  2. import styled from '@emotion/styled';
  3. import type {Location} from 'history';
  4. import {CompactSelect} from 'sentry/components/compactSelect';
  5. import * as Layout from 'sentry/components/layouts/thirds';
  6. import Pagination from 'sentry/components/pagination';
  7. import {AggregateFlamegraphPanel} from 'sentry/components/profiling/aggregateFlamegraphPanel';
  8. import {ProfileEventsTable} from 'sentry/components/profiling/profileEventsTable';
  9. import {SuspectFunctionsTable} from 'sentry/components/profiling/suspectFunctions/suspectFunctionsTable';
  10. import {mobile} from 'sentry/data/platformCategories';
  11. import {t} from 'sentry/locale';
  12. import {space} from 'sentry/styles/space';
  13. import type {PageFilters} from 'sentry/types/core';
  14. import type {Project} from 'sentry/types/project';
  15. import {browserHistory} from 'sentry/utils/browserHistory';
  16. import {useProfileEvents} from 'sentry/utils/profiling/hooks/useProfileEvents';
  17. import {formatSort} from 'sentry/utils/profiling/hooks/utils';
  18. import {decodeScalar} from 'sentry/utils/queryString';
  19. import {ProfilesChart} from 'sentry/views/profiling/landing/profileCharts';
  20. interface ProfileSummaryContentProps {
  21. location: Location;
  22. project: Project;
  23. query: string;
  24. selection: PageFilters;
  25. transaction: string;
  26. }
  27. function ProfileSummaryContent(props: ProfileSummaryContentProps) {
  28. const fields = useMemo(
  29. () => getProfilesTableFields(props.project.platform),
  30. [props.project]
  31. );
  32. const profilesCursor = useMemo(
  33. () => decodeScalar(props.location.query.cursor),
  34. [props.location.query.cursor]
  35. );
  36. const sort = formatSort<ProfilingFieldType>(
  37. decodeScalar(props.location.query.sort),
  38. fields,
  39. {
  40. key: 'timestamp',
  41. order: 'desc',
  42. }
  43. );
  44. const profiles = useProfileEvents<ProfilingFieldType>({
  45. cursor: profilesCursor,
  46. fields,
  47. query: props.query,
  48. sort,
  49. limit: 5,
  50. referrer: 'api.profiling.profile-summary-table',
  51. });
  52. const handleFilterChange = useCallback(
  53. value => {
  54. browserHistory.push({
  55. ...props.location,
  56. query: {...props.location.query, cursor: undefined, sort: value},
  57. });
  58. },
  59. [props.location]
  60. );
  61. return (
  62. <Fragment>
  63. <Layout.Main fullWidth>
  64. <ProfilesChart
  65. referrer="api.profiling.profile-summary-chart"
  66. query={props.query}
  67. hideCount
  68. compact
  69. />
  70. <AggregateFlamegraphPanel transaction={props.transaction} />
  71. <SuspectFunctionsTable
  72. project={props.project}
  73. transaction={props.transaction}
  74. analyticsPageSource="profiling_transaction"
  75. />
  76. <TableHeader>
  77. <CompactSelect
  78. triggerProps={{prefix: t('Filter'), size: 'xs'}}
  79. value={sort.order === 'asc' ? sort.key : `-${sort.key}`}
  80. options={FILTER_OPTIONS}
  81. onChange={opt => handleFilterChange(opt.value)}
  82. />
  83. <StyledPagination
  84. pageLinks={
  85. profiles.status === 'success'
  86. ? profiles.getResponseHeader?.('Link') ?? null
  87. : null
  88. }
  89. size="xs"
  90. />
  91. </TableHeader>
  92. <ProfileEventsTable
  93. columns={fields}
  94. data={profiles.status === 'success' ? profiles.data : null}
  95. error={profiles.status === 'error' ? t('Unable to load profiles') : null}
  96. isLoading={profiles.status === 'loading'}
  97. sort={sort}
  98. />
  99. </Layout.Main>
  100. </Fragment>
  101. );
  102. }
  103. const ALL_FIELDS = [
  104. 'profile.id',
  105. 'timestamp',
  106. 'release',
  107. 'device.model',
  108. 'device.classification',
  109. 'device.arch',
  110. 'transaction.duration',
  111. 'p75()',
  112. 'p95()',
  113. 'p99()',
  114. 'count()',
  115. 'last_seen()',
  116. ] as const;
  117. export type ProfilingFieldType = (typeof ALL_FIELDS)[number];
  118. export function getProfilesTableFields(platform: Project['platform']) {
  119. if (mobile.includes(platform as any)) {
  120. return MOBILE_FIELDS;
  121. }
  122. return DEFAULT_FIELDS;
  123. }
  124. const MOBILE_FIELDS: ProfilingFieldType[] = [
  125. 'profile.id',
  126. 'timestamp',
  127. 'release',
  128. 'device.model',
  129. 'device.classification',
  130. 'device.arch',
  131. 'transaction.duration',
  132. ];
  133. const DEFAULT_FIELDS: ProfilingFieldType[] = [
  134. 'profile.id',
  135. 'timestamp',
  136. 'release',
  137. 'transaction.duration',
  138. ];
  139. const FILTER_OPTIONS = [
  140. {
  141. label: t('Recent Profiles'),
  142. value: '-timestamp',
  143. },
  144. {
  145. label: t('Slowest Profiles'),
  146. value: '-transaction.duration',
  147. },
  148. {
  149. label: t('Fastest Profiles'),
  150. value: 'transaction.duration',
  151. },
  152. ];
  153. const TableHeader = styled('div')`
  154. display: flex;
  155. justify-content: space-between;
  156. margin-bottom: ${space(1)};
  157. `;
  158. const StyledPagination = styled(Pagination)`
  159. margin: 0 0 0 ${space(1)};
  160. `;
  161. export {ProfileSummaryContent};