queuesTable.tsx 4.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192
  1. import {Fragment} from 'react';
  2. import {browserHistory, Link} from 'react-router';
  3. import styled from '@emotion/styled';
  4. import type {Location} from 'history';
  5. import qs from 'qs';
  6. import GridEditable, {
  7. COL_WIDTH_UNDEFINED,
  8. type GridColumnHeader,
  9. } from 'sentry/components/gridEditable';
  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 type {EventsMetaType} from 'sentry/utils/discover/eventView';
  15. import {getFieldRenderer} from 'sentry/utils/discover/fieldRenderers';
  16. import {formatAbbreviatedNumber, formatPercentage} from 'sentry/utils/formatters';
  17. import {useLocation} from 'sentry/utils/useLocation';
  18. import useOrganization from 'sentry/utils/useOrganization';
  19. import {normalizeUrl} from 'sentry/utils/withDomainRequired';
  20. import {useQueueByTransactionQuery} from 'sentry/views/performance/queues/queries/useQueuesByTransactionQuery';
  21. import {renderHeadCell} from 'sentry/views/starfish/components/tableCells/renderHeadCell';
  22. import type {MetricsResponse} from 'sentry/views/starfish/types';
  23. import {QueryParameterNames} from 'sentry/views/starfish/views/queryParameters';
  24. type Row = Pick<
  25. MetricsResponse,
  26. | 'avg_if(span.self_time,span.op,queue.task.celery)'
  27. | 'count_op(queue.submit.celery)'
  28. | 'count_op(queue.task.celery)'
  29. | 'sum(span.self_time)'
  30. | 'transaction'
  31. >;
  32. type Column = GridColumnHeader<string>;
  33. const COLUMN_ORDER: Column[] = [
  34. // TODO: Needs to be updated to display an actual destination, not transaction
  35. {
  36. key: 'transaction',
  37. name: t('Destination'),
  38. width: COL_WIDTH_UNDEFINED,
  39. },
  40. {
  41. key: '', // TODO
  42. name: t('Avg Time in Queue'),
  43. width: COL_WIDTH_UNDEFINED,
  44. },
  45. {
  46. key: 'avg_if(span.self_time,span.op,queue.task.celery)',
  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.submit.celery)',
  57. name: t('Published'),
  58. width: COL_WIDTH_UNDEFINED,
  59. },
  60. {
  61. key: 'count_op(queue.task.celery)',
  62. name: t('Processed'),
  63. width: COL_WIDTH_UNDEFINED,
  64. },
  65. {
  66. key: 'sum(span.self_time)',
  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. pageLinks?: string;
  76. }
  77. export function QueuesTable({error, pageLinks}: Props) {
  78. const location = useLocation();
  79. const organization = useOrganization();
  80. const {data, isLoading, meta} = useQueueByTransactionQuery({});
  81. const handleCursor: CursorHandler = (newCursor, pathname, query) => {
  82. browserHistory.push({
  83. pathname,
  84. query: {...query, [QueryParameterNames.TRANSACTIONS_CURSOR]: newCursor},
  85. });
  86. };
  87. return (
  88. <Fragment>
  89. <GridEditable
  90. aria-label={t('Queues')}
  91. isLoading={isLoading}
  92. error={error}
  93. data={data}
  94. columnOrder={COLUMN_ORDER}
  95. columnSortBy={[]}
  96. grid={{
  97. renderHeadCell: col =>
  98. renderHeadCell({
  99. column: col,
  100. location,
  101. }),
  102. renderBodyCell: (column, row) =>
  103. renderBodyCell(column, row, meta, location, organization),
  104. }}
  105. location={location}
  106. />
  107. <Pagination pageLinks={pageLinks} onCursor={handleCursor} />
  108. </Fragment>
  109. );
  110. }
  111. function renderBodyCell(
  112. column: Column,
  113. row: Row,
  114. meta: EventsMetaType | undefined,
  115. location: Location,
  116. organization: Organization
  117. ) {
  118. const key = column.key;
  119. if (row[key] === undefined) {
  120. return (
  121. <AlignRight>
  122. <NoValue>{' \u2014 '}</NoValue>
  123. </AlignRight>
  124. );
  125. }
  126. if (key === 'transaction') {
  127. return <DestinationCell destination={row[key]} />;
  128. }
  129. if (key.startsWith('count')) {
  130. return <AlignRight>{formatAbbreviatedNumber(row[key])}</AlignRight>;
  131. }
  132. if (key === 'failure_rate()') {
  133. return <AlignRight>{formatPercentage(row[key])}</AlignRight>;
  134. }
  135. if (!meta?.fields) {
  136. return row[column.key];
  137. }
  138. const renderer = getFieldRenderer(column.key, meta.fields, false);
  139. return renderer(row, {
  140. location,
  141. organization,
  142. unit: meta.units?.[column.key],
  143. });
  144. }
  145. function DestinationCell({destination}: {destination: string}) {
  146. const organization = useOrganization();
  147. const {query} = useLocation();
  148. const queryString = {
  149. ...query,
  150. destination,
  151. };
  152. return (
  153. <NoOverflow>
  154. <Link
  155. to={normalizeUrl(
  156. `/organizations/${organization.slug}/performance/queues/destination/?${qs.stringify(queryString)}`
  157. )}
  158. >
  159. {destination}
  160. </Link>
  161. </NoOverflow>
  162. );
  163. }
  164. const NoOverflow = styled('span')`
  165. overflow: hidden;
  166. `;
  167. const AlignRight = styled('span')`
  168. text-align: right;
  169. `;
  170. const NoValue = styled('span')`
  171. color: ${p => p.theme.gray300};
  172. `;