From 7b47beef2ff4258bce7873178e8784d2c4c530fc Mon Sep 17 00:00:00 2001 From: Konrad Lalik Date: Mon, 30 Jan 2023 16:02:01 +0100 Subject: [PATCH] Alerting: Add Rule UID and Clone button to the rule details page (#62321) --- .../alerting/unified/RuleViewer.test.tsx | 33 ++++++++- .../features/alerting/unified/RuleViewer.tsx | 38 ++++++++-- public/app/features/alerting/unified/TODO.md | 1 + .../unified/components/AlertLabels.tsx | 7 +- .../unified/components/DetailsField.tsx | 13 +++- .../components/alert-groups/AlertGroup.tsx | 10 +-- .../alert-groups/AlertGroupAlertsTable.tsx | 5 +- .../components/rules/CloneRuleButton.tsx | 73 +++++++++++++++++++ .../components/rules/RuleActionsButtons.tsx | 41 +---------- .../rules/RuleDetailsActionButtons.tsx | 42 +++++++---- 10 files changed, 188 insertions(+), 75 deletions(-) create mode 100644 public/app/features/alerting/unified/components/rules/CloneRuleButton.tsx diff --git a/public/app/features/alerting/unified/RuleViewer.test.tsx b/public/app/features/alerting/unified/RuleViewer.test.tsx index c57279d70ef..5e9ff4f1e80 100644 --- a/public/app/features/alerting/unified/RuleViewer.test.tsx +++ b/public/app/features/alerting/unified/RuleViewer.test.tsx @@ -15,7 +15,7 @@ import { CombinedRule } from 'app/types/unified-alerting'; import { RuleViewer } from './RuleViewer'; import { useCombinedRule } from './hooks/useCombinedRule'; import { useIsRuleEditable } from './hooks/useIsRuleEditable'; -import { getCloudRule, getGrafanaRule } from './mocks'; +import { getCloudRule, getGrafanaRule, grantUserPermissions } from './mocks'; const mockGrafanaRule = getGrafanaRule({ name: 'Test alert' }); const mockCloudRule = getCloudRule({ name: 'cloud test alert' }); @@ -61,6 +61,7 @@ const renderRuleViewer = () => { const ui = { actionButtons: { edit: byRole('link', { name: /edit/i }), + clone: byRole('link', { name: /clone/i }), delete: byRole('button', { name: /delete/i }), silence: byRole('link', { name: 'Silence' }), }, @@ -200,6 +201,36 @@ describe('RuleDetails RBAC', () => { // Assert expect(ui.actionButtons.silence.query()).toBeInTheDocument(); }); + + it('Should render clone button for users having create rule permission', async () => { + mocks.useIsRuleEditable.mockReturnValue({ loading: false, isEditable: false }); + mockCombinedRule.mockReturnValue({ + result: getGrafanaRule({ name: 'Grafana rule' }), + loading: false, + dispatched: true, + }); + grantUserPermissions([AccessControlAction.AlertingRuleCreate]); + + await renderRuleViewer(); + + expect(ui.actionButtons.clone.get()).toBeInTheDocument(); + }); + + it('Should NOT render clone button for users without create rule permission', async () => { + mocks.useIsRuleEditable.mockReturnValue({ loading: false, isEditable: true }); + mockCombinedRule.mockReturnValue({ + result: getGrafanaRule({ name: 'Grafana rule' }), + loading: false, + dispatched: true, + }); + + const { AlertingRuleRead, AlertingRuleUpdate, AlertingRuleDelete } = AccessControlAction; + grantUserPermissions([AlertingRuleRead, AlertingRuleUpdate, AlertingRuleDelete]); + + await renderRuleViewer(); + + expect(ui.actionButtons.clone.query()).not.toBeInTheDocument(); + }); }); describe('Cloud rules action buttons', () => { let mockCombinedRule: jest.MockedFn; diff --git a/public/app/features/alerting/unified/RuleViewer.tsx b/public/app/features/alerting/unified/RuleViewer.tsx index 3b493ed5f45..7ae268b8225 100644 --- a/public/app/features/alerting/unified/RuleViewer.tsx +++ b/public/app/features/alerting/unified/RuleViewer.tsx @@ -10,6 +10,7 @@ import { Button, Collapse, Icon, + IconButton, LoadingPlaceholder, useStyles2, VerticalGroup, @@ -18,7 +19,7 @@ import { import { GrafanaRouteComponentProps } from 'app/core/navigation/types'; import { DEFAULT_PER_PAGE_PAGINATION } from '../../../core/constants'; -import { AlertQuery } from '../../../types/unified-alerting-dto'; +import { AlertQuery, GrafanaRuleDefinition } from '../../../types/unified-alerting-dto'; import { GrafanaRuleQueryViewer, QueryPreview } from './GrafanaRuleQueryViewer'; import { AlertLabels } from './components/AlertLabels'; @@ -181,7 +182,7 @@ export function RuleViewer({ match }: RuleViewerProps) { )} {!!rule.labels && !!Object.keys(rule.labels).length && ( - + )} @@ -190,7 +191,10 @@ export function RuleViewer({ match }: RuleViewerProps) {
{isFederatedRule && } - {`${rule.namespace.name} / ${rule.group.name}`} + + {rule.namespace.name} / {rule.group.name} + + {isGrafanaRulerRule(rule.rulerRule) && }
@@ -244,6 +248,17 @@ export function RuleViewer({ match }: RuleViewerProps) { ); } +function GrafanaRuleUID({ rule }: { rule: GrafanaRuleDefinition }) { + const styles = useStyles2(getStyles); + const copyUID = () => navigator.clipboard && navigator.clipboard.writeText(rule.uid); + + return ( + + {rule.uid} + + ); +} + function isLoading(data: Record): boolean { return !!Object.values(data).find((d) => d.state === LoadingState.Loading); } @@ -278,13 +293,26 @@ const getStyles = (theme: GrafanaTheme2) => { details: css` display: flex; flex-direction: row; + gap: ${theme.spacing(4)}; `, leftSide: css` flex: 1; `, rightSide: css` - padding-left: 90px; - width: 300px; + padding-right: ${theme.spacing(3)}; + `, + rightSideDetails: css` + & > div:first-child { + width: auto; + } + `, + labels: css` + justify-content: flex-start; + `, + ruleUid: css` + display: flex; + align-items: center; + gap: ${theme.spacing(1)}; `, }; }; diff --git a/public/app/features/alerting/unified/TODO.md b/public/app/features/alerting/unified/TODO.md index 52286bfaaf9..c9d508bb51c 100644 --- a/public/app/features/alerting/unified/TODO.md +++ b/public/app/features/alerting/unified/TODO.md @@ -17,6 +17,7 @@ If the item needs more rationale and you feel like a single sentence is inedequa ## Refactoring - Get rid of "+ Add new" in drop-downs : Let's see if is there a way we can make it work with `