diff --git a/public/app/features/alerting/unified/components/contact-points/ContactPointHeader.tsx b/public/app/features/alerting/unified/components/contact-points/ContactPointHeader.tsx index 40a7afeddeb..1825b5e40a0 100644 --- a/public/app/features/alerting/unified/components/contact-points/ContactPointHeader.tsx +++ b/public/app/features/alerting/unified/components/contact-points/ContactPointHeader.tsx @@ -68,14 +68,14 @@ export const ContactPointHeader = ({ contactPoint, onDelete }: ContactPointHeade */ const isReferencedByAnything = usingK8sApi ? Boolean(numberOfPolicies || numberOfRules) : policies.length > 0; /** Does the current user have permissions to edit the contact point? */ - const hasAbilityToEdit = canEditEntity(contactPoint) || editAllowed; + const hasAbilityToEdit = usingK8sApi ? canEditEntity(contactPoint) : editAllowed; /** Can the contact point actually be edited via the UI? */ const contactPointIsEditable = !provisioned; /** Given the alertmanager, the user's permissions, and the state of the contact point - can it actually be edited? */ const canEdit = editSupported && hasAbilityToEdit && contactPointIsEditable; /** Does the current user have permissions to delete the contact point? */ - const hasAbilityToDelete = canDeleteEntity(contactPoint) || deleteAllowed; + const hasAbilityToDelete = usingK8sApi ? canDeleteEntity(contactPoint) : deleteAllowed; /** Can the contact point actually be deleted, regardless of permissions? i.e. ensuring it isn't provisioned and isn't referenced elsewhere */ const contactPointIsDeleteable = !provisioned && !numberOfPoliciesPreventingDeletion && !numberOfRules; /** Given the alertmanager, the user's permissions, and the state of the contact point - can it actually be deleted? */ diff --git a/public/app/features/alerting/unified/components/contact-points/ContactPoints.test.tsx b/public/app/features/alerting/unified/components/contact-points/ContactPoints.test.tsx index 79cec523d69..db39d474957 100644 --- a/public/app/features/alerting/unified/components/contact-points/ContactPoints.test.tsx +++ b/public/app/features/alerting/unified/components/contact-points/ContactPoints.test.tsx @@ -198,12 +198,37 @@ describe('contact points', () => { const unusedBadge = screen.getAllByLabelText('unused'); expect(unusedBadge).toHaveLength(4); - const viewProvisioned = screen.getByTestId('view-action'); - expect(viewProvisioned).toBeInTheDocument(); - expect(viewProvisioned).toBeEnabled(); + // Two contact points should have view buttons: grafana-default-email (cannot be edited) and provisioned-contact-point (provisioned) + const viewButtons = screen.getAllByRole('link', { name: /^view$/i }); + expect(viewButtons).toHaveLength(2); + + // Check view buttons by their href to verify which contact points they belong to + // The url is the same but the form should be readonly + expect(viewButtons[0]).toHaveAttribute('href', '/alerting/notifications/receivers/grafana-default-email/edit'); + expect(viewButtons[1]).toHaveAttribute( + 'href', + '/alerting/notifications/receivers/provisioned-contact-point/edit' + ); + + viewButtons.forEach((button) => { + expect(button).toBeEnabled(); + }); + + // Three contact points should have edit buttons: lotsa-emails, Slack with multiple channels, OnCall Contact point + const editButtons = screen.getAllByRole('link', { name: /^edit$/i }); + expect(editButtons).toHaveLength(3); + + // Check edit buttons by their href to verify which contact points they belong to + expect(editButtons[0]).toHaveAttribute('href', '/alerting/notifications/receivers/lotsa-emails/edit'); + expect(editButtons[1]).toHaveAttribute( + 'href', + '/alerting/notifications/receivers/OnCall%20Conctact%20point/edit' + ); + expect(editButtons[2]).toHaveAttribute( + 'href', + '/alerting/notifications/receivers/Slack%20with%20multiple%20channels/edit' + ); - const editButtons = screen.getAllByTestId('edit-action'); - expect(editButtons).toHaveLength(4); editButtons.forEach((button) => { expect(button).toBeEnabled(); }); @@ -227,11 +252,11 @@ describe('contact points', () => { expect(screen.getByRole('link', { name: 'add contact point' })).toHaveAttribute('aria-disabled', 'true'); // edit permission is based on API response - we should have 3 buttons - const editButtons = await screen.findAllByTestId('edit-action'); + const editButtons = await screen.findAllByRole('link', { name: /^edit$/i }); expect(editButtons).toHaveLength(3); // there should be view buttons though - one for provisioned, and one for the un-editable contact point - const viewButtons = screen.getAllByTestId('view-action'); + const viewButtons = screen.getAllByRole('link', { name: /^view$/i }); expect(viewButtons).toHaveLength(2); // check buttons in Notification Templates @@ -329,7 +354,18 @@ describe('contact points', () => { }, ]; - const { user } = renderWithProvider(); + // Add the necessary K8s annotations to allow deletion + const contactPointWithDeletePermission: ContactPointWithMetadata = { + ...basicContactPoint, + metadata: { + annotations: { + [K8sAnnotations.AccessDelete]: 'true', + }, + }, + policies, + }; + + const { user } = renderWithProvider(); const moreActions = screen.getByRole('button', { name: /More/ }); await user.click(moreActions); @@ -387,7 +423,7 @@ describe('contact points', () => { const unusedBadge = screen.getAllByLabelText('unused'); expect(unusedBadge).toHaveLength(1); - const editButtons = screen.getAllByTestId('edit-action'); + const editButtons = screen.getAllByRole('link', { name: /^edit$/i }); expect(editButtons).toHaveLength(2); editButtons.forEach((button) => { expect(button).toBeEnabled(); @@ -431,9 +467,9 @@ describe('contact points', () => { expect(screen.queryByRole('link', { name: 'add contact point' })).not.toBeInTheDocument(); - const viewProvisioned = screen.getByTestId('view-action'); - expect(viewProvisioned).toBeInTheDocument(); - expect(viewProvisioned).toBeEnabled(); + const viewButton = screen.getByRole('link', { name: /^view$/i }); + expect(viewButton).toBeInTheDocument(); + expect(viewButton).toBeEnabled(); // check buttons in Notification Templates const notificationTemplatesTab = screen.getByRole('tab', { name: 'Notification Templates' }); diff --git a/public/app/features/alerting/unified/components/contact-points/EditContactPoint.test.tsx b/public/app/features/alerting/unified/components/contact-points/EditContactPoint.test.tsx index 04f6fcdb435..75413e20e33 100644 --- a/public/app/features/alerting/unified/components/contact-points/EditContactPoint.test.tsx +++ b/public/app/features/alerting/unified/components/contact-points/EditContactPoint.test.tsx @@ -44,7 +44,8 @@ beforeEach(() => { grantUserPermissions([AccessControlAction.AlertingNotificationsRead, AccessControlAction.AlertingNotificationsWrite]); }); -const getTemplatePreviewContent = async () => within(screen.getByTestId('template-preview')).findByTestId('mockeditor'); +const getTemplatePreviewContent = async () => + within(await screen.findByTestId('template-preview')).findByTestId('mockeditor'); const templatesSelectorTestId = 'existing-templates-selector'; diff --git a/public/app/features/alerting/unified/components/receivers/form/GrafanaReceiverForm.tsx b/public/app/features/alerting/unified/components/receivers/form/GrafanaReceiverForm.tsx index 44da5c3588b..8971c9b34d7 100644 --- a/public/app/features/alerting/unified/components/receivers/form/GrafanaReceiverForm.tsx +++ b/public/app/features/alerting/unified/components/receivers/form/GrafanaReceiverForm.tsx @@ -135,9 +135,9 @@ export const GrafanaReceiverForm = ({ contactPoint, readOnly = false, editMode } } }; - const isEditable = Boolean( - (!readOnly || (contactPoint && canEditEntity(contactPoint))) && !contactPoint?.provisioned - ); + // If there is no contact point it means we're creating a new one, so scoped permissions doesn't exist yet + const hasScopedEditPermissions = contactPoint ? canEditEntity(contactPoint) : true; + const isEditable = !readOnly && hasScopedEditPermissions && !contactPoint?.provisioned; const isTestable = !readOnly; if (isLoadingNotifiers || isLoadingOnCallIntegration) {