domainsTable.tsx 4.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183
  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. | 'http_response_rate(3)'
  27. | 'http_response_rate(4)'
  28. | 'http_response_rate(5)'
  29. | 'avg(span.self_time)'
  30. | 'sum(span.self_time)'
  31. | 'time_spent_percentage()'
  32. >;
  33. type Column = GridColumnHeader<
  34. | 'span.domain'
  35. | 'spm()'
  36. | 'http_response_rate(3)'
  37. | 'http_response_rate(4)'
  38. | 'http_response_rate(5)'
  39. | 'avg(span.self_time)'
  40. | 'time_spent_percentage()'
  41. >;
  42. const COLUMN_ORDER: Column[] = [
  43. {
  44. key: 'span.domain',
  45. name: t('Domain'),
  46. width: COL_WIDTH_UNDEFINED,
  47. },
  48. {
  49. key: 'spm()',
  50. name: `${t('Requests')} ${RATE_UNIT_TITLE[RateUnit.PER_MINUTE]}`,
  51. width: COL_WIDTH_UNDEFINED,
  52. },
  53. {
  54. key: `http_response_rate(3)`,
  55. name: t('3XXs'),
  56. width: 50,
  57. },
  58. {
  59. key: `http_response_rate(4)`,
  60. name: t('4XXs'),
  61. width: 50,
  62. },
  63. {
  64. key: `http_response_rate(5)`,
  65. name: t('5XXs'),
  66. width: 50,
  67. },
  68. {
  69. key: `avg(span.self_time)`,
  70. name: DataTitles.avg,
  71. width: COL_WIDTH_UNDEFINED,
  72. },
  73. {
  74. key: 'time_spent_percentage()',
  75. name: DataTitles.timeSpent,
  76. width: COL_WIDTH_UNDEFINED,
  77. },
  78. ];
  79. const SORTABLE_FIELDS = [
  80. 'avg(span.self_time)',
  81. 'spm()',
  82. 'http_response_rate(3)',
  83. 'http_response_rate(4)',
  84. 'http_response_rate(5)',
  85. 'time_spent_percentage()',
  86. ] as const;
  87. type ValidSort = Sort & {
  88. field: (typeof SORTABLE_FIELDS)[number];
  89. };
  90. export function isAValidSort(sort: Sort): sort is ValidSort {
  91. return (SORTABLE_FIELDS as unknown as string[]).includes(sort.field);
  92. }
  93. interface Props {
  94. response: {
  95. data: Row[];
  96. isLoading: boolean;
  97. error?: Error | null;
  98. meta?: EventsMetaType;
  99. pageLinks?: string;
  100. };
  101. sort: ValidSort;
  102. }
  103. export function DomainsTable({response, sort}: Props) {
  104. const {data, isLoading, meta, pageLinks} = response;
  105. const location = useLocation();
  106. const organization = useOrganization();
  107. const handleCursor: CursorHandler = (newCursor, pathname, query) => {
  108. browserHistory.push({
  109. pathname,
  110. query: {...query, [QueryParameterNames.DOMAINS_CURSOR]: newCursor},
  111. });
  112. };
  113. return (
  114. <VisuallyCompleteWithData
  115. id="DomainsTable"
  116. hasData={data.length > 0}
  117. isLoading={isLoading}
  118. >
  119. <GridEditable
  120. isLoading={isLoading}
  121. error={response.error}
  122. data={data}
  123. columnOrder={COLUMN_ORDER}
  124. columnSortBy={[
  125. {
  126. key: sort.field,
  127. order: sort.kind,
  128. },
  129. ]}
  130. grid={{
  131. renderHeadCell: column =>
  132. renderHeadCell({
  133. column,
  134. sort,
  135. location,
  136. sortParameterName: QueryParameterNames.DOMAINS_SORT,
  137. }),
  138. renderBodyCell: (column, row) =>
  139. renderBodyCell(column, row, meta, location, organization),
  140. }}
  141. location={location}
  142. />
  143. <Pagination pageLinks={pageLinks} onCursor={handleCursor} />
  144. </VisuallyCompleteWithData>
  145. );
  146. }
  147. function renderBodyCell(
  148. column: Column,
  149. row: Row,
  150. meta: EventsMetaType | undefined,
  151. location: Location,
  152. organization: Organization
  153. ) {
  154. if (column.key === 'span.domain') {
  155. return (
  156. <DomainCell projectId={row['project.id']?.toString()} domain={row['span.domain']} />
  157. );
  158. }
  159. if (!meta?.fields) {
  160. return row[column.key];
  161. }
  162. const renderer = getFieldRenderer(column.key, meta.fields, false);
  163. return renderer(row, {
  164. location,
  165. organization,
  166. unit: meta.units?.[column.key],
  167. });
  168. }