content.tsx 4.3 KB

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