diff --git a/public/app/features/alerting/unified/components/rule-viewer/PausedBadge.tsx b/public/app/features/alerting/unified/components/rule-viewer/PausedBadge.tsx
deleted file mode 100644
index a1764f4ed38..00000000000
--- a/public/app/features/alerting/unified/components/rule-viewer/PausedBadge.tsx
+++ /dev/null
@@ -1,12 +0,0 @@
-import { Trans } from '@grafana/i18n';
-import { Icon, Stack, Text } from '@grafana/ui';
-
-export default function PausedBadge() {
- return (
-
-
- Paused
-
-
- );
-}
diff --git a/public/app/features/alerting/unified/components/rule-viewer/RuleViewer.tsx b/public/app/features/alerting/unified/components/rule-viewer/RuleViewer.tsx
index 5224756b624..de406b59761 100644
--- a/public/app/features/alerting/unified/components/rule-viewer/RuleViewer.tsx
+++ b/public/app/features/alerting/unified/components/rule-viewer/RuleViewer.tsx
@@ -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 (
{returnToHref && (
@@ -321,15 +324,9 @@ export const Title = ({ name, paused = false, state, health, ruleType, ruleOrigi
{name}
- {paused ? (
-
- ) : (
- <>
- {/* recording rules won't have a state */}
- {state && }
- {isRecordingRule && }
- >
- )}
+ {/* recording rules won't have a state */}
+ {state && }
+ {isRecordingRule && }
);
};
diff --git a/public/app/features/alerting/unified/components/rule-viewer/StateBadges.tsx b/public/app/features/alerting/unified/components/rule-viewer/StateBadges.tsx
deleted file mode 100644
index d213f07103f..00000000000
--- a/public/app/features/alerting/unified/components/rule-viewer/StateBadges.tsx
+++ /dev/null
@@ -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 ;
-};
-
-// 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 ;
-};
-
-// the generic badge component
-type BadgeColor = 'success' | 'error' | 'warning' | 'info';
-
-interface BadgeProps {
- color: BadgeColor;
- text: NonNullable;
-}
-
-function Badge({ color, text }: BadgeProps) {
- return (
-
-
-
- {text}
-
-
- );
-}
diff --git a/public/app/features/alerting/unified/rule-list/DataSourceGroupLoader.test.tsx b/public/app/features/alerting/unified/rule-list/DataSourceGroupLoader.test.tsx
index 2c8411e09a9..53b80f1ebaf 100644
--- a/public/app/features/alerting/unified/rule-list/DataSourceGroupLoader.test.tsx
+++ b/public/app/features/alerting/unified/rule-list/DataSourceGroupLoader.test.tsx
@@ -119,14 +119,14 @@ describe('DataSourceGroupLoader', () => {
render();
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();
const promOnlyItem = await ui.ruleItem(/prom-only-rule/).find();
- expect(within(promOnlyItem).getByTitle('Deleting')).toBeInTheDocument();
+ expect(within(promOnlyItem).getByLabelText('Deleting')).toBeInTheDocument();
});
});
});
diff --git a/public/app/features/alerting/unified/rule-list/DataSourceGroupLoader.tsx b/public/app/features/alerting/unified/rule-list/DataSourceGroupLoader.tsx
index 9842f651ec3..bd221cd72ed 100644
--- a/public/app/features/alerting/unified/rule-list/DataSourceGroupLoader.tsx
+++ b/public/app/features/alerting/unified/rule-list/DataSourceGroupLoader.tsx
@@ -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}
/>
))}
diff --git a/public/app/features/alerting/unified/rule-list/GrafanaGroupLoader.test.tsx b/public/app/features/alerting/unified/rule-list/GrafanaGroupLoader.test.tsx
index 3169996b88c..3105c3efd6b 100644
--- a/public/app/features/alerting/unified/rule-list/GrafanaGroupLoader.test.tsx
+++ b/public/app/features/alerting/unified/rule-list/GrafanaGroupLoader.test.tsx
@@ -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' }),
diff --git a/public/app/features/alerting/unified/rule-list/GrafanaRuleListItem.tsx b/public/app/features/alerting/unified/rule-list/GrafanaRuleListItem.tsx
index ed90116bcaa..2e708627128 100644
--- a/public/app/features/alerting/unified/rule-list/GrafanaRuleListItem.tsx
+++ b/public/app/features/alerting/unified/rule-list/GrafanaRuleListItem.tsx
@@ -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;
}
diff --git a/public/app/features/alerting/unified/rule-list/components/AlertRuleListItem.tsx b/public/app/features/alerting/unified/rule-list/components/AlertRuleListItem.tsx
index 38c5a01e7a0..637344db3e1 100644
--- a/public/app/features/alerting/unified/rule-list/components/AlertRuleListItem.tsx
+++ b/public/app/features/alerting/unified/rule-list/components/AlertRuleListItem.tsx
@@ -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 (
{
}
description={}
- icon={}
+ icon={
+
+ }
actions={actions}
meta={metadata}
/>
@@ -207,6 +212,8 @@ export function RecordingRuleListItem({
metadata.push();
}
+ const ruleHealth = normalizeHealth(health);
+
return (
}
description={}
- icon={}
+ icon={}
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({
{name}
}
- icon={}
+ icon={}
meta={metadata}
/>
);
diff --git a/public/app/features/alerting/unified/rule-list/components/AlertRuleListItemLoader.tsx b/public/app/features/alerting/unified/rule-list/components/AlertRuleListItemLoader.tsx
index e8abce41ed8..600d9ae9620 100644
--- a/public/app/features/alerting/unified/rule-list/components/AlertRuleListItemLoader.tsx
+++ b/public/app/features/alerting/unified/rule-list/components/AlertRuleListItemLoader.tsx
@@ -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 (
}
- icon={}
+ icon={}
description={}
actions={}
data-testid="alert-rule-list-item-loader"
diff --git a/public/app/features/alerting/unified/rule-list/components/RuleListIcon.tsx b/public/app/features/alerting/unified/rule-list/components/RuleListIcon.tsx
deleted file mode 100644
index 96025385826..00000000000
--- a/public/app/features/alerting/unified/rule-list/components/RuleListIcon.tsx
+++ /dev/null
@@ -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;
-
-interface RuleListIconProps {
- recording?: boolean;
- state?: PromAlertingRuleState;
- health?: RuleHealth;
- isPaused?: boolean;
- operation?: RuleOperation;
-}
-
-export enum RuleOperation {
- Creating = 'Creating',
- Deleting = 'Deleting',
-}
-
-const icons: Record = {
- [PromAlertingRuleState.Inactive]: 'check-circle',
- [PromAlertingRuleState.Pending]: 'circle',
- [PromAlertingRuleState.Recovering]: 'exclamation-circle',
- [PromAlertingRuleState.Firing]: 'exclamation-circle',
- [PromAlertingRuleState.Unknown]: 'question-circle',
-};
-
-const color: Record = {
- [PromAlertingRuleState.Inactive]: 'success',
- [PromAlertingRuleState.Pending]: 'warning',
- [PromAlertingRuleState.Recovering]: 'warning',
- [PromAlertingRuleState.Firing]: 'error',
- [PromAlertingRuleState.Unknown]: 'info',
-};
-
-const stateNames: Record = {
- [PromAlertingRuleState.Inactive]: 'Normal',
- [PromAlertingRuleState.Pending]: 'Pending',
- [PromAlertingRuleState.Firing]: 'Firing',
- [PromAlertingRuleState.Recovering]: 'Recovering',
- [PromAlertingRuleState.Unknown]: 'Unknown',
-};
-
-const operationIcons: Record = {
- [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) {
- 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 (
-
-
-
-
-
- {/* 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 && (
-
- )}
-