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) {