From f0fe61070f353deee5fccd0f4f07b1ac8e4f5856 Mon Sep 17 00:00:00 2001
From: Gerardo Navarro <gerardo@b310.de>
Date: Mon, 11 Nov 2024 13:37:05 +0100
Subject: [PATCH] Protected containers: Remove field "Minimum access level for
 delete"

- During rollout of the feature "Protected containers", we noticed
  that the field "Minimum access level for delete" is visible, but
  the aspect delete protection has not been implemented yet;
  therefore, the existence / visibility of
  the field "Minimum access level for delete" might lead to unnecessary
  confusion.
- This MR intends to remove the field "Minimum access level for delete"
  from the frontend as suggested here: https://gitlab.com/gitlab-org/gitlab/-/issues/480385#note_2193605295

Changelog: other
---
 .../container_protection_rule_form.vue        | 17 -----------
 .../components/container_protection_rules.vue | 29 +------------------
 ...container_protection_rule.mutation.graphql |  1 -
 ...container_protection_rule.mutation.graphql |  1 -
 ..._registry_protection_rule.mutation.graphql |  1 -
 ...t_container_protection_rules.query.graphql |  1 -
 .../container_protection_rules.md             |  3 --
 locale/gitlab.pot                             |  5 +---
 .../container_protection_rule_form_spec.js    | 28 +-----------------
 .../container_protection_rules_spec.js        | 23 ++-------------
 .../settings/project/settings/mock_data.js    |  3 --
 11 files changed, 5 insertions(+), 107 deletions(-)

diff --git a/app/assets/javascripts/packages_and_registries/settings/project/components/container_protection_rule_form.vue b/app/assets/javascripts/packages_and_registries/settings/project/components/container_protection_rule_form.vue
index 5ec2508e2ac10b..af770e92d1814e 100644
--- a/app/assets/javascripts/packages_and_registries/settings/project/components/container_protection_rule_form.vue
+++ b/app/assets/javascripts/packages_and_registries/settings/project/components/container_protection_rule_form.vue
@@ -12,7 +12,6 @@ import HelpPageLink from '~/vue_shared/components/help_page_link/help_page_link.
 import createProtectionRuleMutation from '~/packages_and_registries/settings/project/graphql/mutations/create_container_protection_rule.mutation.graphql';
 import { s__, __ } from '~/locale';
 
-const GRAPHQL_ACCESS_LEVEL_VALUE_NULL = null;
 const GRAPHQL_ACCESS_LEVEL_VALUE_MAINTAINER = 'MAINTAINER';
 const GRAPHQL_ACCESS_LEVEL_VALUE_OWNER = 'OWNER';
 const GRAPHQL_ACCESS_LEVEL_VALUE_ADMIN = 'ADMIN';
