1 |
- {"version":3,"file":"AlertingRedirectToRule.68531cf96aa6317776ff.js","mappings":"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","sources":["webpack://grafana/./public/app/features/alerting/unified/RedirectToRuleViewer.tsx","webpack://grafana/./public/app/features/alerting/unified/components/rule-viewer/RuleViewerLayout.tsx","webpack://grafana/./public/app/features/alerting/unified/hooks/useCombinedRule.ts","webpack://grafana/./public/app/features/alerting/unified/hooks/useCombinedRuleNamespaces.ts"],"sourcesContent":["import { css } from '@emotion/css';\nimport React from 'react';\nimport { Redirect } from 'react-router-dom';\n\nimport { GrafanaTheme2 } from '@grafana/data';\nimport { Alert, Card, Icon, LoadingPlaceholder, useStyles2, withErrorBoundary } from '@grafana/ui';\nimport { GrafanaRouteComponentProps } from 'app/core/navigation/types';\n\nimport { AlertLabels } from './components/AlertLabels';\nimport { RuleViewerLayout } from './components/rule-viewer/RuleViewerLayout';\nimport { useCombinedRulesMatching } from './hooks/useCombinedRule';\nimport { getRulesSourceByName } from './utils/datasource';\nimport { createViewLink } from './utils/misc';\n\ntype RedirectToRuleViewerProps = GrafanaRouteComponentProps<{ name?: string; sourceName?: string }>;\nconst pageTitle = 'Alerting / Find rule';\n\nexport function RedirectToRuleViewer(props: RedirectToRuleViewerProps): JSX.Element | null {\n const { name, sourceName } = props.match.params;\n const styles = useStyles2(getStyles);\n const { error, loading, result: rules, dispatched } = useCombinedRulesMatching(name, sourceName);\n\n if (error) {\n return (\n <RuleViewerLayout title={pageTitle}>\n <Alert title={`Failed to load rules from ${sourceName}`}>\n <details className={styles.errorMessage}>\n {error.message}\n <br />\n {!!error?.stack && error.stack}\n </details>\n </Alert>\n </RuleViewerLayout>\n );\n }\n\n if (loading || !dispatched || !Array.isArray(rules)) {\n return (\n <RuleViewerLayout title={pageTitle}>\n <LoadingPlaceholder text=\"Loading rule...\" />\n </RuleViewerLayout>\n );\n }\n\n if (!name || !sourceName) {\n return <Redirect to=\"/notfound\" />;\n }\n\n const rulesSource = getRulesSourceByName(sourceName);\n\n if (!rulesSource) {\n return (\n <RuleViewerLayout title={pageTitle}>\n <Alert title=\"Could not view rule\">\n <details className={styles.errorMessage}>{`Could not find data source with name: ${sourceName}.`}</details>\n </Alert>\n </RuleViewerLayout>\n );\n }\n\n if (rules.length === 1) {\n const [rule] = rules;\n return <Redirect to={createViewLink(rulesSource, rule, '/alerting/list')} />;\n }\n\n return (\n <RuleViewerLayout title={pageTitle}>\n <div>\n Several rules in <span className={styles.param}>{sourceName}</span> matched the name{' '}\n <span className={styles.param}>{name}</span>, please select the rule you want to view.\n </div>\n <div className={styles.rules}>\n {rules.map((rule, index) => {\n return (\n <Card key={`${rule.name}-${index}`} href={createViewLink(rulesSource, rule, '/alerting/list')}>\n <Card.Heading>{rule.name}</Card.Heading>\n <Card.Meta separator={''}>\n <Icon name=\"folder\" />\n <span className={styles.namespace}>{`${rule.namespace.name} / ${rule.group.name}`}</span>\n </Card.Meta>\n <Card.Tags>\n <AlertLabels labels={rule.labels} />\n </Card.Tags>\n </Card>\n );\n })}\n </div>\n </RuleViewerLayout>\n );\n}\n\nfunction getStyles(theme: GrafanaTheme2) {\n return {\n param: css`\n font-style: italic;\n color: ${theme.colors.text.secondary};\n `,\n rules: css`\n margin-top: ${theme.spacing(2)};\n `,\n namespace: css`\n margin-left: ${theme.spacing(1)};\n `,\n errorMessage: css`\n white-space: pre-wrap;\n `,\n };\n}\n\nexport default withErrorBoundary(RedirectToRuleViewer, { style: 'page' });\n","import { css } from '@emotion/css';\nimport React from 'react';\n\nimport { GrafanaTheme2 } from '@grafana/data';\nimport { locationService } from '@grafana/runtime';\nimport { PageToolbar, useStyles2 } from '@grafana/ui';\nimport { Page } from 'app/core/components/Page/Page';\n\ntype Props = {\n children: React.ReactNode | React.ReactNode[];\n title: string;\n wrapInContent?: boolean;\n};\n\nexport function RuleViewerLayout(props: Props): JSX.Element | null {\n const { wrapInContent = true, children, title } = props;\n const styles = useStyles2(getPageStyles);\n\n return (\n <Page>\n <PageToolbar title={title} pageIcon=\"bell\" onGoBack={() => locationService.push('/alerting/list')} />\n <div className={styles.content}>{wrapInContent ? <RuleViewerLayoutContent {...props} /> : children}</div>\n </Page>\n );\n}\n\ntype ContentProps = {\n children: React.ReactNode | React.ReactNode[];\n padding?: number;\n};\n\nexport function RuleViewerLayoutContent({ children, padding = 2 }: ContentProps): JSX.Element | null {\n const styles = useStyles2(getContentStyles(padding));\n return <div className={styles.wrapper}>{children}</div>;\n}\n\nconst getPageStyles = (theme: GrafanaTheme2) => {\n return {\n content: css`\n margin: ${theme.spacing(0, 2, 2)};\n max-width: ${theme.breakpoints.values.xxl}px;\n `,\n };\n};\n\nconst getContentStyles = (padding: number) => (theme: GrafanaTheme2) => {\n return {\n wrapper: css`\n background: ${theme.colors.background.primary};\n border: 1px solid ${theme.colors.border.weak};\n border-radius: ${theme.shape.borderRadius()};\n padding: ${theme.spacing(padding)};\n `,\n };\n};\n","import { useMemo } from 'react';\nimport { useDispatch } from 'react-redux';\nimport { useAsync } from 'react-use';\n\nimport { CombinedRule, RuleIdentifier, RuleNamespace } from 'app/types/unified-alerting';\nimport { RulerRulesConfigDTO } from 'app/types/unified-alerting-dto';\n\nimport { fetchPromAndRulerRulesAction } from '../state/actions';\nimport { AsyncRequestMapSlice, AsyncRequestState, initialAsyncRequestState } from '../utils/redux';\nimport * as ruleId from '../utils/rule-id';\nimport { isRulerNotSupportedResponse } from '../utils/rules';\n\nimport { useCombinedRuleNamespaces } from './useCombinedRuleNamespaces';\nimport { useUnifiedAlertingSelector } from './useUnifiedAlertingSelector';\n\nexport function useCombinedRule(\n identifier: RuleIdentifier | undefined,\n ruleSourceName: string | undefined\n): AsyncRequestState<CombinedRule> {\n const requestState = useCombinedRulesLoader(ruleSourceName);\n const combinedRules = useCombinedRuleNamespaces(ruleSourceName);\n\n const rule = useMemo(() => {\n if (!identifier || !ruleSourceName || combinedRules.length === 0) {\n return;\n }\n\n for (const namespace of combinedRules) {\n for (const group of namespace.groups) {\n for (const rule of group.rules) {\n const id = ruleId.fromCombinedRule(ruleSourceName, rule);\n\n if (ruleId.equal(id, identifier)) {\n return rule;\n }\n }\n }\n }\n\n return;\n }, [identifier, ruleSourceName, combinedRules]);\n\n return {\n ...requestState,\n result: rule,\n };\n}\n\nexport function useCombinedRulesMatching(\n ruleName: string | undefined,\n ruleSourceName: string | undefined\n): AsyncRequestState<CombinedRule[]> {\n const requestState = useCombinedRulesLoader(ruleSourceName);\n const combinedRules = useCombinedRuleNamespaces(ruleSourceName);\n\n const rules = useMemo(() => {\n if (!ruleName || !ruleSourceName || combinedRules.length === 0) {\n return [];\n }\n\n const rules: CombinedRule[] = [];\n\n for (const namespace of combinedRules) {\n for (const group of namespace.groups) {\n for (const rule of group.rules) {\n if (rule.name === ruleName) {\n rules.push(rule);\n }\n }\n }\n }\n\n return rules;\n }, [ruleName, ruleSourceName, combinedRules]);\n\n return {\n ...requestState,\n result: rules,\n };\n}\n\nfunction useCombinedRulesLoader(rulesSourceName: string | undefined): AsyncRequestState<void> {\n const dispatch = useDispatch();\n const promRuleRequests = useUnifiedAlertingSelector((state) => state.promRules);\n const promRuleRequest = getRequestState(rulesSourceName, promRuleRequests);\n const rulerRuleRequests = useUnifiedAlertingSelector((state) => state.rulerRules);\n const rulerRuleRequest = getRequestState(rulesSourceName, rulerRuleRequests);\n\n const { loading } = useAsync(async () => {\n if (!rulesSourceName) {\n return;\n }\n\n await dispatch(fetchPromAndRulerRulesAction({ rulesSourceName }));\n }, [dispatch, rulesSourceName]);\n\n return {\n loading,\n error: promRuleRequest.error ?? isRulerNotSupportedResponse(rulerRuleRequest) ? undefined : rulerRuleRequest.error,\n dispatched: promRuleRequest.dispatched && rulerRuleRequest.dispatched,\n };\n}\n\nfunction getRequestState(\n ruleSourceName: string | undefined,\n slice: AsyncRequestMapSlice<RulerRulesConfigDTO | RuleNamespace[] | null>\n): AsyncRequestState<RulerRulesConfigDTO | RuleNamespace[] | null> {\n if (!ruleSourceName) {\n return initialAsyncRequestState;\n }\n\n const state = slice[ruleSourceName];\n\n if (!state) {\n return initialAsyncRequestState;\n }\n\n return state;\n}\n","import { useMemo, useRef } from 'react';\n\nimport {\n CombinedRule,\n CombinedRuleGroup,\n CombinedRuleNamespace,\n Rule,\n RuleGroup,\n RuleNamespace,\n RulesSource,\n} from 'app/types/unified-alerting';\nimport { RulerRuleDTO, RulerRuleGroupDTO, RulerRulesConfigDTO } from 'app/types/unified-alerting-dto';\n\nimport {\n getAllRulesSources,\n getRulesSourceByName,\n isCloudRulesSource,\n isGrafanaRulesSource,\n} from '../utils/datasource';\nimport { isAlertingRule, isAlertingRulerRule, isRecordingRulerRule } from '../utils/rules';\n\nimport { useUnifiedAlertingSelector } from './useUnifiedAlertingSelector';\n\ninterface CacheValue {\n promRules?: RuleNamespace[];\n rulerRules?: RulerRulesConfigDTO | null;\n result: CombinedRuleNamespace[];\n}\n\n// this little monster combines prometheus rules and ruler rules to produce a unified data structure\n// can limit to a single rules source\nexport function useCombinedRuleNamespaces(rulesSourceName?: string): CombinedRuleNamespace[] {\n const promRulesResponses = useUnifiedAlertingSelector((state) => state.promRules);\n const rulerRulesResponses = useUnifiedAlertingSelector((state) => state.rulerRules);\n\n // cache results per rules source, so we only recalculate those for which results have actually changed\n const cache = useRef<Record<string, CacheValue>>({});\n\n const rulesSources = useMemo((): RulesSource[] => {\n if (rulesSourceName) {\n const rulesSource = getRulesSourceByName(rulesSourceName);\n if (!rulesSource) {\n throw new Error(`Unknown rules source: ${rulesSourceName}`);\n }\n return [rulesSource];\n }\n return getAllRulesSources();\n }, [rulesSourceName]);\n\n return useMemo(\n () =>\n rulesSources\n .map((rulesSource): CombinedRuleNamespace[] => {\n const rulesSourceName = isCloudRulesSource(rulesSource) ? rulesSource.name : rulesSource;\n const promRules = promRulesResponses[rulesSourceName]?.result;\n const rulerRules = rulerRulesResponses[rulesSourceName]?.result;\n\n const cached = cache.current[rulesSourceName];\n if (cached && cached.promRules === promRules && cached.rulerRules === rulerRules) {\n return cached.result;\n }\n const namespaces: Record<string, CombinedRuleNamespace> = {};\n\n // first get all the ruler rules in\n Object.entries(rulerRules || {}).forEach(([namespaceName, groups]) => {\n const namespace: CombinedRuleNamespace = {\n rulesSource,\n name: namespaceName,\n groups: [],\n };\n namespaces[namespaceName] = namespace;\n addRulerGroupsToCombinedNamespace(namespace, groups);\n });\n\n // then correlate with prometheus rules\n promRules?.forEach(({ name: namespaceName, groups }) => {\n const ns = (namespaces[namespaceName] = namespaces[namespaceName] || {\n rulesSource,\n name: namespaceName,\n groups: [],\n });\n\n addPromGroupsToCombinedNamespace(ns, groups);\n });\n\n const result = Object.values(namespaces);\n\n cache.current[rulesSourceName] = { promRules, rulerRules, result };\n return result;\n })\n .flat(),\n [promRulesResponses, rulerRulesResponses, rulesSources]\n );\n}\n\n// merge all groups in case of grafana managed, essentially treating namespaces (folders) as groups\nexport function flattenGrafanaManagedRules(namespaces: CombinedRuleNamespace[]) {\n return namespaces.map((namespace) => {\n const newNamespace: CombinedRuleNamespace = {\n ...namespace,\n groups: [],\n };\n\n // add default group with ungrouped rules\n newNamespace.groups.push({\n name: 'default',\n rules: sortRulesByName(namespace.groups.flatMap((group) => group.rules)),\n });\n\n return newNamespace;\n });\n}\n\nexport function sortRulesByName(rules: CombinedRule[]) {\n return rules.sort((a, b) => a.name.localeCompare(b.name));\n}\n\nfunction addRulerGroupsToCombinedNamespace(namespace: CombinedRuleNamespace, groups: RulerRuleGroupDTO[]): void {\n namespace.groups = groups.map((group) => {\n const combinedGroup: CombinedRuleGroup = {\n name: group.name,\n interval: group.interval,\n source_tenants: group.source_tenants,\n rules: [],\n };\n combinedGroup.rules = group.rules.map((rule) => rulerRuleToCombinedRule(rule, namespace, combinedGroup));\n return combinedGroup;\n });\n}\n\nfunction addPromGroupsToCombinedNamespace(namespace: CombinedRuleNamespace, groups: RuleGroup[]): void {\n groups.forEach((group) => {\n let combinedGroup = namespace.groups.find((g) => g.name === group.name);\n if (!combinedGroup) {\n combinedGroup = {\n name: group.name,\n rules: [],\n };\n namespace.groups.push(combinedGroup);\n }\n\n (group.rules ?? []).forEach((rule) => {\n const existingRule = getExistingRuleInGroup(rule, combinedGroup!, namespace.rulesSource);\n if (existingRule) {\n existingRule.promRule = rule;\n } else {\n combinedGroup!.rules.push(promRuleToCombinedRule(rule, namespace, combinedGroup!));\n }\n });\n });\n}\n\nfunction promRuleToCombinedRule(rule: Rule, namespace: CombinedRuleNamespace, group: CombinedRuleGroup): CombinedRule {\n return {\n name: rule.name,\n query: rule.query,\n labels: rule.labels || {},\n annotations: isAlertingRule(rule) ? rule.annotations || {} : {},\n promRule: rule,\n namespace: namespace,\n group,\n };\n}\n\nfunction rulerRuleToCombinedRule(\n rule: RulerRuleDTO,\n namespace: CombinedRuleNamespace,\n group: CombinedRuleGroup\n): CombinedRule {\n return isAlertingRulerRule(rule)\n ? {\n name: rule.alert,\n query: rule.expr,\n labels: rule.labels || {},\n annotations: rule.annotations || {},\n rulerRule: rule,\n namespace,\n group,\n }\n : isRecordingRulerRule(rule)\n ? {\n name: rule.record,\n query: rule.expr,\n labels: rule.labels || {},\n annotations: {},\n rulerRule: rule,\n namespace,\n group,\n }\n : {\n name: rule.grafana_alert.title,\n query: '',\n labels: rule.labels || {},\n annotations: rule.annotations || {},\n rulerRule: rule,\n namespace,\n group,\n };\n}\n\n// find existing rule in group that matches the given prom rule\nfunction getExistingRuleInGroup(\n rule: Rule,\n group: CombinedRuleGroup,\n rulesSource: RulesSource\n): CombinedRule | undefined {\n if (isGrafanaRulesSource(rulesSource)) {\n // assume grafana groups have only the one rule. check name anyway because paranoid\n return group!.rules.find((existingRule) => existingRule.name === rule.name);\n }\n return (\n // try finding a rule that matches name, labels, annotations and query\n group!.rules.find(\n (existingRule) => !existingRule.promRule && isCombinedRuleEqualToPromRule(existingRule, rule, true)\n ) ??\n // if that fails, try finding a rule that only matches name, labels and annotations.\n // loki & prom can sometimes modify the query so it doesnt match, eg `2 > 1` becomes `1`\n group!.rules.find(\n (existingRule) => !existingRule.promRule && isCombinedRuleEqualToPromRule(existingRule, rule, false)\n )\n );\n}\n\nfunction isCombinedRuleEqualToPromRule(combinedRule: CombinedRule, rule: Rule, checkQuery = true): boolean {\n if (combinedRule.name === rule.name) {\n return (\n JSON.stringify([\n checkQuery ? hashQuery(combinedRule.query) : '',\n combinedRule.labels,\n combinedRule.annotations,\n ]) ===\n JSON.stringify([\n checkQuery ? hashQuery(rule.query) : '',\n rule.labels || {},\n isAlertingRule(rule) ? rule.annotations || {} : {},\n ])\n );\n }\n return false;\n}\n\n// there can be slight differences in how prom & ruler render a query, this will hash them accounting for the differences\nfunction hashQuery(query: string) {\n // one of them might be wrapped in parens\n if (query.length > 1 && query[0] === '(' && query[query.length - 1] === ')') {\n query = query.slice(1, -1);\n }\n // whitespace could be added or removed\n query = query.replace(/\\s|\\n/g, '');\n // labels matchers can be reordered, so sort the enitre string, esentially comparing just the character counts\n return query.split('').sort().join('');\n}\n"],"names":["pageTitle","RedirectToRuleViewer","props","name","sourceName","match","params","styles","useStyles2","getStyles","error","loading","result","rules","dispatched","useCombinedRulesMatching","title","Alert","className","errorMessage","message","stack","Array","isArray","LoadingPlaceholder","text","to","rulesSource","getRulesSourceByName","length","rule","createViewLink","param","map","index","Card","href","separator","Icon","namespace","group","labels","theme","css","colors","secondary","spacing","withErrorBoundary","style","RuleViewerLayout","wrapInContent","children","getPageStyles","PageToolbar","pageIcon","onGoBack","locationService","content","RuleViewerLayoutContent","padding","getContentStyles","wrapper","breakpoints","values","xxl","background","primary","border","weak","shape","borderRadius","useCombinedRule","identifier","ruleSourceName","requestState","useCombinedRulesLoader","combinedRules","useCombinedRuleNamespaces","useMemo","groups","id","ruleId","ruleName","push","rulesSourceName","dispatch","useDispatch","promRuleRequests","useUnifiedAlertingSelector","state","promRules","promRuleRequest","getRequestState","rulerRuleRequests","rulerRules","rulerRuleRequest","useAsync","async","fetchPromAndRulerRulesAction","isRulerNotSupportedResponse","undefined","slice","initialAsyncRequestState","promRulesResponses","rulerRulesResponses","cache","useRef","rulesSources","Error","getAllRulesSources","isCloudRulesSource","cached","current","namespaces","Object","entries","forEach","namespaceName","combinedGroup","interval","source_tenants","isAlertingRulerRule","alert","query","expr","annotations","rulerRule","isRecordingRulerRule","record","grafana_alert","rulerRuleToCombinedRule","addRulerGroupsToCombinedNamespace","find","g","existingRule","isGrafanaRulesSource","promRule","isCombinedRuleEqualToPromRule","getExistingRuleInGroup","isAlertingRule","promRuleToCombinedRule","addPromGroupsToCombinedNamespace","flat","flattenGrafanaManagedRules","newNamespace","flatMap","sort","a","b","localeCompare","combinedRule","checkQuery","JSON","stringify","hashQuery","replace","split","join"],"sourceRoot":""}
|