content.tsx 5.2 KB

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