Alerting: Add error handling for missing data source (#101508)

pull/101659/head
Gilles De Mey 4 months ago committed by GitHub
parent 5b61222733
commit 751ea772e6
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
  1. 2
      public/app/features/alerting/unified/api/featureDiscoveryApi.ts
  2. 5
      public/app/features/alerting/unified/hooks/useIsRuleEditable.test.tsx
  3. 21
      public/app/features/alerting/unified/hooks/useIsRuleEditable.ts
  4. 3
      public/app/features/alerting/unified/mocks.ts
  5. 29
      public/app/features/alerting/unified/rule-editor/ExistingRuleEditor.tsx
  6. 14
      public/app/features/alerting/unified/rule-editor/RuleEditorExisting.test.tsx

@ -40,7 +40,7 @@ export const featureDiscoveryApi = alertingApi.injectEndpoints({
queryFn: async (rulesSourceIdentifier) => {
const dataSourceUID = getDataSourceUID(rulesSourceIdentifier);
if (!dataSourceUID) {
return { error: new Error(`Unable to find data source for ${rulesSourceIdentifier}`) };
return { error: new Error(`Unable to find data source for ${JSON.stringify(rulesSourceIdentifier)}`) };
}
if (dataSourceUID === GrafanaRulesSourceSymbol) {

@ -10,6 +10,7 @@ import { AccessControlAction, FolderDTO } from 'app/types';
import { setupMswServer } from '../mockApi';
import { mockDataSource, mockFolder, mockRulerAlertingRule, mockRulerGrafanaRule } from '../mocks';
import { setupDataSources } from '../testSetup/datasources';
import { GRAFANA_RULES_SOURCE_NAME } from '../utils/datasource';
import { useFolder } from './useFolder';
import { useIsRuleEditable } from './useIsRuleEditable';
@ -56,7 +57,9 @@ describe('useIsRuleEditable', () => {
const wrapper = getProviderWrapper();
const { result } = renderHook(() => useIsRuleEditable('grafana', mockRulerGrafanaRule()), { wrapper });
const { result } = renderHook(() => useIsRuleEditable(GRAFANA_RULES_SOURCE_NAME, mockRulerGrafanaRule()), {
wrapper,
});
await waitFor(() => expect(result.current.loading).toBe(false));
expect(result.current.isRemovable).toBe(true);

@ -3,7 +3,6 @@ import { RulerRuleDTO } from 'app/types/unified-alerting-dto';
import { featureDiscoveryApi } from '../api/featureDiscoveryApi';
import { getRulesPermissions } from '../utils/access-control';
import { getDatasourceAPIUid } from '../utils/datasource';
import { isGrafanaRulerRule } from '../utils/rules';
import { useFolder } from './useFolder';
@ -13,11 +12,16 @@ interface ResultBag {
isEditable?: boolean;
isRemovable?: boolean;
loading: boolean;
error?: unknown;
}
export function useIsRuleEditable(rulesSourceName: string, rule?: RulerRuleDTO): ResultBag {
const { currentData: dsFeatures, isLoading } = featureDiscoveryApi.endpoints.discoverDsFeatures.useQuery({
uid: getDatasourceAPIUid(rulesSourceName),
const {
currentData: dsFeatures,
isLoading,
error,
} = featureDiscoveryApi.endpoints.discoverDsFeatures.useQuery({
rulesSourceName,
});
const folderUID = rule && isGrafanaRulerRule(rule) ? rule.grafana_alert.namespace_uid : undefined;
@ -25,6 +29,17 @@ export function useIsRuleEditable(rulesSourceName: string, rule?: RulerRuleDTO):
const rulePermission = getRulesPermissions(rulesSourceName);
const { folder, loading } = useFolder(folderUID);
// handle discovery and data source errors
if (error) {
return {
isEditable: false,
isRemovable: false,
loading: false,
isRulerAvailable: false,
error,
};
}
if (!rule) {
return { isEditable: false, isRemovable: false, loading: false };
}

@ -57,6 +57,7 @@ import {
import { DashboardSearchItem, DashboardSearchItemType } from '../../search/types';
import { SimpleConditionIdentifier } from './components/rule-editor/query-and-alert-condition/SimpleCondition';
import { GRAFANA_RULES_SOURCE_NAME } from './utils/datasource';
import { parsePromQLStyleMatcherLooseSafe } from './utils/matchers';
let nextDataSourceId = 1;
@ -686,7 +687,7 @@ export function getGrafanaRule(override?: Partial<CombinedRule>, rulerOverride?:
namespace: {
groups: [],
name: 'Grafana',
rulesSource: 'grafana',
rulesSource: GRAFANA_RULES_SOURCE_NAME,
},
rulerRule: mockGrafanaRulerRule(rulerOverride),
...override,

@ -17,17 +17,30 @@ interface ExistingRuleEditorProps {
}
export function ExistingRuleEditor({ identifier, prefill }: ExistingRuleEditorProps) {
const [queryParams] = useQueryParams();
const isManualRestore = Boolean(queryParams.isManualRestore);
const {
loading: loadingAlertRule,
result: ruleWithLocation,
error,
error: fetchRuleError,
} = useRuleWithLocation({ ruleIdentifier: identifier });
const [queryParams] = useQueryParams();
const isManualRestore = Boolean(queryParams.isManualRestore);
const ruleSourceName = ruleId.ruleIdentifierToRuleSourceName(identifier);
const {
isEditable,
loading: loadingEditable,
error: errorEditable,
} = useIsRuleEditable(ruleSourceName, ruleWithLocation?.rule);
const { isEditable, loading: loadingEditable } = useIsRuleEditable(ruleSourceName, ruleWithLocation?.rule);
// error handling for fetching rule and rule RBAC
if (fetchRuleError || errorEditable) {
return (
<Alert severity="error" title="Failed to load rule">
{stringifyErrorLike(errorEditable ?? fetchRuleError)}
</Alert>
);
}
const loading = loadingAlertRule || loadingEditable;
@ -35,14 +48,6 @@ export function ExistingRuleEditor({ identifier, prefill }: ExistingRuleEditorPr
return <LoadingPlaceholder text="Loading rule..." />;
}
if (error) {
return (
<Alert severity="error" title="Failed to load rule">
{stringifyErrorLike(error)}
</Alert>
);
}
if (!ruleWithLocation && !loading) {
return <AlertWarning title="Rule not found">Sorry! This rule does not exist.</AlertWarning>;
}

@ -4,7 +4,6 @@ import { render, screen } from 'test/test-utils';
import { contextSrv } from 'app/core/services/context_srv';
import { setFolderResponse } from 'app/features/alerting/unified/mocks/server/configure';
import { MIMIR_DATASOURCE_UID } from 'app/features/alerting/unified/mocks/server/constants';
import { captureRequests } from 'app/features/alerting/unified/mocks/server/events';
import { DashboardSearchItemType } from 'app/features/search/types';
import { AccessControlAction } from 'app/types';
@ -12,6 +11,7 @@ import { AccessControlAction } from 'app/types';
import { setupMswServer } from '../mockApi';
import { grantUserPermissions, mockDataSource, mockFolder } from '../mocks';
import { grafanaRulerRule } from '../mocks/grafanaRulerApi';
import { MIMIR_DATASOURCE_UID } from '../mocks/server/constants';
import { setupDataSources } from '../testSetup/datasources';
import { Annotation } from '../utils/constants';
@ -151,3 +151,15 @@ describe('RuleEditor grafana managed rules', () => {
expect(postBody.interval).toBe('12m');
});
});
describe('Data source managed rules', () => {
beforeEach(() => {
jest.clearAllMocks();
grantUserPermissions([AccessControlAction.AlertingRuleExternalRead, AccessControlAction.AlertingRuleExternalWrite]);
});
it('should show an error if the data source does not exist', async () => {
renderRuleEditor('cri%24grafana-cloudd%24delete me%24delete me 3%24recording_rule_delete_2%24-476183141');
expect(await screen.findByText(/unable to find data source/i)).toBeInTheDocument();
});
});

Loading…
Cancel
Save