Skip to content

Merge request approval policy with block_branch_modification causes generic error

Summary

When attempting to delete a branch rule in a project that is affected by a merge request approval policy you get a Something went wrong while deleting branch rule. error message. While the failure to delete is intentional due to block_branch_modification: true, the error message for affected users is confusing and not actionable.

image

Steps to reproduce

  1. Create a new project
  2. Add a new branch rule for a Branch name or pattern and protect e.g. the pattern protected*
  3. Set up a Merge Request approval policy for this project
    • It can be extremely simple, the key is block_branch_modification: true and branch_type: protected:
    approval_policy:
    - name: Test
      description: ''
      enabled: true
      policy_scope:
        compliance_frameworks:
        - id: 1019736
      rules:
      - type: any_merge_request
        branch_type: protected
        commits: any
      actions:
      - type: send_bot_message
        enabled: true
      approval_settings:
        block_branch_modification: true
        prevent_pushing_and_force_pushing: false
        prevent_approval_by_author: false
        prevent_approval_by_commit_author: false
        remove_approvals_with_new_commit: false
        require_password_to_approve: false
  4. Attempt to delete the branch rule from step 2
  5. Observe Something went wrong while deleting branch rule. error message
  6. Update policy to say branch_type: default
  7. Attempt to delete the branch rule from step 2
  8. Observe successful deletion

Step 2 can also performed after step 3, which makes this a bit confusing: I can create new branch rules, but I can't delete them afterwards. This is in line with our documentation:

When enabled, prevents a user from removing a branch from the protected branches list, deleting a protected branch, or changing the default branch if that branch is included in the security policy.

But when a user who hasn't set up the policy sees the error, they can't really know what is going on.

Example Project

https://gitlab.com/gl-demo-ultimate-mgrabowski/zd-566179 – not very helpful though as this all happens in project settings. Let me know if anyone wants to get added to this project to verify.

What is the current bug behavior?

Attempting to delete a branch rule in these circumstances causes an error message that doesn't explain why the rule can't be deleted.

What is the expected correct behavior?

The error message should state clearly why the rule can't be deleted, ideally mentioning the specific Merge Request Approval Policy name that is blocking deletion.

Relevant logs and/or screenshots

Output of checks

This bug happens on GitLab.com

Possible fixes

  1. The error message comes from index.vue – not sure if the branch_rule_delete.mutation.graphql that it uses can already provide a more detailed error that we just have to use in the frontend. Otherwise we'd likely need to make sure the mutation can return a better error message first. Likely the latter, the current GraphQL response is "message": "The resource that you are attempting to access does not exist or you don't have permission to perform this action".
  2. Disable the button to delete when a security policy is enabled and show a tooltip

While the block_branch_modification setting still prevents the deletion with the new Delete rule button and Edit button of the Rule target, we would prefer to have the buttons disabled with a tooltip for this button and potentially any future buttons.

Similarly, the prevent_pushing_and_force_pushing setting still prevents the change of the Allow force push toggle from being turned on, but disabling it and adding a tooltip would make it more clear.

Scenario Screenshot
Protected branches block_branch_modification protected_change
Protected branch block_force_push protected_push
New page Screenshot_2024-09-26_at_20.53.03
Incomplete Patch for delete button
diff --git a/app/assets/javascripts/projects/settings/branch_rules/components/view/index.vue b/app/assets/javascripts/projects/settings/branch_rules/components/view/index.vue
index 5c07b1f39ff3..3ed10a6ed271 100644
--- a/app/assets/javascripts/projects/settings/branch_rules/components/view/index.vue
+++ b/app/assets/javascripts/projects/settings/branch_rules/components/view/index.vue
@@ -1,7 +1,15 @@
 <script>
 // eslint-disable-next-line no-restricted-imports
 import { mapActions } from 'vuex';
-import { GlSprintf, GlLink, GlLoadingIcon, GlButton, GlModal, GlModalDirective } from '@gitlab/ui';
+import {
+  GlSprintf,
+  GlLink,
+  GlLoadingIcon,
+  GlButton,
+  GlModal,
+  GlModalDirective,
+  GlTooltipDirective,
+} from '@gitlab/ui';
 import { getIdFromGraphQLId } from '~/graphql_shared/utils';
 import { sprintf, n__, s__ } from '~/locale';
 import {
@@ -55,6 +63,7 @@ export default {
   pushRulesHelpDocLink,
   directives: {
     GlModal: GlModalDirective,
+    GlTooltip: GlTooltipDirective,
   },
   editModalId: EDIT_RULE_MODAL_ID,
   components: {
@@ -396,13 +405,21 @@ export default {
     <div v-else>
       <crud-component :title="$options.i18n.ruleTarget" data-testid="rule-target-card">
         <template #actions>
-          <gl-button
-            v-if="glFeatures.editBranchRules && !isPredefinedRule && canAdminProtectedBranches"
-            v-gl-modal="$options.editModalId"
-            data-testid="edit-rule-name-button"
-            size="small"
-            >{{ $options.i18n.edit }}</gl-button
+          <span
+            v-gl-tooltip="{
+              title: $options.i18n.disabledTooltip,
+              disabled: !branchRule.isProtectedBySecurityPolicy,
+            }"
           >
+            <gl-button
+              v-if="glFeatures.editBranchRules && !isPredefinedRule && canAdminProtectedBranches"
+              v-gl-modal="$options.editModalId"
+              :disabled="branchRule.isProtectedBySecurityPolicy"
+              data-testid="edit-rule-name-button"
+              size="small"
+              >{{ $options.i18n.edit }}</gl-button
+            >
+          </span>
         </template>
 
         <div v-if="allBranches" class="gl-mt-2" data-testid="all-branches">*</div>
diff --git a/ee/app/assets/javascripts/projects/settings/branch_rules/queries/branch_rules_details.query.graphql b/ee/app/assets/javascripts/projects/settings/branch_rules/queries/branch_rules_details.query.graphql
index 1d0bce0ac936..bc4daa32fe67 100644
--- a/ee/app/assets/javascripts/projects/settings/branch_rules/queries/branch_rules_details.query.graphql
+++ b/ee/app/assets/javascripts/projects/settings/branch_rules/queries/branch_rules_details.query.graphql
@@ -13,6 +13,7 @@ query getBranchRulesDetailsEE($projectPath: ID!, $buildMissing: Boolean = false)
         branchProtection {
           allowForcePush
           codeOwnerApprovalRequired
+          isProtectedBySecurityPolicy
           mergeAccessLevels {
             edges {
               node {
diff --git a/ee/app/graphql/ee/types/branch_rules/branch_protection_type.rb b/ee/app/graphql/ee/types/branch_rules/branch_protection_type.rb
index 7ac6e3b977fa..62e9ad1cf45d 100644
--- a/ee/app/graphql/ee/types/branch_rules/branch_protection_type.rb
+++ b/ee/app/graphql/ee/types/branch_rules/branch_protection_type.rb
@@ -16,6 +16,11 @@ module BranchProtectionType
             type: GraphQL::Types::Boolean,
             null: false,
             description: 'Enforce code owner approvals before allowing a merge.'
+
+          field :is_protected_by_security_policy,
+            type: GraphQL::Types::Boolean,
+            null: false,
+            description: 'Indicates if the branch is protected by a security policy.'
         end
       end
     end
Edited by 🤖 GitLab Bot 🤖