import { css } from '@emotion/css'; import { isEqual, orderBy, uniqWith } from 'lodash'; import React, { useEffect, useState } from 'react'; import { useDispatch } from 'react-redux'; import { useDebounce } from 'react-use'; import { GrafanaTheme2, SelectableValue } from '@grafana/data'; import { Stack } from '@grafana/experimental'; import { Card, FilterInput, Icon, Pagination, Select, TagList, useStyles2 } from '@grafana/ui'; import { DEFAULT_PER_PAGE_PAGINATION } from 'app/core/constants'; import { getQueryParamValue } from 'app/core/utils/query'; import { FolderState } from 'app/types'; import { CombinedRule } from 'app/types/unified-alerting'; import { useCombinedRuleNamespaces } from './hooks/useCombinedRuleNamespaces'; import { usePagination } from './hooks/usePagination'; import { useURLSearchParams } from './hooks/useURLSearchParams'; import { fetchPromRulesAction, fetchRulerRulesAction } from './state/actions'; import { labelsMatchMatchers, matchersToString, parseMatcher, parseMatchers } from './utils/alertmanager'; import { GRAFANA_RULES_SOURCE_NAME } from './utils/datasource'; import { createViewLink } from './utils/misc'; interface Props { folder: FolderState; } enum SortOrder { Ascending = 'alpha-asc', Descending = 'alpha-desc', } const sortOptions: Array> = [ { label: 'Alphabetically [A-Z]', value: SortOrder.Ascending }, { label: 'Alphabetically [Z-A]', value: SortOrder.Descending }, ]; export const AlertsFolderView = ({ folder }: Props) => { const styles = useStyles2(getStyles); const dispatch = useDispatch(); const onTagClick = (tagName: string) => { const matchers = parseMatchers(labelFilter); const tagMatcherField = parseMatcher(tagName); const uniqueMatchers = uniqWith([...matchers, tagMatcherField], isEqual); const matchersString = matchersToString(uniqueMatchers); setLabelFilter(matchersString); }; useEffect(() => { dispatch(fetchPromRulesAction({ rulesSourceName: GRAFANA_RULES_SOURCE_NAME })); dispatch(fetchRulerRulesAction({ rulesSourceName: GRAFANA_RULES_SOURCE_NAME })); }, [dispatch]); const combinedNamespaces = useCombinedRuleNamespaces(GRAFANA_RULES_SOURCE_NAME); const { nameFilter, labelFilter, sortOrder, setNameFilter, setLabelFilter, setSortOrder } = useAlertsFolderViewParams(); const matchingNamespace = combinedNamespaces.find((namespace) => namespace.name === folder.title); const alertRules = matchingNamespace?.groups.flatMap((group) => group.rules) ?? []; const filteredRules = filterAndSortRules(alertRules, nameFilter, labelFilter, sortOrder ?? SortOrder.Ascending); const hasNoResults = alertRules.length === 0 || filteredRules.length === 0; const { page, numberOfPages, onPageChange, pageItems } = usePagination(filteredRules, 1, DEFAULT_PER_PAGE_PAGINATION); return (
value={sortOrder} onChange={({ value }) => value && setSortOrder(value)} options={sortOptions} width={25} aria-label="Sort" placeholder={`Sort (Default A-Z)`} prefix={} /> {pageItems.map((currentRule) => ( {currentRule.name} `${label}=${value}`)} />
{folder.title}
))}
{hasNoResults &&
No alert rules found
}
); }; enum AlertFolderViewParams { nameFilter = 'nameFilter', labelFilter = 'labelFilter', sortOrder = 'sort', } function useAlertsFolderViewParams() { const [searchParams, setSearchParams] = useURLSearchParams(); const [nameFilter, setNameFilter] = useState(searchParams.get(AlertFolderViewParams.nameFilter) ?? ''); const [labelFilter, setLabelFilter] = useState(searchParams.get(AlertFolderViewParams.labelFilter) ?? ''); const sortParam = searchParams.get(AlertFolderViewParams.sortOrder); const [sortOrder, setSortOrder] = useState( sortParam === SortOrder.Ascending ? SortOrder.Ascending : sortParam === SortOrder.Descending ? SortOrder.Descending : undefined ); useDebounce( () => setSearchParams( { [AlertFolderViewParams.nameFilter]: getQueryParamValue(nameFilter), [AlertFolderViewParams.labelFilter]: getQueryParamValue(labelFilter), [AlertFolderViewParams.sortOrder]: getQueryParamValue(sortOrder), }, true ), 400, [nameFilter, labelFilter, sortOrder] ); return { nameFilter, labelFilter, sortOrder, setNameFilter, setLabelFilter, setSortOrder }; } function filterAndSortRules( originalRules: CombinedRule[], nameFilter: string, labelFilter: string, sortOrder: SortOrder ) { const matchers = parseMatchers(labelFilter); let rules = originalRules.filter( (rule) => rule.name.toLowerCase().includes(nameFilter.toLowerCase()) && labelsMatchMatchers(rule.labels, matchers) ); return orderBy(rules, (x) => x.name.toLowerCase(), [sortOrder === SortOrder.Ascending ? 'asc' : 'desc']); } export const getStyles = (theme: GrafanaTheme2) => ({ container: css` padding: ${theme.spacing(1)}; `, card: css` grid-template-columns: auto 1fr 2fr; margin: 0; `, pagination: css` align-self: center; `, filterLabelsInput: css` flex: 1; width: auto; min-width: 240px; `, noResults: css` padding: ${theme.spacing(2)}; background-color: ${theme.colors.background.secondary}; font-style: italic; `, });