anomaliesTable.tsx 4.4 KB

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