content.tsx 5.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218
  1. import {useCallback, useMemo, useState} 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 {FunctionsTable} from 'sentry/components/profiling/functionsTable';
  9. import {ProfileEventsTable} from 'sentry/components/profiling/profileEventsTable';
  10. import {mobile} from 'sentry/data/platformCategories';
  11. import {t} from 'sentry/locale';
  12. import space from 'sentry/styles/space';
  13. import {PageFilters, Project} from 'sentry/types';
  14. import {useFunctions} from 'sentry/utils/profiling/hooks/useFunctions';
  15. import {
  16. formatSort,
  17. useProfileEvents,
  18. } from 'sentry/utils/profiling/hooks/useProfileEvents';
  19. import {decodeScalar} from 'sentry/utils/queryString';
  20. const FUNCTIONS_CURSOR_NAME = 'functionsCursor';
  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 functionsCursor = useMemo(
  38. () => decodeScalar(props.location.query.functionsCursor),
  39. [props.location.query.functionsCursor]
  40. );
  41. const functionsSort = useMemo(
  42. () => decodeScalar(props.location.query.functionsSort, '-p99'),
  43. [props.location.query.functionsSort]
  44. );
  45. const sort = formatSort<ProfilingFieldType>(
  46. decodeScalar(props.location.query.sort),
  47. fields,
  48. {
  49. key: 'timestamp',
  50. order: 'desc',
  51. }
  52. );
  53. const profiles = useProfileEvents<ProfilingFieldType>({
  54. cursor: profilesCursor,
  55. fields,
  56. query: props.query,
  57. sort,
  58. limit: 5,
  59. referrer: 'api.profiling.profile-summary-table',
  60. });
  61. const [functionType, setFunctionType] = useState<'application' | 'system' | 'all'>(
  62. 'application'
  63. );
  64. const functions = useFunctions({
  65. cursor: functionsCursor,
  66. project: props.project,
  67. query: '', // TODO: This doesnt support the same filters
  68. selection: props.selection,
  69. transaction: props.transaction,
  70. sort: functionsSort,
  71. functionType,
  72. });
  73. const handleFunctionsCursor = useCallback((cursor, pathname, query) => {
  74. browserHistory.push({
  75. pathname,
  76. query: {...query, [FUNCTIONS_CURSOR_NAME]: cursor},
  77. });
  78. }, []);
  79. const handleFilterChange = useCallback(
  80. value => {
  81. browserHistory.push({
  82. ...props.location,
  83. query: {...props.location.query, cursor: undefined, sort: value},
  84. });
  85. },
  86. [props.location]
  87. );
  88. return (
  89. <Layout.Main fullWidth>
  90. <TableHeader>
  91. <CompactSelect
  92. triggerProps={{prefix: t('Filter'), size: 'xs'}}
  93. value={sort.order === 'asc' ? sort.key : `-${sort.key}`}
  94. options={FILTER_OPTIONS}
  95. onChange={opt => handleFilterChange(opt.value)}
  96. />
  97. <StyledPagination
  98. pageLinks={
  99. profiles.status === 'success'
  100. ? profiles.data?.[2]?.getResponseHeader('Link') ?? null
  101. : null
  102. }
  103. size="xs"
  104. />
  105. </TableHeader>
  106. <ProfileEventsTable
  107. columns={fields}
  108. data={profiles.status === 'success' ? profiles.data[0] : null}
  109. error={profiles.status === 'error' ? t('Unable to load profiles') : null}
  110. isLoading={profiles.status === 'loading'}
  111. sort={sort}
  112. />
  113. <TableHeader>
  114. <CompactSelect
  115. triggerProps={{prefix: t('Suspect Functions'), size: 'xs'}}
  116. value={functionType}
  117. options={[
  118. {
  119. label: t('All'),
  120. value: 'all' as const,
  121. },
  122. {
  123. label: t('Application'),
  124. value: 'application' as const,
  125. },
  126. {
  127. label: t('System'),
  128. value: 'system' as const,
  129. },
  130. ]}
  131. onChange={({value}) => setFunctionType(value)}
  132. />
  133. <StyledPagination
  134. pageLinks={functions.type === 'resolved' ? functions.data.pageLinks : null}
  135. onCursor={handleFunctionsCursor}
  136. size="xs"
  137. />
  138. </TableHeader>
  139. <FunctionsTable
  140. error={functions.type === 'errored' ? functions.error : null}
  141. isLoading={functions.type === 'initial' || functions.type === 'loading'}
  142. functions={functions.type === 'resolved' ? functions.data.functions : []}
  143. project={props.project}
  144. sort={functionsSort}
  145. />
  146. </Layout.Main>
  147. );
  148. }
  149. const ALL_FIELDS = [
  150. 'id',
  151. 'timestamp',
  152. 'release',
  153. 'device.model',
  154. 'device.classification',
  155. 'device.arch',
  156. 'profile.duration',
  157. ] as const;
  158. export type ProfilingFieldType = typeof ALL_FIELDS[number];
  159. export function getProfilesTableFields(platform: Project['platform']) {
  160. if (mobile.includes(platform as any)) {
  161. return MOBILE_FIELDS;
  162. }
  163. return DEFAULT_FIELDS;
  164. }
  165. const MOBILE_FIELDS: ProfilingFieldType[] = [...ALL_FIELDS];
  166. const DEFAULT_FIELDS: ProfilingFieldType[] = [
  167. 'id',
  168. 'timestamp',
  169. 'release',
  170. 'device.arch',
  171. 'profile.duration',
  172. ];
  173. const FILTER_OPTIONS = [
  174. {
  175. label: t('Recent Profiles'),
  176. value: '-timestamp',
  177. },
  178. {
  179. label: t('Slowest Profiles'),
  180. value: '-profile.duration',
  181. },
  182. {
  183. label: t('Fastest Profiles'),
  184. value: 'profile.duration',
  185. },
  186. ];
  187. const TableHeader = styled('div')`
  188. display: flex;
  189. justify-content: space-between;
  190. margin-bottom: ${space(1)};
  191. `;
  192. const StyledPagination = styled(Pagination)`
  193. margin: 0 0 0 ${space(1)};
  194. `;
  195. export {ProfileSummaryContent};