mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
Alerting: Add export button for exporting all alert rules in alert list view (#62416)
* Add export button for exporting all alert rules in alert list view * Add RBAC for export button * Use provisioningPermissions.read in getRulesAccess method instead of directly using AccessControlAction.AlertingProvisioningRead
This commit is contained in:
parent
a9d44aa795
commit
d44f250aea
@ -1,12 +1,12 @@
|
||||
import { SerializedError } from '@reduxjs/toolkit';
|
||||
import { render, waitFor, screen } from '@testing-library/react';
|
||||
import { render, screen, waitFor } from '@testing-library/react';
|
||||
import userEvent from '@testing-library/user-event';
|
||||
import React from 'react';
|
||||
import { Provider } from 'react-redux';
|
||||
import { Router } from 'react-router-dom';
|
||||
import { byRole, byTestId, byText } from 'testing-library-selector';
|
||||
|
||||
import { locationService, setDataSourceSrv, logInfo, setBackendSrv } from '@grafana/runtime';
|
||||
import { locationService, logInfo, setBackendSrv, setDataSourceSrv } from '@grafana/runtime';
|
||||
import { backendSrv } from 'app/core/services/backend_srv';
|
||||
import { contextSrv } from 'app/core/services/context_srv';
|
||||
import * as ruleActionButtons from 'app/features/alerting/unified/components/rules/RuleActionsButtons';
|
||||
@ -123,6 +123,7 @@ const ui = {
|
||||
editCloudGroupIcon: byTestId('edit-group'),
|
||||
|
||||
newRuleButton: byRole('link', { name: 'New alert rule' }),
|
||||
exportButton: byRole('button', { name: /export/i }),
|
||||
|
||||
editGroupModal: {
|
||||
namespaceInput: byRole('textbox', { hidden: true, name: /namespace/i }),
|
||||
@ -681,6 +682,34 @@ describe('RuleList', () => {
|
||||
});
|
||||
|
||||
describe('RBAC Enabled', () => {
|
||||
describe('Export button', () => {
|
||||
it('Export button should be visible when the user has alert provisioning read permissions', async () => {
|
||||
enableRBAC();
|
||||
|
||||
grantUserPermissions([AccessControlAction.AlertingProvisioningRead]);
|
||||
|
||||
mocks.getAllDataSourcesMock.mockReturnValue([]);
|
||||
setDataSourceSrv(new MockDataSourceSrv({}));
|
||||
mocks.api.fetchRules.mockResolvedValue([]);
|
||||
mocks.api.fetchRulerRules.mockResolvedValue({});
|
||||
|
||||
renderRuleList();
|
||||
|
||||
expect(ui.exportButton.get()).toBeInTheDocument();
|
||||
});
|
||||
it('Export button should not be visible when the user has no alert provisioning read permissions', async () => {
|
||||
enableRBAC();
|
||||
|
||||
mocks.getAllDataSourcesMock.mockReturnValue([]);
|
||||
setDataSourceSrv(new MockDataSourceSrv({}));
|
||||
mocks.api.fetchRules.mockResolvedValue([]);
|
||||
mocks.api.fetchRulerRules.mockResolvedValue({});
|
||||
|
||||
renderRuleList();
|
||||
|
||||
expect(ui.exportButton.query()).not.toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
describe('Grafana Managed Alerts', () => {
|
||||
it('New alert button should be visible when the user has alert rule create and folder read permissions and no rules exists', async () => {
|
||||
enableRBAC();
|
||||
|
@ -4,6 +4,7 @@ import { useLocation } from 'react-router-dom';
|
||||
import { useAsyncFn, useInterval } from 'react-use';
|
||||
|
||||
import { GrafanaTheme2, urlUtil } from '@grafana/data';
|
||||
import { Stack } from '@grafana/experimental';
|
||||
import { logInfo } from '@grafana/runtime';
|
||||
import { Button, LinkButton, useStyles2, withErrorBoundary } from '@grafana/ui';
|
||||
import { useQueryParams } from 'app/core/hooks/useQueryParams';
|
||||
@ -32,6 +33,8 @@ const VIEWS = {
|
||||
state: RuleListStateView,
|
||||
};
|
||||
|
||||
const onExport = () => window.open(`/api/v1/provisioning/alert-rules/export?download=true`);
|
||||
|
||||
const RuleList = withErrorBoundary(
|
||||
() => {
|
||||
const dispatch = useDispatch();
|
||||
@ -43,7 +46,7 @@ const RuleList = withErrorBoundary(
|
||||
const [queryParams] = useQueryParams();
|
||||
const { filterState, hasActiveFilters } = useRulesFilter();
|
||||
|
||||
const { canCreateGrafanaRules, canCreateCloudRules } = useRulesAccess();
|
||||
const { canCreateGrafanaRules, canCreateCloudRules, canReadProvisioning } = useRulesAccess();
|
||||
|
||||
const view = VIEWS[queryParams['view'] as keyof typeof VIEWS]
|
||||
? (queryParams['view'] as keyof typeof VIEWS)
|
||||
@ -106,15 +109,22 @@ const RuleList = withErrorBoundary(
|
||||
)}
|
||||
<RuleStats namespaces={filteredNamespaces} includeTotal />
|
||||
</div>
|
||||
{(canCreateGrafanaRules || canCreateCloudRules) && (
|
||||
<LinkButton
|
||||
href={urlUtil.renderUrl('alerting/new', { returnTo: location.pathname + location.search })}
|
||||
icon="plus"
|
||||
onClick={() => logInfo(LogMessages.alertRuleFromScratch)}
|
||||
>
|
||||
New alert rule
|
||||
</LinkButton>
|
||||
)}
|
||||
<Stack direction="row" gap={0.5}>
|
||||
{canReadProvisioning && (
|
||||
<Button icon="download-alt" type="button" onClick={onExport}>
|
||||
Export
|
||||
</Button>
|
||||
)}
|
||||
{(canCreateGrafanaRules || canCreateCloudRules) && (
|
||||
<LinkButton
|
||||
href={urlUtil.renderUrl('alerting/new', { returnTo: location.pathname + location.search })}
|
||||
icon="plus"
|
||||
onClick={() => logInfo(LogMessages.alertRuleFromScratch)}
|
||||
>
|
||||
New alert rule
|
||||
</LinkButton>
|
||||
)}
|
||||
</Stack>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
|
@ -7,7 +7,7 @@ import { Matcher } from 'app/plugins/datasource/alertmanager/types';
|
||||
import { CombinedRuleGroup, CombinedRuleNamespace } from 'app/types/unified-alerting';
|
||||
import { isPromAlertingRuleState, PromRuleType, RulerGrafanaRuleDTO } from 'app/types/unified-alerting-dto';
|
||||
|
||||
import { getSearchFilterFromQuery, RulesFilter, applySearchFilterToQuery } from '../search/rulesSearchParser';
|
||||
import { applySearchFilterToQuery, getSearchFilterFromQuery, RulesFilter } from '../search/rulesSearchParser';
|
||||
import { labelsMatchMatchers, matcherToMatcherField, parseMatcher, parseMatchers } from '../utils/alertmanager';
|
||||
import { isCloudRulesSource } from '../utils/datasource';
|
||||
import { getRuleHealth, isAlertingRule, isGrafanaRulerRule, isPromRuleType } from '../utils/rules';
|
||||
|
@ -47,6 +47,11 @@ export const notificationsPermissions = {
|
||||
},
|
||||
};
|
||||
|
||||
export const provisioningPermissions = {
|
||||
read: AccessControlAction.AlertingProvisioningRead,
|
||||
write: AccessControlAction.AlertingProvisioningWrite,
|
||||
};
|
||||
|
||||
const rulesPermissions = {
|
||||
read: {
|
||||
grafana: AccessControlAction.AlertingRuleRead,
|
||||
@ -118,5 +123,6 @@ export function getRulesAccess() {
|
||||
rulesSourceName === GRAFANA_RULES_SOURCE_NAME ? contextSrv.hasEditPermissionInFolders : contextSrv.isEditor;
|
||||
return contextSrv.hasAccess(getRulesPermissions(rulesSourceName).update, permissionFallback);
|
||||
},
|
||||
canReadProvisioning: contextSrv.hasAccess(provisioningPermissions.read, contextSrv.isGrafanaAdmin),
|
||||
};
|
||||
}
|
||||
|
@ -117,6 +117,10 @@ export enum AccessControlAction {
|
||||
AlertingNotificationsExternalWrite = 'alert.notifications.external:write',
|
||||
AlertingNotificationsExternalRead = 'alert.notifications.external:read',
|
||||
|
||||
// Alerting provisioning actions
|
||||
AlertingProvisioningRead = 'alert.provisioning:read',
|
||||
AlertingProvisioningWrite = 'alert.provisioning:write',
|
||||
|
||||
ActionAPIKeysRead = 'apikeys:read',
|
||||
ActionAPIKeysCreate = 'apikeys:create',
|
||||
ActionAPIKeysDelete = 'apikeys:delete',
|
||||
|
Loading…
Reference in New Issue
Block a user