The open and composable observability and data visualization platform. Visualize metrics, logs, and traces from multiple sources like Prometheus, Loki, Elasticsearch, InfluxDB, Postgres and many more.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
grafana/public/app/features/alerting/unified/components/rules/StateHistory.tsx

127 lines
3.9 KiB

import React, { FC } from 'react';
import { uniqueId } from 'lodash';
import { AlertState, dateTimeFormat, GrafanaTheme } from '@grafana/data';
import { Alert, LoadingPlaceholder, useStyles } from '@grafana/ui';
import { css } from '@emotion/css';
import { StateHistoryItem, StateHistoryItemData } from 'app/types/unified-alerting';
import { DynamicTable, DynamicTableColumnProps, DynamicTableItemProps } from '../DynamicTable';
import { AlertStateTag } from './AlertStateTag';
import { useManagedAlertStateHistory } from '../../hooks/useManagedAlertStateHistory';
import { AlertLabel } from '../AlertLabel';
import { GrafanaAlertState, PromAlertingRuleState } from 'app/types/unified-alerting-dto';
type StateHistoryRowItem = {
id: string;
state: PromAlertingRuleState | GrafanaAlertState | AlertState;
text?: string;
data?: StateHistoryItemData;
timestamp?: number;
};
type StateHistoryRow = DynamicTableItemProps<StateHistoryRowItem>;
interface RuleStateHistoryProps {
alertId: string;
}
const StateHistory: FC<RuleStateHistoryProps> = ({ alertId }) => {
const { loading, error, result = [] } = useManagedAlertStateHistory(alertId);
if (loading && !error) {
return <LoadingPlaceholder text={'Loading history...'} />;
}
if (error && !loading) {
return <Alert title={'Failed to fetch alert state history'}>{error.message}</Alert>;
}
const columns: Array<DynamicTableColumnProps<StateHistoryRowItem>> = [
{ id: 'state', label: 'State', size: 'max-content', renderCell: renderStateCell },
{ id: 'value', label: '', size: 'auto', renderCell: renderValueCell },
{ id: 'timestamp', label: 'Time', size: 'max-content', renderCell: renderTimestampCell },
];
const items: StateHistoryRow[] = result
.reduce((acc: StateHistoryRowItem[], item, index) => {
acc.push({
id: String(item.id),
state: item.newState,
text: item.text,
data: item.data,
timestamp: item.updated,
});
// if the preceding state is not the same, create a separate state entry – this likely means the state was reset
if (!hasMatchingPrecedingState(index, result)) {
acc.push({ id: uniqueId(), state: item.prevState });
}
return acc;
}, [])
.map((historyItem) => ({
id: historyItem.id,
data: historyItem,
}));
return <DynamicTable cols={columns} items={items} />;
};
function renderValueCell(item: StateHistoryRow) {
const matches = item.data.data?.evalMatches ?? [];
return (
<>
{item.data.text}
<LabelsWrapper>
{matches.map((match) => (
<AlertLabel key={match.metric} labelKey={match.metric} value={String(match.value)} />
))}
</LabelsWrapper>
</>
);
}
function renderStateCell(item: StateHistoryRow) {
return <AlertStateTag state={item.data.state} />;
}
function renderTimestampCell(item: StateHistoryRow) {
return (
<div className={TimestampStyle}>{item.data.timestamp && <span>{dateTimeFormat(item.data.timestamp)}</span>}</div>
);
}
const LabelsWrapper: FC<{}> = ({ children }) => {
const { wrapper } = useStyles(getStyles);
return <div className={wrapper}>{children}</div>;
};
const TimestampStyle = css`
display: flex;
align-items: flex-end;
flex-direction: column;
`;
const getStyles = (theme: GrafanaTheme) => ({
wrapper: css`
& > * {
margin-right: ${theme.spacing.xs};
}
`,
});
// this function will figure out if a given historyItem has a preceding historyItem where the states match - in other words
// the newState of the previous historyItem is the same as the prevState of the current historyItem
function hasMatchingPrecedingState(index: number, items: StateHistoryItem[]): boolean {
const currentHistoryItem = items[index];
const previousHistoryItem = items[index + 1];
if (!previousHistoryItem) {
return false;
}
return previousHistoryItem.newState === currentHistoryItem.prevState;
}
export { StateHistory };