Alerting: Use Alerting package state components (#110830)
This commit is contained in:
@@ -1,12 +0,0 @@
|
||||
import { Trans } from '@grafana/i18n';
|
||||
import { Icon, Stack, Text } from '@grafana/ui';
|
||||
|
||||
export default function PausedBadge() {
|
||||
return (
|
||||
<Text variant="bodySmall" color="warning">
|
||||
<Stack direction="row" alignItems={'center'} gap={0.25} wrap={'nowrap'} flex={'0 0 auto'}>
|
||||
<Icon name="pause" size="xs" /> <Trans i18nKey="alerting.paused-badge.paused">Paused</Trans>
|
||||
</Stack>
|
||||
</Text>
|
||||
);
|
||||
}
|
||||
@@ -3,6 +3,7 @@ import { chain, truncate } from 'lodash';
|
||||
import { useEffect, useState } from 'react';
|
||||
import { useMeasure } from 'react-use';
|
||||
|
||||
import { StateText } from '@grafana/alerting/unstable';
|
||||
import { NavModelItem, UrlQueryValue } from '@grafana/data';
|
||||
import { Trans, t } from '@grafana/i18n';
|
||||
import {
|
||||
@@ -40,6 +41,7 @@ import { useHasRulerV2 } from '../../hooks/useHasRuler';
|
||||
import { useRuleGroupConsistencyCheck } from '../../hooks/usePrometheusConsistencyCheck';
|
||||
import { useReturnTo } from '../../hooks/useReturnTo';
|
||||
import { PluginOriginBadge } from '../../plugins/PluginOriginBadge';
|
||||
import { normalizeHealth, normalizeState } from '../../rule-list/components/util';
|
||||
import { Annotation } from '../../utils/constants';
|
||||
import { getRulesSourceUid, ruleIdentifierToRuleSourceIdentifier } from '../../utils/datasource';
|
||||
import { labelsSize } from '../../utils/labels';
|
||||
@@ -63,9 +65,7 @@ import { RedirectToCloneRule } from '../rules/CloneRule';
|
||||
|
||||
import { ContactPointLink } from './ContactPointLink';
|
||||
import { FederatedRuleWarning } from './FederatedRuleWarning';
|
||||
import PausedBadge from './PausedBadge';
|
||||
import { useAlertRule } from './RuleContext';
|
||||
import { RecordingBadge, StateBadge } from './StateBadges';
|
||||
import { AlertVersionHistory } from './tabs/AlertVersionHistory';
|
||||
import { Details } from './tabs/Details';
|
||||
import { History } from './tabs/History';
|
||||
@@ -307,6 +307,9 @@ export const Title = ({ name, paused = false, state, health, ruleType, ruleOrigi
|
||||
|
||||
const { returnTo } = useReturnTo(returnToHref);
|
||||
|
||||
const textHealth = normalizeHealth(health);
|
||||
const textState = normalizeState(state);
|
||||
|
||||
return (
|
||||
<Stack direction="row" gap={1} minWidth={0} alignItems="center">
|
||||
{returnToHref && (
|
||||
@@ -321,15 +324,9 @@ export const Title = ({ name, paused = false, state, health, ruleType, ruleOrigi
|
||||
<Text variant="h1" truncate>
|
||||
{name}
|
||||
</Text>
|
||||
{paused ? (
|
||||
<PausedBadge />
|
||||
) : (
|
||||
<>
|
||||
{/* recording rules won't have a state */}
|
||||
{state && <StateBadge state={state} health={health} />}
|
||||
{isRecordingRule && <RecordingBadge health={health} />}
|
||||
</>
|
||||
)}
|
||||
{/* recording rules won't have a state */}
|
||||
{state && <StateText type="alerting" state={textState} health={textHealth} isPaused={paused} />}
|
||||
{isRecordingRule && <StateText type="recording" health={textHealth} isPaused={paused} />}
|
||||
</Stack>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -1,88 +0,0 @@
|
||||
import { ReactNode } from 'react';
|
||||
|
||||
import { Stack, Text } from '@grafana/ui';
|
||||
import { RuleHealth } from 'app/types/unified-alerting';
|
||||
import { PromAlertingRuleState } from 'app/types/unified-alerting-dto';
|
||||
|
||||
import { AlertStateDot } from '../AlertStateDot';
|
||||
|
||||
import { isErrorHealth } from './RuleViewer';
|
||||
|
||||
interface RecordingBadgeProps {
|
||||
health?: RuleHealth;
|
||||
}
|
||||
|
||||
export const RecordingBadge = ({ health }: RecordingBadgeProps) => {
|
||||
const hasError = isErrorHealth(health);
|
||||
|
||||
const color = hasError ? 'error' : 'success';
|
||||
const text = hasError ? 'Recording error' : 'Recording';
|
||||
|
||||
return <Badge color={color} text={text} />;
|
||||
};
|
||||
|
||||
// we're making a distinction here between the "state" of the rule and its "health".
|
||||
interface StateBadgeProps {
|
||||
state: PromAlertingRuleState;
|
||||
health?: RuleHealth;
|
||||
}
|
||||
|
||||
export const StateBadge = ({ state, health }: StateBadgeProps) => {
|
||||
let stateLabel: string;
|
||||
let color: BadgeColor;
|
||||
|
||||
switch (state) {
|
||||
case PromAlertingRuleState.Inactive:
|
||||
color = 'success';
|
||||
stateLabel = 'Normal';
|
||||
break;
|
||||
case PromAlertingRuleState.Firing:
|
||||
color = 'error';
|
||||
stateLabel = 'Firing';
|
||||
break;
|
||||
case PromAlertingRuleState.Pending:
|
||||
color = 'warning';
|
||||
stateLabel = 'Pending';
|
||||
break;
|
||||
case PromAlertingRuleState.Recovering:
|
||||
color = 'warning';
|
||||
stateLabel = 'Recovering';
|
||||
break;
|
||||
case PromAlertingRuleState.Unknown:
|
||||
color = 'info';
|
||||
stateLabel = 'Unknown';
|
||||
break;
|
||||
}
|
||||
|
||||
// if the rule is in "error" health we don't really care about the state
|
||||
if (isErrorHealth(health)) {
|
||||
color = 'error';
|
||||
stateLabel = 'Error';
|
||||
}
|
||||
|
||||
if (health === 'nodata') {
|
||||
color = 'warning';
|
||||
stateLabel = 'No data';
|
||||
}
|
||||
|
||||
return <Badge color={color} text={stateLabel} />;
|
||||
};
|
||||
|
||||
// the generic badge component
|
||||
type BadgeColor = 'success' | 'error' | 'warning' | 'info';
|
||||
|
||||
interface BadgeProps {
|
||||
color: BadgeColor;
|
||||
text: NonNullable<ReactNode>;
|
||||
}
|
||||
|
||||
function Badge({ color, text }: BadgeProps) {
|
||||
return (
|
||||
<Stack direction="row" gap={0.5} wrap={'nowrap'} flex={'0 0 auto'}>
|
||||
<AlertStateDot color={color} />
|
||||
<Text variant="bodySmall" color={color}>
|
||||
{text}
|
||||
</Text>
|
||||
</Stack>
|
||||
);
|
||||
}
|
||||
@@ -119,14 +119,14 @@ describe('DataSourceGroupLoader', () => {
|
||||
render(<DataSourceGroupLoader groupIdentifier={groupIdentifier} />);
|
||||
|
||||
const mimirOnlyItem = await ui.ruleItem(/mimir-only-rule/).find();
|
||||
expect(within(mimirOnlyItem).getByTitle('Creating')).toBeInTheDocument();
|
||||
expect(within(mimirOnlyItem).getByLabelText('Creating')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should render deleting state if a rule is only present in prometheus', async () => {
|
||||
render(<DataSourceGroupLoader groupIdentifier={groupIdentifier} />);
|
||||
|
||||
const promOnlyItem = await ui.ruleItem(/prom-only-rule/).find();
|
||||
expect(within(promOnlyItem).getByTitle('Deleting')).toBeInTheDocument();
|
||||
expect(within(promOnlyItem).getByLabelText('Deleting')).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -28,7 +28,6 @@ import { RuleOperationListItem } from './components/AlertRuleListItem';
|
||||
import { AlertRuleListItemSkeleton } from './components/AlertRuleListItemLoader';
|
||||
import { LoadMoreButton } from './components/LoadMoreButton';
|
||||
import { RuleActionsButtons } from './components/RuleActionsButtons.V2';
|
||||
import { RuleOperation } from './components/RuleListIcon';
|
||||
import { matchRulesGroup } from './ruleMatching';
|
||||
|
||||
const { useDiscoverDsFeaturesQuery } = featureDiscoveryApi;
|
||||
@@ -215,7 +214,7 @@ export function RulerBasedGroupRules({
|
||||
group={groupName}
|
||||
rulesSource={groupIdentifier.rulesSource}
|
||||
application={application}
|
||||
operation={RuleOperation.Creating}
|
||||
operation="creating"
|
||||
showLocation={false}
|
||||
/>
|
||||
);
|
||||
@@ -228,7 +227,7 @@ export function RulerBasedGroupRules({
|
||||
group={groupName}
|
||||
rulesSource={groupIdentifier.rulesSource}
|
||||
application={application}
|
||||
operation={RuleOperation.Deleting}
|
||||
operation="deleting"
|
||||
showLocation={false}
|
||||
/>
|
||||
))}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { render } from 'test/test-utils';
|
||||
import { byRole, byTitle } from 'testing-library-selector';
|
||||
import { byLabelText, byRole } from 'testing-library-selector';
|
||||
|
||||
import { setPluginComponentsHook, setPluginLinksHook } from '@grafana/runtime';
|
||||
import { AccessControlAction } from 'app/types/accessControl';
|
||||
@@ -29,7 +29,7 @@ setupMswServer();
|
||||
|
||||
const ui = {
|
||||
ruleItem: (ruleName: string) => byRole('treeitem', { name: ruleName }),
|
||||
ruleStatus: (status: string) => byTitle(status),
|
||||
ruleStatus: (status: string) => byLabelText(status),
|
||||
ruleLink: (ruleName: string) => byRole('link', { name: ruleName }),
|
||||
editButton: () => byRole('link', { name: 'Edit' }),
|
||||
moreButton: () => byRole('button', { name: 'More' }),
|
||||
|
||||
@@ -14,13 +14,12 @@ import {
|
||||
UnknownRuleListItem,
|
||||
} from './components/AlertRuleListItem';
|
||||
import { RuleActionsButtons } from './components/RuleActionsButtons.V2';
|
||||
import { RuleOperation } from './components/RuleListIcon';
|
||||
|
||||
interface GrafanaRuleListItemProps {
|
||||
rule: GrafanaPromRuleDTO;
|
||||
groupIdentifier: GrafanaRuleGroupIdentifier;
|
||||
namespaceName: string;
|
||||
operation?: RuleOperation;
|
||||
operation?: 'creating' | 'deleting';
|
||||
showLocation?: boolean;
|
||||
}
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@ import { css, cx } from '@emotion/css';
|
||||
import pluralize from 'pluralize';
|
||||
import { ReactNode, forwardRef, memo, useEffect, useId } from 'react';
|
||||
|
||||
import { StateIcon } from '@grafana/alerting/unstable';
|
||||
import { DataSourceInstanceSettings, GrafanaTheme2 } from '@grafana/data';
|
||||
import { Trans, t } from '@grafana/i18n';
|
||||
import { Alert, Stack, Text, TextLink, Tooltip, useStyles2 } from '@grafana/ui';
|
||||
@@ -21,9 +22,8 @@ import { createContactPointSearchLink, makeDataSourceLink } from '../../utils/mi
|
||||
import { RulePluginOrigin } from '../../utils/rules';
|
||||
|
||||
import { ListItem } from './ListItem';
|
||||
import { RuleListIcon, RuleOperation } from './RuleListIcon';
|
||||
import { RuleLocation } from './RuleLocation';
|
||||
import { calculateNextEvaluationEstimate } from './util';
|
||||
import { calculateNextEvaluationEstimate, normalizeHealth, normalizeState } from './util';
|
||||
|
||||
export interface AlertRuleListItemProps {
|
||||
name: string;
|
||||
@@ -47,7 +47,7 @@ export interface AlertRuleListItemProps {
|
||||
contactPoint?: string;
|
||||
actions?: ReactNode;
|
||||
origin?: RulePluginOrigin;
|
||||
operation?: RuleOperation;
|
||||
operation?: 'creating' | 'deleting';
|
||||
// the grouped view doesn't need to show the location again – it's redundant
|
||||
showLocation?: boolean;
|
||||
querySourceUIDs?: string[];
|
||||
@@ -143,6 +143,9 @@ export const AlertRuleListItem = (props: AlertRuleListItemProps) => {
|
||||
);
|
||||
}
|
||||
|
||||
const ruleHealth = normalizeHealth(health);
|
||||
const ruleState = normalizeState(state);
|
||||
|
||||
return (
|
||||
<ListItem
|
||||
aria-labelledby={listItemAriaId}
|
||||
@@ -159,7 +162,9 @@ export const AlertRuleListItem = (props: AlertRuleListItemProps) => {
|
||||
</Stack>
|
||||
}
|
||||
description={<Summary content={summary} error={error} />}
|
||||
icon={<RuleListIcon state={state} health={health} isPaused={isPaused} operation={operation} />}
|
||||
icon={
|
||||
<StateIcon type="alerting" state={ruleState} health={ruleHealth} isPaused={isPaused} operation={operation} />
|
||||
}
|
||||
actions={actions}
|
||||
meta={metadata}
|
||||
/>
|
||||
@@ -207,6 +212,8 @@ export function RecordingRuleListItem({
|
||||
metadata.push(<QuerySourceIcons queriedDatasourceUIDs={querySourceUIDs} />);
|
||||
}
|
||||
|
||||
const ruleHealth = normalizeHealth(health);
|
||||
|
||||
return (
|
||||
<ListItem
|
||||
title={
|
||||
@@ -222,7 +229,7 @@ export function RecordingRuleListItem({
|
||||
</Stack>
|
||||
}
|
||||
description={<Summary error={error} />}
|
||||
icon={<RuleListIcon recording={true} health={health} isPaused={isPaused} />}
|
||||
icon={<StateIcon type="recording" health={ruleHealth} isPaused={isPaused} />}
|
||||
actions={actions}
|
||||
meta={metadata}
|
||||
/>
|
||||
@@ -236,7 +243,7 @@ interface RuleOperationListItemProps {
|
||||
groupUrl?: string;
|
||||
rulesSource?: RulesSourceIdentifier;
|
||||
application?: RulesSourceApplication;
|
||||
operation: RuleOperation;
|
||||
operation: 'creating' | 'deleting';
|
||||
showLocation?: boolean;
|
||||
}
|
||||
|
||||
@@ -275,7 +282,7 @@ export function RuleOperationListItem({
|
||||
<Text id={listItemAriaId}>{name}</Text>
|
||||
</Stack>
|
||||
}
|
||||
icon={<RuleListIcon operation={operation} />}
|
||||
icon={<StateIcon operation={operation} />}
|
||||
meta={metadata}
|
||||
/>
|
||||
);
|
||||
|
||||
+2
-2
@@ -1,5 +1,6 @@
|
||||
import Skeleton from 'react-loading-skeleton';
|
||||
|
||||
import { StateIcon } from '@grafana/alerting/unstable';
|
||||
import { t } from '@grafana/i18n';
|
||||
import { GrafanaRuleIdentifier } from 'app/types/unified-alerting';
|
||||
|
||||
@@ -7,13 +8,12 @@ import { stringifyErrorLike } from '../../utils/misc';
|
||||
|
||||
import { ListItem } from './ListItem';
|
||||
import { RuleActionsSkeleton } from './RuleActionsSkeleton';
|
||||
import { RuleListIcon } from './RuleListIcon';
|
||||
|
||||
export function AlertRuleListItemSkeleton() {
|
||||
return (
|
||||
<ListItem
|
||||
title={<Skeleton width={64} />}
|
||||
icon={<RuleListIcon isPaused={false} />}
|
||||
icon={<StateIcon isPaused={false} />}
|
||||
description={<Skeleton width={256} />}
|
||||
actions={<RuleActionsSkeleton />}
|
||||
data-testid="alert-rule-list-item-loader"
|
||||
|
||||
@@ -1,176 +0,0 @@
|
||||
import { css, keyframes } from '@emotion/css';
|
||||
import { ComponentProps, memo } from 'react';
|
||||
import type { RequireAtLeastOne } from 'type-fest';
|
||||
|
||||
import { GrafanaTheme2 } from '@grafana/data';
|
||||
import { Icon, type IconName, Text, Tooltip, useStyles2, useTheme2 } from '@grafana/ui';
|
||||
import type { RuleHealth } from 'app/types/unified-alerting';
|
||||
import { PromAlertingRuleState } from 'app/types/unified-alerting-dto';
|
||||
|
||||
import { isErrorHealth } from '../../components/rule-viewer/RuleViewer';
|
||||
|
||||
type TextProps = ComponentProps<typeof Text>;
|
||||
|
||||
interface RuleListIconProps {
|
||||
recording?: boolean;
|
||||
state?: PromAlertingRuleState;
|
||||
health?: RuleHealth;
|
||||
isPaused?: boolean;
|
||||
operation?: RuleOperation;
|
||||
}
|
||||
|
||||
export enum RuleOperation {
|
||||
Creating = 'Creating',
|
||||
Deleting = 'Deleting',
|
||||
}
|
||||
|
||||
const icons: Record<PromAlertingRuleState, IconName> = {
|
||||
[PromAlertingRuleState.Inactive]: 'check-circle',
|
||||
[PromAlertingRuleState.Pending]: 'circle',
|
||||
[PromAlertingRuleState.Recovering]: 'exclamation-circle',
|
||||
[PromAlertingRuleState.Firing]: 'exclamation-circle',
|
||||
[PromAlertingRuleState.Unknown]: 'question-circle',
|
||||
};
|
||||
|
||||
const color: Record<PromAlertingRuleState, 'success' | 'error' | 'warning' | 'info'> = {
|
||||
[PromAlertingRuleState.Inactive]: 'success',
|
||||
[PromAlertingRuleState.Pending]: 'warning',
|
||||
[PromAlertingRuleState.Recovering]: 'warning',
|
||||
[PromAlertingRuleState.Firing]: 'error',
|
||||
[PromAlertingRuleState.Unknown]: 'info',
|
||||
};
|
||||
|
||||
const stateNames: Record<PromAlertingRuleState, string> = {
|
||||
[PromAlertingRuleState.Inactive]: 'Normal',
|
||||
[PromAlertingRuleState.Pending]: 'Pending',
|
||||
[PromAlertingRuleState.Firing]: 'Firing',
|
||||
[PromAlertingRuleState.Recovering]: 'Recovering',
|
||||
[PromAlertingRuleState.Unknown]: 'Unknown',
|
||||
};
|
||||
|
||||
const operationIcons: Record<RuleOperation, IconName> = {
|
||||
[RuleOperation.Creating]: 'plus-circle',
|
||||
[RuleOperation.Deleting]: 'minus-circle',
|
||||
};
|
||||
|
||||
// ⚠️ not trivial to update this, you have to re-do the math for the loading spinner
|
||||
const ICON_SIZE = 15;
|
||||
|
||||
/**
|
||||
* Make sure that the order of importance here matches the one we use in the StateBadge component for the detail view
|
||||
* This component is often rendered tens or hundreds of times in a single page, so it's performance is important
|
||||
*/
|
||||
export const RuleListIcon = memo(function RuleListIcon({
|
||||
state,
|
||||
health,
|
||||
recording = false,
|
||||
isPaused = false,
|
||||
operation,
|
||||
}: RequireAtLeastOne<RuleListIconProps>) {
|
||||
const styles = useStyles2(getStyles);
|
||||
const theme = useTheme2();
|
||||
|
||||
let iconName: IconName = state ? icons[state] : 'circle';
|
||||
let iconColor: TextProps['color'] = state ? color[state] : 'secondary';
|
||||
let stateName: string = state ? stateNames[state] : 'unknown';
|
||||
|
||||
if (recording) {
|
||||
iconName = 'record-audio';
|
||||
iconColor = 'success';
|
||||
stateName = 'Recording';
|
||||
}
|
||||
|
||||
if (health === 'nodata') {
|
||||
iconName = 'exclamation-triangle';
|
||||
iconColor = 'warning';
|
||||
stateName = 'Insufficient data';
|
||||
}
|
||||
|
||||
if (isErrorHealth(health)) {
|
||||
iconName = 'times-circle';
|
||||
iconColor = 'error';
|
||||
stateName = 'Failed to evaluate rule';
|
||||
}
|
||||
|
||||
if (isPaused) {
|
||||
iconName = 'pause-circle';
|
||||
iconColor = 'warning';
|
||||
stateName = 'Paused';
|
||||
}
|
||||
|
||||
if (operation) {
|
||||
iconName = operationIcons[operation];
|
||||
iconColor = 'secondary';
|
||||
stateName = operation;
|
||||
}
|
||||
|
||||
return (
|
||||
<Tooltip content={stateName} placement="right">
|
||||
<div>
|
||||
<Text color={iconColor}>
|
||||
<div className={styles.iconsContainer}>
|
||||
<Icon name={iconName} width={ICON_SIZE} height={ICON_SIZE} title={stateName} />
|
||||
{/* this loading spinner works by using an optical illusion;
|
||||
the actual icon is static and the "spinning" part is just a semi-transparent darker circle overlayed on top.
|
||||
This makes it look like there is a small bright colored spinner rotating.
|
||||
*/}
|
||||
{operation && (
|
||||
<svg
|
||||
width={ICON_SIZE}
|
||||
height={ICON_SIZE}
|
||||
viewBox="0 0 20 20"
|
||||
version="1.1"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
className={styles.spinning}
|
||||
>
|
||||
<circle
|
||||
r={ICON_SIZE / 2}
|
||||
cx="10"
|
||||
cy="10"
|
||||
// make sure to match this color to the color of the list item background where it's being used! Works for both light and dark themes.
|
||||
stroke={theme.colors.background.primary}
|
||||
strokeWidth="2"
|
||||
strokeLinecap="round"
|
||||
fill="transparent"
|
||||
strokeOpacity={0.85}
|
||||
strokeDasharray="20px"
|
||||
/>
|
||||
</svg>
|
||||
)}
|
||||
</div>
|
||||
</Text>
|
||||
</div>
|
||||
</Tooltip>
|
||||
);
|
||||
});
|
||||
|
||||
const spin = keyframes({
|
||||
'0%': {
|
||||
transform: 'rotate(0deg)',
|
||||
},
|
||||
'50%': {
|
||||
transform: 'rotate(180deg)',
|
||||
},
|
||||
'100%': {
|
||||
transform: 'rotate(360deg)',
|
||||
},
|
||||
});
|
||||
|
||||
const getStyles = (theme: GrafanaTheme2) => ({
|
||||
iconsContainer: css({
|
||||
position: 'relative',
|
||||
width: ICON_SIZE,
|
||||
height: ICON_SIZE,
|
||||
'> *': {
|
||||
position: 'absolute',
|
||||
},
|
||||
}),
|
||||
spinning: css({
|
||||
[theme.transitions.handleMotion('no-preference')]: {
|
||||
animationName: spin,
|
||||
animationIterationCount: 'infinite',
|
||||
animationDuration: '1s',
|
||||
animationTimingFunction: 'linear',
|
||||
},
|
||||
}),
|
||||
});
|
||||
@@ -1,6 +1,10 @@
|
||||
import { addMilliseconds, formatDistanceToNowStrict, isBefore } from 'date-fns';
|
||||
import { ComponentProps } from 'react';
|
||||
|
||||
import { StateIcon } from '@grafana/alerting/unstable';
|
||||
import { dateTime, dateTimeFormat, isValidDate } from '@grafana/data';
|
||||
import { RuleHealth } from 'app/types/unified-alerting';
|
||||
import { PromAlertingRuleState } from 'app/types/unified-alerting-dto';
|
||||
|
||||
import { isNullDate, parsePrometheusDuration } from '../../utils/time';
|
||||
|
||||
@@ -69,3 +73,49 @@ export function getRelativeEvaluationInterval(lastEvaluation?: string) {
|
||||
|
||||
return formatDistanceToNowStrict(new Date(lastEvaluation));
|
||||
}
|
||||
|
||||
type NormalizedHealth = ComponentProps<typeof StateIcon>['health'];
|
||||
export function normalizeHealth(health?: RuleHealth): NormalizedHealth {
|
||||
if (!health) {
|
||||
return;
|
||||
}
|
||||
|
||||
// backwards compatibility with Prometheus rule state
|
||||
if (health === 'err') {
|
||||
return 'error';
|
||||
}
|
||||
|
||||
if (isValidHealth(health)) {
|
||||
return health;
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
function isValidHealth(health: string): health is NonNullable<NormalizedHealth> {
|
||||
const valid: Array<NonNullable<NormalizedHealth>> = ['nodata', 'error'] as const;
|
||||
return valid.some((v) => v === health);
|
||||
}
|
||||
|
||||
type NormalizedState = ComponentProps<typeof StateIcon>['state'];
|
||||
export function normalizeState(state?: PromAlertingRuleState): NormalizedState {
|
||||
if (!state) {
|
||||
return 'unknown';
|
||||
}
|
||||
|
||||
// backwards compatibility with Prometheus rule state
|
||||
if (state === 'inactive') {
|
||||
return 'normal';
|
||||
}
|
||||
|
||||
if (isValidState(state)) {
|
||||
return state;
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
function isValidState(state: string): state is NonNullable<NormalizedState> {
|
||||
const valid: Array<NonNullable<NormalizedState>> = ['normal', 'firing', 'pending', 'unknown', 'recovering'] as const;
|
||||
return valid.some((v) => v === state);
|
||||
}
|
||||
|
||||
@@ -2058,9 +2058,6 @@
|
||||
"text-loading-rules": "Loading rules...",
|
||||
"title-dashboard-not-saved": "Dashboard not saved"
|
||||
},
|
||||
"paused-badge": {
|
||||
"paused": "Paused"
|
||||
},
|
||||
"payload-editor": {
|
||||
"edit-payload": "Edit payload",
|
||||
"label-add-custom-alert-instance": "Add custom alert instance",
|
||||
|
||||
Reference in New Issue
Block a user