View File Name : AlertRuleListIndex.f0b17a6119cc5ea575ed.js.map
\n );\n});\n\nRulesGroup.displayName = 'RulesGroup';\n\nexport const getStyles = (theme: GrafanaTheme2) => {\n return {\n wrapper: css``,\n header: css`\n display: flex;\n flex-direction: row;\n align-items: center;\n padding: ${theme.spacing(1)} ${theme.spacing(1)} ${theme.spacing(1)} 0;\n flex-wrap: nowrap;\n border-bottom: 1px solid ${theme.colors.border.weak};\n\n &:hover {\n background-color: ${theme.components.table.rowHoverBackground};\n }\n `,\n headerStats: css`\n flex-shrink: 0;\n\n span {\n vertical-align: middle;\n }\n\n ${theme.breakpoints.down('sm')} {\n order: 2;\n width: 100%;\n padding-left: ${theme.spacing(1)};\n }\n `,\n groupName: css`\n margin-left: ${theme.spacing(1)};\n margin-bottom: 0;\n cursor: pointer;\n\n white-space: nowrap;\n overflow: hidden;\n text-overflow: ellipsis;\n `,\n spacer: css`\n flex: 1;\n `,\n collapseToggle: css`\n background: none;\n border: none;\n margin-top: -${theme.spacing(1)};\n margin-bottom: -${theme.spacing(1)};\n\n svg {\n margin-bottom: 0;\n }\n `,\n dataSourceIcon: css`\n width: ${theme.spacing(2)};\n height: ${theme.spacing(2)};\n margin-left: ${theme.spacing(2)};\n `,\n dataSourceOrigin: css`\n margin-right: 1em;\n color: ${theme.colors.text.disabled};\n `,\n actionsSeparator: css`\n margin: 0 ${theme.spacing(2)};\n `,\n actionIcons: css`\n width: 80px;\n align-items: center;\n\n flex-shrink: 0;\n `,\n rulesTable: css`\n margin: ${theme.spacing(2, 0)};\n `,\n rotate90: css`\n transform: rotate(90deg);\n `,\n };\n};\n","import { useMemo } from 'react';\n\nimport { CombinedRuleNamespace } from '../../../../../types/unified-alerting';\n\nexport function useCombinedGroupNamespace(namespaces: CombinedRuleNamespace[]) {\n return useMemo(\n () =>\n namespaces.flatMap((ns) =>\n ns.groups.map((g) => ({\n namespace: ns,\n group: g,\n }))\n ),\n [namespaces]\n );\n}\n","import { css } from '@emotion/css';\nimport pluralize from 'pluralize';\nimport React, { useMemo } from 'react';\nimport { useLocation } from 'react-router-dom';\n\nimport { GrafanaTheme2, urlUtil } from '@grafana/data';\nimport { LinkButton, LoadingPlaceholder, Pagination, Spinner, useStyles2 } from '@grafana/ui';\nimport { CombinedRuleNamespace } from 'app/types/unified-alerting';\n\nimport { DEFAULT_PER_PAGE_PAGINATION } from '../../../../../core/constants';\nimport { AlertingAction, useAlertingAbility } from '../../hooks/useAbilities';\nimport { usePagination } from '../../hooks/usePagination';\nimport { useUnifiedAlertingSelector } from '../../hooks/useUnifiedAlertingSelector';\nimport { getPaginationStyles } from '../../styles/pagination';\nimport { getRulesDataSources, getRulesSourceUid } from '../../utils/datasource';\nimport { isAsyncRequestStatePending } from '../../utils/redux';\n\nimport { RulesGroup } from './RulesGroup';\nimport { useCombinedGroupNamespace } from './useCombinedGroupNamespace';\n\ninterface Props {\n namespaces: CombinedRuleNamespace[];\n expandAll: boolean;\n}\n\nexport const CloudRules = ({ namespaces, expandAll }: Props) => {\n const styles = useStyles2(getStyles);\n\n const dsConfigs = useUnifiedAlertingSelector((state) => state.dataSources);\n const promRules = useUnifiedAlertingSelector((state) => state.promRules);\n const rulesDataSources = useMemo(getRulesDataSources, []);\n const groupsWithNamespaces = useCombinedGroupNamespace(namespaces);\n\n const dataSourcesLoading = useMemo(\n () =>\n rulesDataSources.filter(\n (ds) => isAsyncRequestStatePending(promRules[ds.name]) || isAsyncRequestStatePending(dsConfigs[ds.name])\n ),\n [promRules, dsConfigs, rulesDataSources]\n );\n\n const hasSomeResults = rulesDataSources.some((ds) => Boolean(promRules[ds.name]?.result?.length));\n\n const hasDataSourcesConfigured = rulesDataSources.length > 0;\n const hasDataSourcesLoading = dataSourcesLoading.length > 0;\n const hasNamespaces = namespaces.length > 0;\n\n const { numberOfPages, onPageChange, page, pageItems } = usePagination(\n groupsWithNamespaces,\n 1,\n DEFAULT_PER_PAGE_PAGINATION\n );\n\n return (\n
\n \n
\n
Mimir / Cortex / Loki \n {dataSourcesLoading.length ? (\n
\n ) : (\n
\n )}\n
\n
\n
\n\n {pageItems.map(({ group, namespace }) => {\n return (\n \n );\n })}\n\n {!hasDataSourcesConfigured && There are no Prometheus or Loki data sources configured.
}\n {hasDataSourcesConfigured && !hasDataSourcesLoading && !hasNamespaces && No rules found.
}\n {!hasSomeResults && hasDataSourcesLoading && }\n\n \n \n );\n};\n\nconst getStyles = (theme: GrafanaTheme2) => ({\n loader: css`\n margin-bottom: 0;\n `,\n sectionHeader: css`\n display: flex;\n justify-content: space-between;\n `,\n wrapper: css`\n margin-bottom: ${theme.spacing(4)};\n `,\n spinner: css`\n text-align: center;\n padding: ${theme.spacing(2)};\n `,\n pagination: getPaginationStyles(theme),\n headerRow: css({\n display: 'flex',\n alignItems: 'center',\n justifyContent: 'space-between',\n width: '100%',\n marginBottom: theme.spacing(1),\n }),\n});\n\nexport function CreateRecordingRuleButton() {\n const [createCloudRuleSupported, createCloudRuleAllowed] = useAlertingAbility(AlertingAction.CreateExternalAlertRule);\n\n const location = useLocation();\n\n const canCreateCloudRules = createCloudRuleSupported && createCloudRuleAllowed;\n\n if (canCreateCloudRules) {\n return (\n
\n New recording rule\n \n );\n }\n return null;\n}\n","import React, { useState } from 'react';\n\nimport { LoadingPlaceholder } from '@grafana/ui';\n\nimport { alertRuleApi } from '../../api/alertRuleApi';\n\nimport { FileExportPreview } from './FileExportPreview';\nimport { GrafanaExportDrawer } from './GrafanaExportDrawer';\nimport { allGrafanaExportProviders, ExportFormats } from './providers';\n\ninterface GrafanaRulesExporterProps {\n onClose: () => void;\n}\n\nexport function GrafanaRulesExporter({ onClose }: GrafanaRulesExporterProps) {\n const [activeTab, setActiveTab] = useState
('yaml');\n\n return (\n \n \n \n );\n}\n\ninterface GrafanaRulesExportPreviewProps {\n exportFormat: ExportFormats;\n onClose: () => void;\n}\n\nfunction GrafanaRulesExportPreview({ exportFormat, onClose }: GrafanaRulesExportPreviewProps) {\n const { currentData: rulesDefinition = '', isFetching } = alertRuleApi.endpoints.exportRules.useQuery({\n format: exportFormat,\n });\n\n const downloadFileName = `alert-rules-${new Date().getTime()}`;\n\n if (isFetching) {\n return ;\n }\n\n return (\n \n );\n}\n","import { css } from '@emotion/css';\nimport React from 'react';\nimport { useToggle } from 'react-use';\n\nimport { GrafanaTheme2 } from '@grafana/data';\nimport { Button, LoadingPlaceholder, Pagination, Spinner, useStyles2 } from '@grafana/ui';\nimport { useQueryParams } from 'app/core/hooks/useQueryParams';\nimport { CombinedRuleNamespace } from 'app/types/unified-alerting';\n\nimport { DEFAULT_PER_PAGE_PAGINATION } from '../../../../../core/constants';\nimport { AlertingAction, useAlertingAbility } from '../../hooks/useAbilities';\nimport { flattenGrafanaManagedRules } from '../../hooks/useCombinedRuleNamespaces';\nimport { usePagination } from '../../hooks/usePagination';\nimport { useUnifiedAlertingSelector } from '../../hooks/useUnifiedAlertingSelector';\nimport { getPaginationStyles } from '../../styles/pagination';\nimport { GRAFANA_RULES_SOURCE_NAME } from '../../utils/datasource';\nimport { initialAsyncRequestState } from '../../utils/redux';\nimport { GrafanaRulesExporter } from '../export/GrafanaRulesExporter';\n\nimport { RulesGroup } from './RulesGroup';\nimport { useCombinedGroupNamespace } from './useCombinedGroupNamespace';\n\ninterface Props {\n namespaces: CombinedRuleNamespace[];\n expandAll: boolean;\n}\n\nexport const GrafanaRules = ({ namespaces, expandAll }: Props) => {\n const styles = useStyles2(getStyles);\n const [queryParams] = useQueryParams();\n\n const { prom, ruler } = useUnifiedAlertingSelector((state) => ({\n prom: state.promRules[GRAFANA_RULES_SOURCE_NAME] || initialAsyncRequestState,\n ruler: state.rulerRules[GRAFANA_RULES_SOURCE_NAME] || initialAsyncRequestState,\n }));\n\n const loading = prom.loading || ruler.loading;\n const hasResult = !!prom.result || !!ruler.result;\n\n const wantsListView = queryParams['view'] === 'list';\n const namespacesFormat = wantsListView ? flattenGrafanaManagedRules(namespaces) : namespaces;\n\n const groupsWithNamespaces = useCombinedGroupNamespace(namespacesFormat);\n\n const { numberOfPages, onPageChange, page, pageItems } = usePagination(\n groupsWithNamespaces,\n 1,\n DEFAULT_PER_PAGE_PAGINATION\n );\n\n const [exportRulesSupported, exportRulesAllowed] = useAlertingAbility(AlertingAction.ExportGrafanaManagedRules);\n const canExportRules = exportRulesSupported && exportRulesAllowed;\n\n const [showExportDrawer, toggleShowExportDrawer] = useToggle(false);\n const hasGrafanaAlerts = namespaces.length > 0;\n\n return (\n \n \n
\n
Grafana \n {loading ?
:
}\n {hasGrafanaAlerts && canExportRules && (\n
\n Export rules\n \n )}\n
\n
\n\n {pageItems.map(({ group, namespace }) => (\n \n ))}\n {hasResult && namespacesFormat?.length === 0 && No rules found.
}\n {!hasResult && loading && }\n \n {canExportRules && showExportDrawer && }\n \n );\n};\n\nconst getStyles = (theme: GrafanaTheme2) => ({\n loader: css`\n margin-bottom: 0;\n `,\n sectionHeader: css`\n display: flex;\n justify-content: space-between;\n margin-bottom: ${theme.spacing(1)};\n `,\n wrapper: css`\n margin-bottom: ${theme.spacing(4)};\n `,\n spinner: css`\n text-align: center;\n padding: ${theme.spacing(2)};\n `,\n pagination: getPaginationStyles(theme),\n headerRow: css({\n display: 'flex',\n alignItems: 'center',\n justifyContent: 'space-between',\n width: '100%',\n flexDirection: 'row',\n }),\n});\n","import React, { useEffect, useMemo } from 'react';\n\nimport { CombinedRuleNamespace } from 'app/types/unified-alerting';\n\nimport { LogMessages, logInfo } from '../../Analytics';\nimport { AlertingAction } from '../../hooks/useAbilities';\nimport { isCloudRulesSource, isGrafanaRulesSource } from '../../utils/datasource';\nimport { Authorize } from '../Authorize';\n\nimport { CloudRules } from './CloudRules';\nimport { GrafanaRules } from './GrafanaRules';\n\ninterface Props {\n namespaces: CombinedRuleNamespace[];\n expandAll: boolean;\n}\n\nexport const RuleListGroupView = ({ namespaces, expandAll }: Props) => {\n const [grafanaNamespaces, cloudNamespaces] = useMemo(() => {\n const sorted = namespaces\n .map((namespace) => ({\n ...namespace,\n groups: namespace.groups.sort((a, b) => a.name.localeCompare(b.name)),\n }))\n .sort((a, b) => a.name.localeCompare(b.name));\n return [\n sorted.filter((ns) => isGrafanaRulesSource(ns.rulesSource)),\n sorted.filter((ns) => isCloudRulesSource(ns.rulesSource)),\n ];\n }, [namespaces]);\n\n useEffect(() => {\n logInfo(LogMessages.loadedList);\n }, []);\n\n return (\n <>\n \n \n \n \n \n \n >\n );\n};\n","import { css } from '@emotion/css';\nimport React, { useState } from 'react';\n\nimport { GrafanaTheme2 } from '@grafana/data';\nimport { useStyles2 } from '@grafana/ui';\nimport { CombinedRule } from 'app/types/unified-alerting';\nimport { PromAlertingRuleState } from 'app/types/unified-alerting-dto';\n\nimport { alertStateToReadable } from '../../utils/rules';\nimport { CollapseToggle } from '../CollapseToggle';\n\nimport { RulesTable } from './RulesTable';\n\ninterface Props {\n rules: CombinedRule[];\n state: PromAlertingRuleState;\n defaultCollapsed?: boolean;\n}\n\nexport const RuleListStateSection = ({ rules, state, defaultCollapsed = false }: Props) => {\n const [collapsed, setCollapsed] = useState(defaultCollapsed);\n const styles = useStyles2(getStyles);\n return (\n <>\n \n setCollapsed(!collapsed)}\n />\n {alertStateToReadable(state)} ({rules.length})\n \n {!collapsed && }\n >\n );\n};\n\nconst getStyles = (theme: GrafanaTheme2) => ({\n collapseToggle: css`\n vertical-align: middle;\n `,\n header: css`\n margin-top: ${theme.spacing(2)};\n `,\n rulesTable: css`\n margin-top: ${theme.spacing(3)};\n `,\n});\n","import React, { useMemo } from 'react';\n\nimport { useQueryParams } from 'app/core/hooks/useQueryParams';\nimport { CombinedRule, CombinedRuleNamespace } from 'app/types/unified-alerting';\nimport { PromAlertingRuleState } from 'app/types/unified-alerting-dto';\n\nimport { getFiltersFromUrlParams } from '../../utils/misc';\nimport { isAlertingRule } from '../../utils/rules';\n\nimport { RuleListStateSection } from './RuleListStateSection';\n\ninterface Props {\n namespaces: CombinedRuleNamespace[];\n expandAll?: boolean;\n}\n\ntype GroupedRules = Record;\n\nexport const RuleListStateView = ({ namespaces }: Props) => {\n const filters = getFiltersFromUrlParams(useQueryParams()[0]);\n\n const groupedRules = useMemo(() => {\n const result: GroupedRules = {\n [PromAlertingRuleState.Firing]: [],\n [PromAlertingRuleState.Inactive]: [],\n [PromAlertingRuleState.Pending]: [],\n };\n\n namespaces.forEach((namespace) =>\n namespace.groups.forEach((group) =>\n group.rules.forEach((rule) => {\n if (rule.promRule && isAlertingRule(rule.promRule)) {\n result[rule.promRule.state].push(rule);\n }\n })\n )\n );\n\n Object.values(result).forEach((rules) => rules.sort((a, b) => a.name.localeCompare(b.name)));\n\n return result;\n }, [namespaces]);\n return (\n <>\n {(!filters.alertState || filters.alertState === PromAlertingRuleState.Firing) && (\n \n )}\n {(!filters.alertState || filters.alertState === PromAlertingRuleState.Pending) && (\n \n )}\n {(!filters.alertState || filters.alertState === PromAlertingRuleState.Inactive) && (\n \n )}\n >\n );\n};\n","import uFuzzy from '@leeoniya/ufuzzy';\nimport { produce } from 'immer';\nimport { chain, compact, isEmpty } from 'lodash';\nimport { useCallback, useEffect, useMemo } from 'react';\n\nimport { getDataSourceSrv } from '@grafana/runtime';\nimport { Matcher } from 'app/plugins/datasource/alertmanager/types';\nimport { CombinedRuleGroup, CombinedRuleNamespace, Rule } from 'app/types/unified-alerting';\nimport { isPromAlertingRuleState, PromRuleType, RulerGrafanaRuleDTO } from 'app/types/unified-alerting-dto';\n\nimport { applySearchFilterToQuery, getSearchFilterFromQuery, RulesFilter } from '../search/rulesSearchParser';\nimport { labelsMatchMatchers, matcherToMatcherField, parseMatchers } from '../utils/alertmanager';\nimport { Annotation } from '../utils/constants';\nimport { isCloudRulesSource } from '../utils/datasource';\nimport { parseMatcher } from '../utils/matchers';\nimport { getRuleHealth, isAlertingRule, isGrafanaRulerRule, isPromRuleType } from '../utils/rules';\n\nimport { calculateGroupTotals, calculateRuleFilteredTotals, calculateRuleTotals } from './useCombinedRuleNamespaces';\nimport { useURLSearchParams } from './useURLSearchParams';\n\n// if the search term is longer than MAX_NEEDLE_SIZE we disable Levenshtein distance\nconst MAX_NEEDLE_SIZE = 25;\nconst INFO_THRESHOLD = Infinity;\n\nexport function useRulesFilter() {\n const [queryParams, updateQueryParams] = useURLSearchParams();\n const searchQuery = queryParams.get('search') ?? '';\n\n const filterState = useMemo(() => getSearchFilterFromQuery(searchQuery), [searchQuery]);\n const hasActiveFilters = useMemo(() => Object.values(filterState).some((filter) => !isEmpty(filter)), [filterState]);\n\n const updateFilters = useCallback(\n (newFilter: RulesFilter) => {\n const newSearchQuery = applySearchFilterToQuery(searchQuery, newFilter);\n updateQueryParams({ search: newSearchQuery });\n },\n [searchQuery, updateQueryParams]\n );\n\n const setSearchQuery = useCallback(\n (newSearchQuery: string | undefined) => {\n updateQueryParams({ search: newSearchQuery });\n },\n [updateQueryParams]\n );\n\n // Handle legacy filters\n useEffect(() => {\n const legacyFilters = {\n dataSource: queryParams.get('dataSource') ?? undefined,\n alertState: queryParams.get('alertState') ?? undefined,\n ruleType: queryParams.get('ruleType') ?? undefined,\n labels: parseMatchers(queryParams.get('queryString') ?? '').map(matcherToMatcherField),\n };\n\n const hasLegacyFilters = Object.values(legacyFilters).some((legacyFilter) => !isEmpty(legacyFilter));\n if (hasLegacyFilters) {\n updateQueryParams({ dataSource: undefined, alertState: undefined, ruleType: undefined, queryString: undefined });\n // Existing query filters takes precedence over legacy ones\n updateFilters(\n produce(filterState, (draft) => {\n draft.dataSourceNames ??= legacyFilters.dataSource ? [legacyFilters.dataSource] : [];\n if (legacyFilters.alertState && isPromAlertingRuleState(legacyFilters.alertState)) {\n draft.ruleState ??= legacyFilters.alertState;\n }\n if (legacyFilters.ruleType && isPromRuleType(legacyFilters.ruleType)) {\n draft.ruleType ??= legacyFilters.ruleType;\n }\n if (draft.labels.length === 0 && legacyFilters.labels.length > 0) {\n const legacyLabelsAsStrings = legacyFilters.labels.map(\n ({ name, operator, value }) => `${name}${operator}${value}`\n );\n draft.labels.push(...legacyLabelsAsStrings);\n }\n })\n );\n }\n }, [queryParams, updateFilters, filterState, updateQueryParams]);\n\n return { filterState, hasActiveFilters, searchQuery, setSearchQuery, updateFilters };\n}\n\nexport const useFilteredRules = (namespaces: CombinedRuleNamespace[], filterState: RulesFilter) => {\n return useMemo(() => {\n const filteredRules = filterRules(namespaces, filterState);\n\n // Totals recalculation is a workaround for the lack of server-side filtering\n filteredRules.forEach((namespace) => {\n namespace.groups.forEach((group) => {\n group.rules.forEach((rule) => {\n if (isAlertingRule(rule.promRule)) {\n rule.instanceTotals = calculateRuleTotals(rule.promRule);\n rule.filteredInstanceTotals = calculateRuleFilteredTotals(rule.promRule);\n }\n });\n\n group.totals = calculateGroupTotals({\n rules: group.rules.map((r) => r.promRule).filter((r): r is Rule => !!r),\n });\n });\n });\n\n return filteredRules;\n }, [namespaces, filterState]);\n};\n\nexport const filterRules = (\n namespaces: CombinedRuleNamespace[],\n filterState: RulesFilter = { dataSourceNames: [], labels: [], freeFormWords: [] }\n): CombinedRuleNamespace[] => {\n let filteredNamespaces = namespaces;\n\n const dataSourceFilter = filterState.dataSourceNames;\n if (dataSourceFilter.length) {\n filteredNamespaces = filteredNamespaces.filter(({ rulesSource }) =>\n isCloudRulesSource(rulesSource) ? dataSourceFilter.includes(rulesSource.name) : true\n );\n }\n\n const namespaceFilter = filterState.namespace;\n\n if (namespaceFilter) {\n const namespaceHaystack = filteredNamespaces.map((ns) => ns.name);\n\n const ufuzzy = getSearchInstance(namespaceFilter);\n const [idxs, info, order] = ufuzzy.search(\n namespaceHaystack,\n namespaceFilter,\n getOutOfOrderLimit(namespaceFilter),\n INFO_THRESHOLD\n );\n if (info && order) {\n filteredNamespaces = order.map((idx) => filteredNamespaces[info.idx[idx]]);\n } else if (idxs) {\n filteredNamespaces = idxs.map((idx) => filteredNamespaces[idx]);\n }\n }\n\n // If a namespace and group have rules that match the rules filters then keep them.\n return filteredNamespaces.reduce(reduceNamespaces(filterState), []);\n};\n\nconst reduceNamespaces = (filterState: RulesFilter) => {\n return (namespaceAcc: CombinedRuleNamespace[], namespace: CombinedRuleNamespace) => {\n const groupNameFilter = filterState.groupName;\n let filteredGroups = namespace.groups;\n\n if (groupNameFilter) {\n const groupsHaystack = filteredGroups.map((g) => g.name);\n const ufuzzy = getSearchInstance(groupNameFilter);\n\n const [idxs, info, order] = ufuzzy.search(\n groupsHaystack,\n groupNameFilter,\n getOutOfOrderLimit(groupNameFilter),\n INFO_THRESHOLD\n );\n if (info && order) {\n filteredGroups = order.map((idx) => filteredGroups[info.idx[idx]]);\n } else if (idxs) {\n filteredGroups = idxs.map((idx) => filteredGroups[idx]);\n }\n }\n\n filteredGroups = filteredGroups.reduce(reduceGroups(filterState), []);\n\n if (filteredGroups.length) {\n namespaceAcc.push({\n ...namespace,\n groups: filteredGroups,\n });\n }\n\n return namespaceAcc;\n };\n};\n\n// Reduces groups to only groups that have rules matching the filters\nconst reduceGroups = (filterState: RulesFilter) => {\n const ruleNameQuery = filterState.ruleName ?? filterState.freeFormWords.join(' ');\n\n return (groupAcc: CombinedRuleGroup[], group: CombinedRuleGroup) => {\n let filteredRules = group.rules;\n\n if (ruleNameQuery) {\n const rulesHaystack = filteredRules.map((r) => r.name);\n const ufuzzy = getSearchInstance(ruleNameQuery);\n\n const [idxs, info, order] = ufuzzy.search(\n rulesHaystack,\n ruleNameQuery,\n getOutOfOrderLimit(ruleNameQuery),\n INFO_THRESHOLD\n );\n if (info && order) {\n filteredRules = order.map((idx) => filteredRules[info.idx[idx]]);\n } else if (idxs) {\n filteredRules = idxs.map((idx) => filteredRules[idx]);\n }\n }\n\n filteredRules = filteredRules.filter((rule) => {\n const promRuleDefition = rule.promRule;\n\n // this will track what properties we're checking predicates for\n // all predicates must be \"true\" to include the rule in the result set\n // (this will result in an AND operation for our matchers)\n const matchesFilterFor = chain(filterState)\n // ⚠️ keep this list of properties we filter for here up-to-date ⚠️\n // We are ignoring predicates we've matched before we get here (like \"freeFormWords\")\n .pick(['ruleType', 'dataSourceNames', 'ruleHealth', 'labels', 'ruleState', 'dashboardUid'])\n .omitBy(isEmpty)\n .mapValues(() => false)\n .value();\n\n if ('ruleType' in matchesFilterFor && filterState.ruleType === promRuleDefition?.type) {\n matchesFilterFor.ruleType = true;\n }\n\n if ('dataSourceNames' in matchesFilterFor) {\n if (isGrafanaRulerRule(rule.rulerRule)) {\n const doesNotQueryDs = isQueryingDataSource(rule.rulerRule, filterState);\n\n if (doesNotQueryDs) {\n matchesFilterFor.dataSourceNames = true;\n }\n } else {\n matchesFilterFor.dataSourceNames = true;\n }\n }\n\n if ('ruleHealth' in filterState && promRuleDefition) {\n const ruleHealth = getRuleHealth(promRuleDefition.health);\n const match = filterState.ruleHealth === ruleHealth;\n\n if (match) {\n matchesFilterFor.ruleHealth = true;\n }\n }\n\n // Query strings can match alert name, label keys, and label values\n if ('labels' in matchesFilterFor) {\n const matchers = compact(filterState.labels.map(looseParseMatcher));\n\n // check if the label we query for exists in _either_ the rule definition or in any of its alerts\n const doRuleLabelsMatchQuery = matchers.length > 0 && labelsMatchMatchers(rule.labels, matchers);\n const doAlertsContainMatchingLabels =\n matchers.length > 0 &&\n promRuleDefition &&\n promRuleDefition.type === PromRuleType.Alerting &&\n promRuleDefition.alerts &&\n promRuleDefition.alerts.some((alert) => labelsMatchMatchers(alert.labels, matchers));\n\n if (doRuleLabelsMatchQuery || doAlertsContainMatchingLabels) {\n matchesFilterFor.labels = true;\n }\n }\n\n if ('ruleState' in matchesFilterFor) {\n const promRule = rule.promRule;\n const hasPromRuleDefinition = promRule && isAlertingRule(promRule);\n\n const ruleStateMatches = hasPromRuleDefinition && promRule.state === filterState.ruleState;\n\n if (ruleStateMatches) {\n matchesFilterFor.ruleState = true;\n }\n }\n\n if (\n 'dashboardUid' in matchesFilterFor &&\n rule.annotations[Annotation.dashboardUID] === filterState.dashboardUid\n ) {\n matchesFilterFor.dashboardUid = true;\n }\n\n return Object.values(matchesFilterFor).every((match) => match === true);\n });\n\n // Add rules to the group that match the rule list filters\n if (filteredRules.length) {\n groupAcc.push({\n ...group,\n rules: filteredRules,\n });\n }\n return groupAcc;\n };\n};\n\n// apply an outOfOrder limit which helps to limit the number of permutations to search for\n// and prevents the browser from hanging\nfunction getOutOfOrderLimit(searchTerm: string) {\n const ufuzzy = getSearchInstance(searchTerm);\n\n const termCount = ufuzzy.split(searchTerm).length;\n return termCount < 5 ? 4 : 0;\n}\n\nfunction looseParseMatcher(matcherQuery: string): Matcher | undefined {\n try {\n return parseMatcher(matcherQuery);\n } catch {\n // Try to createa a matcher than matches all values for a given key\n return { name: matcherQuery, value: '', isRegex: true, isEqual: true };\n }\n}\n\n// determine which search instance to use, very long search terms should match without checking for Levenshtein distance\nfunction getSearchInstance(searchTerm: string): uFuzzy {\n const searchTermExeedsMaxNeedleSize = searchTerm.length > MAX_NEEDLE_SIZE;\n\n // Options details can be found here https://github.com/leeoniya/uFuzzy#options\n // The following configuration complies with Damerau-Levenshtein distance\n // https://en.wikipedia.org/wiki/Damerau%E2%80%93Levenshtein_distance\n return new uFuzzy({\n // we will disable Levenshtein distance for very long search terms – this will help with performance\n // as it will avoid creating a very complex regular expression\n intraMode: searchTermExeedsMaxNeedleSize ? 0 : 1,\n // split search terms only on whitespace, this will significantly reduce the amount of regex permutations to test\n // and is important for performance with large amount of rules and large needle\n interSplit: '\\\\s+',\n });\n}\n\nconst isQueryingDataSource = (rulerRule: RulerGrafanaRuleDTO, filterState: RulesFilter): boolean => {\n if (!filterState.dataSourceNames?.length) {\n return true;\n }\n\n return !!rulerRule.grafana_alert.data.find((query) => {\n if (!query.datasourceUid) {\n return false;\n }\n const ds = getDataSourceSrv().getInstanceSettings(query.datasourceUid);\n return ds?.name && filterState?.dataSourceNames?.includes(ds.name);\n });\n};\n","import React, { useState } from 'react';\nimport { PopValueActionMeta, RemoveValueActionMeta } from 'react-select';\n\nimport {\n DataSourceInstanceSettings,\n getDataSourceUID,\n isUnsignedPluginSignature,\n SelectableValue,\n} from '@grafana/data';\nimport { selectors } from '@grafana/e2e-selectors';\nimport { getDataSourceSrv, DataSourcePickerState, DataSourcePickerProps } from '@grafana/runtime';\nimport { ExpressionDatasourceRef } from '@grafana/runtime/src/utils/DataSourceWithBackend';\nimport { ActionMeta, HorizontalGroup, PluginSignatureBadge, MultiSelect } from '@grafana/ui';\n\nimport { isDataSourceManagingAlerts } from '../../utils/datasource';\n\nexport interface MultipleDataSourcePickerProps extends Omit {\n onChange: (ds: DataSourceInstanceSettings, action: 'add' | 'remove') => void;\n current: string[] | undefined;\n}\n\nexport const MultipleDataSourcePicker = (props: MultipleDataSourcePickerProps) => {\n const dataSourceSrv = getDataSourceSrv();\n\n const [state, setState] = useState();\n\n const onChange = (items: Array>, actionMeta: ActionMeta) => {\n if (actionMeta.action === 'clear' && props.onClear) {\n props.onClear();\n return;\n }\n\n const selectedItem = items[items.length - 1];\n\n let dataSourceName, action: 'add' | 'remove';\n\n if (actionMeta.action === 'pop-value' || actionMeta.action === 'remove-value') {\n const castedActionMeta:\n | RemoveValueActionMeta>\n | PopValueActionMeta> = actionMeta;\n dataSourceName = castedActionMeta.removedValue?.value;\n action = 'remove';\n } else {\n dataSourceName = selectedItem.value;\n action = 'add';\n }\n\n const dsSettings = dataSourceSrv.getInstanceSettings(dataSourceName);\n\n if (dsSettings) {\n props.onChange(dsSettings, action);\n setState({ error: undefined });\n }\n };\n\n const getCurrentValue = (): Array> | undefined => {\n const { current, hideTextValue, noDefault } = props;\n if (!current && noDefault) {\n return;\n }\n\n return current?.map((dataSourceName: string) => {\n const ds = dataSourceSrv.getInstanceSettings(dataSourceName);\n if (ds) {\n return {\n label: ds.name.slice(0, 37),\n value: ds.name,\n imgUrl: ds.meta.info.logos.small,\n hideText: hideTextValue,\n meta: ds.meta,\n };\n }\n\n const uid = getDataSourceUID(dataSourceName);\n\n if (uid === ExpressionDatasourceRef.uid || uid === ExpressionDatasourceRef.name) {\n return { label: uid, value: uid, hideText: hideTextValue };\n }\n\n return {\n label: (uid ?? 'no name') + ' - not found',\n value: uid ?? undefined,\n imgUrl: '',\n hideText: hideTextValue,\n };\n });\n };\n\n const getDataSourceOptions = () => {\n const { alerting, tracing, metrics, mixed, dashboard, variables, annotations, pluginId, type, filter, logs } =\n props;\n\n const dataSources = dataSourceSrv.getList({\n alerting,\n tracing,\n metrics,\n logs,\n dashboard,\n mixed,\n variables,\n annotations,\n pluginId,\n filter,\n type,\n });\n\n const alertManagingDs = dataSources.filter(isDataSourceManagingAlerts).map((ds) => ({\n value: ds.name,\n label: `${ds.name}${ds.isDefault ? ' (default)' : ''}`,\n imgUrl: ds.meta.info.logos.small,\n meta: ds.meta,\n }));\n\n const nonAlertManagingDs = dataSources\n .filter((ds) => !isDataSourceManagingAlerts(ds))\n .map((ds) => ({\n value: ds.name,\n label: `${ds.name}${ds.isDefault ? ' (default)' : ''}`,\n imgUrl: ds.meta.info.logos.small,\n meta: ds.meta,\n }));\n\n const groupedOptions = [\n { label: 'Data sources with configured alert rules', options: alertManagingDs, expanded: true },\n { label: 'Other data sources', options: nonAlertManagingDs, expanded: true },\n ];\n\n return groupedOptions;\n };\n\n const {\n autoFocus,\n onBlur,\n onClear,\n openMenuOnFocus,\n placeholder,\n width,\n inputId,\n disabled = false,\n isLoading = false,\n } = props;\n\n const options = getDataSourceOptions();\n const value = getCurrentValue();\n const isClearable = typeof onClear === 'function';\n\n return (\n \n
{\n if (o.meta && isUnsignedPluginSignature(o.meta.signature) && o !== value) {\n return (\n \n {o.label} \n \n );\n }\n return o.label || '';\n }}\n />\n \n );\n};\n","import { css } from '@emotion/css';\nimport React, { useEffect, useRef, useState } from 'react';\nimport { useForm } from 'react-hook-form';\n\nimport { DataSourceInstanceSettings, GrafanaTheme2, SelectableValue } from '@grafana/data';\nimport { Button, Field, Icon, Input, Label, RadioButtonGroup, Stack, Tooltip, useStyles2 } from '@grafana/ui';\nimport { DashboardPicker } from 'app/core/components/Select/DashboardPicker';\nimport { useQueryParams } from 'app/core/hooks/useQueryParams';\nimport { PromAlertingRuleState, PromRuleType } from 'app/types/unified-alerting-dto';\n\nimport { logInfo, LogMessages } from '../../Analytics';\nimport { useRulesFilter } from '../../hooks/useFilteredRules';\nimport { RuleHealth } from '../../search/rulesSearchParser';\nimport { alertStateToReadable } from '../../utils/rules';\nimport { HoverCard } from '../HoverCard';\n\nimport { MultipleDataSourcePicker } from './MultipleDataSourcePicker';\n\nconst ViewOptions: SelectableValue[] = [\n {\n icon: 'folder',\n label: 'Grouped',\n value: 'grouped',\n },\n {\n icon: 'list-ul',\n label: 'List',\n value: 'list',\n },\n {\n icon: 'heart-rate',\n label: 'State',\n value: 'state',\n },\n];\n\nconst RuleTypeOptions: SelectableValue[] = [\n {\n label: 'Alert ',\n value: PromRuleType.Alerting,\n },\n {\n label: 'Recording ',\n value: PromRuleType.Recording,\n },\n];\n\nconst RuleHealthOptions: SelectableValue[] = [\n { label: 'Ok', value: RuleHealth.Ok },\n { label: 'No Data', value: RuleHealth.NoData },\n { label: 'Error', value: RuleHealth.Error },\n];\n\ninterface RulesFilerProps {\n onFilterCleared?: () => void;\n}\n\nconst RuleStateOptions = Object.entries(PromAlertingRuleState).map(([key, value]) => ({\n label: alertStateToReadable(value),\n value,\n}));\n\nconst RulesFilter = ({ onFilterCleared = () => undefined }: RulesFilerProps) => {\n const styles = useStyles2(getStyles);\n const [queryParams, setQueryParams] = useQueryParams();\n const { filterState, hasActiveFilters, searchQuery, setSearchQuery, updateFilters } = useRulesFilter();\n\n // This key is used to force a rerender on the inputs when the filters are cleared\n const [filterKey, setFilterKey] = useState(Math.floor(Math.random() * 100));\n const dataSourceKey = `dataSource-${filterKey}`;\n const queryStringKey = `queryString-${filterKey}`;\n\n const searchQueryRef = useRef(null);\n const { handleSubmit, register, setValue } = useForm<{ searchQuery: string }>({ defaultValues: { searchQuery } });\n const { ref, ...rest } = register('searchQuery');\n\n useEffect(() => {\n setValue('searchQuery', searchQuery);\n }, [searchQuery, setValue]);\n\n const handleDataSourceChange = (dataSourceValue: DataSourceInstanceSettings, action: 'add' | 'remove') => {\n const dataSourceNames =\n action === 'add'\n ? [...filterState.dataSourceNames].concat([dataSourceValue.name])\n : filterState.dataSourceNames.filter((name) => name !== dataSourceValue.name);\n\n updateFilters({\n ...filterState,\n dataSourceNames,\n });\n\n setFilterKey((key) => key + 1);\n };\n\n const handleDashboardChange = (dashboardUid: string | undefined) => {\n updateFilters({ ...filterState, dashboardUid });\n };\n\n const clearDataSource = () => {\n updateFilters({ ...filterState, dataSourceNames: [] });\n setFilterKey((key) => key + 1);\n };\n\n const handleAlertStateChange = (value: PromAlertingRuleState) => {\n logInfo(LogMessages.clickingAlertStateFilters);\n updateFilters({ ...filterState, ruleState: value });\n };\n\n const handleViewChange = (view: string) => {\n setQueryParams({ view });\n };\n\n const handleRuleTypeChange = (ruleType: PromRuleType) => {\n updateFilters({ ...filterState, ruleType });\n };\n\n const handleRuleHealthChange = (ruleHealth: RuleHealth) => {\n updateFilters({ ...filterState, ruleHealth });\n };\n\n const handleClearFiltersClick = () => {\n setSearchQuery(undefined);\n onFilterCleared();\n\n setTimeout(() => setFilterKey(filterKey + 1), 100);\n };\n\n const searchIcon = ;\n return (\n \n
\n \n \n \n Search by data sources \n \n \n Data sources containing configured alert rules are Mimir or Loki data sources where alert\n rules are stored and evaluated in the data source itself.\n
\n \n In these data sources, you can select Manage alerts via Alerting UI to be able to manage these\n alert rules in the Grafana UI as well as in the data source where they were configured.\n
\n \n }\n >\n \n \n \n \n }\n >\n \n \n\n Dashboard}\n >\n {/* The key prop is to clear the picker value */}\n {/* DashboardPicker doesn't do that itself when value is undefined */}\n handleDashboardChange(value?.uid)}\n isClearable\n cacheOptions\n />\n \n\n \n State \n \n
\n \n Rule type \n \n
\n \n Health \n \n
\n \n \n \n \n \n View as \n \n
\n \n {hasActiveFilters && (\n \n \n Clear filters\n \n
\n )}\n \n \n \n );\n};\n\nconst getStyles = (theme: GrafanaTheme2) => {\n return {\n container: css({\n marginBottom: theme.spacing(1),\n }),\n dsPickerContainer: css({\n width: theme.spacing(60),\n flexGrow: 0,\n margin: 0,\n }),\n dashboardPickerContainer: css({\n minWidth: theme.spacing(50),\n }),\n searchInput: css({\n flex: 1,\n margin: 0,\n }),\n };\n};\n\nfunction SearchQueryHelp() {\n const styles = useStyles2(helpStyles);\n\n return (\n