mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
Alerting: misc rule list fixes (#34400)
* misc rule table fixes * rule stats for all rules * inactive -> normal * always show tooltip for rule error
This commit is contained in:
parent
22043d7872
commit
eaf964a827
@ -12,10 +12,18 @@ const durationMap: { [key in Required<keyof Duration>]: string[] } = {
|
||||
seconds: ['s', 'S', 'seconds'],
|
||||
};
|
||||
|
||||
export function intervalToAbbreviatedDurationString(interval: Interval): string {
|
||||
/**
|
||||
* intervalToAbbreviatedDurationString convers interval to readable duration string
|
||||
*
|
||||
* @param interval - interval to convert
|
||||
* @param includeSeconds - optional, default true. If false, will not include seconds unless interval is less than 1 minute
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
export function intervalToAbbreviatedDurationString(interval: Interval, includeSeconds = true): string {
|
||||
const duration = intervalToDuration(interval);
|
||||
return (Object.entries(duration) as Array<[keyof Duration, number | undefined]>).reduce((str, [unit, value]) => {
|
||||
if (value && value !== 0) {
|
||||
if (value && value !== 0 && !(unit === 'seconds' && !includeSeconds && str)) {
|
||||
const padding = str !== '' ? ' ' : '';
|
||||
return str + `${padding}${value}${durationMap[unit][0]}`;
|
||||
}
|
||||
|
@ -1,5 +1,5 @@
|
||||
import { DataSourceInstanceSettings, GrafanaTheme, urlUtil } from '@grafana/data';
|
||||
import { useStyles, ButtonGroup, ToolbarButton, Alert, LinkButton, withErrorBoundary } from '@grafana/ui';
|
||||
import { useStyles, Alert, LinkButton, withErrorBoundary } from '@grafana/ui';
|
||||
import { SerializedError } from '@reduxjs/toolkit';
|
||||
import React, { useEffect, useMemo } from 'react';
|
||||
import { useDispatch } from 'react-redux';
|
||||
@ -19,6 +19,7 @@ import { RuleListStateView } from './components/rules/RuleListStateView';
|
||||
import { useQueryParams } from 'app/core/hooks/useQueryParams';
|
||||
import { useLocation } from 'react-router-dom';
|
||||
import { contextSrv } from 'app/core/services/context_srv';
|
||||
import { RuleStats } from './components/rules/RuleStats';
|
||||
|
||||
const VIEWS = {
|
||||
groups: RuleListGroupView,
|
||||
@ -117,18 +118,7 @@ export const RuleList = withErrorBoundary(
|
||||
<RulesFilter />
|
||||
<div className={styles.break} />
|
||||
<div className={styles.buttonsContainer}>
|
||||
<ButtonGroup>
|
||||
<a href={urlUtil.renderUrl('alerting/list', { ...queryParams, view: 'group' })}>
|
||||
<ToolbarButton variant={view === 'groups' ? 'active' : 'default'} icon="folder">
|
||||
Groups
|
||||
</ToolbarButton>
|
||||
</a>
|
||||
<a href={urlUtil.renderUrl('alerting/list', { ...queryParams, view: 'state' })}>
|
||||
<ToolbarButton variant={view === 'state' ? 'active' : 'default'} icon="heart-rate">
|
||||
State
|
||||
</ToolbarButton>
|
||||
</a>
|
||||
</ButtonGroup>
|
||||
<RuleStats showInactive={true} showRecording={true} namespaces={filteredNamespaces} />
|
||||
<div />
|
||||
{(contextSrv.hasEditPermissionInFolders || contextSrv.isEditor) && (
|
||||
<LinkButton
|
||||
|
@ -5,7 +5,7 @@ import { css } from '@emotion/css';
|
||||
import React, { FC } from 'react';
|
||||
|
||||
type Props = {
|
||||
status: PromAlertingRuleState;
|
||||
status: PromAlertingRuleState | 'neutral';
|
||||
};
|
||||
|
||||
export const StateColoredText: FC<Props> = ({ children, status }) => {
|
||||
@ -24,4 +24,7 @@ const getStyles = (theme: GrafanaTheme2) => ({
|
||||
[PromAlertingRuleState.Firing]: css`
|
||||
color: ${theme.colors.error.text};
|
||||
`,
|
||||
neutral: css`
|
||||
color: ${theme.colors.text.secondary};
|
||||
`,
|
||||
});
|
||||
|
@ -24,6 +24,9 @@ const getStyles = (theme: GrafanaTheme2) => ({
|
||||
padding: ${theme.spacing(0.5, 1)};
|
||||
text-transform: capitalize;
|
||||
line-height: 1.2;
|
||||
min-width: ${theme.spacing(8)};
|
||||
text-align: center;
|
||||
font-weight: ${theme.typography.fontWeightBold};
|
||||
`,
|
||||
good: css`
|
||||
background-color: ${theme.colors.success.main};
|
||||
|
@ -0,0 +1,35 @@
|
||||
import { css } from '@emotion/css';
|
||||
import { GrafanaTheme2 } from '@grafana/data';
|
||||
import { Icon, Tooltip, useStyles2 } from '@grafana/ui';
|
||||
import { Rule } from 'app/types/unified-alerting';
|
||||
import React, { FC } from 'react';
|
||||
|
||||
interface Prom {
|
||||
rule: Rule;
|
||||
}
|
||||
|
||||
export const RuleHealth: FC<Prom> = ({ rule }) => {
|
||||
const style = useStyles2(getStyle);
|
||||
if (rule.health === 'err' || rule.health === 'error') {
|
||||
return (
|
||||
<Tooltip theme="error" content={rule.lastError || 'No error message provided.'}>
|
||||
<div className={style.warn}>
|
||||
<Icon name="exclamation-triangle" />
|
||||
<span>error</span>
|
||||
</div>
|
||||
</Tooltip>
|
||||
);
|
||||
}
|
||||
return <>{rule.health}</>;
|
||||
};
|
||||
|
||||
const getStyle = (theme: GrafanaTheme2) => ({
|
||||
warn: css`
|
||||
display: inline-flex;
|
||||
flex-direction: row;
|
||||
color: ${theme.colors.warning.text};
|
||||
& > * + * {
|
||||
margin-left: ${theme.spacing(1)};
|
||||
}
|
||||
`,
|
||||
});
|
@ -0,0 +1,92 @@
|
||||
import { css } from '@emotion/css';
|
||||
import { GrafanaTheme2, intervalToAbbreviatedDurationString } from '@grafana/data';
|
||||
import { HorizontalGroup, Spinner, useStyles2 } from '@grafana/ui';
|
||||
import { CombinedRule } from 'app/types/unified-alerting';
|
||||
import { PromAlertingRuleState } from 'app/types/unified-alerting-dto';
|
||||
import React, { FC, useMemo } from 'react';
|
||||
import { isAlertingRule, isRecordingRule } from '../../utils/rules';
|
||||
import { AlertStateTag } from './AlertStateTag';
|
||||
|
||||
interface Props {
|
||||
rule: CombinedRule;
|
||||
isDeleting: boolean;
|
||||
isCreating: boolean;
|
||||
}
|
||||
|
||||
export const RuleState: FC<Props> = ({ rule, isDeleting, isCreating }) => {
|
||||
const style = useStyles2(getStyle);
|
||||
const { promRule } = rule;
|
||||
|
||||
// return how long the rule has been in it's firing state, if any
|
||||
const forTime = useMemo(() => {
|
||||
if (
|
||||
promRule &&
|
||||
isAlertingRule(promRule) &&
|
||||
promRule.alerts?.length &&
|
||||
promRule.state !== PromAlertingRuleState.Inactive
|
||||
) {
|
||||
// find earliest alert
|
||||
const firstActiveAt = promRule.alerts.reduce((prev, alert) => {
|
||||
if (alert.activeAt) {
|
||||
const activeAt = new Date(alert.activeAt);
|
||||
if (prev === null || prev.getTime() > activeAt.getTime()) {
|
||||
return activeAt;
|
||||
}
|
||||
}
|
||||
return prev;
|
||||
}, null as Date | null);
|
||||
|
||||
// caclulate time elapsed from earliest alert
|
||||
if (firstActiveAt) {
|
||||
return (
|
||||
<span title={String(firstActiveAt)} className={style.for}>
|
||||
for{' '}
|
||||
{intervalToAbbreviatedDurationString(
|
||||
{
|
||||
start: firstActiveAt,
|
||||
end: new Date(),
|
||||
},
|
||||
false
|
||||
)}
|
||||
</span>
|
||||
);
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}, [promRule, style]);
|
||||
|
||||
if (isDeleting) {
|
||||
return (
|
||||
<HorizontalGroup>
|
||||
<Spinner />
|
||||
deleting
|
||||
</HorizontalGroup>
|
||||
);
|
||||
} else if (isCreating) {
|
||||
return (
|
||||
<HorizontalGroup>
|
||||
{' '}
|
||||
<Spinner />
|
||||
creating
|
||||
</HorizontalGroup>
|
||||
);
|
||||
} else if (promRule && isAlertingRule(promRule)) {
|
||||
return (
|
||||
<HorizontalGroup>
|
||||
<AlertStateTag state={promRule.state} />
|
||||
{forTime}
|
||||
</HorizontalGroup>
|
||||
);
|
||||
} else if (promRule && isRecordingRule(promRule)) {
|
||||
return <>Recording rule</>;
|
||||
}
|
||||
return <>n/a</>;
|
||||
};
|
||||
|
||||
const getStyle = (theme: GrafanaTheme2) => ({
|
||||
for: css`
|
||||
font-size: ${theme.typography.bodySmall.fontSize};
|
||||
color: ${theme.colors.text.secondary};
|
||||
white-space: nowrap;
|
||||
`,
|
||||
});
|
@ -0,0 +1,113 @@
|
||||
import { CombinedRule, CombinedRuleGroup, CombinedRuleNamespace } from 'app/types/unified-alerting';
|
||||
import { PromAlertingRuleState } from 'app/types/unified-alerting-dto';
|
||||
import pluralize from 'pluralize';
|
||||
import React, { FC, Fragment, useMemo } from 'react';
|
||||
import { isAlertingRule, isRecordingRule, isRecordingRulerRule } from '../../utils/rules';
|
||||
import { StateColoredText } from '../StateColoredText';
|
||||
|
||||
interface Props {
|
||||
showInactive?: boolean;
|
||||
showRecording?: boolean;
|
||||
group?: CombinedRuleGroup;
|
||||
namespaces?: CombinedRuleNamespace[];
|
||||
}
|
||||
|
||||
const emptyStats = {
|
||||
total: 0,
|
||||
recording: 0,
|
||||
[PromAlertingRuleState.Firing]: 0,
|
||||
[PromAlertingRuleState.Pending]: 0,
|
||||
[PromAlertingRuleState.Inactive]: 0,
|
||||
error: 0,
|
||||
} as const;
|
||||
|
||||
export const RuleStats: FC<Props> = ({ showInactive, showRecording, group, namespaces }) => {
|
||||
const calculated = useMemo(() => {
|
||||
const stats = { ...emptyStats };
|
||||
const calcRule = (rule: CombinedRule) => {
|
||||
if (rule.promRule && isAlertingRule(rule.promRule)) {
|
||||
stats[rule.promRule.state] += 1;
|
||||
}
|
||||
if (rule.promRule?.health === 'err' || rule.promRule?.health === 'error') {
|
||||
stats.error += 1;
|
||||
}
|
||||
if (
|
||||
(rule.promRule && isRecordingRule(rule.promRule)) ||
|
||||
(rule.rulerRule && isRecordingRulerRule(rule.rulerRule))
|
||||
) {
|
||||
stats.recording += 1;
|
||||
}
|
||||
stats.total += 1;
|
||||
};
|
||||
if (group) {
|
||||
group.rules.forEach(calcRule);
|
||||
}
|
||||
if (namespaces) {
|
||||
namespaces.forEach((namespace) => namespace.groups.forEach((group) => group.rules.forEach(calcRule)));
|
||||
}
|
||||
return stats;
|
||||
}, [group, namespaces]);
|
||||
|
||||
const statsComponents: React.ReactNode[] = [];
|
||||
if (calculated[PromAlertingRuleState.Firing]) {
|
||||
statsComponents.push(
|
||||
<StateColoredText key="firing" status={PromAlertingRuleState.Firing}>
|
||||
{calculated[PromAlertingRuleState.Firing]} firing
|
||||
</StateColoredText>
|
||||
);
|
||||
}
|
||||
if (calculated.error) {
|
||||
statsComponents.push(
|
||||
<StateColoredText key="errors" status={PromAlertingRuleState.Firing}>
|
||||
{calculated.error} errors
|
||||
</StateColoredText>
|
||||
);
|
||||
}
|
||||
if (calculated[PromAlertingRuleState.Pending]) {
|
||||
statsComponents.push(
|
||||
<StateColoredText key="pending" status={PromAlertingRuleState.Pending}>
|
||||
{calculated[PromAlertingRuleState.Pending]} pending
|
||||
</StateColoredText>
|
||||
);
|
||||
}
|
||||
if (showInactive && calculated[PromAlertingRuleState.Inactive]) {
|
||||
statsComponents.push(
|
||||
<StateColoredText key="inactive" status="neutral">
|
||||
{calculated[PromAlertingRuleState.Inactive]} normal
|
||||
</StateColoredText>
|
||||
);
|
||||
}
|
||||
if (showRecording && calculated.recording) {
|
||||
statsComponents.push(
|
||||
<StateColoredText key="recording" status="neutral">
|
||||
{calculated.recording} recording
|
||||
</StateColoredText>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div>
|
||||
<span>
|
||||
{calculated.total} {pluralize('rule', calculated.total)}
|
||||
</span>
|
||||
{!!statsComponents.length && (
|
||||
<>
|
||||
<span>: </span>
|
||||
{statsComponents.reduce<React.ReactNode[]>(
|
||||
(prev, curr, idx) =>
|
||||
prev.length
|
||||
? [
|
||||
prev,
|
||||
<Fragment key={idx}>
|
||||
<span>, </span>
|
||||
</Fragment>,
|
||||
curr,
|
||||
]
|
||||
: [curr],
|
||||
[]
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
@ -1,6 +1,6 @@
|
||||
import React, { FormEvent, useState } from 'react';
|
||||
import { Button, Icon, Input, Label, RadioButtonGroup, useStyles } from '@grafana/ui';
|
||||
import { DataSourceInstanceSettings, GrafanaTheme } from '@grafana/data';
|
||||
import { DataSourceInstanceSettings, GrafanaTheme, SelectableValue } from '@grafana/data';
|
||||
import { css, cx } from '@emotion/css';
|
||||
import { debounce } from 'lodash';
|
||||
|
||||
@ -10,6 +10,19 @@ import { getFiltersFromUrlParams } from '../../utils/misc';
|
||||
import { DataSourcePicker } from '@grafana/runtime';
|
||||
import { alertStateToReadable } from '../../utils/rules';
|
||||
|
||||
const ViewOptions: SelectableValue[] = [
|
||||
{
|
||||
icon: 'folder',
|
||||
label: 'Groups',
|
||||
value: 'group',
|
||||
},
|
||||
{
|
||||
icon: 'heart-rate',
|
||||
label: 'State',
|
||||
value: 'state',
|
||||
},
|
||||
];
|
||||
|
||||
const RulesFilter = () => {
|
||||
const [queryParams, setQueryParams] = useQueryParams();
|
||||
// This key is used to force a rerender on the inputs when the filters are cleared
|
||||
@ -38,6 +51,10 @@ const RulesFilter = () => {
|
||||
setQueryParams({ alertState: value });
|
||||
};
|
||||
|
||||
const handleViewChange = (view: string) => {
|
||||
setQueryParams({ view });
|
||||
};
|
||||
|
||||
const handleClearFiltersClick = () => {
|
||||
setQueryParams({
|
||||
alertState: null,
|
||||
@ -74,8 +91,17 @@ const RulesFilter = () => {
|
||||
/>
|
||||
</div>
|
||||
<div className={styles.rowChild}>
|
||||
<Label>State</Label>
|
||||
<RadioButtonGroup options={stateOptions} value={alertState} onChange={handleAlertStateChange} />
|
||||
</div>
|
||||
<div className={styles.rowChild}>
|
||||
<Label>View as</Label>
|
||||
<RadioButtonGroup
|
||||
options={ViewOptions}
|
||||
value={queryParams['view'] || 'group'}
|
||||
onChange={handleViewChange}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
{(dataSource || alertState || queryString) && (
|
||||
<div className={styles.flexRow}>
|
||||
|
@ -1,19 +1,17 @@
|
||||
import { CombinedRuleGroup, CombinedRuleNamespace } from 'app/types/unified-alerting';
|
||||
import React, { FC, useMemo, useState, Fragment } from 'react';
|
||||
import React, { FC, useState } from 'react';
|
||||
import { Icon, Tooltip, useStyles2 } from '@grafana/ui';
|
||||
import { GrafanaTheme2 } from '@grafana/data';
|
||||
import { css } from '@emotion/css';
|
||||
import { isAlertingRule, isGrafanaRulerRule } from '../../utils/rules';
|
||||
import { PromAlertingRuleState } from 'app/types/unified-alerting-dto';
|
||||
import { StateColoredText } from '../StateColoredText';
|
||||
import { isGrafanaRulerRule } from '../../utils/rules';
|
||||
import { CollapseToggle } from '../CollapseToggle';
|
||||
import { RulesTable } from './RulesTable';
|
||||
import { GRAFANA_RULES_SOURCE_NAME, isCloudRulesSource } from '../../utils/datasource';
|
||||
import { ActionIcon } from './ActionIcon';
|
||||
import pluralize from 'pluralize';
|
||||
import { useHasRuler } from '../../hooks/useHasRuler';
|
||||
import kbn from 'app/core/utils/kbn';
|
||||
import { useFolder } from '../../hooks/useFolder';
|
||||
import { RuleStats } from './RuleStats';
|
||||
|
||||
interface Props {
|
||||
namespace: CombinedRuleNamespace;
|
||||
@ -31,40 +29,6 @@ export const RulesGroup: FC<Props> = React.memo(({ group, namespace }) => {
|
||||
const folderUID = (rulerRule && isGrafanaRulerRule(rulerRule) && rulerRule.grafana_alert.namespace_uid) || undefined;
|
||||
const { folder } = useFolder(folderUID);
|
||||
|
||||
const stats = useMemo(
|
||||
(): Record<PromAlertingRuleState, number> =>
|
||||
group.rules.reduce<Record<PromAlertingRuleState, number>>(
|
||||
(stats, rule) => {
|
||||
if (rule.promRule && isAlertingRule(rule.promRule)) {
|
||||
stats[rule.promRule.state] += 1;
|
||||
}
|
||||
return stats;
|
||||
},
|
||||
{
|
||||
[PromAlertingRuleState.Firing]: 0,
|
||||
[PromAlertingRuleState.Pending]: 0,
|
||||
[PromAlertingRuleState.Inactive]: 0,
|
||||
}
|
||||
),
|
||||
[group]
|
||||
);
|
||||
|
||||
const statsComponents: React.ReactNode[] = [];
|
||||
if (stats[PromAlertingRuleState.Firing]) {
|
||||
statsComponents.push(
|
||||
<StateColoredText key="firing" status={PromAlertingRuleState.Firing}>
|
||||
{stats[PromAlertingRuleState.Firing]} firing
|
||||
</StateColoredText>
|
||||
);
|
||||
}
|
||||
if (stats[PromAlertingRuleState.Pending]) {
|
||||
statsComponents.push(
|
||||
<StateColoredText key="pending" status={PromAlertingRuleState.Pending}>
|
||||
{stats[PromAlertingRuleState.Pending]} pending
|
||||
</StateColoredText>
|
||||
);
|
||||
}
|
||||
|
||||
const actionIcons: React.ReactNode[] = [];
|
||||
|
||||
// for grafana, link to folder views
|
||||
@ -112,16 +76,7 @@ export const RulesGroup: FC<Props> = React.memo(({ group, namespace }) => {
|
||||
<h6 className={styles.heading}>{groupName}</h6>
|
||||
<div className={styles.spacer} />
|
||||
<div className={styles.headerStats}>
|
||||
{group.rules.length} {pluralize('rule', group.rules.length)}
|
||||
{!!statsComponents.length && (
|
||||
<>
|
||||
:{' '}
|
||||
{statsComponents.reduce<React.ReactNode[]>(
|
||||
(prev, curr, idx) => (prev.length ? [prev, <Fragment key={idx}>, </Fragment>, curr] : [curr]),
|
||||
[]
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
<RuleStats showInactive={false} group={group} />
|
||||
</div>
|
||||
{!!actionIcons.length && (
|
||||
<>
|
||||
@ -130,7 +85,9 @@ export const RulesGroup: FC<Props> = React.memo(({ group, namespace }) => {
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
{!isCollapsed && <RulesTable className={styles.rulesTable} showGuidelines={true} rules={group.rules} />}
|
||||
{!isCollapsed && (
|
||||
<RulesTable showSummaryColumn={true} className={styles.rulesTable} showGuidelines={true} rules={group.rules} />
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
});
|
||||
|
@ -1,7 +1,6 @@
|
||||
import { GrafanaTheme2 } from '@grafana/data';
|
||||
import { useStyles2 } from '@grafana/ui';
|
||||
import React, { FC, Fragment, useState } from 'react';
|
||||
import { isAlertingRule, isRecordingRule } from '../../utils/rules';
|
||||
import { CollapseToggle } from '../CollapseToggle';
|
||||
import { css, cx } from '@emotion/css';
|
||||
import { RuleDetails } from './RuleDetails';
|
||||
@ -9,12 +8,15 @@ import { getAlertTableStyles } from '../../styles/table';
|
||||
import { isCloudRulesSource } from '../../utils/datasource';
|
||||
import { useHasRuler } from '../../hooks/useHasRuler';
|
||||
import { CombinedRule } from 'app/types/unified-alerting';
|
||||
import { AlertStateTag } from './AlertStateTag';
|
||||
import { Annotation } from '../../utils/constants';
|
||||
import { RuleState } from './RuleState';
|
||||
import { RuleHealth } from './RuleHealth';
|
||||
|
||||
interface Props {
|
||||
rules: CombinedRule[];
|
||||
showGuidelines?: boolean;
|
||||
showGroupColumn?: boolean;
|
||||
showSummaryColumn?: boolean;
|
||||
emptyMessage?: string;
|
||||
className?: string;
|
||||
}
|
||||
@ -25,6 +27,7 @@ export const RulesTable: FC<Props> = ({
|
||||
showGuidelines = false,
|
||||
emptyMessage = 'No rules found.',
|
||||
showGroupColumn = false,
|
||||
showSummaryColumn = false,
|
||||
}) => {
|
||||
const hasRuler = useHasRuler();
|
||||
|
||||
@ -49,10 +52,10 @@ export const RulesTable: FC<Props> = ({
|
||||
<table className={tableStyles.table} data-testid="rules-table">
|
||||
<colgroup>
|
||||
<col className={tableStyles.colExpand} />
|
||||
<col className={styles.colState} />
|
||||
<col />
|
||||
<col className={styles.state} />
|
||||
<col />
|
||||
<col />
|
||||
{showSummaryColumn && <col />}
|
||||
{showGroupColumn && <col />}
|
||||
</colgroup>
|
||||
<thead>
|
||||
@ -62,8 +65,9 @@ export const RulesTable: FC<Props> = ({
|
||||
</th>
|
||||
<th>State</th>
|
||||
<th>Name</th>
|
||||
<th>Health</th>
|
||||
{showSummaryColumn && <th>Summary</th>}
|
||||
{showGroupColumn && <th>Group</th>}
|
||||
<th>Status</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@ -79,11 +83,16 @@ export const RulesTable: FC<Props> = ({
|
||||
seenKeys.push(key);
|
||||
const isExpanded = expandedKeys.includes(key);
|
||||
const { promRule, rulerRule } = rule;
|
||||
const statuses = [
|
||||
promRule?.health,
|
||||
hasRuler(rulesSource) && promRule && !rulerRule ? 'deleting' : '',
|
||||
hasRuler(rulesSource) && rulerRule && !promRule ? 'creating' : '',
|
||||
].filter((x) => !!x);
|
||||
const isDeleting = !!(hasRuler(rulesSource) && promRule && !rulerRule);
|
||||
const isCreating = !!(hasRuler(rulesSource) && rulerRule && !promRule);
|
||||
|
||||
let detailsColspan = 3;
|
||||
if (showGroupColumn) {
|
||||
detailsColspan += 1;
|
||||
}
|
||||
if (showSummaryColumn) {
|
||||
detailsColspan += 1;
|
||||
}
|
||||
return (
|
||||
<Fragment key={key}>
|
||||
<tr className={ruleIdx % 2 === 0 ? tableStyles.evenRow : undefined}>
|
||||
@ -103,19 +112,14 @@ export const RulesTable: FC<Props> = ({
|
||||
/>
|
||||
</td>
|
||||
<td>
|
||||
{promRule && isAlertingRule(promRule) ? (
|
||||
<AlertStateTag state={promRule.state} />
|
||||
) : promRule && isRecordingRule(promRule) ? (
|
||||
'Recording rule'
|
||||
) : (
|
||||
'n/a'
|
||||
)}
|
||||
<RuleState rule={rule} isDeleting={isDeleting} isCreating={isCreating} />
|
||||
</td>
|
||||
<td>{rule.name}</td>
|
||||
<td>{promRule && <RuleHealth rule={promRule} />}</td>
|
||||
{showSummaryColumn && <td>{rule.annotations[Annotation.summary] ?? ''}</td>}
|
||||
{showGroupColumn && (
|
||||
<td>{isCloudRulesSource(rulesSource) ? `${namespace.name} > ${group.name}` : namespace.name}</td>
|
||||
)}
|
||||
<td>{statuses.join(', ') || 'n/a'}</td>
|
||||
</tr>
|
||||
{isExpanded && (
|
||||
<tr className={ruleIdx % 2 === 0 ? tableStyles.evenRow : undefined}>
|
||||
@ -124,7 +128,7 @@ export const RulesTable: FC<Props> = ({
|
||||
<div className={cx(styles.ruleContentGuideline, styles.guideline)} />
|
||||
)}
|
||||
</td>
|
||||
<td colSpan={showGroupColumn ? 4 : 3}>
|
||||
<td colSpan={detailsColspan}>
|
||||
<RuleDetails rulesSource={rulesSource} rule={rule} />
|
||||
</td>
|
||||
</tr>
|
||||
@ -172,9 +176,6 @@ export const getStyles = (theme: GrafanaTheme2) => ({
|
||||
evenRow: css`
|
||||
background-color: ${theme.colors.background.primary};
|
||||
`,
|
||||
colState: css`
|
||||
width: 110px;
|
||||
`,
|
||||
relative: css`
|
||||
position: relative;
|
||||
`,
|
||||
@ -201,4 +202,7 @@ export const getStyles = (theme: GrafanaTheme2) => ({
|
||||
top: -24px;
|
||||
bottom: 0;
|
||||
`,
|
||||
state: css`
|
||||
width: 110px;
|
||||
`,
|
||||
});
|
||||
|
@ -18,7 +18,6 @@ export type Alert = {
|
||||
state: PromAlertingRuleState | GrafanaAlertState;
|
||||
value: string;
|
||||
};
|
||||
|
||||
interface RuleBase {
|
||||
health: string;
|
||||
name: string;
|
||||
|
Loading…
Reference in New Issue
Block a user