transactionsTable.tsx 5.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210
  1. import {Fragment} from 'react';
  2. import type {Location} from 'history';
  3. import GridEditable, {
  4. COL_WIDTH_UNDEFINED,
  5. type GridColumnHeader,
  6. } from 'sentry/components/gridEditable';
  7. import type {CursorHandler} from 'sentry/components/pagination';
  8. import Pagination from 'sentry/components/pagination';
  9. import {t} from 'sentry/locale';
  10. import type {Organization} from 'sentry/types/organization';
  11. import {trackAnalytics} from 'sentry/utils/analytics';
  12. import type {EventsMetaType} from 'sentry/utils/discover/eventView';
  13. import {getFieldRenderer} from 'sentry/utils/discover/fieldRenderers';
  14. import {RATE_UNIT_TITLE, RateUnit, type Sort} from 'sentry/utils/discover/fields';
  15. import {useLocation} from 'sentry/utils/useLocation';
  16. import {useNavigate} from 'sentry/utils/useNavigate';
  17. import useOrganization from 'sentry/utils/useOrganization';
  18. import {TransactionCell} from 'sentry/views/insights/cache/components/tables/transactionCell';
  19. import {renderHeadCell} from 'sentry/views/insights/common/components/tableCells/renderHeadCell';
  20. import {QueryParameterNames} from 'sentry/views/insights/common/views/queryParameters';
  21. import {DataTitles} from 'sentry/views/insights/common/views/spans/types';
  22. import {
  23. MetricsFields,
  24. type MetricsResponse,
  25. ModuleName,
  26. SpanFunction,
  27. SpanMetricsField,
  28. type SpanMetricsResponse,
  29. } from 'sentry/views/insights/types';
  30. const {CACHE_MISS_RATE, SPM, TIME_SPENT_PERCENTAGE} = SpanFunction;
  31. const {TRANSACTION_DURATION} = MetricsFields;
  32. const {CACHE_ITEM_SIZE} = SpanMetricsField;
  33. type Row = Pick<
  34. SpanMetricsResponse,
  35. | 'project'
  36. | 'project.id'
  37. | 'transaction'
  38. | 'spm()'
  39. | 'cache_miss_rate()'
  40. | 'sum(span.self_time)'
  41. | 'time_spent_percentage()'
  42. | 'avg(cache.item_size)'
  43. > &
  44. Pick<MetricsResponse, 'avg(transaction.duration)'>;
  45. type Column = GridColumnHeader<
  46. | 'transaction'
  47. | 'spm()'
  48. | 'cache_miss_rate()'
  49. | 'time_spent_percentage()'
  50. | 'project'
  51. | 'avg(transaction.duration)'
  52. | 'avg(cache.item_size)'
  53. >;
  54. const COLUMN_ORDER: Column[] = [
  55. {
  56. key: 'transaction',
  57. name: t('Transaction'),
  58. width: COL_WIDTH_UNDEFINED,
  59. },
  60. {
  61. key: 'project',
  62. name: t('Project'),
  63. width: COL_WIDTH_UNDEFINED,
  64. },
  65. {
  66. key: `avg(${CACHE_ITEM_SIZE})`,
  67. name: DataTitles[`avg(${CACHE_ITEM_SIZE})`],
  68. width: COL_WIDTH_UNDEFINED,
  69. },
  70. {
  71. key: `${SPM}()`,
  72. name: `${t('Requests')} ${RATE_UNIT_TITLE[RateUnit.PER_MINUTE]}`,
  73. width: COL_WIDTH_UNDEFINED,
  74. },
  75. {
  76. key: `avg(${TRANSACTION_DURATION})`,
  77. name: DataTitles[`avg(${TRANSACTION_DURATION})`],
  78. width: COL_WIDTH_UNDEFINED,
  79. },
  80. {
  81. key: `${CACHE_MISS_RATE}()`,
  82. name: DataTitles[`${SpanFunction.CACHE_MISS_RATE}()`],
  83. width: COL_WIDTH_UNDEFINED,
  84. },
  85. {
  86. key: `${TIME_SPENT_PERCENTAGE}()`,
  87. name: DataTitles.timeSpent,
  88. width: COL_WIDTH_UNDEFINED,
  89. },
  90. ];
  91. const SORTABLE_FIELDS = [
  92. `${SPM}()`,
  93. `${CACHE_MISS_RATE}()`,
  94. `${TIME_SPENT_PERCENTAGE}()`,
  95. `avg(${CACHE_ITEM_SIZE})`,
  96. ] as const;
  97. type ValidSort = Sort & {
  98. field: (typeof SORTABLE_FIELDS)[number];
  99. };
  100. export function isAValidSort(sort: Sort): sort is ValidSort {
  101. return (SORTABLE_FIELDS as unknown as string[]).includes(sort.field);
  102. }
  103. interface Props {
  104. data: Row[];
  105. isLoading: boolean;
  106. sort: ValidSort;
  107. error?: Error | null;
  108. meta?: EventsMetaType;
  109. pageLinks?: string;
  110. }
  111. export function TransactionsTable({
  112. data,
  113. isLoading,
  114. error,
  115. meta,
  116. pageLinks,
  117. sort,
  118. }: Props) {
  119. const navigate = useNavigate();
  120. const location = useLocation();
  121. const organization = useOrganization();
  122. const handleCursor: CursorHandler = (newCursor, pathname, query) => {
  123. navigate({
  124. pathname,
  125. query: {...query, [QueryParameterNames.TRANSACTIONS_CURSOR]: newCursor},
  126. });
  127. };
  128. return (
  129. <Fragment>
  130. <GridEditable
  131. aria-label={t('Transactions')}
  132. isLoading={isLoading}
  133. error={error}
  134. data={data}
  135. columnOrder={COLUMN_ORDER}
  136. columnSortBy={[
  137. {
  138. key: sort.field,
  139. order: sort.kind,
  140. },
  141. ]}
  142. grid={{
  143. renderHeadCell: col =>
  144. renderHeadCell({
  145. column: col,
  146. sort,
  147. location,
  148. sortParameterName: QueryParameterNames.TRANSACTIONS_SORT,
  149. }),
  150. renderBodyCell: (column, row) =>
  151. renderBodyCell(column, row, meta, location, organization),
  152. }}
  153. />
  154. <Pagination
  155. pageLinks={pageLinks}
  156. onCursor={handleCursor}
  157. paginationAnalyticsEvent={(direction: string) => {
  158. trackAnalytics('insight.general.table_paginate', {
  159. organization,
  160. source: ModuleName.CACHE,
  161. direction,
  162. });
  163. }}
  164. />
  165. </Fragment>
  166. );
  167. }
  168. function renderBodyCell(
  169. column: Column,
  170. row: Row,
  171. meta: EventsMetaType | undefined,
  172. location: Location,
  173. organization: Organization
  174. ) {
  175. if (column.key === 'transaction') {
  176. return (
  177. <TransactionCell
  178. project={String(row['project.id'])}
  179. transaction={row.transaction}
  180. transactionMethod={row['transaction.method']}
  181. />
  182. );
  183. }
  184. if (!meta?.fields) {
  185. return row[column.key];
  186. }
  187. const renderer = getFieldRenderer(column.key, meta.fields, false);
  188. return renderer(row, {
  189. location,
  190. organization,
  191. unit: meta.units?.[column.key],
  192. });
  193. }