queriesTable.tsx 4.5 KB

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