Skip to content

Implement BranchRule class to facilitate ApprovalProjectRule and ExternalStatusCheck associations when protected branches are not linked

Related: #372844 (comment 1112495519)

Context

As part of the BranchRulesMVC we are creating additional GraphQL types to expose the branch rule information. The proposed UI designs show that branch rules can optionally have protected badge if the branchRule has a branchProtection record i.e. who can merge and push to the branch(es) matching the branchProtect.name. We also have branch rules which match All Branches and All Protected Branches.

The current implementation cannot achieve this due to the structure of the data. We expose project -> branchRules -> branchProtection, however, in this case the branch rule and branch protection objects are actually the same ProtectedBranch record in the database.

Current structure
erDiagram
    project ||--|{ branchProtection : has_many
    project {
        string id FK "Global ID of the project"
    }
    branchProtection ||--|{ mergeAccessLevel : has_many
    branchProtection ||--|{ pushAccessLevel : has_many
    branchProtection ||--|{ pushAccessLevel : has_many
    branchProtection ||--|{ unprotectAccessLevel : has_many-EE
    branchProtection {
        string name
        boolean allowForcePush
        boolean codeOwnerApprovalRequired
        integer matchingBranchesCount
        string createdAt
        string updatedAt

    }
    mergeAccessLevel {
        integer accessLevel
        string accessLevelDescription
    }
    pushAccessLevel {
        integer accessLevel
        string accessLevelDescription
    }
    unprotectAccessLevel {
        integer accessLevel
        string accessLevelDescription
    }
    branchProtection ||--o{ approvalProjectRule : has_and_belongs_to_many-EE
    branchProtection ||--o{ externalStatusCheck : has_and_belongs_to_many-EE
    approvalProjectRule {
        string id
        string name
        string type "Enum value upcased e.g. REPORT_APPROVER"
    }
    externalStatusCheck {
        string url
    }

Currently approvalProjectRule and externalStatusCheck can both be orphans too when the user selects "All branches" or "All protected branches"

Proposal

To solve this I'm proposing we introduce a new class called BranchRule which would behave as though it were an ActiveRecord model exposing the required details for branch_rule.project, branch_rule.protected_branch, branch_rule.approval_project_rules, and branch_rule.external_status_checks. This change would allow us to display All branches and All Protected Branches through the API as we could query for them separately to the project.protected_branches and then manually initialize BranchRule objects before resolving the query.

Proposed structure
erDiagram
    project ||--|{ branchRule : has_many
    project {
        string id FK "Global ID of the project"
    }
    branchRule ||--o| branchProtection : self
    branchRule {
        string name
        integer matchingBranchesCount
        string createdAt
        string updatedAt
    }
    branchProtection ||--|{ mergeAccessLevel : has_many
    branchProtection ||--|{ pushAccessLevel : has_many
    branchProtection ||--|{ pushAccessLevel : has_many
    branchProtection ||--|{ unprotectAccessLevel : has_many-EE
    branchProtection {
        boolean allowForcePush
        boolean codeOwnerApprovalRequired
    }
    mergeAccessLevel {
        integer accessLevel
        string accessLevelDescription
    }
    pushAccessLevel {
        integer accessLevel
        string accessLevelDescription
    }
    unprotectAccessLevel {
        integer accessLevel
        string accessLevelDescription
    }
    branchRule ||--o{ approvalProjectRule : has_and_belongs_to_many-EE
    branchRule ||--o{ externalStatusCheck : has_and_belongs_to_many-EE
    approvalProjectRule {
        string id
        string name
        string type "Enum value upcased e.g. REPORT_APPROVER"
    }
    externalStatusCheck {
        string url
    }

I think we can implement this system without needing to migrate any data if we use the BranchRule class to present each ProtectedBranch. We can also bring in these records that use All branches and All protected branches by querying for them separately and then building individual BranchRule objects for the graphql logic to consume.

Edited by Joe Woodward