domainsTable.tsx 5.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207
  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 {browserHistory} from 'sentry/utils/browserHistory';
  10. import type {EventsMetaType} from 'sentry/utils/discover/eventView';
  11. import {getFieldRenderer} from 'sentry/utils/discover/fieldRenderers';
  12. import type {Sort} from 'sentry/utils/discover/fields';
  13. import {RATE_UNIT_TITLE, RateUnit} from 'sentry/utils/discover/fields';
  14. import {VisuallyCompleteWithData} from 'sentry/utils/performanceForSentry';
  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 {QueryParameterNames} from 'sentry/views/insights/common/views/queryParameters';
  19. import {DataTitles} from 'sentry/views/insights/common/views/spans/types';
  20. import {DomainCell} from 'sentry/views/insights/http/components/tables/domainCell';
  21. import {ModuleName, type SpanMetricsResponse} from 'sentry/views/insights/types';
  22. type Row = Pick<
  23. SpanMetricsResponse,
  24. | 'project'
  25. | 'project.id'
  26. | 'span.domain'
  27. | 'spm()'
  28. | 'http_response_rate(3)'
  29. | 'http_response_rate(4)'
  30. | 'http_response_rate(5)'
  31. | 'avg(span.self_time)'
  32. | 'sum(span.self_time)'
  33. | 'time_spent_percentage()'
  34. >;
  35. type Column = GridColumnHeader<
  36. | 'span.domain'
  37. | 'project'
  38. | 'spm()'
  39. | 'http_response_rate(3)'
  40. | 'http_response_rate(4)'
  41. | 'http_response_rate(5)'
  42. | 'avg(span.self_time)'
  43. | 'time_spent_percentage()'
  44. >;
  45. const COLUMN_ORDER: Column[] = [
  46. {
  47. key: 'span.domain',
  48. name: t('Domain'),
  49. width: COL_WIDTH_UNDEFINED,
  50. },
  51. {
  52. key: 'project',
  53. name: t('Project'),
  54. width: COL_WIDTH_UNDEFINED,
  55. },
  56. {
  57. key: 'spm()',
  58. name: `${t('Requests')} ${RATE_UNIT_TITLE[RateUnit.PER_MINUTE]}`,
  59. width: COL_WIDTH_UNDEFINED,
  60. },
  61. {
  62. key: `http_response_rate(3)`,
  63. name: t('3XXs'),
  64. width: 50,
  65. },
  66. {
  67. key: `http_response_rate(4)`,
  68. name: t('4XXs'),
  69. width: 50,
  70. },
  71. {
  72. key: `http_response_rate(5)`,
  73. name: t('5XXs'),
  74. width: 50,
  75. },
  76. {
  77. key: `avg(span.self_time)`,
  78. name: DataTitles.avg,
  79. width: COL_WIDTH_UNDEFINED,
  80. },
  81. {
  82. key: 'time_spent_percentage()',
  83. name: DataTitles.timeSpent,
  84. width: COL_WIDTH_UNDEFINED,
  85. },
  86. ];
  87. const SORTABLE_FIELDS = [
  88. 'avg(span.self_time)',
  89. 'spm()',
  90. 'http_response_rate(3)',
  91. 'http_response_rate(4)',
  92. 'http_response_rate(5)',
  93. 'time_spent_percentage()',
  94. ] as const;
  95. type ValidSort = Sort & {
  96. field: (typeof SORTABLE_FIELDS)[number];
  97. };
  98. export function isAValidSort(sort: Sort): sort is ValidSort {
  99. return (SORTABLE_FIELDS as unknown as string[]).includes(sort.field);
  100. }
  101. interface Props {
  102. response: {
  103. data: Row[];
  104. isLoading: boolean;
  105. error?: Error | null;
  106. meta?: EventsMetaType;
  107. pageLinks?: string;
  108. };
  109. sort: ValidSort;
  110. }
  111. export function DomainsTable({response, sort}: Props) {
  112. const {data, isLoading, meta, pageLinks} = response;
  113. const location = useLocation();
  114. const organization = useOrganization();
  115. const handleCursor: CursorHandler = (newCursor, pathname, query) => {
  116. browserHistory.push({
  117. pathname,
  118. query: {...query, [QueryParameterNames.DOMAINS_CURSOR]: newCursor},
  119. });
  120. };
  121. return (
  122. <VisuallyCompleteWithData
  123. id="DomainsTable"
  124. hasData={data.length > 0}
  125. isLoading={isLoading}
  126. >
  127. <GridEditable
  128. aria-label={t('Domains')}
  129. isLoading={isLoading}
  130. error={response.error}
  131. data={data}
  132. columnOrder={COLUMN_ORDER}
  133. columnSortBy={[
  134. {
  135. key: sort.field,
  136. order: sort.kind,
  137. },
  138. ]}
  139. grid={{
  140. renderHeadCell: column =>
  141. renderHeadCell({
  142. column,
  143. sort,
  144. location,
  145. sortParameterName: QueryParameterNames.DOMAINS_SORT,
  146. }),
  147. renderBodyCell: (column, row) =>
  148. renderBodyCell(column, row, meta, location, organization),
  149. }}
  150. />
  151. <Pagination
  152. pageLinks={pageLinks}
  153. onCursor={handleCursor}
  154. paginationAnalyticsEvent={(direction: string) => {
  155. trackAnalytics('insight.general.table_paginate', {
  156. organization,
  157. source: ModuleName.HTTP,
  158. direction,
  159. });
  160. }}
  161. />
  162. </VisuallyCompleteWithData>
  163. );
  164. }
  165. function renderBodyCell(
  166. column: Column,
  167. row: Row,
  168. meta: EventsMetaType | undefined,
  169. location: Location,
  170. organization: Organization
  171. ) {
  172. if (column.key === 'span.domain') {
  173. return (
  174. <DomainCell projectId={row['project.id']?.toString()} domain={row['span.domain']} />
  175. );
  176. }
  177. if (!meta?.fields) {
  178. return row[column.key];
  179. }
  180. const renderer = getFieldRenderer(column.key, meta.fields, false);
  181. return renderer(
  182. {
  183. 'span.op': 'http.client',
  184. ...row,
  185. },
  186. {
  187. location,
  188. organization,
  189. unit: meta.units?.[column.key],
  190. }
  191. );
  192. }