import type {ReactNode} from 'react';
import styled from '@emotion/styled';
import type {Location} from 'history';
import Count from 'sentry/components/count';
import type {GridColumnOrder} from 'sentry/components/gridEditable';
import GridEditable, {COL_WIDTH_UNDEFINED} from 'sentry/components/gridEditable';
import SortLink from 'sentry/components/gridEditable/sortLink';
import {IconArrow} from 'sentry/icons';
import {t} from 'sentry/locale';
import {space} from 'sentry/styles/space';
import type {Organization} from 'sentry/types';
import {getFieldRenderer} from 'sentry/utils/discover/fieldRenderers';
import type {ColumnType} from 'sentry/utils/discover/fields';
import {fieldAlignment} from 'sentry/utils/discover/fields';
import type {AnomalyInfo} from 'sentry/utils/performance/anomalies/anomaliesQuery';
type Props = {
isLoading: boolean;
location: Location;
organization: Organization;
anomalies?: AnomalyInfo[];
};
const transformRow = (anom: AnomalyInfo): TableDataRowWithExtras => {
return {
anomaly: `#${anom.id}`,
confidence: anom.confidence,
timestamp: new Date(anom.start),
timeInterval: anom.end - anom.start,
expected: anom.expected,
received: anom.received,
};
};
export default function AnomaliesTable(props: Props) {
const {location, organization, isLoading, anomalies} = props;
const data: TableDataRowWithExtras[] = anomalies?.map(transformRow) || [];
return (
);
}
function renderHeadCell(column: TableColumn, _index: number): ReactNode {
const align = fieldAlignment(column.key, COLUMN_TYPE[column.key]);
return (
undefined}
/>
);
}
function renderBodyCellWithMeta(location: Location, organization: Organization) {
return function (
column: TableColumn,
dataRow: TableDataRowWithExtras
): React.ReactNode {
const fieldRenderer = getFieldRenderer(column.key, COLUMN_TYPE);
if (column.key === 'confidence') {
return (
{dataRow.confidence === 'low' ? (
{t('Low Confidence')}
) : (
{t('High Confidence')}
)}
);
}
if (column.key === 'expected') {
return (
);
}
if (column.key === 'received') {
return (
dataRow.expected ? 'up' : 'down'}
/>
);
}
return fieldRenderer(dataRow, {location, organization});
};
}
const NumberCell = styled('div')`
display: flex;
justify-content: flex-end;
align-items: center;
gap: ${space(0.5)};
`;
const LowConfidence = styled('div')`
color: ${p => p.theme.yellow400};
`;
const HighConfidence = styled('div')`
color: ${p => p.theme.red400};
`;
const ConfidenceCell = styled('div')`
text-align: left;
justify-self: flex-end;
flex-grow: 1;
`;
type TableColumnKey =
| 'anomaly'
| 'confidence'
| 'timeInterval'
| 'timestamp'
| 'expected'
| 'received';
type TableColumn = GridColumnOrder;
type TableDataRow = Record;
type TableDataRowWithExtras = TableDataRow & {};
const COLUMNS: Record = {
anomaly: {
key: 'anomaly',
name: t('Anomaly'),
width: COL_WIDTH_UNDEFINED,
},
confidence: {
key: 'confidence',
name: t('Confidence'),
width: COL_WIDTH_UNDEFINED,
},
timeInterval: {
key: 'timeInterval',
name: t('Time Interval'),
width: COL_WIDTH_UNDEFINED,
},
timestamp: {
key: 'timestamp',
name: t('Timestamp'),
width: COL_WIDTH_UNDEFINED,
},
expected: {
key: 'expected',
name: t('Expected'),
width: COL_WIDTH_UNDEFINED,
},
received: {
key: 'received',
name: t('Received'),
width: COL_WIDTH_UNDEFINED,
},
};
const COLUMN_TYPE: Record = {
anomaly: 'string',
confidence: 'string',
timeInterval: 'duration',
timestamp: 'date',
expected: 'number',
received: 'number',
};