Alerting: Silence drawer being forcefully closed (#96579)

Co-authored-by: Tom Ratcliffe <tom.ratcliffe@grafana.com>
This commit is contained in:
Gilles De Mey 2024-11-19 12:32:45 +01:00 committed by GitHub
parent ed31457c00
commit 73ae4a51b2
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 52 additions and 38 deletions

View File

@ -1,16 +1,18 @@
import { css } from '@emotion/css';
import { useMemo } from 'react'; import { useMemo } from 'react';
import { useMeasure } from 'react-use';
import { GrafanaTheme2 } from '@grafana/data'; import { Counter, LoadingBar, Pagination, Stack } from '@grafana/ui';
import { Counter, Pagination, Stack, useStyles2 } from '@grafana/ui';
import { DEFAULT_PER_PAGE_PAGINATION } from 'app/core/constants'; import { DEFAULT_PER_PAGE_PAGINATION } from 'app/core/constants';
import { CombinedRule, CombinedRuleNamespace } from 'app/types/unified-alerting'; import { CombinedRule, CombinedRuleNamespace } from 'app/types/unified-alerting';
import { PromAlertingRuleState } from 'app/types/unified-alerting-dto'; import { PromAlertingRuleState } from 'app/types/unified-alerting-dto';
import { usePagination } from '../../hooks/usePagination'; import { usePagination } from '../../hooks/usePagination';
import { useUnifiedAlertingSelector } from '../../hooks/useUnifiedAlertingSelector';
import { AlertRuleListItem } from '../../rule-list/components/AlertRuleListItem'; import { AlertRuleListItem } from '../../rule-list/components/AlertRuleListItem';
import { ListSection } from '../../rule-list/components/ListSection'; import { ListSection } from '../../rule-list/components/ListSection';
import { getRulesDataSources, GRAFANA_RULES_SOURCE_NAME } from '../../utils/datasource';
import { createViewLink } from '../../utils/misc'; import { createViewLink } from '../../utils/misc';
import { isAsyncRequestStatePending } from '../../utils/redux';
import { hashRule } from '../../utils/rule-id'; import { hashRule } from '../../utils/rule-id';
import { getRulePluginOrigin, isAlertingRule, isProvisionedRule } from '../../utils/rules'; import { getRulePluginOrigin, isAlertingRule, isProvisionedRule } from '../../utils/rules';
import { calculateTotalInstances } from '../rule-viewer/RuleViewer'; import { calculateTotalInstances } from '../rule-viewer/RuleViewer';
@ -24,7 +26,9 @@ interface Props {
type GroupedRules = Map<PromAlertingRuleState, CombinedRule[]>; type GroupedRules = Map<PromAlertingRuleState, CombinedRule[]>;
export const RuleListStateView = ({ namespaces }: Props) => { export const RuleListStateView = ({ namespaces }: Props) => {
const styles = useStyles2(getStyles); const [ref, { width }] = useMeasure<HTMLUListElement>();
const isLoading = useDataSourcesLoadingState();
const groupedRules = useMemo(() => { const groupedRules = useMemo(() => {
const result: GroupedRules = new Map([ const result: GroupedRules = new Map([
@ -54,10 +58,13 @@ export const RuleListStateView = ({ namespaces }: Props) => {
const entries = groupedRules.entries(); const entries = groupedRules.entries();
return ( return (
<ul className={styles.columnStack} role="tree"> <ul role="tree" ref={ref}>
{Array.from(entries).map(([state, rules]) => ( {isLoading && <LoadingBar width={width} />}
<RulesByState key={state} state={state} rules={rules} /> <Stack direction="column">
))} {Array.from(entries).map(([state, rules]) => (
<RulesByState key={state} state={state} rules={rules} />
))}
</Stack>
</ul> </ul>
); );
}; };
@ -71,7 +78,7 @@ const STATE_TITLES: Record<PromAlertingRuleState, string> = {
const RulesByState = ({ state, rules }: { state: PromAlertingRuleState; rules: CombinedRule[] }) => { const RulesByState = ({ state, rules }: { state: PromAlertingRuleState; rules: CombinedRule[] }) => {
const { page, pageItems, numberOfPages, onPageChange } = usePagination(rules, 1, DEFAULT_PER_PAGE_PAGINATION); const { page, pageItems, numberOfPages, onPageChange } = usePagination(rules, 1, DEFAULT_PER_PAGE_PAGINATION);
const isNotFiringState = state !== PromAlertingRuleState.Firing; const isFiringState = state !== PromAlertingRuleState.Firing;
const hasRulesMatchingState = rules.length > 0; const hasRulesMatchingState = rules.length > 0;
return ( return (
@ -82,7 +89,7 @@ const RulesByState = ({ state, rules }: { state: PromAlertingRuleState; rules: C
<Counter value={rules.length} /> <Counter value={rules.length} />
</Stack> </Stack>
} }
collapsed={isNotFiringState || hasRulesMatchingState} collapsed={isFiringState || hasRulesMatchingState}
pagination={ pagination={
<Pagination <Pagination
currentPage={page} currentPage={page}
@ -127,10 +134,20 @@ const RulesByState = ({ state, rules }: { state: PromAlertingRuleState; rules: C
); );
}; };
const getStyles = (theme: GrafanaTheme2) => ({ function useDataSourcesLoadingState() {
columnStack: css({ const promRules = useUnifiedAlertingSelector((state) => state.promRules);
display: 'flex', const rulesDataSources = useMemo(getRulesDataSources, []);
flexDirection: 'column',
gap: theme.spacing(1), const grafanaLoading = useUnifiedAlertingSelector((state) => {
}), const promLoading = isAsyncRequestStatePending(state.promRules[GRAFANA_RULES_SOURCE_NAME]);
}); const rulerLoading = isAsyncRequestStatePending(state.rulerRules[GRAFANA_RULES_SOURCE_NAME]);
return promLoading || rulerLoading;
});
const externalDataSourcesLoading = rulesDataSources.some((ds) => isAsyncRequestStatePending(promRules[ds.name]));
const loading = grafanaLoading || externalDataSourcesLoading;
return loading;
}

View File

@ -3,7 +3,7 @@ import { useEffect, useMemo } from 'react';
import Skeleton from 'react-loading-skeleton'; import Skeleton from 'react-loading-skeleton';
import { GrafanaTheme2 } from '@grafana/data'; import { GrafanaTheme2 } from '@grafana/data';
import { LoadingPlaceholder, Pagination, Tooltip, useStyles2 } from '@grafana/ui'; import { Pagination, Tooltip, useStyles2 } from '@grafana/ui';
import { CombinedRule } from 'app/types/unified-alerting'; import { CombinedRule } from 'app/types/unified-alerting';
import { DEFAULT_PER_PAGE_PAGINATION } from '../../../../../core/constants'; import { DEFAULT_PER_PAGE_PAGINATION } from '../../../../../core/constants';
@ -63,18 +63,9 @@ export const RulesTable = ({
const { pageItems, page, numberOfPages, onPageChange } = usePagination(rules, 1, DEFAULT_PER_PAGE_PAGINATION); const { pageItems, page, numberOfPages, onPageChange } = usePagination(rules, 1, DEFAULT_PER_PAGE_PAGINATION);
const [lazyLoadRules, { result: rulesWithRulerDefinitions, status: rulerRulesLoadingStatus }] = const { result: rulesWithRulerDefinitions, status: rulerRulesLoadingStatus } = useLazyLoadRulerRules(pageItems);
useLazyLoadRulerRules(pageItems);
const isLoadingRulerGroup = useMemo(
() => !rulerRulesLoadingStatus || rulerRulesLoadingStatus === 'loading',
[rulerRulesLoadingStatus]
);
useEffect(() => { const isLoadingRulerGroup = rulerRulesLoadingStatus === 'loading';
if (pageItems.length > 0) {
lazyLoadRules.execute();
}
}, [lazyLoadRules, pageItems, rulerRulesLoadingStatus]);
const items = useMemo((): RuleTableItemProps[] => { const items = useMemo((): RuleTableItemProps[] => {
return rulesWithRulerDefinitions.map((rule, ruleIdx) => { return rulesWithRulerDefinitions.map((rule, ruleIdx) => {
@ -91,10 +82,6 @@ export const RulesTable = ({
return <div className={cx(wrapperClass, styles.emptyMessage)}>{emptyMessage}</div>; return <div className={cx(wrapperClass, styles.emptyMessage)}>{emptyMessage}</div>;
} }
if (isLoadingRulerGroup) {
return <LoadingPlaceholder text="Loading..." />;
}
const TableComponent = showGuidelines ? DynamicTableWithGuidelines : DynamicTable; const TableComponent = showGuidelines ? DynamicTableWithGuidelines : DynamicTable;
return ( return (
@ -127,11 +114,8 @@ function useLazyLoadRulerRules(rules: CombinedRule[]) {
const [fetchRulerRuleGroup] = useLazyGetRuleGroupForNamespaceQuery(); const [fetchRulerRuleGroup] = useLazyGetRuleGroupForNamespaceQuery();
const [fetchDsFeatures] = useLazyDiscoverDsFeaturesQuery(); const [fetchDsFeatures] = useLazyDiscoverDsFeaturesQuery();
return useAsync(async () => { const [actions, state] = useAsync(async () => {
if (!prometheusRulesPrimary) { const result = Promise.all(
return rules;
}
return Promise.all(
rules.map(async (rule) => { rules.map(async (rule) => {
const dsFeatures = await fetchDsFeatures( const dsFeatures = await fetchDsFeatures(
{ rulesSourceName: getRulesSourceName(rule.namespace.rulesSource) }, { rulesSourceName: getRulesSourceName(rule.namespace.rulesSource) },
@ -156,7 +140,20 @@ function useLazyLoadRulerRules(rules: CombinedRule[]) {
return rule; return rule;
}) })
); );
return result;
}, rules); }, rules);
useEffect(() => {
if (prometheusRulesPrimary) {
actions.execute();
} else {
// We need to reset the actions to update the rules if they changed
// Otherwise useAsync acts like a cache and always return the first rules passed to it
actions.reset();
}
}, [rules, actions]);
return state;
} }
export const getStyles = (theme: GrafanaTheme2) => ({ export const getStyles = (theme: GrafanaTheme2) => ({