mirror of https://github.com/grafana/grafana.git
Alerting: Add ruleUid prop and improve test coverage (#111118)
* add isruleEditable check in per rule ui and details page * add tests * update translations * revert logic for not editable rules => we will allow adding/deleting/updating enrichments per this rule * update translations * update test * pr feedback * lint
This commit is contained in:
parent
0e7a5ffc86
commit
96f70df167
|
|
@ -0,0 +1,97 @@
|
|||
import { ComponentType } from 'react';
|
||||
import { render, screen } from 'test/test-utils';
|
||||
|
||||
import {
|
||||
EnrichmentDrawerExtension,
|
||||
EnrichmentDrawerExtensionProps,
|
||||
addEnrichmentDrawerExtension,
|
||||
} from './EnrichmentDrawerExtension';
|
||||
|
||||
// Mock component for testing
|
||||
const MockEnrichmentDrawer: ComponentType<EnrichmentDrawerExtensionProps> = ({ ruleUid, onClose }) => (
|
||||
<div data-testid="enrichment-drawer">
|
||||
<div data-testid="rule-uid">{ruleUid}</div>
|
||||
<button data-testid="close-button" onClick={onClose}>
|
||||
Close
|
||||
</button>
|
||||
</div>
|
||||
);
|
||||
|
||||
describe('EnrichmentDrawerExtension', () => {
|
||||
const mockOnClose = jest.fn();
|
||||
const defaultProps = {
|
||||
ruleUid: 'test-rule-uid',
|
||||
onClose: mockOnClose,
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
});
|
||||
|
||||
it('should render nothing when no extension is registered', () => {
|
||||
render(<EnrichmentDrawerExtension {...defaultProps} />);
|
||||
|
||||
expect(screen.queryByTestId('enrichment-drawer')).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should render registered extension with correct props', () => {
|
||||
addEnrichmentDrawerExtension(MockEnrichmentDrawer);
|
||||
|
||||
render(<EnrichmentDrawerExtension {...defaultProps} />);
|
||||
|
||||
expect(screen.getByTestId('enrichment-drawer')).toBeInTheDocument();
|
||||
expect(screen.getByTestId('rule-uid')).toHaveTextContent('test-rule-uid');
|
||||
});
|
||||
|
||||
it('should call onClose when close button is clicked', () => {
|
||||
addEnrichmentDrawerExtension(MockEnrichmentDrawer);
|
||||
|
||||
render(<EnrichmentDrawerExtension {...defaultProps} />);
|
||||
|
||||
const closeButton = screen.getByTestId('close-button');
|
||||
closeButton.click();
|
||||
|
||||
expect(mockOnClose).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it('should handle different rule UIDs', () => {
|
||||
addEnrichmentDrawerExtension(MockEnrichmentDrawer);
|
||||
|
||||
const differentRuleUid = 'different-rule-uid';
|
||||
render(<EnrichmentDrawerExtension {...defaultProps} ruleUid={differentRuleUid} />);
|
||||
|
||||
expect(screen.getByTestId('rule-uid')).toHaveTextContent(differentRuleUid);
|
||||
});
|
||||
|
||||
it('should re-render when ruleUid prop changes', () => {
|
||||
addEnrichmentDrawerExtension(MockEnrichmentDrawer);
|
||||
|
||||
const { rerender } = render(<EnrichmentDrawerExtension {...defaultProps} ruleUid="rule-1" />);
|
||||
|
||||
expect(screen.getByTestId('rule-uid')).toHaveTextContent('rule-1');
|
||||
|
||||
rerender(<EnrichmentDrawerExtension {...defaultProps} ruleUid="rule-2" />);
|
||||
|
||||
expect(screen.getByTestId('rule-uid')).toHaveTextContent('rule-2');
|
||||
});
|
||||
|
||||
it('should handle error boundary when extension throws', () => {
|
||||
const consoleSpy = jest.spyOn(console, 'error').mockImplementation(() => {});
|
||||
|
||||
const FailingComponent: ComponentType<EnrichmentDrawerExtensionProps> = () => {
|
||||
throw new Error('Test error');
|
||||
};
|
||||
|
||||
addEnrichmentDrawerExtension(FailingComponent);
|
||||
|
||||
// Should not throw, error boundary should catch it
|
||||
expect(() => {
|
||||
render(<EnrichmentDrawerExtension {...defaultProps} />);
|
||||
}).not.toThrow();
|
||||
|
||||
// Error boundary should render fallback UI
|
||||
expect(screen.getByText(/Enrichment Drawer Extension failed to load/i)).toBeInTheDocument();
|
||||
|
||||
consoleSpy.mockRestore();
|
||||
});
|
||||
});
|
||||
|
|
@ -15,9 +15,7 @@ import {
|
|||
AlertRuleAction,
|
||||
skipToken,
|
||||
useGrafanaPromRuleAbilities,
|
||||
useGrafanaPromRuleAbility,
|
||||
useRulerRuleAbilities,
|
||||
useRulerRuleAbility,
|
||||
} from '../../hooks/useAbilities';
|
||||
import { createShareLink, isLocalDevEnv, isOpenSourceEdition } from '../../utils/misc';
|
||||
import * as ruleId from '../../utils/rule-id';
|
||||
|
|
@ -89,16 +87,6 @@ const AlertRuleMenu = ({
|
|||
AlertRuleAction.ModifyExport,
|
||||
]);
|
||||
|
||||
const [editRuleSupported, editRuleAllowed] = useRulerRuleAbility(rulerRule, groupIdentifier, AlertRuleAction.Update);
|
||||
// If the consumer of this component comes from the alert list view, we need to use promRule to check abilities and permissions,
|
||||
// as we have removed all requests to the ruler API in the list view.
|
||||
const [grafanaEditRuleSupported, grafanaEditRuleAllowed] = useGrafanaPromRuleAbility(
|
||||
prometheusRuleType.grafana.rule(promRule) ? promRule : skipToken,
|
||||
AlertRuleAction.Update
|
||||
);
|
||||
|
||||
const canEditRule = (editRuleSupported && editRuleAllowed) || (grafanaEditRuleSupported && grafanaEditRuleAllowed);
|
||||
|
||||
const [pauseSupported, pauseAllowed] = rulerPauseAbility;
|
||||
const [grafanaPauseSupported, grafanaPauseAllowed] = grafanaPauseAbility;
|
||||
const canPause = (pauseSupported && pauseAllowed) || (grafanaPauseSupported && grafanaPauseAllowed);
|
||||
|
|
@ -148,7 +136,6 @@ const AlertRuleMenu = ({
|
|||
|
||||
// todo: make this new menu item for enrichments an extension of the alertrulemenu items. For first iteration, we'll keep it here.
|
||||
const canManageEnrichments =
|
||||
canEditRule &&
|
||||
ruleUid &&
|
||||
handleManageEnrichments &&
|
||||
config.featureToggles.alertingEnrichmentPerRule &&
|
||||
|
|
|
|||
|
|
@ -30,6 +30,7 @@ import { stringifyIdentifier } from '../../utils/rule-id';
|
|||
|
||||
import { AlertRuleProvider } from './RuleContext';
|
||||
import RuleViewer, { ActiveTab } from './RuleViewer';
|
||||
import { addRulePageEnrichmentSection } from './tabs/extensions/RuleViewerExtension';
|
||||
|
||||
// metadata and interactive elements
|
||||
const ELEMENTS = {
|
||||
|
|
@ -411,6 +412,79 @@ describe('RuleViewer', () => {
|
|||
expect(ELEMENTS.details.pendingPeriod.get()).toHaveTextContent(/15m/i);
|
||||
});
|
||||
});
|
||||
|
||||
describe('Enrichment tab', () => {
|
||||
const mockRule = getGrafanaRule(
|
||||
{
|
||||
name: 'Test alert',
|
||||
uid: 'test-rule-uid',
|
||||
annotations: {
|
||||
[Annotation.summary]: 'This is the summary for the rule',
|
||||
},
|
||||
labels: {
|
||||
team: 'operations',
|
||||
severity: 'low',
|
||||
},
|
||||
group: {
|
||||
name: 'my-group',
|
||||
interval: '15m',
|
||||
rules: [],
|
||||
totals: { alerting: 1 },
|
||||
},
|
||||
},
|
||||
{ uid: grafanaRulerRule.grafana_alert.uid }
|
||||
);
|
||||
const mockRuleIdentifier = ruleId.fromCombinedRule('grafana', mockRule);
|
||||
|
||||
beforeEach(() => {
|
||||
grantPermissionsHelper([
|
||||
AccessControlAction.AlertingRuleCreate,
|
||||
AccessControlAction.AlertingRuleRead,
|
||||
AccessControlAction.AlertingRuleUpdate,
|
||||
AccessControlAction.AlertingRuleDelete,
|
||||
AccessControlAction.AlertingInstanceRead,
|
||||
AccessControlAction.AlertingInstanceCreate,
|
||||
AccessControlAction.AlertingInstancesExternalRead,
|
||||
AccessControlAction.AlertingInstancesExternalWrite,
|
||||
]);
|
||||
});
|
||||
|
||||
it('should pass correct props to enrichment section extension for editable rule', async () => {
|
||||
const mockEnrichmentExtension = jest.fn(() => <div data-testid="enrichment-section">Enrichment Section</div>);
|
||||
addRulePageEnrichmentSection(mockEnrichmentExtension);
|
||||
|
||||
await renderRuleViewer(mockRule, mockRuleIdentifier, ActiveTab.Enrichment);
|
||||
|
||||
expect(mockEnrichmentExtension).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
ruleUid: 'test-rule-uid',
|
||||
}),
|
||||
expect.any(Object)
|
||||
);
|
||||
expect(screen.getByTestId('enrichment-section')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should pass correct props to enrichment section extension for read-only rule', async () => {
|
||||
grantPermissionsHelper([
|
||||
AccessControlAction.AlertingRuleRead,
|
||||
AccessControlAction.AlertingInstanceRead,
|
||||
AccessControlAction.AlertingInstancesExternalRead,
|
||||
]);
|
||||
|
||||
const mockEnrichmentExtension = jest.fn(() => <div data-testid="enrichment-section">Enrichment Section</div>);
|
||||
addRulePageEnrichmentSection(mockEnrichmentExtension);
|
||||
|
||||
await renderRuleViewer(mockRule, mockRuleIdentifier, ActiveTab.Enrichment);
|
||||
|
||||
expect(mockEnrichmentExtension).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
ruleUid: 'test-rule-uid',
|
||||
}),
|
||||
expect.any(Object)
|
||||
);
|
||||
expect(screen.getByTestId('enrichment-section')).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
const renderRuleViewer = async (rule: CombinedRule, identifier: RuleIdentifier, tab: ActiveTab = ActiveTab.Query) => {
|
||||
|
|
|
|||
|
|
@ -174,7 +174,7 @@ const RuleViewer = () => {
|
|||
{activeTab === ActiveTab.VersionHistory && rulerRuleType.grafana.rule(rule.rulerRule) && (
|
||||
<AlertVersionHistory rule={rule.rulerRule} />
|
||||
)}
|
||||
{activeTab === ActiveTab.Enrichment && <RulePageEnrichmentSectionExtension />}
|
||||
{activeTab === ActiveTab.Enrichment && rule.uid && <RulePageEnrichmentSectionExtension ruleUid={rule.uid} />}
|
||||
</TabContent>
|
||||
</Stack>
|
||||
{duplicateRuleIdentifier && (
|
||||
|
|
|
|||
|
|
@ -5,7 +5,9 @@ import { withErrorBoundary } from '@grafana/ui';
|
|||
|
||||
import { logError } from '../../../../Analytics';
|
||||
|
||||
export interface RuleViewerExtensionProps {}
|
||||
export interface RuleViewerExtensionProps {
|
||||
ruleUid: string;
|
||||
}
|
||||
|
||||
let InternalRulePageEnrichmentSection: ComponentType<RuleViewerExtensionProps> | null = null;
|
||||
|
||||
|
|
|
|||
|
|
@ -1,5 +1,7 @@
|
|||
import { beforeEach, describe, expect, it } from '@jest/globals';
|
||||
|
||||
import { addRulePageEnrichmentSection } from '../../components/rule-viewer/tabs/extensions/RuleViewerExtension';
|
||||
|
||||
import { __clearRuleViewTabsForTests, addEnrichmentSection, getRuleViewExtensionTabs } from './extensions';
|
||||
|
||||
describe('rule-view-page navigation', () => {
|
||||
|
|
@ -28,4 +30,28 @@ describe('rule-view-page navigation', () => {
|
|||
expect(enrichment).toBeTruthy();
|
||||
expect(enrichment!.active).toBe(true);
|
||||
});
|
||||
|
||||
describe('enrichment section registration', () => {
|
||||
it('should register enrichment section with correct prop interface', () => {
|
||||
const mockEnrichmentSection = jest.fn(() => null);
|
||||
|
||||
// This should not throw an error
|
||||
expect(() => {
|
||||
addRulePageEnrichmentSection(mockEnrichmentSection);
|
||||
}).not.toThrow();
|
||||
});
|
||||
|
||||
it('should handle enrichment section with required props', () => {
|
||||
const mockEnrichmentSection = jest.fn((props: { ruleUid: string }) => {
|
||||
expect(props).toHaveProperty('ruleUid');
|
||||
expect(typeof props.ruleUid).toBe('string');
|
||||
return null;
|
||||
});
|
||||
|
||||
addRulePageEnrichmentSection(mockEnrichmentSection);
|
||||
|
||||
// The registration should succeed
|
||||
expect(mockEnrichmentSection).toBeDefined();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
|||
Loading…
Reference in New Issue