queuesTable.tsx 5.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196
  1. import {Fragment} from 'react';
  2. import styled from '@emotion/styled';
  3. import type {Location} from 'history';
  4. import qs from 'qs';
  5. import GridEditable, {
  6. COL_WIDTH_UNDEFINED,
  7. type GridColumnHeader,
  8. } from 'sentry/components/gridEditable';
  9. import Link from 'sentry/components/links/link';
  10. import type {CursorHandler} from 'sentry/components/pagination';
  11. import Pagination from 'sentry/components/pagination';
  12. import {t} from 'sentry/locale';
  13. import type {Organization} from 'sentry/types';
  14. import {browserHistory} from 'sentry/utils/browserHistory';
  15. import type {EventsMetaType} from 'sentry/utils/discover/eventView';
  16. import {FIELD_FORMATTERS, getFieldRenderer} from 'sentry/utils/discover/fieldRenderers';
  17. import {formatAbbreviatedNumber, formatPercentage} from 'sentry/utils/formatters';
  18. import {useLocation} from 'sentry/utils/useLocation';
  19. import useOrganization from 'sentry/utils/useOrganization';
  20. import {normalizeUrl} from 'sentry/utils/withDomainRequired';
  21. import {useQueuesByDestinationQuery} from 'sentry/views/performance/queues/queries/useQueuesByDestinationQuery';
  22. import {renderHeadCell} from 'sentry/views/starfish/components/tableCells/renderHeadCell';
  23. import type {SpanMetricsResponse} from 'sentry/views/starfish/types';
  24. import {QueryParameterNames} from 'sentry/views/starfish/views/queryParameters';
  25. type Row = Pick<
  26. SpanMetricsResponse,
  27. | 'sum(span.duration)'
  28. | 'messaging.destination.name'
  29. | 'avg(messaging.message.receive.latency)'
  30. | `avg_if(${string},${string},${string})`
  31. | `count_op(${string})`
  32. >;
  33. type Column = GridColumnHeader<string>;
  34. const COLUMN_ORDER: Column[] = [
  35. {
  36. key: 'messaging.destination.name',
  37. name: t('Destination'),
  38. width: COL_WIDTH_UNDEFINED,
  39. },
  40. {
  41. key: 'avg(messaging.message.receive.latency)',
  42. name: t('Avg Time in Queue'),
  43. width: COL_WIDTH_UNDEFINED,
  44. },
  45. {
  46. key: 'avg_if(span.duration,span.op,queue.process)',
  47. name: t('Avg Processing Time'),
  48. width: COL_WIDTH_UNDEFINED,
  49. },
  50. {
  51. key: 'failure_rate()',
  52. name: t('Error Rate'),
  53. width: COL_WIDTH_UNDEFINED,
  54. },
  55. {
  56. key: 'count_op(queue.publish)',
  57. name: t('Published'),
  58. width: COL_WIDTH_UNDEFINED,
  59. },
  60. {
  61. key: 'count_op(queue.process)',
  62. name: t('Processed'),
  63. width: COL_WIDTH_UNDEFINED,
  64. },
  65. {
  66. key: 'sum(span.duration)',
  67. name: t('Time Spent'),
  68. width: COL_WIDTH_UNDEFINED,
  69. },
  70. ];
  71. interface Props {
  72. domain?: string;
  73. error?: Error | null;
  74. meta?: EventsMetaType;
  75. }
  76. export function QueuesTable({error}: Props) {
  77. const location = useLocation();
  78. const organization = useOrganization();
  79. const {data, isLoading, meta, pageLinks} = useQueuesByDestinationQuery({});
  80. const handleCursor: CursorHandler = (newCursor, pathname, query) => {
  81. browserHistory.push({
  82. pathname,
  83. query: {...query, [QueryParameterNames.DESTINATIONS_CURSOR]: newCursor},
  84. });
  85. };
  86. return (
  87. <Fragment>
  88. <GridEditable
  89. aria-label={t('Queues')}
  90. isLoading={isLoading}
  91. error={error}
  92. data={data}
  93. columnOrder={COLUMN_ORDER}
  94. columnSortBy={[]}
  95. grid={{
  96. renderHeadCell: col =>
  97. renderHeadCell({
  98. column: col,
  99. location,
  100. }),
  101. renderBodyCell: (column, row) =>
  102. renderBodyCell(column, row, meta, location, organization),
  103. }}
  104. location={location}
  105. />
  106. <Pagination pageLinks={pageLinks} onCursor={handleCursor} />
  107. </Fragment>
  108. );
  109. }
  110. function renderBodyCell(
  111. column: Column,
  112. row: Row,
  113. meta: EventsMetaType | undefined,
  114. location: Location,
  115. organization: Organization
  116. ) {
  117. const key = column.key;
  118. if (row[key] === undefined) {
  119. return (
  120. <AlignRight>
  121. <NoValue>{' \u2014 '}</NoValue>
  122. </AlignRight>
  123. );
  124. }
  125. if (key === 'messaging.destination.name' && row[key]) {
  126. return <DestinationCell destination={row[key]} />;
  127. }
  128. if (key.startsWith('count')) {
  129. return <AlignRight>{formatAbbreviatedNumber(row[key])}</AlignRight>;
  130. }
  131. if (key === 'failure_rate()') {
  132. return <AlignRight>{formatPercentage(row[key])}</AlignRight>;
  133. }
  134. if (key.startsWith('avg')) {
  135. const renderer = FIELD_FORMATTERS.duration.renderFunc;
  136. return renderer(key, row);
  137. }
  138. if (!meta?.fields) {
  139. return row[column.key];
  140. }
  141. const renderer = getFieldRenderer(column.key, meta.fields, false);
  142. return renderer(row, {
  143. location,
  144. organization,
  145. unit: meta.units?.[column.key],
  146. });
  147. }
  148. function DestinationCell({destination}: {destination: string}) {
  149. const organization = useOrganization();
  150. const {query} = useLocation();
  151. const queryString = {
  152. ...query,
  153. destination,
  154. };
  155. return (
  156. <NoOverflow>
  157. <Link
  158. to={normalizeUrl(
  159. `/organizations/${organization.slug}/performance/queues/destination/?${qs.stringify(queryString)}`
  160. )}
  161. >
  162. {destination}
  163. </Link>
  164. </NoOverflow>
  165. );
  166. }
  167. const NoOverflow = styled('span')`
  168. overflow: hidden;
  169. `;
  170. const AlignRight = styled('span')`
  171. text-align: right;
  172. `;
  173. const NoValue = styled('span')`
  174. color: ${p => p.theme.gray300};
  175. `;