content.tsx 4.7 KB

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