mirror of
https://github.com/grafana/grafana.git
synced 2026-07-29 15:59:50 -05:00
Alerting: misc fixes (#33070)
This commit is contained in:
@@ -125,6 +125,11 @@ describe('RuleList', () => {
|
|||||||
groups: [
|
groups: [
|
||||||
mockPromRuleGroup({
|
mockPromRuleGroup({
|
||||||
name: 'grafana-group',
|
name: 'grafana-group',
|
||||||
|
rules: [
|
||||||
|
mockPromAlertingRule({
|
||||||
|
query: '[]',
|
||||||
|
}),
|
||||||
|
],
|
||||||
}),
|
}),
|
||||||
],
|
],
|
||||||
}),
|
}),
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ import RulesFilter from './components/rules/RulesFilter';
|
|||||||
import { RuleListGroupView } from './components/rules/RuleListGroupView';
|
import { RuleListGroupView } from './components/rules/RuleListGroupView';
|
||||||
import { RuleListStateView } from './components/rules/RuleListStateView';
|
import { RuleListStateView } from './components/rules/RuleListStateView';
|
||||||
import { useQueryParams } from 'app/core/hooks/useQueryParams';
|
import { useQueryParams } from 'app/core/hooks/useQueryParams';
|
||||||
|
import { config } from '@grafana/runtime';
|
||||||
|
|
||||||
const VIEWS = {
|
const VIEWS = {
|
||||||
groups: RuleListGroupView,
|
groups: RuleListGroupView,
|
||||||
@@ -105,13 +106,15 @@ export const RuleList: FC = () => {
|
|||||||
)}
|
)}
|
||||||
{promReqeustErrors.map(({ dataSource, error }) => (
|
{promReqeustErrors.map(({ dataSource, error }) => (
|
||||||
<div key={dataSource.name}>
|
<div key={dataSource.name}>
|
||||||
Failed to load rules state from <a href={`datasources/edit/${dataSource.id}`}>{dataSource.name}</a>:{' '}
|
Failed to load rules state from{' '}
|
||||||
|
<a href={`${config.appSubUrl ?? ''}/datasources/edit/${dataSource.id}`}>{dataSource.name}</a>:{' '}
|
||||||
{error.message || 'Unknown error.'}
|
{error.message || 'Unknown error.'}
|
||||||
</div>
|
</div>
|
||||||
))}
|
))}
|
||||||
{rulerRequestErrors.map(({ dataSource, error }) => (
|
{rulerRequestErrors.map(({ dataSource, error }) => (
|
||||||
<div key={dataSource.name}>
|
<div key={dataSource.name}>
|
||||||
Failed to load rules config from <a href={`datasources/edit/${dataSource.id}`}>{dataSource.name}</a>:{' '}
|
Failed to load rules config from{' '}
|
||||||
|
<a href={`${config.appSubUrl ?? ''}/datasources/edit/${dataSource.id}`}>{dataSource.name}</a>:{' '}
|
||||||
{error.message || 'Unknown error.'}
|
{error.message || 'Unknown error.'}
|
||||||
</div>
|
</div>
|
||||||
))}
|
))}
|
||||||
@@ -123,19 +126,19 @@ export const RuleList: FC = () => {
|
|||||||
<div className={styles.break} />
|
<div className={styles.break} />
|
||||||
<div className={styles.buttonsContainer}>
|
<div className={styles.buttonsContainer}>
|
||||||
<ButtonGroup>
|
<ButtonGroup>
|
||||||
<a href={urlUtil.renderUrl('/alerting/list', { ...queryParams, view: 'group' })}>
|
<a href={urlUtil.renderUrl(`${config.appSubUrl ?? ''}/alerting/list`, { ...queryParams, view: 'group' })}>
|
||||||
<ToolbarButton variant={view === 'groups' ? 'active' : 'default'} icon="folder">
|
<ToolbarButton variant={view === 'groups' ? 'active' : 'default'} icon="folder">
|
||||||
Groups
|
Groups
|
||||||
</ToolbarButton>
|
</ToolbarButton>
|
||||||
</a>
|
</a>
|
||||||
<a href={urlUtil.renderUrl('/alerting/list', { ...queryParams, view: 'state' })}>
|
<a href={urlUtil.renderUrl(`${config.appSubUrl ?? ''}/alerting/list`, { ...queryParams, view: 'state' })}>
|
||||||
<ToolbarButton variant={view === 'state' ? 'active' : 'default'} icon="heart-rate">
|
<ToolbarButton variant={view === 'state' ? 'active' : 'default'} icon="heart-rate">
|
||||||
State
|
State
|
||||||
</ToolbarButton>
|
</ToolbarButton>
|
||||||
</a>
|
</a>
|
||||||
</ButtonGroup>
|
</ButtonGroup>
|
||||||
<div />
|
<div />
|
||||||
<a href="/alerting/new">
|
<a href={`${config.appSubUrl ?? ''}/alerting/new`}>
|
||||||
<Button icon="plus">New alert rule</Button>
|
<Button icon="plus">New alert rule</Button>
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ import { useDispatch } from 'react-redux';
|
|||||||
import { useCleanup } from 'app/core/hooks/useCleanup';
|
import { useCleanup } from 'app/core/hooks/useCleanup';
|
||||||
import { rulerRuleToFormValues, defaultFormValues } from '../../utils/rule-form';
|
import { rulerRuleToFormValues, defaultFormValues } from '../../utils/rule-form';
|
||||||
import { Link } from 'react-router-dom';
|
import { Link } from 'react-router-dom';
|
||||||
|
import { config } from '@grafana/runtime';
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
existing?: RuleWithLocation;
|
existing?: RuleWithLocation;
|
||||||
@@ -56,6 +57,7 @@ export const AlertRuleForm: FC<Props> = ({ existing }) => {
|
|||||||
dispatch(
|
dispatch(
|
||||||
saveRuleFormAction({
|
saveRuleFormAction({
|
||||||
values: {
|
values: {
|
||||||
|
...defaultValues,
|
||||||
...values,
|
...values,
|
||||||
annotations: values.annotations?.filter(({ key }) => !!key) ?? [],
|
annotations: values.annotations?.filter(({ key }) => !!key) ?? [],
|
||||||
labels: values.labels?.filter(({ key }) => !!key) ?? [],
|
labels: values.labels?.filter(({ key }) => !!key) ?? [],
|
||||||
@@ -70,7 +72,7 @@ export const AlertRuleForm: FC<Props> = ({ existing }) => {
|
|||||||
<FormContext {...formAPI}>
|
<FormContext {...formAPI}>
|
||||||
<form onSubmit={handleSubmit((values) => submit(values, false))} className={styles.form}>
|
<form onSubmit={handleSubmit((values) => submit(values, false))} className={styles.form}>
|
||||||
<PageToolbar title="Create alert rule" pageIcon="bell" className={styles.toolbar}>
|
<PageToolbar title="Create alert rule" pageIcon="bell" className={styles.toolbar}>
|
||||||
<Link to="/alerting/list">
|
<Link to={`${config.appSubUrl ?? ''}/alerting/list`}>
|
||||||
<ToolbarButton variant="default" disabled={submitState.loading} type="button">
|
<ToolbarButton variant="default" disabled={submitState.loading} type="button">
|
||||||
Cancel
|
Cancel
|
||||||
</ToolbarButton>
|
</ToolbarButton>
|
||||||
|
|||||||
@@ -1,11 +1,12 @@
|
|||||||
import EmptyListCTA from 'app/core/components/EmptyListCTA/EmptyListCTA';
|
import EmptyListCTA from 'app/core/components/EmptyListCTA/EmptyListCTA';
|
||||||
import React, { FC } from 'react';
|
import React, { FC } from 'react';
|
||||||
|
import { config } from '@grafana/runtime';
|
||||||
|
|
||||||
export const NoRulesSplash: FC = () => (
|
export const NoRulesSplash: FC = () => (
|
||||||
<EmptyListCTA
|
<EmptyListCTA
|
||||||
title="You haven`t created any alert rules yet"
|
title="You haven`t created any alert rules yet"
|
||||||
buttonIcon="bell"
|
buttonIcon="bell"
|
||||||
buttonLink="/alerting/new"
|
buttonLink={`${config.appSubUrl ?? ''}/alerting/new`}
|
||||||
buttonTitle="New alert rule"
|
buttonTitle="New alert rule"
|
||||||
proTip="you can also create alert rules from existing panels and queries."
|
proTip="you can also create alert rules from existing panels and queries."
|
||||||
proTipLink="https://grafana.com/docs/"
|
proTipLink="https://grafana.com/docs/"
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { CombinedRule, RulesSource } from 'app/types/unified-alerting';
|
import { CombinedRule, RulesSource } from 'app/types/unified-alerting';
|
||||||
import React, { FC } from 'react';
|
import React, { FC, useMemo } from 'react';
|
||||||
import { useStyles } from '@grafana/ui';
|
import { useStyles } from '@grafana/ui';
|
||||||
import { css, cx } from '@emotion/css';
|
import { css, cx } from '@emotion/css';
|
||||||
import { GrafanaTheme } from '@grafana/data';
|
import { GrafanaTheme } from '@grafana/data';
|
||||||
@@ -10,6 +10,7 @@ import { AlertLabels } from '../AlertLabels';
|
|||||||
import { AlertInstancesTable } from './AlertInstancesTable';
|
import { AlertInstancesTable } from './AlertInstancesTable';
|
||||||
import { DetailsField } from './DetailsField';
|
import { DetailsField } from './DetailsField';
|
||||||
import { RuleQuery } from './RuleQuery';
|
import { RuleQuery } from './RuleQuery';
|
||||||
|
import { getDataSourceSrv } from '@grafana/runtime';
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
rule: CombinedRule;
|
rule: CombinedRule;
|
||||||
@@ -23,6 +24,23 @@ export const RuleDetails: FC<Props> = ({ rule, rulesSource }) => {
|
|||||||
|
|
||||||
const annotations = Object.entries(rule.annotations);
|
const annotations = Object.entries(rule.annotations);
|
||||||
|
|
||||||
|
const dataSources: Array<{ name: string; icon?: string }> = useMemo(() => {
|
||||||
|
if (isCloudRulesSource(rulesSource)) {
|
||||||
|
return [{ name: rulesSource.name, icon: rulesSource.meta.info.logos.small }];
|
||||||
|
} else if (rule.queries) {
|
||||||
|
return rule.queries
|
||||||
|
.map(({ datasource }) => {
|
||||||
|
const ds = getDataSourceSrv().getInstanceSettings(datasource);
|
||||||
|
if (ds) {
|
||||||
|
return { name: ds.name, icon: ds.meta.info.logos.small };
|
||||||
|
}
|
||||||
|
return { name: datasource };
|
||||||
|
})
|
||||||
|
.filter(({ name }) => name !== '__expr__');
|
||||||
|
}
|
||||||
|
return [];
|
||||||
|
}, [rule, rulesSource]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<div className={styles.wrapper}>
|
<div className={styles.wrapper}>
|
||||||
@@ -42,9 +60,18 @@ export const RuleDetails: FC<Props> = ({ rule, rulesSource }) => {
|
|||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
<div className={styles.rightSide}>
|
<div className={styles.rightSide}>
|
||||||
{isCloudRulesSource(rulesSource) && (
|
{!!dataSources.length && (
|
||||||
<DetailsField label="Data source">
|
<DetailsField label="Data source">
|
||||||
<img className={styles.dataSourceIcon} src={rulesSource.meta.info.logos.small} /> {rulesSource.name}
|
{dataSources.map(({ name, icon }) => (
|
||||||
|
<div key={name}>
|
||||||
|
{icon && (
|
||||||
|
<>
|
||||||
|
<img className={styles.dataSourceIcon} src={icon} />{' '}
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
{name}
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
</DetailsField>
|
</DetailsField>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
|
import { useQueryParams } from 'app/core/hooks/useQueryParams';
|
||||||
import { CombinedRule, CombinedRuleNamespace } from 'app/types/unified-alerting';
|
import { CombinedRule, CombinedRuleNamespace } from 'app/types/unified-alerting';
|
||||||
import { PromAlertingRuleState } from 'app/types/unified-alerting-dto';
|
import { PromAlertingRuleState } from 'app/types/unified-alerting-dto';
|
||||||
import React, { FC, useMemo } from 'react';
|
import React, { FC, useMemo } from 'react';
|
||||||
|
import { getFiltersFromUrlParams } from '../../utils/misc';
|
||||||
import { isAlertingRule } from '../../utils/rules';
|
import { isAlertingRule } from '../../utils/rules';
|
||||||
import { RuleListStateSection } from './RuleListSateSection';
|
import { RuleListStateSection } from './RuleListSateSection';
|
||||||
|
|
||||||
@@ -11,6 +13,8 @@ interface Props {
|
|||||||
type GroupedRules = Record<PromAlertingRuleState, CombinedRule[]>;
|
type GroupedRules = Record<PromAlertingRuleState, CombinedRule[]>;
|
||||||
|
|
||||||
export const RuleListStateView: FC<Props> = ({ namespaces }) => {
|
export const RuleListStateView: FC<Props> = ({ namespaces }) => {
|
||||||
|
const filters = getFiltersFromUrlParams(useQueryParams()[0]);
|
||||||
|
|
||||||
const groupedRules = useMemo(() => {
|
const groupedRules = useMemo(() => {
|
||||||
const result: GroupedRules = {
|
const result: GroupedRules = {
|
||||||
[PromAlertingRuleState.Firing]: [],
|
[PromAlertingRuleState.Firing]: [],
|
||||||
@@ -34,13 +38,22 @@ export const RuleListStateView: FC<Props> = ({ namespaces }) => {
|
|||||||
}, [namespaces]);
|
}, [namespaces]);
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<RuleListStateSection state={PromAlertingRuleState.Firing} rules={groupedRules[PromAlertingRuleState.Firing]} />
|
{(!filters.alertState || filters.alertState === PromAlertingRuleState.Firing) && (
|
||||||
<RuleListStateSection state={PromAlertingRuleState.Pending} rules={groupedRules[PromAlertingRuleState.Pending]} />
|
<RuleListStateSection state={PromAlertingRuleState.Firing} rules={groupedRules[PromAlertingRuleState.Firing]} />
|
||||||
<RuleListStateSection
|
)}
|
||||||
defaultCollapsed={true}
|
{(!filters.alertState || filters.alertState === PromAlertingRuleState.Pending) && (
|
||||||
state={PromAlertingRuleState.Inactive}
|
<RuleListStateSection
|
||||||
rules={groupedRules[PromAlertingRuleState.Inactive]}
|
state={PromAlertingRuleState.Pending}
|
||||||
/>
|
rules={groupedRules[PromAlertingRuleState.Pending]}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
{(!filters.alertState || filters.alertState === PromAlertingRuleState.Inactive) && (
|
||||||
|
<RuleListStateSection
|
||||||
|
defaultCollapsed={filters.alertState !== PromAlertingRuleState.Inactive}
|
||||||
|
state={PromAlertingRuleState.Inactive}
|
||||||
|
rules={groupedRules[PromAlertingRuleState.Inactive]}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import React, { FC, useMemo, useState, Fragment } from 'react';
|
|||||||
import { Icon, Tooltip, useStyles } from '@grafana/ui';
|
import { Icon, Tooltip, useStyles } from '@grafana/ui';
|
||||||
import { GrafanaTheme } from '@grafana/data';
|
import { GrafanaTheme } from '@grafana/data';
|
||||||
import { css } from '@emotion/css';
|
import { css } from '@emotion/css';
|
||||||
import { isAlertingRule } from '../../utils/rules';
|
import { isAlertingRule, isGrafanaRulerRule } from '../../utils/rules';
|
||||||
import { PromAlertingRuleState } from 'app/types/unified-alerting-dto';
|
import { PromAlertingRuleState } from 'app/types/unified-alerting-dto';
|
||||||
import { StateColoredText } from '../StateColoredText';
|
import { StateColoredText } from '../StateColoredText';
|
||||||
import { CollapseToggle } from '../CollapseToggle';
|
import { CollapseToggle } from '../CollapseToggle';
|
||||||
@@ -12,6 +12,9 @@ import { GRAFANA_RULES_SOURCE_NAME, isCloudRulesSource } from '../../utils/datas
|
|||||||
import { ActionIcon } from './ActionIcon';
|
import { ActionIcon } from './ActionIcon';
|
||||||
import pluralize from 'pluralize';
|
import pluralize from 'pluralize';
|
||||||
import { useHasRuler } from '../../hooks/useHasRuler';
|
import { useHasRuler } from '../../hooks/useHasRuler';
|
||||||
|
import kbn from 'app/core/utils/kbn';
|
||||||
|
import { config } from '@grafana/runtime';
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
namespace: CombinedRuleNamespace;
|
namespace: CombinedRuleNamespace;
|
||||||
group: CombinedRuleGroup;
|
group: CombinedRuleGroup;
|
||||||
@@ -60,11 +63,28 @@ export const RulesGroup: FC<Props> = React.memo(({ group, namespace }) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const actionIcons: React.ReactNode[] = [];
|
const actionIcons: React.ReactNode[] = [];
|
||||||
if (hasRuler(rulesSource)) {
|
|
||||||
actionIcons.push(<ActionIcon key="edit" icon="pen" tooltip="edit" />);
|
// for grafana, link to folder views
|
||||||
}
|
|
||||||
if (rulesSource === GRAFANA_RULES_SOURCE_NAME) {
|
if (rulesSource === GRAFANA_RULES_SOURCE_NAME) {
|
||||||
actionIcons.push(<ActionIcon key="manage-perms" icon="lock" tooltip="manage permissions" />);
|
const rulerRule = group.rules[0]?.rulerRule;
|
||||||
|
const folderUID = rulerRule && isGrafanaRulerRule(rulerRule) && rulerRule.grafana_alert.namespace_uid;
|
||||||
|
if (folderUID) {
|
||||||
|
const baseUrl = `${config.appSubUrl ?? ''}/dashboards/f/${folderUID}/${kbn.slugifyForUrl(namespace.name)}`;
|
||||||
|
actionIcons.push(
|
||||||
|
<ActionIcon key="edit" icon="pen" tooltip="edit" href={baseUrl + '/settings'} target="__blank" />
|
||||||
|
);
|
||||||
|
actionIcons.push(
|
||||||
|
<ActionIcon
|
||||||
|
key="manage-perms"
|
||||||
|
icon="lock"
|
||||||
|
tooltip="manage permissions"
|
||||||
|
href={baseUrl + '/permissions'}
|
||||||
|
target="__blank"
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
} else if (hasRuler(rulesSource)) {
|
||||||
|
actionIcons.push(<ActionIcon key="edit" icon="pen" tooltip="edit" />); // @TODO
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const groupName = isCloudRulesSource(rulesSource) ? `${namespace.name} > ${group.name}` : namespace.name;
|
const groupName = isCloudRulesSource(rulesSource) ? `${namespace.name} > ${group.name}` : namespace.name;
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ import { useDispatch } from 'react-redux';
|
|||||||
import { deleteRuleAction } from '../../state/actions';
|
import { deleteRuleAction } from '../../state/actions';
|
||||||
import { useHasRuler } from '../../hooks/useHasRuler';
|
import { useHasRuler } from '../../hooks/useHasRuler';
|
||||||
import { CombinedRule } from 'app/types/unified-alerting';
|
import { CombinedRule } from 'app/types/unified-alerting';
|
||||||
|
import { config } from '@grafana/runtime';
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
rules: CombinedRule[];
|
rules: CombinedRule[];
|
||||||
@@ -145,7 +146,7 @@ export const RulesTable: FC<Props> = ({
|
|||||||
<ActionIcon
|
<ActionIcon
|
||||||
icon="pen"
|
icon="pen"
|
||||||
tooltip="edit rule"
|
tooltip="edit rule"
|
||||||
href={`/alerting/${encodeURIComponent(
|
href={`${config.appSubUrl ?? ''}/alerting/${encodeURIComponent(
|
||||||
stringifyRuleIdentifier(
|
stringifyRuleIdentifier(
|
||||||
getRuleIdentifier(getRulesSourceName(rulesSource), namespace.name, group.name, rulerRule)
|
getRuleIdentifier(getRulesSourceName(rulesSource), namespace.name, group.name, rulerRule)
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -120,6 +120,7 @@ function promRuleToCombinedRule(rule: Rule, namespace: CombinedRuleNamespace, gr
|
|||||||
return {
|
return {
|
||||||
name: rule.name,
|
name: rule.name,
|
||||||
query: rule.query,
|
query: rule.query,
|
||||||
|
queries: rule.query && isGrafanaRulesSource(namespace.rulesSource) ? JSON.parse(rule.query) : undefined,
|
||||||
labels: rule.labels || {},
|
labels: rule.labels || {},
|
||||||
annotations: isAlertingRule(rule) ? rule.annotations || {} : {},
|
annotations: isAlertingRule(rule) ? rule.annotations || {} : {},
|
||||||
promRule: rule,
|
promRule: rule,
|
||||||
@@ -155,9 +156,10 @@ function rulerRuleToCombinedRule(
|
|||||||
}
|
}
|
||||||
: {
|
: {
|
||||||
name: rule.grafana_alert.title,
|
name: rule.grafana_alert.title,
|
||||||
|
queries: rule.grafana_alert.data.map((d) => d.model),
|
||||||
query: '',
|
query: '',
|
||||||
labels: rule.grafana_alert.labels || {},
|
labels: rule.labels || {},
|
||||||
annotations: rule.grafana_alert.annotations || {},
|
annotations: rule.annotations || {},
|
||||||
rulerRule: rule,
|
rulerRule: rule,
|
||||||
namespace,
|
namespace,
|
||||||
group,
|
group,
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { useMemo } from 'react';
|
import { useMemo } from 'react';
|
||||||
|
|
||||||
import { CombinedRuleGroup, CombinedRuleNamespace, RuleFilterState } from 'app/types/unified-alerting';
|
import { CombinedRuleGroup, CombinedRuleNamespace, RuleFilterState } from 'app/types/unified-alerting';
|
||||||
import { isCloudRulesSource } from '../utils/datasource';
|
import { isCloudRulesSource, isGrafanaRulesSource } from '../utils/datasource';
|
||||||
import { isAlertingRule } from '../utils/rules';
|
import { isAlertingRule } from '../utils/rules';
|
||||||
import { getFiltersFromUrlParams } from '../utils/misc';
|
import { getFiltersFromUrlParams } from '../utils/misc';
|
||||||
import { useQueryParams } from 'app/core/hooks/useQueryParams';
|
import { useQueryParams } from 'app/core/hooks/useQueryParams';
|
||||||
@@ -45,7 +45,13 @@ const reduceNamespaces = (filters: RuleFilterState) => {
|
|||||||
const reduceGroups = (filters: RuleFilterState) => {
|
const reduceGroups = (filters: RuleFilterState) => {
|
||||||
return (groupAcc: CombinedRuleGroup[], group: CombinedRuleGroup) => {
|
return (groupAcc: CombinedRuleGroup[], group: CombinedRuleGroup) => {
|
||||||
const rules = group.rules.filter((rule) => {
|
const rules = group.rules.filter((rule) => {
|
||||||
let shouldKeep = true;
|
if (
|
||||||
|
filters.dataSource &&
|
||||||
|
isGrafanaRulesSource(rule.namespace.rulesSource) &&
|
||||||
|
!rule.queries?.find(({ datasource }) => datasource === filters.dataSource)
|
||||||
|
) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
// Query strings can match alert name, label keys, and label values
|
// Query strings can match alert name, label keys, and label values
|
||||||
if (filters.queryString) {
|
if (filters.queryString) {
|
||||||
const normalizedQueryString = filters.queryString.toLocaleLowerCase();
|
const normalizedQueryString = filters.queryString.toLocaleLowerCase();
|
||||||
@@ -56,16 +62,17 @@ const reduceGroups = (filters: RuleFilterState) => {
|
|||||||
key.toLocaleLowerCase().includes(normalizedQueryString) ||
|
key.toLocaleLowerCase().includes(normalizedQueryString) ||
|
||||||
value.toLocaleLowerCase().includes(normalizedQueryString)
|
value.toLocaleLowerCase().includes(normalizedQueryString)
|
||||||
);
|
);
|
||||||
shouldKeep = doesNameContainsQueryString || doLabelsContainQueryString;
|
if (!(doesNameContainsQueryString || doLabelsContainQueryString)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if (filters.alertState) {
|
if (
|
||||||
const matchesAlertState = Boolean(
|
filters.alertState &&
|
||||||
rule.promRule && isAlertingRule(rule.promRule) && rule.promRule.state === filters.alertState
|
!(rule.promRule && isAlertingRule(rule.promRule) && rule.promRule.state === filters.alertState)
|
||||||
);
|
) {
|
||||||
|
return false;
|
||||||
shouldKeep = shouldKeep && matchesAlertState;
|
|
||||||
}
|
}
|
||||||
return shouldKeep;
|
return true;
|
||||||
});
|
});
|
||||||
// Add rules to the group that match the rule list filters
|
// Add rules to the group that match the rule list filters
|
||||||
if (rules.length) {
|
if (rules.length) {
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { AppEvents } from '@grafana/data';
|
import { AppEvents } from '@grafana/data';
|
||||||
import { locationService } from '@grafana/runtime';
|
import { locationService, config } from '@grafana/runtime';
|
||||||
import { createAsyncThunk } from '@reduxjs/toolkit';
|
import { createAsyncThunk } from '@reduxjs/toolkit';
|
||||||
import { appEvents } from 'app/core/core';
|
import { appEvents } from 'app/core/core';
|
||||||
import { AlertManagerCortexConfig, Silence } from 'app/plugins/datasource/alertmanager/types';
|
import { AlertManagerCortexConfig, Silence } from 'app/plugins/datasource/alertmanager/types';
|
||||||
@@ -159,7 +159,8 @@ export function deleteRuleAction(ruleIdentifier: RuleIdentifier): ThunkResult<vo
|
|||||||
}
|
}
|
||||||
await deleteRule(ruleWithLocation);
|
await deleteRule(ruleWithLocation);
|
||||||
// refetch rules for this rules source
|
// refetch rules for this rules source
|
||||||
return dispatch(fetchRulerRulesAction(ruleWithLocation.ruleSourceName));
|
dispatch(fetchRulerRulesAction(ruleWithLocation.ruleSourceName));
|
||||||
|
dispatch(fetchPromRulesAction(ruleWithLocation.ruleSourceName));
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -295,10 +296,12 @@ export const saveRuleFormAction = createAsyncThunk(
|
|||||||
throw new Error('Unexpected rule form type');
|
throw new Error('Unexpected rule form type');
|
||||||
}
|
}
|
||||||
if (exitOnSave) {
|
if (exitOnSave) {
|
||||||
locationService.push('/alerting/list');
|
locationService.push(`${config.appSubUrl ?? ''}/alerting/list`);
|
||||||
} else {
|
} else {
|
||||||
// redirect to edit page
|
// redirect to edit page
|
||||||
const newLocation = `/alerting/${encodeURIComponent(stringifyRuleIdentifier(identifier))}/edit`;
|
const newLocation = `${config.appSubUrl ?? ''}/alerting/${encodeURIComponent(
|
||||||
|
stringifyRuleIdentifier(identifier)
|
||||||
|
)}/edit`;
|
||||||
if (locationService.getLocation().pathname !== newLocation) {
|
if (locationService.getLocation().pathname !== newLocation) {
|
||||||
locationService.replace(newLocation);
|
locationService.replace(newLocation);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
import { describeInterval, secondsToHms } from '@grafana/data/src/datetime/rangeutil';
|
|
||||||
import { RuleWithLocation } from 'app/types/unified-alerting';
|
import { RuleWithLocation } from 'app/types/unified-alerting';
|
||||||
import {
|
import {
|
||||||
Annotations,
|
Annotations,
|
||||||
@@ -55,11 +54,6 @@ function parseInterval(value: string): [number, string] {
|
|||||||
throw new Error(`Invalid interval description: ${value}`);
|
throw new Error(`Invalid interval description: ${value}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
function intervalToSeconds(interval: string): number {
|
|
||||||
const { sec, count } = describeInterval(interval);
|
|
||||||
return sec * count;
|
|
||||||
}
|
|
||||||
|
|
||||||
function listifyLabelsOrAnnotations(item: Labels | Annotations | undefined): Array<{ key: string; value: string }> {
|
function listifyLabelsOrAnnotations(item: Labels | Annotations | undefined): Array<{ key: string; value: string }> {
|
||||||
return [...recordToArray(item || {}), { key: '', value: '' }];
|
return [...recordToArray(item || {}), { key: '', value: '' }];
|
||||||
}
|
}
|
||||||
@@ -71,13 +65,13 @@ export function formValuesToRulerGrafanaRuleDTO(values: RuleFormValues): Postabl
|
|||||||
grafana_alert: {
|
grafana_alert: {
|
||||||
title: name,
|
title: name,
|
||||||
condition,
|
condition,
|
||||||
for: intervalToSeconds(evaluateFor), // @TODO provide raw string once backend supports it
|
|
||||||
no_data_state: noDataState,
|
no_data_state: noDataState,
|
||||||
exec_err_state: execErrState,
|
exec_err_state: execErrState,
|
||||||
data: queries,
|
data: queries,
|
||||||
annotations: arrayToRecord(values.annotations || []),
|
|
||||||
labels: arrayToRecord(values.labels || []),
|
|
||||||
},
|
},
|
||||||
|
for: evaluateFor,
|
||||||
|
annotations: arrayToRecord(values.annotations || []),
|
||||||
|
labels: arrayToRecord(values.labels || []),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
throw new Error('Cannot create rule without specifying alert condition');
|
throw new Error('Cannot create rule without specifying alert condition');
|
||||||
@@ -93,14 +87,14 @@ export function rulerRuleToFormValues(ruleWithLocation: RuleWithLocation): RuleF
|
|||||||
name: ga.title,
|
name: ga.title,
|
||||||
type: RuleFormType.threshold,
|
type: RuleFormType.threshold,
|
||||||
dataSourceName: ga.data[0]?.model.datasource,
|
dataSourceName: ga.data[0]?.model.datasource,
|
||||||
evaluateFor: secondsToHms(ga.for),
|
evaluateFor: rule.for,
|
||||||
evaluateEvery: group.interval || defaultFormValues.evaluateEvery,
|
evaluateEvery: group.interval || defaultFormValues.evaluateEvery,
|
||||||
noDataState: ga.no_data_state,
|
noDataState: ga.no_data_state,
|
||||||
execErrState: ga.exec_err_state,
|
execErrState: ga.exec_err_state,
|
||||||
queries: ga.data,
|
queries: ga.data,
|
||||||
condition: ga.condition,
|
condition: ga.condition,
|
||||||
annotations: listifyLabelsOrAnnotations(ga.annotations),
|
annotations: listifyLabelsOrAnnotations(rule.annotations),
|
||||||
labels: listifyLabelsOrAnnotations(ga.labels),
|
labels: listifyLabelsOrAnnotations(rule.labels),
|
||||||
folder: { title: namespace, id: -1 },
|
folder: { title: namespace, id: -1 },
|
||||||
};
|
};
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -113,12 +113,9 @@ export interface PostableGrafanaRuleDefinition {
|
|||||||
uid?: string;
|
uid?: string;
|
||||||
title: string;
|
title: string;
|
||||||
condition: string;
|
condition: string;
|
||||||
for: number; //@TODO Sofia will update to accept string
|
|
||||||
no_data_state: GrafanaAlertState;
|
no_data_state: GrafanaAlertState;
|
||||||
exec_err_state: GrafanaAlertState;
|
exec_err_state: GrafanaAlertState;
|
||||||
data: GrafanaQuery[];
|
data: GrafanaQuery[];
|
||||||
annotations: Annotations;
|
|
||||||
labels: Labels;
|
|
||||||
}
|
}
|
||||||
export interface GrafanaRuleDefinition extends PostableGrafanaRuleDefinition {
|
export interface GrafanaRuleDefinition extends PostableGrafanaRuleDefinition {
|
||||||
uid: string;
|
uid: string;
|
||||||
@@ -127,12 +124,16 @@ export interface GrafanaRuleDefinition extends PostableGrafanaRuleDefinition {
|
|||||||
|
|
||||||
export interface RulerGrafanaRuleDTO {
|
export interface RulerGrafanaRuleDTO {
|
||||||
grafana_alert: GrafanaRuleDefinition;
|
grafana_alert: GrafanaRuleDefinition;
|
||||||
// labels?: Labels; @TODO to be discussed
|
for: string;
|
||||||
// annotations?: Annotations;
|
annotations: Annotations;
|
||||||
|
labels: Labels;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface PostableRuleGrafanaRuleDTO {
|
export interface PostableRuleGrafanaRuleDTO {
|
||||||
grafana_alert: PostableGrafanaRuleDefinition;
|
grafana_alert: PostableGrafanaRuleDefinition;
|
||||||
|
for: string;
|
||||||
|
annotations: Annotations;
|
||||||
|
labels: Labels;
|
||||||
}
|
}
|
||||||
|
|
||||||
export type RulerRuleDTO = RulerAlertingRuleDTO | RulerRecordingRuleDTO | RulerGrafanaRuleDTO;
|
export type RulerRuleDTO = RulerAlertingRuleDTO | RulerRecordingRuleDTO | RulerGrafanaRuleDTO;
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import {
|
|||||||
Labels,
|
Labels,
|
||||||
Annotations,
|
Annotations,
|
||||||
RulerRuleGroupDTO,
|
RulerRuleGroupDTO,
|
||||||
|
GrafanaQueryModel,
|
||||||
} from './unified-alerting-dto';
|
} from './unified-alerting-dto';
|
||||||
|
|
||||||
export type Alert = {
|
export type Alert = {
|
||||||
@@ -81,6 +82,7 @@ export interface CombinedRule {
|
|||||||
rulerRule?: RulerRuleDTO;
|
rulerRule?: RulerRuleDTO;
|
||||||
group: CombinedRuleGroup;
|
group: CombinedRuleGroup;
|
||||||
namespace: CombinedRuleNamespace;
|
namespace: CombinedRuleNamespace;
|
||||||
|
queries?: GrafanaQueryModel[];
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface CombinedRuleGroup {
|
export interface CombinedRuleGroup {
|
||||||
|
|||||||
Reference in New Issue
Block a user