queryTransactionsTable.tsx 5.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187
  1. import {Fragment} from 'react';
  2. import type {Location} from 'history';
  3. import * as qs from 'query-string';
  4. import type {GridColumnHeader} from 'sentry/components/gridEditable';
  5. import GridEditable, {COL_WIDTH_UNDEFINED} from 'sentry/components/gridEditable';
  6. import Link from 'sentry/components/links/link';
  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 {browserHistory} from 'sentry/utils/browserHistory';
  12. import type {EventsMetaType} from 'sentry/utils/discover/eventView';
  13. import {getFieldRenderer} from 'sentry/utils/discover/fieldRenderers';
  14. import type {Sort} from 'sentry/utils/discover/fields';
  15. import {useLocation} from 'sentry/utils/useLocation';
  16. import useOrganization from 'sentry/utils/useOrganization';
  17. import {renderHeadCell} from 'sentry/views/insights/common/components/tableCells/renderHeadCell';
  18. import {OverflowEllipsisTextContainer} from 'sentry/views/insights/common/components/textAlign';
  19. import {useModuleURL} from 'sentry/views/insights/common/utils/useModuleURL';
  20. import {QueryParameterNames} from 'sentry/views/insights/common/views/queryParameters';
  21. import {
  22. DataTitles,
  23. getThroughputTitle,
  24. } from 'sentry/views/insights/common/views/spans/types';
  25. import type {SpanMetricsResponse} from 'sentry/views/insights/types';
  26. import {SpanMetricsField} from 'sentry/views/insights/types';
  27. type Row = Pick<
  28. SpanMetricsResponse,
  29. | 'transaction'
  30. | 'transaction.method'
  31. | 'spm()'
  32. | 'avg(span.self_time)'
  33. | 'sum(span.self_time)'
  34. | 'time_spent_percentage()'
  35. >;
  36. type Column = GridColumnHeader<
  37. 'transaction' | 'spm()' | 'avg(span.self_time)' | 'time_spent_percentage()'
  38. >;
  39. const COLUMN_ORDER: Column[] = [
  40. {
  41. key: 'transaction',
  42. name: t('Found In'),
  43. width: COL_WIDTH_UNDEFINED,
  44. },
  45. {
  46. key: 'spm()',
  47. name: getThroughputTitle('db'),
  48. width: COL_WIDTH_UNDEFINED,
  49. },
  50. {
  51. key: `avg(${SpanMetricsField.SPAN_SELF_TIME})`,
  52. name: DataTitles.avg,
  53. width: COL_WIDTH_UNDEFINED,
  54. },
  55. {
  56. key: 'time_spent_percentage()',
  57. name: DataTitles.timeSpent,
  58. width: COL_WIDTH_UNDEFINED,
  59. },
  60. ];
  61. const SORTABLE_FIELDS = [
  62. 'avg(span.self_time)',
  63. 'spm()',
  64. 'time_spent_percentage()',
  65. ] as const;
  66. type ValidSort = Sort & {
  67. field: (typeof SORTABLE_FIELDS)[number];
  68. };
  69. export function isAValidSort(sort: Sort): sort is ValidSort {
  70. return (SORTABLE_FIELDS as unknown as string[]).includes(sort.field);
  71. }
  72. interface Props {
  73. data: Row[];
  74. isLoading: boolean;
  75. sort: ValidSort;
  76. span: Pick<SpanMetricsResponse, SpanMetricsField.SPAN_GROUP | SpanMetricsField.SPAN_OP>;
  77. error?: Error | null;
  78. meta?: EventsMetaType;
  79. pageLinks?: string;
  80. }
  81. export function QueryTransactionsTable({
  82. data,
  83. isLoading,
  84. error,
  85. meta,
  86. pageLinks,
  87. sort,
  88. span,
  89. }: Props) {
  90. const moduleURL = useModuleURL('db');
  91. const location = useLocation();
  92. const organization = useOrganization();
  93. const handleCursor: CursorHandler = (newCursor, pathname, query) => {
  94. browserHistory.push({
  95. pathname,
  96. query: {...query, [QueryParameterNames.TRANSACTIONS_CURSOR]: newCursor},
  97. });
  98. };
  99. return (
  100. <Fragment>
  101. <GridEditable
  102. aria-label={t('Transactions')}
  103. isLoading={isLoading}
  104. error={error}
  105. data={data}
  106. columnOrder={COLUMN_ORDER}
  107. columnSortBy={[
  108. {
  109. key: sort.field,
  110. order: sort.kind,
  111. },
  112. ]}
  113. grid={{
  114. renderHeadCell: col =>
  115. renderHeadCell({
  116. column: col,
  117. sort,
  118. location,
  119. sortParameterName: QueryParameterNames.TRANSACTIONS_SORT,
  120. }),
  121. renderBodyCell: (column, row) =>
  122. renderBodyCell(moduleURL, column, row, meta, span, location, organization),
  123. }}
  124. />
  125. <Pagination pageLinks={pageLinks} onCursor={handleCursor} />
  126. </Fragment>
  127. );
  128. }
  129. function renderBodyCell(
  130. moduleURL: string,
  131. column: Column,
  132. row: Row,
  133. meta: EventsMetaType | undefined,
  134. span: Pick<SpanMetricsResponse, SpanMetricsField.SPAN_GROUP | SpanMetricsField.SPAN_OP>,
  135. location: Location,
  136. organization: Organization
  137. ) {
  138. if (column.key === 'transaction') {
  139. const label =
  140. row['transaction.method'] && !row.transaction.startsWith(row['transaction.method'])
  141. ? `${row['transaction.method']} ${row.transaction}`
  142. : row.transaction;
  143. const pathname = `${moduleURL}/spans/span/${encodeURIComponent(span[SpanMetricsField.SPAN_GROUP])}`;
  144. const query: {[key: string]: string | undefined} = {
  145. ...location.query,
  146. transaction: row.transaction,
  147. transactionMethod: row['transaction.method'],
  148. };
  149. return (
  150. <OverflowEllipsisTextContainer>
  151. <Link to={`${pathname}?${qs.stringify(query)}`}>{label}</Link>
  152. </OverflowEllipsisTextContainer>
  153. );
  154. }
  155. if (!meta || !meta?.fields) {
  156. return row[column.key];
  157. }
  158. const renderer = getFieldRenderer(column.key, meta.fields, false);
  159. const rendered = renderer(
  160. {...row, 'span.op': span['span.op']},
  161. {
  162. location,
  163. organization,
  164. unit: meta.units?.[column.key],
  165. }
  166. );
  167. return rendered;
  168. }