diff --git a/.betterer.results b/.betterer.results index ee5b0e9e79d..e60a71edb7b 100644 --- a/.betterer.results +++ b/.betterer.results @@ -2102,13 +2102,6 @@ exports[`better eslint`] = { "public/app/features/alerting/unified/components/rules/RuleState.tsx:5381": [ [0, 0, 0, "Styles should be written using objects.", "0"] ], - "public/app/features/alerting/unified/components/rules/RulesFilter.tsx:5381": [ - [0, 0, 0, "Styles should be written using objects.", "0"], - [0, 0, 0, "Styles should be written using objects.", "1"], - [0, 0, 0, "Styles should be written using objects.", "2"], - [0, 0, 0, "Styles should be written using objects.", "3"], - [0, 0, 0, "Styles should be written using objects.", "4"] - ], "public/app/features/alerting/unified/components/rules/RulesGroup.tsx:5381": [ [0, 0, 0, "Styles should be written using objects.", "0"], [0, 0, 0, "Styles should be written using objects.", "1"], diff --git a/public/app/app.ts b/public/app/app.ts index 5d91e64dca8..7d3bde2ea47 100644 --- a/public/app/app.ts +++ b/public/app/app.ts @@ -71,6 +71,7 @@ import { GrafanaJavascriptAgentBackend } from './core/services/echo/backends/gra import { KeybindingSrv } from './core/services/keybindingSrv'; import { startMeasure, stopMeasure } from './core/utils/metrics'; import { initDevFeatures } from './dev'; +import { initAlerting } from './features/alerting/unified/initAlerting'; import { initAuthConfig } from './features/auth-config'; import { getTimeSrv } from './features/dashboard/services/TimeSrv'; import { EmbeddedDashboardLazy } from './features/dashboard-scene/embedding/EmbeddedDashboardLazy'; @@ -153,6 +154,8 @@ export class GrafanaApp { configureStore(); initExtensions(); + initAlerting(); + standardEditorsRegistry.setInit(getAllOptionEditors); standardFieldConfigEditorRegistry.setInit(getAllStandardFieldConfigs); standardTransformersRegistry.setInit(getStandardTransformers); diff --git a/public/app/features/alerting/unified/api/alertRuleApi.ts b/public/app/features/alerting/unified/api/alertRuleApi.ts index 7dac69bbd11..178833f6745 100644 --- a/public/app/features/alerting/unified/api/alertRuleApi.ts +++ b/public/app/features/alerting/unified/api/alertRuleApi.ts @@ -154,15 +154,15 @@ export const alertRuleApi = alertingApi.injectEndpoints({ prometheusRuleNamespaces: build.query< RuleNamespace[], - { ruleSourceName: string; namespace?: string; groupName?: string; ruleName?: string } + { ruleSourceName: string; namespace?: string; groupName?: string; ruleName?: string; dashboardUid?: string } >({ - query: ({ ruleSourceName, namespace, groupName, ruleName }) => { - const queryParams: Record = {}; - // if (isPrometheusRuleIdentifier(ruleIdentifier) || isCloudRuleIdentifier(ruleIdentifier)) { - queryParams['file'] = namespace; - queryParams['rule_group'] = groupName; - queryParams['rule_name'] = ruleName; - // } + query: ({ ruleSourceName, namespace, groupName, ruleName, dashboardUid }) => { + const queryParams: Record = { + file: namespace, + rule_group: groupName, + rule_name: ruleName, + dashboard_uid: dashboardUid, // Supported only by Grafana managed rules + }; return { url: `api/prometheus/${getDatasourceAPIUid(ruleSourceName)}/api/v1/rules`, diff --git a/public/app/features/alerting/unified/components/rules/RulesFilter.tsx b/public/app/features/alerting/unified/components/rules/RulesFilter.tsx index d84b3c1804e..d462cb653f7 100644 --- a/public/app/features/alerting/unified/components/rules/RulesFilter.tsx +++ b/public/app/features/alerting/unified/components/rules/RulesFilter.tsx @@ -3,7 +3,8 @@ import React, { useEffect, useRef, useState } from 'react'; import { useForm } from 'react-hook-form'; import { DataSourceInstanceSettings, GrafanaTheme2, SelectableValue } from '@grafana/data'; -import { Button, Field, Icon, Input, Label, RadioButtonGroup, Tooltip, useStyles2, Stack } from '@grafana/ui'; +import { Button, Field, Icon, Input, Label, RadioButtonGroup, Stack, Tooltip, useStyles2 } from '@grafana/ui'; +import { DashboardPicker } from 'app/core/components/Select/DashboardPicker'; import { useQueryParams } from 'app/core/hooks/useQueryParams'; import { PromAlertingRuleState, PromRuleType } from 'app/types/unified-alerting-dto'; @@ -91,6 +92,10 @@ const RulesFilter = ({ onFilterCleared = () => undefined }: RulesFilerProps) => setFilterKey((key) => key + 1); }; + const handleDashboardChange = (dashboardUid: string | undefined) => { + updateFilters({ ...filterState, dashboardUid }); + }; + const clearDataSource = () => { updateFilters({ ...filterState, dataSourceNames: [] }); setFilterKey((key) => key + 1); @@ -99,7 +104,6 @@ const RulesFilter = ({ onFilterCleared = () => undefined }: RulesFilerProps) => const handleAlertStateChange = (value: PromAlertingRuleState) => { logInfo(LogMessages.clickingAlertStateFilters); updateFilters({ ...filterState, ruleState: value }); - setFilterKey((key) => key + 1); }; const handleViewChange = (view: string) => { @@ -108,12 +112,10 @@ const RulesFilter = ({ onFilterCleared = () => undefined }: RulesFilerProps) => const handleRuleTypeChange = (ruleType: PromRuleType) => { updateFilters({ ...filterState, ruleType }); - setFilterKey((key) => key + 1); }; const handleRuleHealthChange = (ruleHealth: RuleHealth) => { updateFilters({ ...filterState, ruleHealth }); - setFilterKey((key) => key + 1); }; const handleClearFiltersClick = () => { @@ -127,7 +129,7 @@ const RulesFilter = ({ onFilterCleared = () => undefined }: RulesFilerProps) => return (
- + undefined }: RulesFilerProps) =>
} > - + @@ -165,6 +167,22 @@ const RulesFilter = ({ onFilterCleared = () => undefined }: RulesFilerProps) => /> + Dashboard} + > + {/* The key prop is to clear the picker value */} + {/* DashboardPicker doesn't do that itself when value is undefined */} + handleDashboardChange(value?.uid)} + isClearable + cacheOptions + /> + +
undefined }: RulesFilerProps) => const getStyles = (theme: GrafanaTheme2) => { return { - container: css` - margin-bottom: ${theme.spacing(1)}; - `, - dsPickerContainer: css` - width: 550px; - flex-grow: 0; - margin: 0; - `, - searchInput: css` - flex: 1; - margin: 0; - `, + container: css({ + marginBottom: theme.spacing(1), + }), + dsPickerContainer: css({ + width: theme.spacing(60), + flexGrow: 0, + margin: 0, + }), + dashboardPickerContainer: css({ + minWidth: theme.spacing(50), + }), + searchInput: css({ + flex: 1, + margin: 0, + }), }; }; @@ -279,6 +300,7 @@ function SearchQueryHelp() { +
); @@ -296,16 +318,16 @@ function HelpRow({ title, expr }: { title: string; expr: string }) { } const helpStyles = (theme: GrafanaTheme2) => ({ - grid: css` - display: grid; - grid-template-columns: max-content auto; - gap: ${theme.spacing(1)}; - align-items: center; - `, - code: css` - display: block; - text-align: center; - `, + grid: css({ + display: 'grid', + gridTemplateColumns: 'max-content auto', + gap: theme.spacing(1), + alignItems: 'center', + }), + code: css({ + display: 'block', + textAlign: 'center', + }), }); export default RulesFilter; diff --git a/public/app/features/alerting/unified/hooks/useFilteredRules.test.ts b/public/app/features/alerting/unified/hooks/useFilteredRules.test.ts index 5b2c151e34d..b102a5b20c6 100644 --- a/public/app/features/alerting/unified/hooks/useFilteredRules.test.ts +++ b/public/app/features/alerting/unified/hooks/useFilteredRules.test.ts @@ -15,6 +15,7 @@ import { mockRulerGrafanaRule, } from '../mocks'; import { RuleHealth } from '../search/rulesSearchParser'; +import { Annotation } from '../utils/constants'; import { getFilter } from '../utils/search'; import { filterRules } from './useFilteredRules'; @@ -230,4 +231,29 @@ describe('filterRules', function () { expect(filtered[0].groups[0].rules).toHaveLength(1); expect(filtered[0].groups[0].rules[0].name).toBe('Memory too low'); }); + + it('should filter out rules by dashboard UID', () => { + const rules = [ + mockCombinedRule({ + name: 'Memory too low', + annotations: { [Annotation.dashboardUID]: 'dashboard-memory' }, + }), + mockCombinedRule({ + name: 'CPU too high', + annotations: { [Annotation.dashboardUID]: 'dashboard-cpu' }, + }), + mockCombinedRule({ + name: 'Disk is dead', + }), + ]; + + const ns = mockCombinedRuleNamespace({ + groups: [mockCombinedRuleGroup('Resources usage group', rules)], + }); + + const filtered = filterRules([ns], getFilter({ dashboardUid: 'dashboard-cpu' })); + + expect(filtered[0]?.groups[0]?.rules).toHaveLength(1); + expect(filtered[0]?.groups[0]?.rules[0]?.name).toBe('CPU too high'); + }); }); diff --git a/public/app/features/alerting/unified/hooks/useFilteredRules.ts b/public/app/features/alerting/unified/hooks/useFilteredRules.ts index e8d7b222b76..68ca7c4cc1d 100644 --- a/public/app/features/alerting/unified/hooks/useFilteredRules.ts +++ b/public/app/features/alerting/unified/hooks/useFilteredRules.ts @@ -10,6 +10,7 @@ import { isPromAlertingRuleState, PromRuleType, RulerGrafanaRuleDTO } from 'app/ import { applySearchFilterToQuery, getSearchFilterFromQuery, RulesFilter } from '../search/rulesSearchParser'; import { labelsMatchMatchers, matcherToMatcherField, parseMatchers } from '../utils/alertmanager'; +import { Annotation } from '../utils/constants'; import { isCloudRulesSource } from '../utils/datasource'; import { parseMatcher } from '../utils/matchers'; import { getRuleHealth, isAlertingRule, isGrafanaRulerRule, isPromRuleType } from '../utils/rules'; @@ -194,7 +195,7 @@ const reduceGroups = (filterState: RulesFilter) => { const matchesFilterFor = chain(filterState) // ⚠️ keep this list of properties we filter for here up-to-date ⚠️ // We are ignoring predicates we've matched before we get here (like "freeFormWords") - .pick(['ruleType', 'dataSourceNames', 'ruleHealth', 'labels', 'ruleState']) + .pick(['ruleType', 'dataSourceNames', 'ruleHealth', 'labels', 'ruleState', 'dashboardUid']) .omitBy(isEmpty) .mapValues(() => false) .value(); @@ -253,6 +254,13 @@ const reduceGroups = (filterState: RulesFilter) => { } } + if ( + 'dashboardUid' in matchesFilterFor && + rule.annotations[Annotation.dashboardUID] === filterState.dashboardUid + ) { + matchesFilterFor.dashboardUid = true; + } + return Object.values(matchesFilterFor).every((match) => match === true); }); diff --git a/public/app/features/alerting/unified/initAlerting.tsx b/public/app/features/alerting/unified/initAlerting.tsx new file mode 100644 index 00000000000..3a55139ced8 --- /dev/null +++ b/public/app/features/alerting/unified/initAlerting.tsx @@ -0,0 +1,21 @@ +import React from 'react'; + +import { config } from '@grafana/runtime'; + +import { addCustomRightAction } from '../../dashboard/components/DashNav/DashNav'; + +const AlertRulesToolbarButton = React.lazy( + () => import(/* webpackChunkName: "alert-rules-toolbar-button" */ './integration/AlertRulesToolbarButton') +); + +export function initAlerting() { + addCustomRightAction({ + show: () => config.unifiedAlertingEnabled, + component: ({ dashboard }) => ( + + {dashboard && } + + ), + index: -2, + }); +} diff --git a/public/app/features/alerting/unified/integration/AlertRulesDrawer.tsx b/public/app/features/alerting/unified/integration/AlertRulesDrawer.tsx new file mode 100644 index 00000000000..8c9bfaff1f6 --- /dev/null +++ b/public/app/features/alerting/unified/integration/AlertRulesDrawer.tsx @@ -0,0 +1,38 @@ +import React from 'react'; + +import { Drawer, LoadingPlaceholder, Stack, TextLink } from '@grafana/ui'; + +import { t } from '../../../../core/internationalization'; +import { createUrl } from '../utils/url'; + +const AlertRulesDrawerContent = React.lazy( + () => import(/* webpackChunkName: "alert-rules-drawer-content" */ './AlertRulesDrawerContent') +); + +interface Props { + dashboardUid: string; + onClose: () => void; +} + +export function AlertRulesDrawer({ dashboardUid, onClose }: Props) { + return ( + } onClose={onClose} size="lg"> + }> + + + + ); +} + +function DrawerSubtitle({ dashboardUid }: { dashboardUid: string }) { + const searchParams = new URLSearchParams({ search: `dashboard:${dashboardUid}` }); + + return ( + +
{t('dashboard.toolbar.alert-rules.subtitle', 'Alert rules related to this dashboard')}
+ + {t('dashboard.toolbar.alert-rules.redirect-link', 'List in Grafana Alerting')} + +
+ ); +} diff --git a/public/app/features/alerting/unified/integration/AlertRulesDrawerContent.tsx b/public/app/features/alerting/unified/integration/AlertRulesDrawerContent.tsx new file mode 100644 index 00000000000..d3b0ea88796 --- /dev/null +++ b/public/app/features/alerting/unified/integration/AlertRulesDrawerContent.tsx @@ -0,0 +1,39 @@ +import React from 'react'; +import { useAsync } from 'react-use'; + +import { LoadingPlaceholder } from '@grafana/ui'; +import { useDispatch } from 'app/types'; + +import { RulesTable } from '../components/rules/RulesTable'; +import { useCombinedRuleNamespaces } from '../hooks/useCombinedRuleNamespaces'; +import { fetchPromAndRulerRulesAction } from '../state/actions'; +import { Annotation } from '../utils/constants'; +import { GRAFANA_RULES_SOURCE_NAME } from '../utils/datasource'; + +interface Props { + dashboardUid: string; +} + +export default function AlertRulesDrawerContent({ dashboardUid }: Props) { + const dispatch = useDispatch(); + + const { loading } = useAsync(async () => { + await dispatch(fetchPromAndRulerRulesAction({ rulesSourceName: GRAFANA_RULES_SOURCE_NAME })); + }, [dispatch]); + + const grafanaNamespaces = useCombinedRuleNamespaces(GRAFANA_RULES_SOURCE_NAME); + const rules = grafanaNamespaces + .flatMap((ns) => ns.groups) + .flatMap((g) => g.rules) + .filter((rule) => rule.annotations[Annotation.dashboardUID] === dashboardUid); + + return ( + <> + {loading ? ( + + ) : ( + + )} + + ); +} diff --git a/public/app/features/alerting/unified/integration/AlertRulesToolbarButton.tsx b/public/app/features/alerting/unified/integration/AlertRulesToolbarButton.tsx new file mode 100644 index 00000000000..a71ce470234 --- /dev/null +++ b/public/app/features/alerting/unified/integration/AlertRulesToolbarButton.tsx @@ -0,0 +1,39 @@ +import React from 'react'; +import { useToggle } from 'react-use'; + +import { ToolbarButton } from '@grafana/ui'; + +import { t } from '../../../../core/internationalization'; +import { alertRuleApi } from '../api/alertRuleApi'; +import { GRAFANA_RULES_SOURCE_NAME } from '../utils/datasource'; + +import { AlertRulesDrawer } from './AlertRulesDrawer'; + +interface AlertRulesToolbarButtonProps { + dashboardUid: string; +} + +export default function AlertRulesToolbarButton({ dashboardUid }: AlertRulesToolbarButtonProps) { + const [showDrawer, toggleShowDrawer] = useToggle(false); + + const { data: namespaces = [] } = alertRuleApi.endpoints.prometheusRuleNamespaces.useQuery({ + ruleSourceName: GRAFANA_RULES_SOURCE_NAME, + dashboardUid: dashboardUid, + }); + + if (namespaces.length === 0) { + return null; + } + + return ( + <> + + {showDrawer && } + + ); +} diff --git a/public/app/features/alerting/unified/search/rulesSearchParser.ts b/public/app/features/alerting/unified/search/rulesSearchParser.ts index 8e9b6157180..d1d47f707e6 100644 --- a/public/app/features/alerting/unified/search/rulesSearchParser.ts +++ b/public/app/features/alerting/unified/search/rulesSearchParser.ts @@ -20,6 +20,7 @@ export interface RulesFilter { dataSourceNames: string[]; labels: string[]; ruleHealth?: RuleHealth; + dashboardUid?: string; } const filterSupportedTerms: FilterSupportedTerm[] = [ @@ -31,6 +32,7 @@ const filterSupportedTerms: FilterSupportedTerm[] = [ FilterSupportedTerm.state, FilterSupportedTerm.type, FilterSupportedTerm.health, + FilterSupportedTerm.dashboard, ]; export enum RuleHealth { @@ -53,6 +55,7 @@ export function getSearchFilterFromQuery(query: string): RulesFilter { [terms.StateToken]: (value) => (filter.ruleState = parseStateToken(value)), [terms.TypeToken]: (value) => (isPromRuleType(value) ? (filter.ruleType = value) : undefined), [terms.HealthToken]: (value) => (filter.ruleHealth = getRuleHealth(value)), + [terms.DashboardToken]: (value) => (filter.dashboardUid = value), [terms.FreeFormExpression]: (value) => filter.freeFormWords.push(value), }; @@ -92,6 +95,9 @@ export function applySearchFilterToQuery(query: string, filter: RulesFilter): st if (filter.labels) { filterStateArray.push(...filter.labels.map((l) => ({ type: terms.LabelToken, value: l }))); } + if (filter.dashboardUid) { + filterStateArray.push({ type: terms.DashboardToken, value: filter.dashboardUid }); + } if (filter.freeFormWords) { filterStateArray.push(...filter.freeFormWords.map((word) => ({ type: terms.FreeFormExpression, value: word }))); } diff --git a/public/app/features/alerting/unified/search/search.grammar b/public/app/features/alerting/unified/search/search.grammar index a8d64b6dceb..f1be5445220 100644 --- a/public/app/features/alerting/unified/search/search.grammar +++ b/public/app/features/alerting/unified/search/search.grammar @@ -1,6 +1,6 @@ @top AlertRuleSearch { expression+ } -@dialects { dataSourceFilter, nameSpaceFilter, labelFilter, groupFilter, ruleFilter, stateFilter, typeFilter, healthFilter } +@dialects { dataSourceFilter, nameSpaceFilter, labelFilter, groupFilter, ruleFilter, stateFilter, typeFilter, healthFilter, dashboardFilter } expression { (FilterExpression | FreeFormExpression) expression } @@ -14,7 +14,8 @@ FilterExpression { filter | filter | filter | - filter + filter | + filter } filter { token FilterValue } @@ -41,6 +42,7 @@ filter { token FilterValue } StateToken[@dialect=stateFilter] { filterToken<"state"> } TypeToken[@dialect=typeFilter] { filterToken<"type"> } HealthToken[@dialect=healthFilter] { filterToken<"health"> } + DashboardToken[@dialect=dashboardFilter] { filterToken<"dashboard"> } @precedence { DataSourceToken, word } @precedence { NameSpaceToken, word } @@ -50,5 +52,6 @@ filter { token FilterValue } @precedence { StateToken, word } @precedence { TypeToken, word } @precedence { HealthToken, word } + @precedence { DashboardToken, word } } diff --git a/public/app/features/alerting/unified/search/search.js b/public/app/features/alerting/unified/search/search.js index 8ee40ac1d7c..8dd0e3c309e 100644 --- a/public/app/features/alerting/unified/search/search.js +++ b/public/app/features/alerting/unified/search/search.js @@ -3,28 +3,29 @@ import { LRParser } from '@lezer/lr'; export const parser = LRParser.deserialize({ version: 14, states: - "!vOQOPOOOrOPO'#ChOOOO'#Ch'#ChOQOPO'#ClOOOO'#Ci'#CiQQOPOOO!gOQO'#C^O!lOPO'#CjO!qOPO,59SOOOO,59W,59WOOOO-E6g-E6gOOOO,58x,58xOOOO,59U,59UOOOO-E6h-E6h", + "!vOQOPOOOuOPO'#CiOOOO'#Ci'#CiOQOPO'#CmOOOO'#Cj'#CjQQOPOOO!mOQO'#C^O!rOPO'#CkO!wOPO,59TOOOO,59X,59XOOOO-E6h-E6hOOOO,58x,58xOOOO,59V,59VOOOO-E6i-E6i", stateData: - '$O~ORUOTUOUUOVUOWUOXUOYUOZUOaPOcQO~ObVOR[XT[XU[XV[XW[XX[XY[XZ[Xa[Xc[X~OSZO~Oa[O~ObVOR[aT[aU[aV[aW[aX[aY[aZ[aa[ac[a~OR~T~U~V~W~Y~Z~RZYXWVUTa~', - goto: 'zaPPbPPPPPPPPPbgmPsVRORTQTORYTQWPR]WSSOTRXR', + '$[~ORUOTUOUUOVUOWUOXUOYUOZUO[UObPOdQO~OcVOR]XT]XU]XV]XW]XX]XY]XZ]X[]Xb]Xd]X~OSZO~Ob[O~OcVOR]aT]aU]aV]aW]aX]aY]aZ]a[]ab]ad]a~OR~T~U~V~W~Y~Z~[~R[ZYXWVUTb~', + goto: '{bPPcPPPPPPPPPPchnPtVRORTQTORYTQWPR]WSSOTRXR', nodeNames: - '⚠ AlertRuleSearch FilterExpression DataSourceToken FilterValue NameSpaceToken LabelToken GroupToken RuleToken StateToken TypeToken HealthToken FreeFormExpression', - maxTerm: 19, + '⚠ AlertRuleSearch FilterExpression DataSourceToken FilterValue NameSpaceToken LabelToken GroupToken RuleToken StateToken TypeToken HealthToken DashboardToken FreeFormExpression', + maxTerm: 20, skippedNodes: [0], repeatNodeCount: 2, tokenData: - "#$QRRqqr#Yrs&fst#Ytu#Yuv#Yvw#Ywx#Yxy#Yyz#Yz{#Y{|#Y|!P#Y!P!Q#Y!Q![#Y![!]+h!]#W#Y#W#X,z#X#Z#Y#Z#[=b#[#]F[#]#`#Y#`#a!!n#a#b#Y#b#c!+h#c#f#Y#f#g!:f#g#h!Av#h#i!Jp#i$Ch#Y$JU;'S#Y;'S;(d&Y;(d;(e&`<%lO#YR#acSQaPqr#Yst#Ytu#Yuv#Yvw#Ywx#Yxy#Yyz#Yz{#Y{|#Y|!P#Y!P!Q#Y!Q![#Y![!]$l!]$Ch#Y$JU;'S#Y;'S;(d&Y;(d;(e&`<%lO#YQ$qcSQqr$lst$ltu$luv$lvw$lwx$lxy$lyz$lz{$l{|$l|!P$l!P!Q$l!Q![$l![!]$l!]$Ch$l$JU;'S$l;'S;(d%|;(d;(e&S<%lO$lQ&PP;=`<%l$lQ&VP;=`;NQ$lR&]P;=`<%l#YR&cP;=`;NQ#YR&irX^(spq(sqr(sst(stu(suv(svw(swx(sxy(syz(sz{(s{|(s|!P(s!P!Q(s!Q![(s![!](s!]#y(s#y#z(s#z$f(s$f$g(s$g#BY(s#BY#BZ(s#BZ$Ch(s$IS$I_(s$I|$JO(s$JT$JU(s$JU$KV(s$KV$KW(s$KW&FU(s&FU&FV(s&FV;'S(s;'S;(d+[;(d;(e+b<%lO(sR(vsX^(spq(sqr(srs+Tst(stu(suv(svw(swx(sxy(syz(sz{(s{|(s|!P(s!P!Q(s!Q![(s![!](s!]#y(s#y#z(s#z$f(s$f$g(s$g#BY(s#BY#BZ(s#BZ$Ch(s$IS$I_(s$I|$JO(s$JT$JU(s$JU$KV(s$KV$KW(s$KW&FU(s&FU&FV(s&FV;'S(s;'S;(d+[;(d;(e+b<%lO(sR+[OSQcPR+_P;=`<%l(sR+eP;=`;NQ(sR+ocSQbPqr$lst$ltu$luv$lvw$lwx$lxy$lyz$lz{$l{|$l|!P$l!P!Q$l!Q![$l![!]$l!]$Ch$l$JU;'S$l;'S;(d%|;(d;(e&S<%lO$lR-ReSQaPqr#Yst#Ytu#Yuv#Yvw#Ywx#Yxy#Yyz#Yz{#Y{|#Y|!P#Y!P!Q#Y!Q![#Y![!]$l!]#T#Y#T#U.d#U$Ch#Y$JU;'S#Y;'S;(d&Y;(d;(e&`<%lO#YR.keSQaPqr#Yst#Ytu#Yuv#Yvw#Ywx#Yxy#Yyz#Yz{#Y{|#Y|!P#Y!P!Q#Y!Q![#Y![!]$l!]#h#Y#h#i/|#i$Ch#Y$JU;'S#Y;'S;(d&Y;(d;(e&`<%lO#YR0TeSQaPqr#Yst#Ytu#Yuv#Yvw#Ywx#Yxy#Yyz#Yz{#Y{|#Y|!P#Y!P!Q#Y!Q![#Y![!]$l!]#T#Y#T#U1f#U$Ch#Y$JU;'S#Y;'S;(d&Y;(d;(e&`<%lO#YR1meSQaPqr#Yst#Ytu#Yuv#Yvw#Ywx#Yxy#Yyz#Yz{#Y{|#Y|!P#Y!P!Q#Y!Q![#Y![!]$l!]#g#Y#g#h3O#h$Ch#Y$JU;'S#Y;'S;(d&Y;(d;(e&`<%lO#YR3VeSQaPqr#Yst#Ytu#Yuv#Yvw#Ywx#Yxy#Yyz#Yz{#Y{|#Y|!P#Y!P!Q#Y!Q![#Y![!]$l!]#c#Y#c#d4h#d$Ch#Y$JU;'S#Y;'S;(d&Y;(d;(e&`<%lO#YR4oeSQaPqr#Yst#Ytu#Yuv#Yvw#Ywx#Yxy#Yyz#Yz{#Y{|#Y|!P#Y!P!Q#Y!Q![#Y![!]$l!]#i#Y#i#j6Q#j$Ch#Y$JU;'S#Y;'S;(d&Y;(d;(e&`<%lO#YR6XeSQaPqr#Yst#Ytu#Yuv#Yvw#Ywx#Yxy#Yyz#Yz{#Y{|#Y|!P#Y!P!Q#Y!Q![#Y![!]$l!]#f#Y#f#g7j#g$Ch#Y$JU;'S#Y;'S;(d&Y;(d;(e&`<%lO#YR7qeSQaPqr#Yst#Ytu#Yuv#Yvw#Ywx#Yxy#Yyz#Yz{#Y{|#Y|!P#Y!P!Q#Y!Q![#Y![!]$l!]#V#Y#V#W9S#W$Ch#Y$JU;'S#Y;'S;(d&Y;(d;(e&`<%lO#YR9ZeSQaPqr#Yst#Ytu#Yuv#Yvw#Ywx#Yxy#Yyz#Yz{#Y{|#Y|!P#Y!P!Q#Y!Q![#Y![!]$l!]#X#Y#X#Y:l#Y$Ch#Y$JU;'S#Y;'S;(d&Y;(d;(e&`<%lO#YR:scSQaPqr#Yst#Ytu#Yuv#Yvw#Ywx#Yxy#Yyz#Yz{#Y{|#Y|!P#Y!P!Q#Y!Q![#Y![!]z#g$Ch#Y$JU;'S#Y;'S;(d&Y;(d;(e&`<%lO#YR?ReSQaPqr#Yst#Ytu#Yuv#Yvw#Ywx#Yxy#Yyz#Yz{#Y{|#Y|!P#Y!P!Q#Y!Q![#Y![!]$l!]#c#Y#c#d@d#d$Ch#Y$JU;'S#Y;'S;(d&Y;(d;(e&`<%lO#YR@keSQaPqr#Yst#Ytu#Yuv#Yvw#Ywx#Yxy#Yyz#Yz{#Y{|#Y|!P#Y!P!Q#Y!Q![#Y![!]$l!]#i#Y#i#jA|#j$Ch#Y$JU;'S#Y;'S;(d&Y;(d;(e&`<%lO#YRBTeSQaPqr#Yst#Ytu#Yuv#Yvw#Ywx#Yxy#Yyz#Yz{#Y{|#Y|!P#Y!P!Q#Y!Q![#Y![!]$l!]#d#Y#d#eCf#e$Ch#Y$JU;'S#Y;'S;(d&Y;(d;(e&`<%lO#YRCmcSQaPqr#Yst#Ytu#Yuv#Yvw#Ywx#Yxy#Yyz#Yz{#Y{|#Y|!P#Y!P!Q#Y!Q![#Y![!]Dx!]$Ch#Y$JU;'S#Y;'S;(d&Y;(d;(e&`<%lO#YREPcSQVPqr$lst$ltu$luv$lvw$lwx$lxy$lyz$lz{$l{|$l|!P$l!P!Q$l!Q![$l![!]$l!]$Ch$l$JU;'S$l;'S;(d%|;(d;(e&S<%lO$lRFceSQaPqr#Yst#Ytu#Yuv#Yvw#Ywx#Yxy#Yyz#Yz{#Y{|#Y|!P#Y!P!Q#Y!Q![#Y![!]$l!]#X#Y#X#YGt#Y$Ch#Y$JU;'S#Y;'S;(d&Y;(d;(e&`<%lO#YRG{eSQaPqr#Yst#Ytu#Yuv#Yvw#Ywx#Yxy#Yyz#Yz{#Y{|#Y|!P#Y!P!Q#Y!Q![#Y![!]$l!]#T#Y#T#UI^#U$Ch#Y$JU;'S#Y;'S;(d&Y;(d;(e&`<%lO#YRIeeSQaPqr#Yst#Ytu#Yuv#Yvw#Ywx#Yxy#Yyz#Yz{#Y{|#Y|!P#Y!P!Q#Y!Q![#Y![!]$l!]#`#Y#`#aJv#a$Ch#Y$JU;'S#Y;'S;(d&Y;(d;(e&`<%lO#YRJ}eSQaPqr#Yst#Ytu#Yuv#Yvw#Ywx#Yxy#Yyz#Yz{#Y{|#Y|!P#Y!P!Q#Y!Q![#Y![!]$l!]#h#Y#h#iL`#i$Ch#Y$JU;'S#Y;'S;(d&Y;(d;(e&`<%lO#YRLgeSQaPqr#Yst#Ytu#Yuv#Yvw#Ywx#Yxy#Yyz#Yz{#Y{|#Y|!P#Y!P!Q#Y!Q![#Y![!]$l!]#[#Y#[#]Mx#]$Ch#Y$JU;'S#Y;'S;(d&Y;(d;(e&`<%lO#YRNPcSQaPqr#Yst#Ytu#Yuv#Yvw#Ywx#Yxy#Yyz#Yz{#Y{|#Y|!P#Y!P!Q#Y!Q![#Y![!]! [!]$Ch#Y$JU;'S#Y;'S;(d&Y;(d;(e&`<%lO#YR! ccSQZPqr$lst$ltu$luv$lvw$lwx$lxy$lyz$lz{$l{|$l|!P$l!P!Q$l!Q![$l![!]$l!]$Ch$l$JU;'S$l;'S;(d%|;(d;(e&S<%lO$lR!!ueSQaPqr#Yst#Ytu#Yuv#Yvw#Ywx#Yxy#Yyz#Yz{#Y{|#Y|!P#Y!P!Q#Y!Q![#Y![!]$l!]#T#Y#T#U!$W#U$Ch#Y$JU;'S#Y;'S;(d&Y;(d;(e&`<%lO#YR!$_eSQaPqr#Yst#Ytu#Yuv#Yvw#Ywx#Yxy#Yyz#Yz{#Y{|#Y|!P#Y!P!Q#Y!Q![#Y![!]$l!]#U#Y#U#V!%p#V$Ch#Y$JU;'S#Y;'S;(d&Y;(d;(e&`<%lO#YR!%weSQaPqr#Yst#Ytu#Yuv#Yvw#Ywx#Yxy#Yyz#Yz{#Y{|#Y|!P#Y!P!Q#Y!Q![#Y![!]$l!]#X#Y#X#Y!'Y#Y$Ch#Y$JU;'S#Y;'S;(d&Y;(d;(e&`<%lO#YR!'aeSQaPqr#Yst#Ytu#Yuv#Yvw#Ywx#Yxy#Yyz#Yz{#Y{|#Y|!P#Y!P!Q#Y!Q![#Y![!]$l!]#`#Y#`#a!(r#a$Ch#Y$JU;'S#Y;'S;(d&Y;(d;(e&`<%lO#YR!(ycSQaPqr#Yst#Ytu#Yuv#Yvw#Ywx#Yxy#Yyz#Yz{#Y{|#Y|!P#Y!P!Q#Y!Q![#Y![!]!*U!]$Ch#Y$JU;'S#Y;'S;(d&Y;(d;(e&`<%lO#YR!*]cSQUPqr$lst$ltu$luv$lvw$lwx$lxy$lyz$lz{$l{|$l|!P$l!P!Q$l!Q![$l![!]$l!]$Ch$l$JU;'S$l;'S;(d%|;(d;(e&S<%lO$lR!+oeSQaPqr#Yst#Ytu#Yuv#Yvw#Ywx#Yxy#Yyz#Yz{#Y{|#Y|!P#Y!P!Q#Y!Q![#Y![!]$l!]#T#Y#T#U!-Q#U$Ch#Y$JU;'S#Y;'S;(d&Y;(d;(e&`<%lO#YR!-XeSQaPqr#Yst#Ytu#Yuv#Yvw#Ywx#Yxy#Yyz#Yz{#Y{|#Y|!P#Y!P!Q#Y!Q![#Y![!]$l!]#a#Y#a#b!.j#b$Ch#Y$JU;'S#Y;'S;(d&Y;(d;(e&`<%lO#YR!.qeSQaPqr#Yst#Ytu#Yuv#Yvw#Ywx#Yxy#Yyz#Yz{#Y{|#Y|!P#Y!P!Q#Y!Q![#Y![!]$l!]#X#Y#X#Y!0S#Y$Ch#Y$JU;'S#Y;'S;(d&Y;(d;(e&`<%lO#YR!0ZeSQaPqr#Yst#Ytu#Yuv#Yvw#Ywx#Yxy#Yyz#Yz{#Y{|#Y|!P#Y!P!Q#Y!Q![#Y![!]$l!]#g#Y#g#h!1l#h$Ch#Y$JU;'S#Y;'S;(d&Y;(d;(e&`<%lO#YR!1seSQaPqr#Yst#Ytu#Yuv#Yvw#Ywx#Yxy#Yyz#Yz{#Y{|#Y|!P#Y!P!Q#Y!Q![#Y![!]$l!]#d#Y#d#e!3U#e$Ch#Y$JU;'S#Y;'S;(d&Y;(d;(e&`<%lO#YR!3]eSQaPqr#Yst#Ytu#Yuv#Yvw#Ywx#Yxy#Yyz#Yz{#Y{|#Y|!P#Y!P!Q#Y!Q![#Y![!]$l!]#T#Y#T#U!4n#U$Ch#Y$JU;'S#Y;'S;(d&Y;(d;(e&`<%lO#YR!4ueSQaPqr#Yst#Ytu#Yuv#Yvw#Ywx#Yxy#Yyz#Yz{#Y{|#Y|!P#Y!P!Q#Y!Q![#Y![!]$l!]#V#Y#V#W!6W#W$Ch#Y$JU;'S#Y;'S;(d&Y;(d;(e&`<%lO#YR!6_eSQaPqr#Yst#Ytu#Yuv#Yvw#Ywx#Yxy#Yyz#Yz{#Y{|#Y|!P#Y!P!Q#Y!Q![#Y![!]$l!]#X#Y#X#Y!7p#Y$Ch#Y$JU;'S#Y;'S;(d&Y;(d;(e&`<%lO#YR!7wcSQaPqr#Yst#Ytu#Yuv#Yvw#Ywx#Yxy#Yyz#Yz{#Y{|#Y|!P#Y!P!Q#Y!Q![#Y![!]!9S!]$Ch#Y$JU;'S#Y;'S;(d&Y;(d;(e&`<%lO#YR!9ZcSQTPqr$lst$ltu$luv$lvw$lwx$lxy$lyz$lz{$l{|$l|!P$l!P!Q$l!Q![$l![!]$l!]$Ch$l$JU;'S$l;'S;(d%|;(d;(e&S<%lO$lR!:meSQaPqr#Yst#Ytu#Yuv#Yvw#Ywx#Yxy#Yyz#Yz{#Y{|#Y|!P#Y!P!Q#Y!Q![#Y![!]$l!]#i#Y#i#j!}#h$Ch#Y$JU;'S#Y;'S;(d&Y;(d;(e&`<%lO#YR?UeSQbPqr#Yst#Ytu#Yuv#Yvw#Ywx#Yxy#Yyz#Yz{#Y{|#Y|!P#Y!P!Q#Y!Q![#Y![!]$l!]#c#Y#c#d@g#d$Ch#Y$JU;'S#Y;'S;(d&Y;(d;(e&`<%lO#YR@neSQbPqr#Yst#Ytu#Yuv#Yvw#Ywx#Yxy#Yyz#Yz{#Y{|#Y|!P#Y!P!Q#Y!Q![#Y![!]$l!]#i#Y#i#jBP#j$Ch#Y$JU;'S#Y;'S;(d&Y;(d;(e&`<%lO#YRBWeSQbPqr#Yst#Ytu#Yuv#Yvw#Ywx#Yxy#Yyz#Yz{#Y{|#Y|!P#Y!P!Q#Y!Q![#Y![!]$l!]#f#Y#f#gCi#g$Ch#Y$JU;'S#Y;'S;(d&Y;(d;(e&`<%lO#YRCpeSQbPqr#Yst#Ytu#Yuv#Yvw#Ywx#Yxy#Yyz#Yz{#Y{|#Y|!P#Y!P!Q#Y!Q![#Y![!]$l!]#V#Y#V#WER#W$Ch#Y$JU;'S#Y;'S;(d&Y;(d;(e&`<%lO#YREYeSQbPqr#Yst#Ytu#Yuv#Yvw#Ywx#Yxy#Yyz#Yz{#Y{|#Y|!P#Y!P!Q#Y!Q![#Y![!]$l!]#X#Y#X#YFk#Y$Ch#Y$JU;'S#Y;'S;(d&Y;(d;(e&`<%lO#YRFrcSQbPqr#Yst#Ytu#Yuv#Yvw#Ywx#Yxy#Yyz#Yz{#Y{|#Y|!P#Y!P!Q#Y!Q![#Y![!]G}!]$Ch#Y$JU;'S#Y;'S;(d&Y;(d;(e&`<%lO#YRHUcSQRPqr$lst$ltu$luv$lvw$lwx$lxy$lyz$lz{$l{|$l|!P$l!P!Q$l!Q![$l![!]$l!]$Ch$l$JU;'S$l;'S;(d%|;(d;(e&S<%lO$lRIheSQbPqr#Yst#Ytu#Yuv#Yvw#Ywx#Yxy#Yyz#Yz{#Y{|#Y|!P#Y!P!Q#Y!Q![#Y![!]$l!]#f#Y#f#gJy#g$Ch#Y$JU;'S#Y;'S;(d&Y;(d;(e&`<%lO#YRKQeSQbPqr#Yst#Ytu#Yuv#Yvw#Ywx#Yxy#Yyz#Yz{#Y{|#Y|!P#Y!P!Q#Y!Q![#Y![!]$l!]#c#Y#c#dLc#d$Ch#Y$JU;'S#Y;'S;(d&Y;(d;(e&`<%lO#YRLjeSQbPqr#Yst#Ytu#Yuv#Yvw#Ywx#Yxy#Yyz#Yz{#Y{|#Y|!P#Y!P!Q#Y!Q![#Y![!]$l!]#i#Y#i#jM{#j$Ch#Y$JU;'S#Y;'S;(d&Y;(d;(e&`<%lO#YRNSeSQbPqr#Yst#Ytu#Yuv#Yvw#Ywx#Yxy#Yyz#Yz{#Y{|#Y|!P#Y!P!Q#Y!Q![#Y![!]$l!]#d#Y#d#e! e#e$Ch#Y$JU;'S#Y;'S;(d&Y;(d;(e&`<%lO#YR! lcSQbPqr#Yst#Ytu#Yuv#Yvw#Ywx#Yxy#Yyz#Yz{#Y{|#Y|!P#Y!P!Q#Y!Q![#Y![!]!!w!]$Ch#Y$JU;'S#Y;'S;(d&Y;(d;(e&`<%lO#YR!#OcSQVPqr$lst$ltu$luv$lvw$lwx$lxy$lyz$lz{$l{|$l|!P$l!P!Q$l!Q![$l![!]$l!]$Ch$l$JU;'S$l;'S;(d%|;(d;(e&S<%lO$lR!$beSQbPqr#Yst#Ytu#Yuv#Yvw#Ywx#Yxy#Yyz#Yz{#Y{|#Y|!P#Y!P!Q#Y!Q![#Y![!]$l!]#X#Y#X#Y!%s#Y$Ch#Y$JU;'S#Y;'S;(d&Y;(d;(e&`<%lO#YR!%zeSQbPqr#Yst#Ytu#Yuv#Yvw#Ywx#Yxy#Yyz#Yz{#Y{|#Y|!P#Y!P!Q#Y!Q![#Y![!]$l!]#T#Y#T#U!']#U$Ch#Y$JU;'S#Y;'S;(d&Y;(d;(e&`<%lO#YR!'deSQbPqr#Yst#Ytu#Yuv#Yvw#Ywx#Yxy#Yyz#Yz{#Y{|#Y|!P#Y!P!Q#Y!Q![#Y![!]$l!]#`#Y#`#a!(u#a$Ch#Y$JU;'S#Y;'S;(d&Y;(d;(e&`<%lO#YR!(|eSQbPqr#Yst#Ytu#Yuv#Yvw#Ywx#Yxy#Yyz#Yz{#Y{|#Y|!P#Y!P!Q#Y!Q![#Y![!]$l!]#h#Y#h#i!*_#i$Ch#Y$JU;'S#Y;'S;(d&Y;(d;(e&`<%lO#YR!*feSQbPqr#Yst#Ytu#Yuv#Yvw#Ywx#Yxy#Yyz#Yz{#Y{|#Y|!P#Y!P!Q#Y!Q![#Y![!]$l!]#[#Y#[#]!+w#]$Ch#Y$JU;'S#Y;'S;(d&Y;(d;(e&`<%lO#YR!,OcSQbPqr#Yst#Ytu#Yuv#Yvw#Ywx#Yxy#Yyz#Yz{#Y{|#Y|!P#Y!P!Q#Y!Q![#Y![!]!-Z!]$Ch#Y$JU;'S#Y;'S;(d&Y;(d;(e&`<%lO#YR!-bcSQZPqr$lst$ltu$luv$lvw$lwx$lxy$lyz$lz{$l{|$l|!P$l!P!Q$l!Q![$l![!]$l!]$Ch$l$JU;'S$l;'S;(d%|;(d;(e&S<%lO$lR!.teSQbPqr#Yst#Ytu#Yuv#Yvw#Ywx#Yxy#Yyz#Yz{#Y{|#Y|!P#Y!P!Q#Y!Q![#Y![!]$l!]#T#Y#T#U!0V#U$Ch#Y$JU;'S#Y;'S;(d&Y;(d;(e&`<%lO#YR!0^eSQbPqr#Yst#Ytu#Yuv#Yvw#Ywx#Yxy#Yyz#Yz{#Y{|#Y|!P#Y!P!Q#Y!Q![#Y![!]$l!]#U#Y#U#V!1o#V$Ch#Y$JU;'S#Y;'S;(d&Y;(d;(e&`<%lO#YR!1veSQbPqr#Yst#Ytu#Yuv#Yvw#Ywx#Yxy#Yyz#Yz{#Y{|#Y|!P#Y!P!Q#Y!Q![#Y![!]$l!]#X#Y#X#Y!3X#Y$Ch#Y$JU;'S#Y;'S;(d&Y;(d;(e&`<%lO#YR!3`eSQbPqr#Yst#Ytu#Yuv#Yvw#Ywx#Yxy#Yyz#Yz{#Y{|#Y|!P#Y!P!Q#Y!Q![#Y![!]$l!]#`#Y#`#a!4q#a$Ch#Y$JU;'S#Y;'S;(d&Y;(d;(e&`<%lO#YR!4xcSQbPqr#Yst#Ytu#Yuv#Yvw#Ywx#Yxy#Yyz#Yz{#Y{|#Y|!P#Y!P!Q#Y!Q![#Y![!]!6T!]$Ch#Y$JU;'S#Y;'S;(d&Y;(d;(e&`<%lO#YR!6[cSQUPqr$lst$ltu$luv$lvw$lwx$lxy$lyz$lz{$l{|$l|!P$l!P!Q$l!Q![$l![!]$l!]$Ch$l$JU;'S$l;'S;(d%|;(d;(e&S<%lO$lR!7neSQbPqr#Yst#Ytu#Yuv#Yvw#Ywx#Yxy#Yyz#Yz{#Y{|#Y|!P#Y!P!Q#Y!Q![#Y![!]$l!]#T#Y#T#U!9P#U$Ch#Y$JU;'S#Y;'S;(d&Y;(d;(e&`<%lO#YR!9WeSQbPqr#Yst#Ytu#Yuv#Yvw#Ywx#Yxy#Yyz#Yz{#Y{|#Y|!P#Y!P!Q#Y!Q![#Y![!]$l!]#a#Y#a#b!:i#b$Ch#Y$JU;'S#Y;'S;(d&Y;(d;(e&`<%lO#YR!:peSQbPqr#Yst#Ytu#Yuv#Yvw#Ywx#Yxy#Yyz#Yz{#Y{|#Y|!P#Y!P!Q#Y!Q![#Y![!]$l!]#X#Y#X#Y! = { [terms.StateToken]: 'state', [terms.TypeToken]: 'type', [terms.HealthToken]: 'health', + [terms.DashboardToken]: 'dashboard', }; // This enum allows to configure parser behavior @@ -27,6 +28,7 @@ export enum FilterSupportedTerm { state = 'stateFilter', type = 'typeFilter', health = 'healthFilter', + dashboard = 'dashboardFilter', } export type QueryFilterMapper = Record void>; diff --git a/public/app/features/alerting/unified/state/actions.ts b/public/app/features/alerting/unified/state/actions.ts index ec6d5a9b4eb..a609c09e06e 100644 --- a/public/app/features/alerting/unified/state/actions.ts +++ b/public/app/features/alerting/unified/state/actions.ts @@ -189,7 +189,7 @@ export function fetchPromAndRulerRulesAction({ limitAlerts?: number; matcher?: Matcher[]; state?: string[]; -}): ThunkResult { +}): ThunkResult> { return async (dispatch, getState) => { await dispatch(fetchRulesSourceBuildInfoAction({ rulesSourceName })); const dsConfig = getDataSourceConfig(getState, rulesSourceName);