@@ -42,7 +41,6 @@ export default {
       protectionRuleFormData: {
         repositoryPathPattern: '',
         minimumAccessLevelForPush: GRAPHQL_ACCESS_LEVEL_VALUE_MAINTAINER,
-        minimumAccessLevelForDelete: GRAPHQL_ACCESS_LEVEL_VALUE_MAINTAINER,
       },
       updateInProgress: false,
       alertErrorMessages: [],
@@ -66,12 +64,10 @@ export default {
         projectPath: this.projectPath,
         repositoryPathPattern: this.protectionRuleFormData.repositoryPathPattern,
         minimumAccessLevelForPush: this.protectionRuleFormData.minimumAccessLevelForPush,
-        minimumAccessLevelForDelete: this.protectionRuleFormData.minimumAccessLevelForDelete,
       };
     },
     minimumAccessLevelOptions() {
       return [
-        { value: GRAPHQL_ACCESS_LEVEL_VALUE_NULL, text: __('Developer (default)') },
         { value: GRAPHQL_ACCESS_LEVEL_VALUE_MAINTAINER, text: __('Maintainer') },
         { value: GRAPHQL_ACCESS_LEVEL_VALUE_OWNER, text: __('Owner') },
         { value: GRAPHQL_ACCESS_LEVEL_VALUE_ADMIN, text: __('Admin') },
@@ -170,19 +166,6 @@ export default {
       />
     </gl-form-group>
 
-    <gl-form-group
-      :label="s__('ContainerRegistry|Minimum access level for delete')"
-      label-for="input-minimum-access-level-for-delete"
-      :disabled="isFieldDisabled"
-    >
-      <gl-form-select
-        id="input-minimum-access-level-for-delete"
-        v-model="protectionRuleFormData.minimumAccessLevelForDelete"
-        :options="minimumAccessLevelOptions"
-        :disabled="isFieldDisabled"
-      />
-    </gl-form-group>
-
     <div class="gl-flex gl-justify-start">
       <gl-button
         variant="confirm"
diff --git a/app/assets/javascripts/packages_and_registries/settings/project/components/container_protection_rules.vue b/app/assets/javascripts/packages_and_registries/settings/project/components/container_protection_rules.vue
index b70029fcdbaebe..328cbb4e39b48a 100644
--- a/app/assets/javascripts/packages_and_registries/settings/project/components/container_protection_rules.vue
+++ b/app/assets/javascripts/packages_and_registries/settings/project/components/container_protection_rules.vue
@@ -22,9 +22,6 @@ import { s__, __ } from '~/locale';
 const PAGINATION_DEFAULT_PER_PAGE = 10;
 
 const I18N_MINIMUM_ACCESS_LEVEL_FOR_PUSH = s__('ContainerRegistry|Minimum access level for push');
-const I18N_MINIMUM_ACCESS_LEVEL_FOR_DELETE = s__(
-  'ContainerRegistry|Minimum access level for delete',
-);
 
 export default {
   components: {
@@ -48,7 +45,7 @@ export default {
   i18n: {
     settingBlockTitle: s__('ContainerRegistry|Protected containers'),
     settingBlockDescription: s__(
-      'ContainerRegistry|When a container is protected, only certain user roles can push and delete the protected container image, which helps to avoid tampering with the container image.',
+      'ContainerRegistry|When a container is protected, only certain user roles can push the protected container image, which helps to avoid tampering with the container image.',
     ),
     protectionRuleDeletionConfirmModal: {
       title: s__('ContainerRegistry|Delete container protection rule?'),
@@ -60,7 +57,6 @@ export default {
       ),
     },
     minimumAccessLevelForPush: I18N_MINIMUM_ACCESS_LEVEL_FOR_PUSH,
-    minimumAccessLevelForDelete: I18N_MINIMUM_ACCESS_LEVEL_FOR_DELETE,
   },
   apollo: {
     protectionRulesQueryPayload: {
@@ -94,7 +90,6 @@ export default {
       return this.protectionRulesQueryResult.map((protectionRule) => {
         return {
           id: protectionRule.id,
-          minimumAccessLevelForDelete: protectionRule.minimumAccessLevelForDelete,
           minimumAccessLevelForPush: protectionRule.minimumAccessLevelForPush,
           repositoryPathPattern: protectionRule.repositoryPathPattern,
         };
@@ -210,11 +205,6 @@ export default {
         minimumAccessLevelForPush: protectionRule.minimumAccessLevelForPush,
       });
     },
-    updateProtectionRuleMinimumAccessLevelForDelete(protectionRule) {
-      this.updateProtectionRule(protectionRule, {
-        minimumAccessLevelForDelete: protectionRule.minimumAccessLevelForDelete,
-      });
-    },
     updateProtectionRule(protectionRule, updateData) {
       this.clearAlertMessage();
 
@@ -259,11 +249,6 @@ export default {
       label: I18N_MINIMUM_ACCESS_LEVEL_FOR_PUSH,
       tdClass: '!gl-align-middle',
     },
-    {
-      key: 'minimumAccessLevelForDelete',
-      label: I18N_MINIMUM_ACCESS_LEVEL_FOR_DELETE,
-      tdClass: '!gl-align-middle',
-    },
     {
       key: 'rowActions',
       label: __('Actions'),
@@ -326,18 +311,6 @@ export default {
             />
           </template>
 
-          <template #cell(minimumAccessLevelForDelete)="{ item }">
-            <gl-form-select
-              v-model="item.minimumAccessLevelForDelete"
-              class="gl-max-w-34"
-              required
-              :aria-label="$options.i18n.minimumAccessLevelForDelete"
-              :options="minimumAccessLevelOptions"
-              :disabled="isProtectionRuleMinimumAccessLevelForPushFormSelectDisabled(item)"
-              @change="updateProtectionRuleMinimumAccessLevelForDelete(item)"
-            />
-          </template>
-
           <template #cell(rowActions)="{ item }">
             <gl-button
               v-gl-tooltip
diff --git a/app/assets/javascripts/packages_and_registries/settings/project/graphql/mutations/create_container_protection_rule.mutation.graphql b/app/assets/javascripts/packages_and_registries/settings/project/graphql/mutations/create_container_protection_rule.mutation.graphql
index 224aee2040c75b..668258223903d6 100644
--- a/app/assets/javascripts/packages_and_registries/settings/project/graphql/mutations/create_container_protection_rule.mutation.graphql
+++ b/app/assets/javascripts/packages_and_registries/settings/project/graphql/mutations/create_container_protection_rule.mutation.graphql
@@ -4,7 +4,6 @@ mutation createContainerProtectionRule($input: CreateContainerRegistryProtection
       id
       repositoryPathPattern
       minimumAccessLevelForPush
-      minimumAccessLevelForDelete
     }
     errors
   }
diff --git a/app/assets/javascripts/packages_and_registries/settings/project/graphql/mutations/delete_container_protection_rule.mutation.graphql b/app/assets/javascripts/packages_and_registries/settings/project/graphql/mutations/delete_container_protection_rule.mutation.graphql
index f92e126a5e4762..35c4290ba091e3 100644
--- a/app/assets/javascripts/packages_and_registries/settings/project/graphql/mutations/delete_container_protection_rule.mutation.graphql
+++ b/app/assets/javascripts/packages_and_registries/settings/project/graphql/mutations/delete_container_protection_rule.mutation.graphql
@@ -6,7 +6,6 @@ mutation deleteContainerRegistryProtectionRule(
       id
       repositoryPathPattern
       minimumAccessLevelForPush
-      minimumAccessLevelForDelete
     }
     errors
   }
diff --git a/app/assets/javascripts/packages_and_registries/settings/project/graphql/mutations/update_container_registry_protection_rule.mutation.graphql b/app/assets/javascripts/packages_and_registries/settings/project/graphql/mutations/update_container_registry_protection_rule.mutation.graphql
index 4aa907c83ddcbf..ae6dda75785347 100644
--- a/app/assets/javascripts/packages_and_registries/settings/project/graphql/mutations/update_container_registry_protection_rule.mutation.graphql
+++ b/app/assets/javascripts/packages_and_registries/settings/project/graphql/mutations/update_container_registry_protection_rule.mutation.graphql
@@ -6,7 +6,6 @@ mutation updateContainerRegistryProtectionRule(
       id
       repositoryPathPattern
       minimumAccessLevelForPush
-      minimumAccessLevelForDelete
     }
     errors
   }
diff --git a/app/assets/javascripts/packages_and_registries/settings/project/graphql/queries/get_container_protection_rules.query.graphql b/app/assets/javascripts/packages_and_registries/settings/project/graphql/queries/get_container_protection_rules.query.graphql
index 371e08fbeff8d4..aa599b6769a343 100644
--- a/app/assets/javascripts/packages_and_registries/settings/project/graphql/queries/get_container_protection_rules.query.graphql
+++ b/app/assets/javascripts/packages_and_registries/settings/project/graphql/queries/get_container_protection_rules.query.graphql
@@ -14,7 +14,6 @@ query getProjectContainerProtectionRules(
         id
         repositoryPathPattern
         minimumAccessLevelForPush
-        minimumAccessLevelForDelete
       }
       pageInfo {
         ...PageInfo
diff --git a/doc/user/packages/container_registry/container_protection_rules.md b/doc/user/packages/container_registry/container_protection_rules.md
index 6bc3695acc7af3..6e9a96d37a01ea 100644
--- a/doc/user/packages/container_registry/container_protection_rules.md
+++ b/doc/user/packages/container_registry/container_protection_rules.md
@@ -31,7 +31,6 @@ When a container repository is protected, the default behavior enforces these re
 | Protect a container repository and its container images    | The Maintainer role. |
 | Push or create a new image in a container repository       | The role set in the [**Minimum access level for push**](#protect-a-container-repository-and-create-a-protection-rule) setting.   |
 | Push or update an existing image in a container repository | The role set in the [**Minimum access level for push**](#protect-a-container-repository-and-create-a-protection-rule) setting    |
-| Delete an existing image from a container repository       | The role set in the [**Minimum access level for delete**](#protect-a-container-repository-and-create-a-protection-rule) setting. |
 
 You can use a wildcard (`*`) to protect multiple container repositories with the same container protection rule.
 For example, you can protect different container repositories containing temporary container images built during a CI/CD pipeline.
@@ -65,8 +64,6 @@ To protect a container repository:
      The pattern can include a wildcard (`*`).
    - **Minimum access level for push** describes the minimum access level required
      to push (create or update) to the protected container repository path.
-   - **Minimum access level for delete** describes the minimum access level required
-     to delete from the protected container repository path.
 1. Select **Protect**.
 
 The container protection rule is created, and appears in the settings.
diff --git a/locale/gitlab.pot b/locale/gitlab.pot
index efa19835aa78f4..011e7cc05000af 100644
--- a/locale/gitlab.pot
+++ b/locale/gitlab.pot
@@ -15248,9 +15248,6 @@ msgstr ""
 msgid "ContainerRegistry|Manifest media type: %{mediaType}"
 msgstr ""
 
-msgid "ContainerRegistry|Minimum access level for delete"
-msgstr ""
-
 msgid "ContainerRegistry|Minimum access level for push"
 msgstr ""
 
@@ -15442,7 +15439,7 @@ msgstr ""
 msgid "ContainerRegistry|We are having trouble connecting to the Container Registry. Please try refreshing the page. If this error persists, please review  %{docLinkStart}the troubleshooting documentation%{docLinkEnd}."
 msgstr ""
 
-msgid "ContainerRegistry|When a container is protected, only certain user roles can push and delete the protected container image, which helps to avoid tampering with the container image."
+msgid "ContainerRegistry|When a container is protected, only certain user roles can push the protected container image, which helps to avoid tampering with the container image."
 msgstr ""
 
 msgid "ContainerRegistry|While the rename is in progress, new uploads to the container registry are blocked. Ongoing uploads may fail and need to be retried."
diff --git a/spec/frontend/packages_and_registries/settings/project/settings/components/container_protection_rule_form_spec.js b/spec/frontend/packages_and_registries/settings/project/settings/components/container_protection_rule_form_spec.js
index 1321549354d01d..1a5c5ce3809e9d 100644
--- a/spec/frontend/packages_and_registries/settings/project/settings/components/container_protection_rule_form_spec.js
+++ b/spec/frontend/packages_and_registries/settings/project/settings/components/container_protection_rule_form_spec.js
@@ -27,8 +27,6 @@ describe('container Protection Rule Form', () => {
     wrapper.findByRole('textbox', { name: /repository path pattern/i });
   const findMinimumAccessLevelForPushSelect = () =>
     wrapper.findByRole('combobox', { name: /minimum access level for push/i });
-  const findMinimumAccessLevelForDeleteSelect = () =>
-    wrapper.findByRole('combobox', { name: /minimum access level for delete/i });
   const findSubmitButton = () => wrapper.findByRole('button', { name: /add rule/i });
 
   const mountComponent = ({ config, provide = defaultProvidedValues } = {}) => {
@@ -58,7 +56,7 @@ describe('container Protection Rule Form', () => {
           .findAll('option')
           .wrappers.map((option) => option.element.value);
 
-      it.each(['', 'MAINTAINER', 'OWNER', 'ADMIN'])(
+      it.each(['MAINTAINER', 'OWNER', 'ADMIN'])(
         'includes the access level "%s" as an option',
         (accessLevel) => {
           mountComponent();
@@ -80,7 +78,6 @@ describe('container Protection Rule Form', () => {
         expect(findSubmitButton().props('disabled')).toBe(true);
         expect(findRepositoryPathPatternInput().attributes('disabled')).toBe('disabled');
         expect(findMinimumAccessLevelForPushSelect().attributes('disabled')).toBe('disabled');
-        expect(findMinimumAccessLevelForDeleteSelect().attributes('disabled')).toBe('disabled');
       });
 
       it('displays a loading spinner', () => {
@@ -165,29 +162,6 @@ describe('container Protection Rule Form', () => {
         });
       });
 
-      it('dispatches correct apollo mutation when no minimumAccessLevelForPush is selected', async () => {
-        const mutationResolver = jest
-          .fn()
-          .mockResolvedValue(createContainerProtectionRuleMutationPayload());
-
-        mountComponentWithApollo({ mutationResolver });
-
-        await findRepositoryPathPatternInput().setValue(
-          createContainerProtectionRuleMutationInput.repositoryPathPattern,
-        );
-        await findMinimumAccessLevelForPushSelect().setValue('');
-
-        await submitForm();
-
-        expect(mutationResolver).toHaveBeenCalledWith({
-          input: {
-            projectPath: 'path',
-            ...createContainerProtectionRuleMutationInput,
-            minimumAccessLevelForPush: null,
-          },
-        });
-      });
-
       it('emits event "submit" when apollo mutation successful', async () => {
         const mutationResolver = jest
           .fn()
diff --git a/spec/frontend/packages_and_registries/settings/project/settings/components/container_protection_rules_spec.js b/spec/frontend/packages_and_registries/settings/project/settings/components/container_protection_rules_spec.js
index 39af02bbc1c1ef..662f77b8c479bf 100644
--- a/spec/frontend/packages_and_registries/settings/project/settings/components/container_protection_rules_spec.js
+++ b/spec/frontend/packages_and_registries/settings/project/settings/components/container_protection_rules_spec.js
@@ -113,28 +113,10 @@ describe('Container protection rules project settings', () => {
         (protectionRule, i) => {
           expect(findTableRowCell(i, 0).text()).toBe(protectionRule.repositoryPathPattern);
           expect(findTableRowCellComboboxSelectedOption(i, 1).text).toBe('Maintainer');
-          expect(findTableRowCellComboboxSelectedOption(i, 2).text).toBe('Maintainer');
         },
       );
     });
 
-    it('renders table with container protection rule with blank minimumAccessLevelForDelete', async () => {
-      const containerProtectionRuleQueryResolver = jest.fn().mockResolvedValue(
-        containerProtectionRuleQueryPayload({
-          nodes: [{ ...containerProtectionRulesData[0], minimumAccessLevelForDelete: null }],
-        }),
-      );
-      createComponent({ containerProtectionRuleQueryResolver });
-
-      await waitForPromises();
-
-      expect(findTableRowCell(0, 0).text()).toBe(
-        containerProtectionRulesData[0].repositoryPathPattern,
-      );
-      expect(findTableRowCellComboboxSelectedOption(0, 1).text).toBe('Maintainer');
-      expect(findTableRowCellComboboxSelectedOption(0, 2).text).toBe('Developer (default)');
-    });
-
     it('displays table in busy state and shows loading icon inside table', async () => {
       createComponent();
 
@@ -298,9 +280,8 @@ describe('Container protection rules project settings', () => {
     });
 
     describe.each`
-      comboboxName                         | minimumAccessLevelAttribute
-      ${'Minimum access level for push'}   | ${'minimumAccessLevelForPush'}
-      ${'Minimum access level for delete'} | ${'minimumAccessLevelForDelete'}
+      comboboxName                       | minimumAccessLevelAttribute
+      ${'Minimum access level for push'} | ${'minimumAccessLevelForPush'}
     `(
       'column "$comboboxName" with selectbox (combobox)',
       ({ comboboxName, minimumAccessLevelAttribute }) => {
diff --git a/spec/frontend/packages_and_registries/settings/project/settings/mock_data.js b/spec/frontend/packages_and_registries/settings/project/settings/mock_data.js
index 913109d4028bce..a50bc8768aa6d1 100644
--- a/spec/frontend/packages_and_registries/settings/project/settings/mock_data.js
+++ b/spec/frontend/packages_and_registries/settings/project/settings/mock_data.js
@@ -171,13 +171,11 @@ export const containerProtectionRulesData = [
     id: `gid://gitlab/ContainerRegistry::Protection::Rule/${i}`,
     repositoryPathPattern: `@flight/flight/maintainer-${i}-*`,
     minimumAccessLevelForPush: 'MAINTAINER',
-    minimumAccessLevelForDelete: 'MAINTAINER',
   })),
   {
     id: 'gid://gitlab/ContainerRegistry::Protection::Rule/16',
     repositoryPathPattern: '@flight/flight/owner-16-*',
     minimumAccessLevelForPush: 'OWNER',
-    minimumAccessLevelForDelete: 'OWNER',
   },
 ];
 
@@ -218,7 +216,6 @@ export const createContainerProtectionRuleMutationPayload = ({ override, errors
 export const createContainerProtectionRuleMutationInput = {
   repositoryPathPattern: `@flight/flight-maintainer-14-*`,
   minimumAccessLevelForPush: 'MAINTAINER',
-  minimumAccessLevelForDelete: 'MAINTAINER',
 };
 
 export const createContainerProtectionRuleMutationPayloadErrors = [
-- 
GitLab