Alerting: improve rule list error message (#37734)

This commit is contained in:
Domas 2021-08-12 10:57:52 +03:00 committed by GitHub
parent 28229fc344
commit e76848acee
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 110 additions and 57 deletions

View File

@ -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');
});

View File

@ -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;
`,

View File

@ -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;
`,
});