anomaliesTable.tsx 4.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184
  1. import type {ReactNode} from 'react';
  2. import styled from '@emotion/styled';
  3. import type {Location} from 'history';
  4. import Count from 'sentry/components/count';
  5. import type {GridColumnOrder} from 'sentry/components/gridEditable';
  6. import GridEditable, {COL_WIDTH_UNDEFINED} from 'sentry/components/gridEditable';
  7. import SortLink from 'sentry/components/gridEditable/sortLink';
  8. import {IconArrow} from 'sentry/icons';
  9. import {t} from 'sentry/locale';
  10. import {space} from 'sentry/styles/space';
  11. import type {Organization} from 'sentry/types';
  12. import {getFieldRenderer} from 'sentry/utils/discover/fieldRenderers';
  13. import type {ColumnType} from 'sentry/utils/discover/fields';
  14. import {fieldAlignment} from 'sentry/utils/discover/fields';
  15. import type {AnomalyInfo} from 'sentry/utils/performance/anomalies/anomaliesQuery';
  16. type Props = {
  17. isLoading: boolean;
  18. location: Location;
  19. organization: Organization;
  20. anomalies?: AnomalyInfo[];
  21. };
  22. const transformRow = (anom: AnomalyInfo): TableDataRowWithExtras => {
  23. return {
  24. anomaly: `#${anom.id}`,
  25. confidence: anom.confidence,
  26. timestamp: new Date(anom.start),
  27. timeInterval: anom.end - anom.start,
  28. expected: anom.expected,
  29. received: anom.received,
  30. };
  31. };
  32. export default function AnomaliesTable(props: Props) {
  33. const {location, organization, isLoading, anomalies} = props;
  34. const data: TableDataRowWithExtras[] = anomalies?.map(transformRow) || [];
  35. return (
  36. <GridEditable
  37. isLoading={isLoading}
  38. data={data}
  39. columnOrder={Object.values(COLUMNS)}
  40. columnSortBy={[]}
  41. grid={{
  42. renderHeadCell,
  43. renderBodyCell: renderBodyCellWithMeta(location, organization),
  44. }}
  45. location={location}
  46. />
  47. );
  48. }
  49. function renderHeadCell(column: TableColumn, _index: number): ReactNode {
  50. const align = fieldAlignment(column.key, COLUMN_TYPE[column.key]);
  51. return (
  52. <SortLink
  53. title={column.name}
  54. align={align}
  55. direction={undefined}
  56. canSort={false}
  57. generateSortLink={() => undefined}
  58. />
  59. );
  60. }
  61. function renderBodyCellWithMeta(location: Location, organization: Organization) {
  62. return function (
  63. column: TableColumn,
  64. dataRow: TableDataRowWithExtras
  65. ): React.ReactNode {
  66. const fieldRenderer = getFieldRenderer(column.key, COLUMN_TYPE);
  67. if (column.key === 'confidence') {
  68. return (
  69. <ConfidenceCell>
  70. {dataRow.confidence === 'low' ? (
  71. <LowConfidence>{t('Low Confidence')}</LowConfidence>
  72. ) : (
  73. <HighConfidence>{t('High Confidence')}</HighConfidence>
  74. )}
  75. </ConfidenceCell>
  76. );
  77. }
  78. if (column.key === 'expected') {
  79. return (
  80. <NumberCell>
  81. <Count value={dataRow.expected} />
  82. </NumberCell>
  83. );
  84. }
  85. if (column.key === 'received') {
  86. return (
  87. <NumberCell>
  88. <Count value={dataRow.received} />
  89. <IconArrow
  90. size="sm"
  91. direction={dataRow.received > dataRow.expected ? 'up' : 'down'}
  92. />
  93. </NumberCell>
  94. );
  95. }
  96. return fieldRenderer(dataRow, {location, organization});
  97. };
  98. }
  99. const NumberCell = styled('div')`
  100. display: flex;
  101. justify-content: flex-end;
  102. align-items: center;
  103. gap: ${space(0.5)};
  104. `;
  105. const LowConfidence = styled('div')`
  106. color: ${p => p.theme.yellow400};
  107. `;
  108. const HighConfidence = styled('div')`
  109. color: ${p => p.theme.red400};
  110. `;
  111. const ConfidenceCell = styled('div')`
  112. text-align: left;
  113. justify-self: flex-end;
  114. flex-grow: 1;
  115. `;
  116. type TableColumnKey =
  117. | 'anomaly'
  118. | 'confidence'
  119. | 'timeInterval'
  120. | 'timestamp'
  121. | 'expected'
  122. | 'received';
  123. type TableColumn = GridColumnOrder<TableColumnKey>;
  124. type TableDataRow = Record<TableColumnKey, any>;
  125. type TableDataRowWithExtras = TableDataRow & {};
  126. const COLUMNS: Record<TableColumnKey, TableColumn> = {
  127. anomaly: {
  128. key: 'anomaly',
  129. name: t('Anomaly'),
  130. width: COL_WIDTH_UNDEFINED,
  131. },
  132. confidence: {
  133. key: 'confidence',
  134. name: t('Confidence'),
  135. width: COL_WIDTH_UNDEFINED,
  136. },
  137. timeInterval: {
  138. key: 'timeInterval',
  139. name: t('Time Interval'),
  140. width: COL_WIDTH_UNDEFINED,
  141. },
  142. timestamp: {
  143. key: 'timestamp',
  144. name: t('Timestamp'),
  145. width: COL_WIDTH_UNDEFINED,
  146. },
  147. expected: {
  148. key: 'expected',
  149. name: t('Expected'),
  150. width: COL_WIDTH_UNDEFINED,
  151. },
  152. received: {
  153. key: 'received',
  154. name: t('Received'),
  155. width: COL_WIDTH_UNDEFINED,
  156. },
  157. };
  158. const COLUMN_TYPE: Record<TableColumnKey, ColumnType> = {
  159. anomaly: 'string',
  160. confidence: 'string',
  161. timeInterval: 'duration',
  162. timestamp: 'date',
  163. expected: 'number',
  164. received: 'number',
  165. };