mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
Alerting: improve rule list error message (#37734)
This commit is contained in:
parent
28229fc344
commit
e76848acee
@ -3,7 +3,7 @@ import { render, waitFor } from '@testing-library/react';
|
||||
import { configureStore } from 'app/store/configureStore';
|
||||
import { Provider } from 'react-redux';
|
||||
import { RuleList } from './RuleList';
|
||||
import { byTestId, byText } from 'testing-library-selector';
|
||||
import { byRole, byTestId, byText } from 'testing-library-selector';
|
||||
import { typeAsJestMock } from 'test/helpers/typeAsJestMock';
|
||||
import { getAllDataSources } from './utils/config';
|
||||
import { fetchRules } from './api/prometheus';
|
||||
@ -77,6 +77,7 @@ const ui = {
|
||||
ruleRow: byTestId('row'),
|
||||
expandedContent: byTestId('expanded-content'),
|
||||
rulesFilterInput: byTestId('search-query-input'),
|
||||
moreErrorsButton: byRole('button', { name: /more errors/ }),
|
||||
};
|
||||
|
||||
describe('RuleList', () => {
|
||||
@ -164,6 +165,10 @@ describe('RuleList', () => {
|
||||
|
||||
const errors = await ui.cloudRulesSourceErrors.find();
|
||||
|
||||
expect(errors).not.toHaveTextContent(
|
||||
'Failed to load rules state from Prometheus-broken: this datasource is broken'
|
||||
);
|
||||
userEvent.click(ui.moreErrorsButton.get());
|
||||
expect(errors).toHaveTextContent('Failed to load rules state from Prometheus-broken: this datasource is broken');
|
||||
});
|
||||
|
||||
|
@ -1,6 +1,5 @@
|
||||
import { DataSourceInstanceSettings, GrafanaTheme, urlUtil } from '@grafana/data';
|
||||
import { useStyles, Alert, LinkButton, withErrorBoundary } from '@grafana/ui';
|
||||
import { SerializedError } from '@reduxjs/toolkit';
|
||||
import { GrafanaTheme2, urlUtil } from '@grafana/data';
|
||||
import { useStyles2, LinkButton, withErrorBoundary } from '@grafana/ui';
|
||||
import React, { useEffect, useMemo } from 'react';
|
||||
import { useDispatch } from 'react-redux';
|
||||
import { AlertingPageWrapper } from './components/AlertingPageWrapper';
|
||||
@ -8,11 +7,10 @@ import { NoRulesSplash } from './components/rules/NoRulesCTA';
|
||||
import { useUnifiedAlertingSelector } from './hooks/useUnifiedAlertingSelector';
|
||||
import { useFilteredRules } from './hooks/useFilteredRules';
|
||||
import { fetchAllPromAndRulerRulesAction } from './state/actions';
|
||||
import { getAllRulesSourceNames, getRulesDataSources, GRAFANA_RULES_SOURCE_NAME } from './utils/datasource';
|
||||
import { getAllRulesSourceNames } from './utils/datasource';
|
||||
import { css } from '@emotion/css';
|
||||
import { useCombinedRuleNamespaces } from './hooks/useCombinedRuleNamespaces';
|
||||
import { RULE_LIST_POLL_INTERVAL_MS } from './utils/constants';
|
||||
import { isRulerNotSupportedResponse } from './utils/rules';
|
||||
import RulesFilter from './components/rules/RulesFilter';
|
||||
import { RuleListGroupView } from './components/rules/RuleListGroupView';
|
||||
import { RuleListStateView } from './components/rules/RuleListStateView';
|
||||
@ -20,6 +18,7 @@ import { useQueryParams } from 'app/core/hooks/useQueryParams';
|
||||
import { useLocation } from 'react-router-dom';
|
||||
import { contextSrv } from 'app/core/services/context_srv';
|
||||
import { RuleStats } from './components/rules/RuleStats';
|
||||
import { RuleListErrors } from './components/rules/RuleListErrors';
|
||||
|
||||
const VIEWS = {
|
||||
groups: RuleListGroupView,
|
||||
@ -29,7 +28,7 @@ const VIEWS = {
|
||||
export const RuleList = withErrorBoundary(
|
||||
() => {
|
||||
const dispatch = useDispatch();
|
||||
const styles = useStyles(getStyles);
|
||||
const styles = useStyles2(getStyles);
|
||||
const rulesDataSourceNames = useMemo(getAllRulesSourceNames, []);
|
||||
const location = useLocation();
|
||||
|
||||
@ -65,54 +64,13 @@ export const RuleList = withErrorBoundary(
|
||||
(Object.keys(rulerRuleRequests[name]?.result || {}).length && !rulerRuleRequests[name]?.error)
|
||||
);
|
||||
|
||||
const [promReqeustErrors, rulerRequestErrors] = useMemo(
|
||||
() =>
|
||||
[promRuleRequests, rulerRuleRequests].map((requests) =>
|
||||
getRulesDataSources().reduce<Array<{ error: SerializedError; dataSource: DataSourceInstanceSettings }>>(
|
||||
(result, dataSource) => {
|
||||
const error = requests[dataSource.name]?.error;
|
||||
if (requests[dataSource.name] && error && !isRulerNotSupportedResponse(requests[dataSource.name])) {
|
||||
return [...result, { dataSource, error }];
|
||||
}
|
||||
return result;
|
||||
},
|
||||
[]
|
||||
)
|
||||
),
|
||||
[promRuleRequests, rulerRuleRequests]
|
||||
);
|
||||
|
||||
const grafanaPromError = promRuleRequests[GRAFANA_RULES_SOURCE_NAME]?.error;
|
||||
const grafanaRulerError = rulerRuleRequests[GRAFANA_RULES_SOURCE_NAME]?.error;
|
||||
|
||||
const showNewAlertSplash = dispatched && !loading && !haveResults;
|
||||
|
||||
const combinedNamespaces = useCombinedRuleNamespaces();
|
||||
const filteredNamespaces = useFilteredRules(combinedNamespaces);
|
||||
return (
|
||||
<AlertingPageWrapper pageId="alert-list" isLoading={loading && !haveResults}>
|
||||
{(promReqeustErrors.length || rulerRequestErrors.length || grafanaPromError) && (
|
||||
<Alert data-testid="cloud-rulessource-errors" title="Errors loading rules" severity="error">
|
||||
{grafanaPromError && (
|
||||
<div>Failed to load Grafana rules state: {grafanaPromError.message || 'Unknown error.'}</div>
|
||||
)}
|
||||
{grafanaRulerError && (
|
||||
<div>Failed to load Grafana rules config: {grafanaRulerError.message || 'Unknown error.'}</div>
|
||||
)}
|
||||
{promReqeustErrors.map(({ dataSource, error }) => (
|
||||
<div key={dataSource.name}>
|
||||
Failed to load rules state from <a href={`datasources/edit/${dataSource.uid}`}>{dataSource.name}</a>:{' '}
|
||||
{error.message || 'Unknown error.'}
|
||||
</div>
|
||||
))}
|
||||
{rulerRequestErrors.map(({ dataSource, error }) => (
|
||||
<div key={dataSource.name}>
|
||||
Failed to load rules config from <a href={'datasources/edit/${dataSource.uid}'}>{dataSource.name}</a>:{' '}
|
||||
{error.message || 'Unknown error.'}
|
||||
</div>
|
||||
))}
|
||||
</Alert>
|
||||
)}
|
||||
<RuleListErrors />
|
||||
{!showNewAlertSplash && (
|
||||
<>
|
||||
<RulesFilter />
|
||||
@ -139,19 +97,15 @@ export const RuleList = withErrorBoundary(
|
||||
{ style: 'page' }
|
||||
);
|
||||
|
||||
const getStyles = (theme: GrafanaTheme) => ({
|
||||
const getStyles = (theme: GrafanaTheme2) => ({
|
||||
break: css`
|
||||
width: 100%;
|
||||
height: 0;
|
||||
margin-bottom: ${theme.spacing.md};
|
||||
border-bottom: solid 1px ${theme.colors.border2};
|
||||
`,
|
||||
iconError: css`
|
||||
color: ${theme.palette.red};
|
||||
margin-right: ${theme.spacing.md};
|
||||
margin-bottom: ${theme.spacing(2)};
|
||||
border-bottom: solid 1px ${theme.colors.border.medium};
|
||||
`,
|
||||
buttonsContainer: css`
|
||||
margin-bottom: ${theme.spacing.md};
|
||||
margin-bottom: ${theme.spacing(2)};
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
`,
|
||||
|
@ -0,0 +1,94 @@
|
||||
import { css } from '@emotion/css';
|
||||
import { DataSourceInstanceSettings, GrafanaTheme2 } from '@grafana/data';
|
||||
import { Alert, Button, useStyles2 } from '@grafana/ui';
|
||||
import { SerializedError } from '@reduxjs/toolkit';
|
||||
import pluralize from 'pluralize';
|
||||
import React, { useMemo, ReactElement, useState } from 'react';
|
||||
import { useUnifiedAlertingSelector } from '../../hooks/useUnifiedAlertingSelector';
|
||||
import { getRulesDataSources, GRAFANA_RULES_SOURCE_NAME } from '../../utils/datasource';
|
||||
import { isRulerNotSupportedResponse } from '../../utils/rules';
|
||||
|
||||
export function RuleListErrors(): ReactElement {
|
||||
const [expanded, setExpanded] = useState(false);
|
||||
const [closed, setClosed] = useState(false);
|
||||
const promRuleRequests = useUnifiedAlertingSelector((state) => state.promRules);
|
||||
const rulerRuleRequests = useUnifiedAlertingSelector((state) => state.rulerRules);
|
||||
const styles = useStyles2(getStyles);
|
||||
|
||||
const errors = useMemo((): JSX.Element[] => {
|
||||
const [promRequestErrors, rulerRequestErrors] = [promRuleRequests, rulerRuleRequests].map((requests) =>
|
||||
getRulesDataSources().reduce<Array<{ error: SerializedError; dataSource: DataSourceInstanceSettings }>>(
|
||||
(result, dataSource) => {
|
||||
const error = requests[dataSource.name]?.error;
|
||||
if (requests[dataSource.name] && error && !isRulerNotSupportedResponse(requests[dataSource.name])) {
|
||||
return [...result, { dataSource, error }];
|
||||
}
|
||||
return result;
|
||||
},
|
||||
[]
|
||||
)
|
||||
);
|
||||
const grafanaPromError = promRuleRequests[GRAFANA_RULES_SOURCE_NAME]?.error;
|
||||
const grafanaRulerError = rulerRuleRequests[GRAFANA_RULES_SOURCE_NAME]?.error;
|
||||
|
||||
const result: JSX.Element[] = [];
|
||||
|
||||
if (grafanaPromError) {
|
||||
result.push(<>Failed to load Grafana rules state: {grafanaPromError.message || 'Unknown error.'}</>);
|
||||
}
|
||||
if (grafanaRulerError) {
|
||||
result.push(<>Failed to load Grafana rules config: {grafanaRulerError.message || 'Unknown error.'}</>);
|
||||
}
|
||||
|
||||
promRequestErrors.forEach(({ dataSource, error }) =>
|
||||
result.push(
|
||||
<>
|
||||
Failed to load rules state from <a href={`datasources/edit/${dataSource.uid}`}>{dataSource.name}</a>:{' '}
|
||||
{error.message || 'Unknown error.'}
|
||||
</>
|
||||
)
|
||||
);
|
||||
|
||||
rulerRequestErrors.forEach(({ dataSource, error }) =>
|
||||
result.push(
|
||||
<>
|
||||
Failed to load rules config from <a href={'datasources/edit/${dataSource.uid}'}>{dataSource.name}</a>:{' '}
|
||||
{error.message || 'Unknown error.'}
|
||||
</>
|
||||
)
|
||||
);
|
||||
|
||||
return result;
|
||||
}, [promRuleRequests, rulerRuleRequests]);
|
||||
|
||||
return (
|
||||
<>
|
||||
{errors.length && !closed && (
|
||||
<Alert
|
||||
data-testid="cloud-rulessource-errors"
|
||||
title="Errors loading rules"
|
||||
severity="error"
|
||||
onRemove={() => setClosed(true)}
|
||||
>
|
||||
{expanded && errors.map((item, idx) => <div key={idx}>{item}</div>)}
|
||||
{!expanded && (
|
||||
<>
|
||||
<div>{errors[0]}</div>
|
||||
{errors.length >= 2 && (
|
||||
<Button className={styles.moreButton} variant="link" size="sm" onClick={() => setExpanded(true)}>
|
||||
{errors.length - 1} more {pluralize('error', errors.length - 1)}
|
||||
</Button>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</Alert>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
const getStyles = (theme: GrafanaTheme2) => ({
|
||||
moreButton: css`
|
||||
padding: 0;
|
||||
`,
|
||||
});
|
Loading…
Reference in New Issue
Block a user