queuesTable.tsx 4.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192
  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 {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 {MetricsResponse} from 'sentry/views/starfish/types';
  24. import {QueryParameterNames} from 'sentry/views/starfish/views/queryParameters';
  25. type Row = Pick<
  26. MetricsResponse,
  27. | 'avg_if(span.self_time,span.op,queue.task.celery)'
  28. | 'count_op(queue.submit.celery)'
  29. | 'count_op(queue.task.celery)'
  30. | 'sum(span.self_time)'
  31. | 'transaction'
  32. >;
  33. type Column = GridColumnHeader<string>;
  34. const COLUMN_ORDER: Column[] = [
  35. // TODO: Needs to be updated to display an actual destination, not transaction
  36. {
  37. key: 'transaction',
  38. name: t('Destination'),
  39. width: COL_WIDTH_UNDEFINED,
  40. },
  41. {
  42. key: '', // TODO
  43. name: t('Avg Time in Queue'),
  44. width: COL_WIDTH_UNDEFINED,
  45. },
  46. {
  47. key: 'avg_if(span.self_time,span.op,queue.task.celery)',
  48. name: t('Avg Processing Time'),
  49. width: COL_WIDTH_UNDEFINED,
  50. },
  51. {
  52. key: 'failure_rate()',
  53. name: t('Error Rate'),
  54. width: COL_WIDTH_UNDEFINED,
  55. },
  56. {
  57. key: 'count_op(queue.submit.celery)',
  58. name: t('Published'),
  59. width: COL_WIDTH_UNDEFINED,
  60. },
  61. {
  62. key: 'count_op(queue.task.celery)',
  63. name: t('Processed'),
  64. width: COL_WIDTH_UNDEFINED,
  65. },
  66. {
  67. key: 'sum(span.self_time)',
  68. name: t('Time Spent'),
  69. width: COL_WIDTH_UNDEFINED,
  70. },
  71. ];
  72. interface Props {
  73. domain?: string;
  74. error?: Error | null;
  75. meta?: EventsMetaType;
  76. }
  77. export function QueuesTable({error}: Props) {
  78. const location = useLocation();
  79. const organization = useOrganization();
  80. const {data, isLoading, meta, pageLinks} = useQueuesByDestinationQuery({});
  81. const handleCursor: CursorHandler = (newCursor, pathname, query) => {
  82. browserHistory.push({
  83. pathname,
  84. query: {...query, [QueryParameterNames.DESTINATIONS_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. `;