From 382cab6406636cccc29e7c9665291004624eecda Mon Sep 17 00:00:00 2001 From: Domas Date: Mon, 19 Apr 2021 12:53:02 +0300 Subject: [PATCH] Alerting: misc fixes (#33070) --- .../alerting/unified/RuleList.test.tsx | 5 +++ .../features/alerting/unified/RuleList.tsx | 13 +++++--- .../components/rule-editor/AlertRuleForm.tsx | 4 ++- .../unified/components/rules/NoRulesCTA.tsx | 3 +- .../unified/components/rules/RuleDetails.tsx | 33 +++++++++++++++++-- .../components/rules/RuleListStateView.tsx | 27 +++++++++++---- .../unified/components/rules/RulesGroup.tsx | 30 ++++++++++++++--- .../unified/components/rules/RulesTable.tsx | 3 +- .../hooks/useCombinedRuleNamespaces.ts | 6 ++-- .../unified/hooks/useFilteredRules.ts | 27 +++++++++------ .../alerting/unified/state/actions.ts | 11 ++++--- .../alerting/unified/utils/rule-form.ts | 18 ++++------ public/app/types/unified-alerting-dto.ts | 11 ++++--- public/app/types/unified-alerting.ts | 2 ++ 14 files changed, 137 insertions(+), 56 deletions(-) diff --git a/public/app/features/alerting/unified/RuleList.test.tsx b/public/app/features/alerting/unified/RuleList.test.tsx index 654e9bd3c43..e9cca96f929 100644 --- a/public/app/features/alerting/unified/RuleList.test.tsx +++ b/public/app/features/alerting/unified/RuleList.test.tsx @@ -125,6 +125,11 @@ describe('RuleList', () => { groups: [ mockPromRuleGroup({ name: 'grafana-group', + rules: [ + mockPromAlertingRule({ + query: '[]', + }), + ], }), ], }), diff --git a/public/app/features/alerting/unified/RuleList.tsx b/public/app/features/alerting/unified/RuleList.tsx index a94681f3a15..ef3fd1dbe65 100644 --- a/public/app/features/alerting/unified/RuleList.tsx +++ b/public/app/features/alerting/unified/RuleList.tsx @@ -17,6 +17,7 @@ import RulesFilter from './components/rules/RulesFilter'; import { RuleListGroupView } from './components/rules/RuleListGroupView'; import { RuleListStateView } from './components/rules/RuleListStateView'; import { useQueryParams } from 'app/core/hooks/useQueryParams'; +import { config } from '@grafana/runtime'; const VIEWS = { groups: RuleListGroupView, @@ -105,13 +106,15 @@ export const RuleList: FC = () => { )} {promReqeustErrors.map(({ dataSource, error }) => (
- Failed to load rules state from {dataSource.name}:{' '} + Failed to load rules state from{' '} + {dataSource.name}:{' '} {error.message || 'Unknown error.'}
))} {rulerRequestErrors.map(({ dataSource, error }) => (
- Failed to load rules config from {dataSource.name}:{' '} + Failed to load rules config from{' '} + {dataSource.name}:{' '} {error.message || 'Unknown error.'}
))} @@ -123,19 +126,19 @@ export const RuleList: FC = () => {
- + Groups - + State
- +
diff --git a/public/app/features/alerting/unified/components/rule-editor/AlertRuleForm.tsx b/public/app/features/alerting/unified/components/rule-editor/AlertRuleForm.tsx index 2e44a5d1f95..b45ed2b8841 100644 --- a/public/app/features/alerting/unified/components/rule-editor/AlertRuleForm.tsx +++ b/public/app/features/alerting/unified/components/rule-editor/AlertRuleForm.tsx @@ -18,6 +18,7 @@ import { useDispatch } from 'react-redux'; import { useCleanup } from 'app/core/hooks/useCleanup'; import { rulerRuleToFormValues, defaultFormValues } from '../../utils/rule-form'; import { Link } from 'react-router-dom'; +import { config } from '@grafana/runtime'; type Props = { existing?: RuleWithLocation; @@ -56,6 +57,7 @@ export const AlertRuleForm: FC = ({ existing }) => { dispatch( saveRuleFormAction({ values: { + ...defaultValues, ...values, annotations: values.annotations?.filter(({ key }) => !!key) ?? [], labels: values.labels?.filter(({ key }) => !!key) ?? [], @@ -70,7 +72,7 @@ export const AlertRuleForm: FC = ({ existing }) => {
submit(values, false))} className={styles.form}> - + Cancel diff --git a/public/app/features/alerting/unified/components/rules/NoRulesCTA.tsx b/public/app/features/alerting/unified/components/rules/NoRulesCTA.tsx index 52476ce7663..9e8476bfa33 100644 --- a/public/app/features/alerting/unified/components/rules/NoRulesCTA.tsx +++ b/public/app/features/alerting/unified/components/rules/NoRulesCTA.tsx @@ -1,11 +1,12 @@ import EmptyListCTA from 'app/core/components/EmptyListCTA/EmptyListCTA'; import React, { FC } from 'react'; +import { config } from '@grafana/runtime'; export const NoRulesSplash: FC = () => ( = ({ rule, rulesSource }) => { 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 (
@@ -42,9 +60,18 @@ export const RuleDetails: FC = ({ rule, rulesSource }) => { ))}
- {isCloudRulesSource(rulesSource) && ( + {!!dataSources.length && ( - {rulesSource.name} + {dataSources.map(({ name, icon }) => ( +
+ {icon && ( + <> + {' '} + + )} + {name} +
+ ))}
)}
diff --git a/public/app/features/alerting/unified/components/rules/RuleListStateView.tsx b/public/app/features/alerting/unified/components/rules/RuleListStateView.tsx index 597c86cf377..9a682aae4ad 100644 --- a/public/app/features/alerting/unified/components/rules/RuleListStateView.tsx +++ b/public/app/features/alerting/unified/components/rules/RuleListStateView.tsx @@ -1,6 +1,8 @@ +import { useQueryParams } from 'app/core/hooks/useQueryParams'; import { CombinedRule, CombinedRuleNamespace } from 'app/types/unified-alerting'; import { PromAlertingRuleState } from 'app/types/unified-alerting-dto'; import React, { FC, useMemo } from 'react'; +import { getFiltersFromUrlParams } from '../../utils/misc'; import { isAlertingRule } from '../../utils/rules'; import { RuleListStateSection } from './RuleListSateSection'; @@ -11,6 +13,8 @@ interface Props { type GroupedRules = Record; export const RuleListStateView: FC = ({ namespaces }) => { + const filters = getFiltersFromUrlParams(useQueryParams()[0]); + const groupedRules = useMemo(() => { const result: GroupedRules = { [PromAlertingRuleState.Firing]: [], @@ -34,13 +38,22 @@ export const RuleListStateView: FC = ({ namespaces }) => { }, [namespaces]); return ( <> - - - + {(!filters.alertState || filters.alertState === PromAlertingRuleState.Firing) && ( + + )} + {(!filters.alertState || filters.alertState === PromAlertingRuleState.Pending) && ( + + )} + {(!filters.alertState || filters.alertState === PromAlertingRuleState.Inactive) && ( + + )} ); }; diff --git a/public/app/features/alerting/unified/components/rules/RulesGroup.tsx b/public/app/features/alerting/unified/components/rules/RulesGroup.tsx index 72242546214..ac521aca3fd 100644 --- a/public/app/features/alerting/unified/components/rules/RulesGroup.tsx +++ b/public/app/features/alerting/unified/components/rules/RulesGroup.tsx @@ -3,7 +3,7 @@ import React, { FC, useMemo, useState, Fragment } from 'react'; import { Icon, Tooltip, useStyles } from '@grafana/ui'; import { GrafanaTheme } from '@grafana/data'; 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 { StateColoredText } from '../StateColoredText'; import { CollapseToggle } from '../CollapseToggle'; @@ -12,6 +12,9 @@ import { GRAFANA_RULES_SOURCE_NAME, isCloudRulesSource } from '../../utils/datas import { ActionIcon } from './ActionIcon'; import pluralize from 'pluralize'; import { useHasRuler } from '../../hooks/useHasRuler'; +import kbn from 'app/core/utils/kbn'; +import { config } from '@grafana/runtime'; + interface Props { namespace: CombinedRuleNamespace; group: CombinedRuleGroup; @@ -60,11 +63,28 @@ export const RulesGroup: FC = React.memo(({ group, namespace }) => { } const actionIcons: React.ReactNode[] = []; - if (hasRuler(rulesSource)) { - actionIcons.push(); - } + + // for grafana, link to folder views if (rulesSource === GRAFANA_RULES_SOURCE_NAME) { - actionIcons.push(); + 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( + + ); + actionIcons.push( + + ); + } else if (hasRuler(rulesSource)) { + actionIcons.push(); // @TODO + } } const groupName = isCloudRulesSource(rulesSource) ? `${namespace.name} > ${group.name}` : namespace.name; diff --git a/public/app/features/alerting/unified/components/rules/RulesTable.tsx b/public/app/features/alerting/unified/components/rules/RulesTable.tsx index 925390ebfc0..4380558d907 100644 --- a/public/app/features/alerting/unified/components/rules/RulesTable.tsx +++ b/public/app/features/alerting/unified/components/rules/RulesTable.tsx @@ -14,6 +14,7 @@ import { useDispatch } from 'react-redux'; import { deleteRuleAction } from '../../state/actions'; import { useHasRuler } from '../../hooks/useHasRuler'; import { CombinedRule } from 'app/types/unified-alerting'; +import { config } from '@grafana/runtime'; interface Props { rules: CombinedRule[]; @@ -145,7 +146,7 @@ export const RulesTable: FC = ({ d.model), query: '', - labels: rule.grafana_alert.labels || {}, - annotations: rule.grafana_alert.annotations || {}, + labels: rule.labels || {}, + annotations: rule.annotations || {}, rulerRule: rule, namespace, group, diff --git a/public/app/features/alerting/unified/hooks/useFilteredRules.ts b/public/app/features/alerting/unified/hooks/useFilteredRules.ts index d9538c2a15c..1a9e8c3ffd5 100644 --- a/public/app/features/alerting/unified/hooks/useFilteredRules.ts +++ b/public/app/features/alerting/unified/hooks/useFilteredRules.ts @@ -1,7 +1,7 @@ import { useMemo } from 'react'; 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 { getFiltersFromUrlParams } from '../utils/misc'; import { useQueryParams } from 'app/core/hooks/useQueryParams'; @@ -45,7 +45,13 @@ const reduceNamespaces = (filters: RuleFilterState) => { const reduceGroups = (filters: RuleFilterState) => { return (groupAcc: CombinedRuleGroup[], group: CombinedRuleGroup) => { 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 if (filters.queryString) { const normalizedQueryString = filters.queryString.toLocaleLowerCase(); @@ -56,16 +62,17 @@ const reduceGroups = (filters: RuleFilterState) => { key.toLocaleLowerCase().includes(normalizedQueryString) || value.toLocaleLowerCase().includes(normalizedQueryString) ); - shouldKeep = doesNameContainsQueryString || doLabelsContainQueryString; + if (!(doesNameContainsQueryString || doLabelsContainQueryString)) { + return false; + } } - if (filters.alertState) { - const matchesAlertState = Boolean( - rule.promRule && isAlertingRule(rule.promRule) && rule.promRule.state === filters.alertState - ); - - shouldKeep = shouldKeep && matchesAlertState; + if ( + filters.alertState && + !(rule.promRule && isAlertingRule(rule.promRule) && rule.promRule.state === filters.alertState) + ) { + return false; } - return shouldKeep; + return true; }); // Add rules to the group that match the rule list filters if (rules.length) { diff --git a/public/app/features/alerting/unified/state/actions.ts b/public/app/features/alerting/unified/state/actions.ts index 7bc471b5fa8..6efeb3824c6 100644 --- a/public/app/features/alerting/unified/state/actions.ts +++ b/public/app/features/alerting/unified/state/actions.ts @@ -1,5 +1,5 @@ import { AppEvents } from '@grafana/data'; -import { locationService } from '@grafana/runtime'; +import { locationService, config } from '@grafana/runtime'; import { createAsyncThunk } from '@reduxjs/toolkit'; import { appEvents } from 'app/core/core'; import { AlertManagerCortexConfig, Silence } from 'app/plugins/datasource/alertmanager/types'; @@ -159,7 +159,8 @@ export function deleteRuleAction(ruleIdentifier: RuleIdentifier): ThunkResult { return [...recordToArray(item || {}), { key: '', value: '' }]; } @@ -71,13 +65,13 @@ export function formValuesToRulerGrafanaRuleDTO(values: RuleFormValues): Postabl grafana_alert: { title: name, condition, - for: intervalToSeconds(evaluateFor), // @TODO provide raw string once backend supports it no_data_state: noDataState, exec_err_state: execErrState, 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'); @@ -93,14 +87,14 @@ export function rulerRuleToFormValues(ruleWithLocation: RuleWithLocation): RuleF name: ga.title, type: RuleFormType.threshold, dataSourceName: ga.data[0]?.model.datasource, - evaluateFor: secondsToHms(ga.for), + evaluateFor: rule.for, evaluateEvery: group.interval || defaultFormValues.evaluateEvery, noDataState: ga.no_data_state, execErrState: ga.exec_err_state, queries: ga.data, condition: ga.condition, - annotations: listifyLabelsOrAnnotations(ga.annotations), - labels: listifyLabelsOrAnnotations(ga.labels), + annotations: listifyLabelsOrAnnotations(rule.annotations), + labels: listifyLabelsOrAnnotations(rule.labels), folder: { title: namespace, id: -1 }, }; } else { diff --git a/public/app/types/unified-alerting-dto.ts b/public/app/types/unified-alerting-dto.ts index 8abfda7da51..9a8e14abd89 100644 --- a/public/app/types/unified-alerting-dto.ts +++ b/public/app/types/unified-alerting-dto.ts @@ -113,12 +113,9 @@ export interface PostableGrafanaRuleDefinition { uid?: string; title: string; condition: string; - for: number; //@TODO Sofia will update to accept string no_data_state: GrafanaAlertState; exec_err_state: GrafanaAlertState; data: GrafanaQuery[]; - annotations: Annotations; - labels: Labels; } export interface GrafanaRuleDefinition extends PostableGrafanaRuleDefinition { uid: string; @@ -127,12 +124,16 @@ export interface GrafanaRuleDefinition extends PostableGrafanaRuleDefinition { export interface RulerGrafanaRuleDTO { grafana_alert: GrafanaRuleDefinition; - // labels?: Labels; @TODO to be discussed - // annotations?: Annotations; + for: string; + annotations: Annotations; + labels: Labels; } export interface PostableRuleGrafanaRuleDTO { grafana_alert: PostableGrafanaRuleDefinition; + for: string; + annotations: Annotations; + labels: Labels; } export type RulerRuleDTO = RulerAlertingRuleDTO | RulerRecordingRuleDTO | RulerGrafanaRuleDTO; diff --git a/public/app/types/unified-alerting.ts b/public/app/types/unified-alerting.ts index 0033bcad14e..5b13e136948 100644 --- a/public/app/types/unified-alerting.ts +++ b/public/app/types/unified-alerting.ts @@ -8,6 +8,7 @@ import { Labels, Annotations, RulerRuleGroupDTO, + GrafanaQueryModel, } from './unified-alerting-dto'; export type Alert = { @@ -81,6 +82,7 @@ export interface CombinedRule { rulerRule?: RulerRuleDTO; group: CombinedRuleGroup; namespace: CombinedRuleNamespace; + queries?: GrafanaQueryModel[]; } export interface CombinedRuleGroup {