domainsTable.tsx 4.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154
  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 {DomainCell} from 'sentry/views/performance/http/domainCell';
  17. import {renderHeadCell} from 'sentry/views/starfish/components/tableCells/renderHeadCell';
  18. import type {MetricsResponse} from 'sentry/views/starfish/types';
  19. import {QueryParameterNames} from 'sentry/views/starfish/views/queryParameters';
  20. import {DataTitles} from 'sentry/views/starfish/views/spans/types';
  21. type Row = Pick<
  22. MetricsResponse,
  23. | 'project.id'
  24. | 'span.domain'
  25. | 'spm()'
  26. | 'avg(span.self_time)'
  27. | 'sum(span.self_time)'
  28. | 'time_spent_percentage()'
  29. >;
  30. type Column = GridColumnHeader<
  31. 'span.domain' | 'spm()' | 'avg(span.self_time)' | 'time_spent_percentage()'
  32. >;
  33. const COLUMN_ORDER: Column[] = [
  34. {
  35. key: 'span.domain',
  36. name: t('Domain'),
  37. width: COL_WIDTH_UNDEFINED,
  38. },
  39. {
  40. key: 'spm()',
  41. name: `${t('Requests')} ${RATE_UNIT_TITLE[RateUnit.PER_MINUTE]}`,
  42. width: COL_WIDTH_UNDEFINED,
  43. },
  44. {
  45. key: `avg(span.self_time)`,
  46. name: DataTitles.avg,
  47. width: COL_WIDTH_UNDEFINED,
  48. },
  49. {
  50. key: 'time_spent_percentage()',
  51. name: DataTitles.timeSpent,
  52. width: COL_WIDTH_UNDEFINED,
  53. },
  54. ];
  55. const SORTABLE_FIELDS = [
  56. 'avg(span.self_time)',
  57. 'spm()',
  58. 'time_spent_percentage()',
  59. ] as const;
  60. type ValidSort = Sort & {
  61. field: (typeof SORTABLE_FIELDS)[number];
  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. }
  76. export function DomainsTable({response, sort}: Props) {
  77. const {data, isLoading, meta, pageLinks} = response;
  78. const location = useLocation();
  79. const organization = useOrganization();
  80. const handleCursor: CursorHandler = (newCursor, pathname, query) => {
  81. browserHistory.push({
  82. pathname,
  83. query: {...query, [QueryParameterNames.DOMAINS_CURSOR]: newCursor},
  84. });
  85. };
  86. return (
  87. <VisuallyCompleteWithData
  88. id="DomainsTable"
  89. hasData={data.length > 0}
  90. isLoading={isLoading}
  91. >
  92. <GridEditable
  93. isLoading={isLoading}
  94. error={response.error}
  95. data={data}
  96. columnOrder={COLUMN_ORDER}
  97. columnSortBy={[
  98. {
  99. key: sort.field,
  100. order: sort.kind,
  101. },
  102. ]}
  103. grid={{
  104. renderHeadCell: column =>
  105. renderHeadCell({
  106. column,
  107. sort,
  108. location,
  109. sortParameterName: QueryParameterNames.DOMAINS_SORT,
  110. }),
  111. renderBodyCell: (column, row) =>
  112. renderBodyCell(column, row, meta, location, organization),
  113. }}
  114. location={location}
  115. />
  116. <Pagination pageLinks={pageLinks} onCursor={handleCursor} />
  117. </VisuallyCompleteWithData>
  118. );
  119. }
  120. function renderBodyCell(
  121. column: Column,
  122. row: Row,
  123. meta: EventsMetaType | undefined,
  124. location: Location,
  125. organization: Organization
  126. ) {
  127. if (column.key === 'span.domain') {
  128. return <DomainCell domain={row['span.domain']} />;
  129. }
  130. if (!meta?.fields) {
  131. return row[column.key];
  132. }
  133. const renderer = getFieldRenderer(column.key, meta.fields, false);
  134. return renderer(row, {
  135. location,
  136. organization,
  137. unit: meta.units?.[column.key],
  138. });
  139. }