queriesTable.tsx 5.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177
  1. import type {Location} from 'history';
  2. import type {GridColumnHeader} from 'sentry/components/gridEditable';
  3. import GridEditable, {COL_WIDTH_UNDEFINED} from 'sentry/components/gridEditable';
  4. import type {CursorHandler} from 'sentry/components/pagination';
  5. import Pagination from 'sentry/components/pagination';
  6. import {t} from 'sentry/locale';
  7. import type {Organization} from 'sentry/types/organization';
  8. import {trackAnalytics} from 'sentry/utils/analytics';
  9. import type {EventsMetaType} from 'sentry/utils/discover/eventView';
  10. import {getFieldRenderer} from 'sentry/utils/discover/fieldRenderers';
  11. import type {Sort} from 'sentry/utils/discover/fields';
  12. import {RATE_UNIT_TITLE, RateUnit} from 'sentry/utils/discover/fields';
  13. import {VisuallyCompleteWithData} from 'sentry/utils/performanceForSentry';
  14. import {useLocation} from 'sentry/utils/useLocation';
  15. import {useNavigate} from 'sentry/utils/useNavigate';
  16. import useOrganization from 'sentry/utils/useOrganization';
  17. import {renderHeadCell} from 'sentry/views/insights/common/components/tableCells/renderHeadCell';
  18. import {SpanDescriptionCell} from 'sentry/views/insights/common/components/tableCells/spanDescriptionCell';
  19. import {QueryParameterNames} from 'sentry/views/insights/common/views/queryParameters';
  20. import {DataTitles} from 'sentry/views/insights/common/views/spans/types';
  21. import type {SpanMetricsResponse} from 'sentry/views/insights/types';
  22. import {ModuleName} from 'sentry/views/insights/types';
  23. type Row = Pick<
  24. SpanMetricsResponse,
  25. | 'project.id'
  26. | 'span.description'
  27. | 'span.group'
  28. | 'span.action'
  29. | 'spm()'
  30. | 'avg(span.self_time)'
  31. | 'sum(span.self_time)'
  32. | 'time_spent_percentage()'
  33. >;
  34. type Column = GridColumnHeader<
  35. 'span.description' | 'spm()' | 'avg(span.self_time)' | 'time_spent_percentage()'
  36. >;
  37. const COLUMN_ORDER: Column[] = [
  38. {
  39. key: 'span.description',
  40. name: t('Query Description'),
  41. width: COL_WIDTH_UNDEFINED,
  42. },
  43. {
  44. key: 'spm()',
  45. name: `${t('Queries')} ${RATE_UNIT_TITLE[RateUnit.PER_MINUTE]}`,
  46. width: COL_WIDTH_UNDEFINED,
  47. },
  48. {
  49. key: `avg(span.self_time)`,
  50. name: DataTitles.avg,
  51. width: COL_WIDTH_UNDEFINED,
  52. },
  53. {
  54. key: 'time_spent_percentage()',
  55. name: DataTitles.timeSpent,
  56. width: COL_WIDTH_UNDEFINED,
  57. },
  58. ];
  59. const SORTABLE_FIELDS = ['avg(span.self_time)', 'spm()', 'time_spent_percentage()'];
  60. type ValidSort = Sort & {
  61. field: 'spm()' | 'avg(span.self_time)' | 'time_spent_percentage()';
  62. };
  63. export function isAValidSort(sort: Sort): sort is ValidSort {
  64. return (SORTABLE_FIELDS as unknown as string[]).includes(sort.field);
  65. }
  66. interface Props {
  67. response: {
  68. data: Row[];
  69. isLoading: boolean;
  70. error?: Error | null;
  71. meta?: EventsMetaType;
  72. pageLinks?: string;
  73. };
  74. sort: ValidSort;
  75. system?: string;
  76. }
  77. export function QueriesTable({response, sort, system}: Props) {
  78. const {data, isLoading, meta, pageLinks} = response;
  79. const navigate = useNavigate();
  80. const location = useLocation();
  81. const organization = useOrganization();
  82. const handleCursor: CursorHandler = (newCursor, pathname, query) => {
  83. navigate({
  84. pathname,
  85. query: {...query, [QueryParameterNames.SPANS_CURSOR]: newCursor},
  86. });
  87. };
  88. return (
  89. <VisuallyCompleteWithData
  90. id="QueriesTable"
  91. hasData={data.length > 0}
  92. isLoading={isLoading}
  93. >
  94. <GridEditable
  95. isLoading={isLoading}
  96. error={response.error}
  97. data={data}
  98. columnOrder={COLUMN_ORDER}
  99. columnSortBy={[
  100. {
  101. key: sort.field,
  102. order: sort.kind,
  103. },
  104. ]}
  105. grid={{
  106. renderHeadCell: column =>
  107. renderHeadCell({
  108. column,
  109. sort,
  110. location,
  111. sortParameterName: QueryParameterNames.SPANS_SORT,
  112. }),
  113. renderBodyCell: (column, row) =>
  114. renderBodyCell(column, row, meta, location, organization, system),
  115. }}
  116. />
  117. <Pagination
  118. pageLinks={pageLinks}
  119. onCursor={handleCursor}
  120. paginationAnalyticsEvent={(direction: string) => {
  121. trackAnalytics('insight.general.table_paginate', {
  122. organization,
  123. source: ModuleName.DB,
  124. direction,
  125. });
  126. }}
  127. />
  128. </VisuallyCompleteWithData>
  129. );
  130. }
  131. function renderBodyCell(
  132. column: Column,
  133. row: Row,
  134. meta: EventsMetaType | undefined,
  135. location: Location,
  136. organization: Organization,
  137. system?: string
  138. ) {
  139. if (column.key === 'span.description') {
  140. return (
  141. <SpanDescriptionCell
  142. moduleName={ModuleName.DB}
  143. description={row['span.description']}
  144. group={row['span.group']}
  145. projectId={row['project.id']}
  146. system={system}
  147. spanAction={row['span.action']}
  148. />
  149. );
  150. }
  151. if (!meta || !meta?.fields) {
  152. return row[column.key];
  153. }
  154. const renderer = getFieldRenderer(column.key, meta.fields, false);
  155. const rendered = renderer(row, {
  156. location,
  157. organization,
  158. unit: meta.units?.[column.key],
  159. });
  160. return rendered;
  161. }