Alerting: Add GMA export to the new list page (#109784)
* Add GMA export to the More menu action on the list page * Add Grafana rules export option to the new list page * Update translations
This commit is contained in:
@@ -645,7 +645,7 @@ export const grantUserPermissions = (permissions: AccessControlAction[]) => {
|
||||
};
|
||||
|
||||
export const grantUserRole = (role: string) => {
|
||||
jest.spyOn(contextSrv, 'hasRole').mockReturnValue(true);
|
||||
jest.spyOn(contextSrv, 'hasRole').mockImplementation((checkRole) => checkRole === role);
|
||||
};
|
||||
|
||||
export function mockUnifiedAlertingStore(unifiedAlerting?: Partial<StoreState['unifiedAlerting']>) {
|
||||
|
||||
@@ -1,11 +1,14 @@
|
||||
import { HttpResponse } from 'msw';
|
||||
import { render } from 'test/test-utils';
|
||||
import { byRole, byTestId } from 'testing-library-selector';
|
||||
|
||||
import { OrgRole } from '@grafana/data';
|
||||
import { setPluginComponentsHook, setPluginLinksHook } from '@grafana/runtime';
|
||||
import { AccessControlAction } from 'app/types/accessControl';
|
||||
|
||||
import { setupMswServer } from '../mockApi';
|
||||
import { grantUserPermissions } from '../mocks';
|
||||
import { grantUserPermissions, grantUserRole } from '../mocks';
|
||||
import { setGrafanaRuleGroupExportResolver } from '../mocks/server/configure';
|
||||
import { alertingFactory } from '../mocks/server/db';
|
||||
import { RulesFilter } from '../search/rulesSearchParser';
|
||||
import { testWithFeatureToggles } from '../test/test-utils';
|
||||
@@ -162,11 +165,16 @@ describe('RuleListActions', () => {
|
||||
newAlertRuleForExport: byRole('link', { name: /new alert rule for export/i }),
|
||||
newGrafanaRecordingRule: byRole('link', { name: /new grafana recording rule/i }),
|
||||
newDataSourceRecordingRule: byRole('link', { name: /new data source recording rule/i }),
|
||||
importAlertRules: byRole('link', { name: /import alert rules/i }),
|
||||
exportAllGrafanaRules: byRole('menuitem', { name: /export all grafana rules/i }),
|
||||
},
|
||||
exportDrawer: byRole('dialog', { name: /export/i }),
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
// Default to Viewer role (non-admin)
|
||||
grantUserRole(OrgRole.Viewer);
|
||||
});
|
||||
|
||||
it.each([
|
||||
@@ -243,6 +251,90 @@ describe('RuleListActions', () => {
|
||||
expect(ui.menuOptions.newGrafanaRecordingRule.query(menu)).toBeInTheDocument();
|
||||
expect(ui.menuOptions.newDataSourceRecordingRule.query(menu)).toBeInTheDocument();
|
||||
});
|
||||
|
||||
describe('Import Alert Rules', () => {
|
||||
testWithFeatureToggles(['alertingMigrationUI']);
|
||||
|
||||
it('should show "Import alert rules" option when user is admin and feature toggle is enabled', async () => {
|
||||
grantUserRole(OrgRole.Admin);
|
||||
grantUserPermissions([AccessControlAction.AlertingRuleRead]);
|
||||
|
||||
const { user } = render(<RuleListActions />);
|
||||
|
||||
await user.click(ui.moreButton.get());
|
||||
const menu = await ui.moreMenu.find();
|
||||
|
||||
expect(ui.menuOptions.importAlertRules.query(menu)).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should not show "Import alert rules" option when user is not admin', async () => {
|
||||
// Keep default Viewer role
|
||||
grantUserPermissions([AccessControlAction.AlertingRuleRead]);
|
||||
|
||||
const { user } = render(<RuleListActions />);
|
||||
|
||||
await user.click(ui.moreButton.get());
|
||||
const menu = await ui.moreMenu.find();
|
||||
|
||||
expect(ui.menuOptions.importAlertRules.query(menu)).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should have correct URL for "Import alert rules" menu item', async () => {
|
||||
grantUserRole(OrgRole.Admin);
|
||||
grantUserPermissions([AccessControlAction.AlertingRuleRead]);
|
||||
|
||||
const { user } = render(<RuleListActions />);
|
||||
|
||||
await user.click(ui.moreButton.get());
|
||||
const menu = await ui.moreMenu.find();
|
||||
const importMenuItem = ui.menuOptions.importAlertRules.get(menu);
|
||||
|
||||
expect(importMenuItem).toHaveAttribute('href', '/alerting/import-datasource-managed-rules');
|
||||
});
|
||||
});
|
||||
|
||||
describe('Export All Grafana Rules', () => {
|
||||
it('should show "Export all Grafana rules" option when user has export permissions', async () => {
|
||||
grantUserPermissions([AccessControlAction.AlertingRuleRead]);
|
||||
|
||||
const { user } = render(<RuleListActions />);
|
||||
|
||||
await user.click(ui.moreButton.get());
|
||||
const menu = await ui.moreMenu.find();
|
||||
|
||||
expect(ui.menuOptions.exportAllGrafanaRules.query(menu)).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should not show "Export all Grafana rules" option when user lacks export permissions', async () => {
|
||||
grantUserPermissions([]); // No permissions
|
||||
|
||||
const { user } = render(<RuleListActions />);
|
||||
|
||||
await user.click(ui.moreButton.get());
|
||||
const menu = await ui.moreMenu.find();
|
||||
|
||||
expect(ui.menuOptions.exportAllGrafanaRules.query(menu)).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should open export drawer when "Export all Grafana rules" is clicked', async () => {
|
||||
// Set up MSW mock for export endpoint
|
||||
setGrafanaRuleGroupExportResolver(() => {
|
||||
return HttpResponse.text('# Mock YAML export content\ngroups: []');
|
||||
});
|
||||
|
||||
grantUserPermissions([AccessControlAction.AlertingRuleRead]);
|
||||
|
||||
const { user } = render(<RuleListActions />);
|
||||
|
||||
await user.click(ui.moreButton.get());
|
||||
const menu = await ui.moreMenu.find();
|
||||
const exportMenuItem = ui.menuOptions.exportAllGrafanaRules.get(menu);
|
||||
|
||||
await user.click(exportMenuItem);
|
||||
|
||||
expect(ui.exportDrawer.query()).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('RuleList v2 - View switching', () => {
|
||||
|
||||
@@ -1,10 +1,12 @@
|
||||
import { useMemo } from 'react';
|
||||
import { useToggle } from 'react-use';
|
||||
|
||||
import { Trans, t } from '@grafana/i18n';
|
||||
import { config } from '@grafana/runtime';
|
||||
import { Button, Dropdown, Icon, LinkButton, Menu, Stack } from '@grafana/ui';
|
||||
|
||||
import { AlertingPageWrapper } from '../components/AlertingPageWrapper';
|
||||
import { GrafanaRulesExporter } from '../components/export/GrafanaRulesExporter';
|
||||
import RulesFilter from '../components/rules/Filter/RulesFilter';
|
||||
import { useListViewMode } from '../components/rules/Filter/RulesViewModeSelector';
|
||||
import { AIAlertRuleButtonComponent } from '../enterprise-components/AI/AIGenAlertRuleButton/addAIAlertRuleButton';
|
||||
@@ -35,13 +37,17 @@ function RuleList() {
|
||||
export function RuleListActions() {
|
||||
const [createGrafanaRuleSupported, createGrafanaRuleAllowed] = useAlertingAbility(AlertingAction.CreateAlertRule);
|
||||
const [createCloudRuleSupported, createCloudRuleAllowed] = useAlertingAbility(AlertingAction.CreateExternalAlertRule);
|
||||
const [exportRulesSupported, exportRulesAllowed] = useAlertingAbility(AlertingAction.ExportGrafanaManagedRules);
|
||||
|
||||
const canCreateGrafanaRules = createGrafanaRuleSupported && createGrafanaRuleAllowed;
|
||||
const canCreateCloudRules = createCloudRuleSupported && createCloudRuleAllowed;
|
||||
const canExportRules = exportRulesSupported && exportRulesAllowed;
|
||||
|
||||
const canCreateRules = canCreateGrafanaRules || canCreateCloudRules;
|
||||
const canImportRulesToGMA = isAdmin() && config.featureToggles.alertingMigrationUI;
|
||||
|
||||
const [showExportDrawer, toggleShowExportDrawer] = useToggle(false);
|
||||
|
||||
const moreActionsMenu = useMemo(
|
||||
() => (
|
||||
<Menu>
|
||||
@@ -51,10 +57,17 @@ export function RuleListActions() {
|
||||
icon="file-export"
|
||||
url="/alerting/export-new-rule"
|
||||
/>
|
||||
{canExportRules && (
|
||||
<Menu.Item
|
||||
label={t('alerting.rule-list.export-all-grafana-rules', 'Export all Grafana rules')}
|
||||
icon="download-alt"
|
||||
onClick={toggleShowExportDrawer}
|
||||
/>
|
||||
)}
|
||||
{canImportRulesToGMA && (
|
||||
<Menu.Item
|
||||
label={t('alerting.rule-list-v2.import-to-gma', 'Import alert rules')}
|
||||
icon="import"
|
||||
icon="upload"
|
||||
url="/alerting/import-datasource-managed-rules"
|
||||
/>
|
||||
)}
|
||||
@@ -77,7 +90,7 @@ export function RuleListActions() {
|
||||
</Menu.Group>
|
||||
</Menu>
|
||||
),
|
||||
[canCreateGrafanaRules, canCreateCloudRules, canImportRulesToGMA]
|
||||
[canCreateGrafanaRules, canCreateCloudRules, canImportRulesToGMA, canExportRules, toggleShowExportDrawer]
|
||||
);
|
||||
|
||||
return (
|
||||
@@ -93,6 +106,7 @@ export function RuleListActions() {
|
||||
<Trans i18nKey="alerting.rule-list.more">More</Trans> <Icon name="angle-down" />
|
||||
</Button>
|
||||
</Dropdown>
|
||||
{canExportRules && showExportDrawer && <GrafanaRulesExporter onClose={toggleShowExportDrawer} />}
|
||||
</Stack>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -2467,6 +2467,7 @@
|
||||
},
|
||||
"empty-data-source": "No rules found",
|
||||
"error-button": "Error",
|
||||
"export-all-grafana-rules": "Export all Grafana rules",
|
||||
"filter-view": {
|
||||
"cancel-search": "Cancel search",
|
||||
"no-more-results": "No more results – found {{numberOfRules}} rules",
|
||||
|
||||
Reference in New Issue
Block a user