1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330
|
---
stage: Create
group: Code Review
info: Any user with at least the Maintainer role can merge updates to this content. For details, see https://docs.gitlab.com/ee/development/development_processes.html#development-guidelines-review.
description: "Developer documentation explaining the design and workflow of merge request approval rules."
---
# Approval rules development guidelines
This document explains the backend design and flow of all related functionality
about [merge request approval rules](../../user/project/merge_requests/approvals/index.md).
This should help contributors to understand the code design easier and to also
help see if there are parts to improve as the feature and its implementation
evolves.
It's intentional that it doesn't contain too much implementation detail as they
can change often. The code should explain those things better. The components
mentioned here are the major parts of the application for the approval rules
feature to work.
NOTE:
This is a living document and should be updated accordingly when parts
of the codebase touched in this document are changed or removed, or when new components
are added.
## Data Model
```mermaid
%%{init: { "fontFamily": "GitLab Sans" }}%%
erDiagram
accTitle: Approval rules data model
accDescr: Entity relationship diagram of approval rules
Project ||--o{ MergeRequest: " "
Project ||--o{ ApprovalProjectRule: " "
ApprovalProjectRule }o--o{ User: " "
ApprovalProjectRule }o--o{ Group: " "
ApprovalProjectRule }o--o{ ProtectedBranch: " "
MergeRequest ||--|| ApprovalState: " "
ApprovalState ||--o{ ApprovalWrappedRule: " "
MergeRequest ||--o{ Approval: " "
MergeRequest ||--o{ ApprovalMergeRequestRule: " "
ApprovalMergeRequestRule }o--o{ User: " "
ApprovalMergeRequestRule }o--o{ Group: " "
ApprovalMergeRequestRule ||--o| ApprovalProjectRule: " "
```
### `Project` and `MergeRequest`
`Project` and `MergeRequest` models are defined in `ee/app/models/ee/project.rb`
and `ee/app/models/ee/merge_request.rb`. They extend the non-EE versions, because
approval rules are an EE-only feature. Associations and other related stuff to
merge request approvals are defined here.
### `ApprovalState`
```mermaid
%%{init: { "fontFamily": "GitLab Sans" }}%%
erDiagram
accTitle: ApprovalState
accDescr: Entity relationship diagram between MergeRequest and ApprovalState
MergeRequest ||--|| ApprovalState: " "
```
`ApprovalState` class is defined in `ee/app/models/approval_state.rb`. It's not
an actual `ActiveRecord` model. This class encapsulates all logic related to the
state of the approvals for a certain merge request like:
- Knowing the approval rules that are applicable to the merge request based on
its target branch.
- Knowing the approval rules that are applicable to a certain target branch.
- Checking if all rules were approved.
- Checking if approval is required.
- Knowing how many approvals were given or still required.
It gets the approval rules data from the project (`ApprovalProjectRule`) or the
merge request (`ApprovalMergeRequestRule`) and wraps it as `ApprovalWrappedRule`.
### `ApprovalProjectRule`
```mermaid
%%{init: { "fontFamily": "GitLab Sans" }}%%
erDiagram
accTitle: ApprovalProjectRule diagram
accDescr: Entity relationship diagram between projects and ApprovalProjectRule
Project ||--o{ ApprovalProjectRule: " "
ApprovalProjectRule }o--o{ User: " "
ApprovalProjectRule }o--o{ Group: " "
ApprovalProjectRule }o--o{ ProtectedBranch: " "
```
`ApprovalProjectRule` model is defined in `ee/app/models/approval_project_rule.rb`.
A record is created/updated/deleted when an approval rule is added/edited/removed
via project settings or the [project level approvals API](../../api/merge_request_approvals.md#project-level-mr-approvals).
The `ApprovalState` model gets these records when approval rules are not
overwritten.
The `protected_branches` attribute is set and used when a rule is scoped to
protected branches. See [Approvals for protected branches](../../user/project/merge_requests/approvals/rules.md#approvals-for-protected-branches)
for more information about the feature.
### `ApprovalMergeRequestRule`
```mermaid
%%{init: { "fontFamily": "GitLab Sans" }}%%
erDiagram
accTitle: ApprovalMergeRequestRule diagram
accDescr: Entity relationship diagram between MergeRequest and ApprovalMergeRequestRule
MergeRequest ||--o{ ApprovalMergeRequestRule: " "
ApprovalMergeRequestRule }o--o{ User: " "
ApprovalMergeRequestRule }o--o{ Group: " "
ApprovalMergeRequestRule ||--o| ApprovalProjectRule: " "
```
`ApprovalMergeRequestRule` model is defined in `ee/app/models/approval_merge_request_rule.rb`.
A record is created/updated/deleted when a rule is added/edited/removed via merge
request create/edit form or the [merge request level approvals API](../../api/merge_request_approvals.md#merge-request-level-mr-approvals).
The `approval_project_rule` is set when it is based from an existing `ApprovalProjectRule`.
An `ApprovalMergeRequestRule` doesn't have `protected_branches` as it inherits
them from the `approval_project_rule` if not overridden.
### `ApprovalWrappedRule`
```mermaid
%%{init: { "fontFamily": "GitLab Sans" }}%%
erDiagram
accTitle: ApprovalWrappedRule diagram
accDescr: Entity relationship diagram between ApprovalState and ApprovalWrappedRule
ApprovalState ||--o{ ApprovalWrappedRule: " "
```
`ApprovalWrappedRule` is defined in `ee/app/modes/approval_wrapped_rule.rb` and
is not an `ActiveRecord` model. It's used to wrap an `ApprovalProjectRule` or
`ApprovalMergeRequestRule` for common interface. It also has the following sub
types:
- `ApprovalWrappedAnyApprovalRule` - for wrapping an `any_approver` rule.
- `ApprovalWrappedCodeOwnerRule` - for wrapping a `code_owner` rule.
This class delegates most of the responsibilities to the approval rule it wraps
but it's also responsible for:
- Checking if the approval rule is approved.
- Knowing how many approvals were given or still required for the approval rule.
It gets this information from the approval rule and the `Approval` records from
the merge request.
### `Approval`
```mermaid
%%{init: { "fontFamily": "GitLab Sans" }}%%
erDiagram
accTitle: Approval diagram
accDescr: Entity relationship diagram between MergeRequest and Approval
MergeRequest ||--o{ Approval: " "
```
`Approval` model is defined in `ee/app/models/approval.rb`. This model is
responsible for storing information about an approval made on a merge request.
Whenever an approval is given/revoked, a record is created/deleted.
## Controllers and Services
The following controllers and services below are being used for the approval
rules feature to work.
### `API::ProjectApprovalSettings`
This private API is defined in `ee/lib/api/project_approval_settings.rb`.
This is used for the following:
- Listing the approval rules in project settings.
- Creating/updating/deleting rules in project settings.
- Listing the approval rules on create merge request form.
### `Projects::MergeRequests::CreationsController`
This controller is defined in `app/controllers/projects/merge_requests/creations_controller.rb`.
The `create` action of this controller is used when the create merge request form is
submitted. It accepts the `approval_rules_attributes` parameter for creating/updating/deleting
`ApprovalMergeRequestRule` records. It passes the parameter along when it executes
`MergeRequests::CreateService`.
### `Projects::MergeRequestsController`
This controller is defined in `app/controllers/projects/merge_requests_controller.rb`.
The `update` action of this controller is used when the edit merge request form is
submitted. It's like `Projects::MergeRequests::CreationsController` but it executes
`MergeRequests::UpdateService` instead.
### `API::MergeRequestApprovals`
This API is defined in `ee/lib/api/merge_request_approvals.rb`.
The [Approvals API endpoint](../../api/merge_request_approvals.md#merge-request-level-mr-approvals)
is requested when a merge request page loads.
The `/projects/:id/merge_requests/:merge_request_iid/approval_settings` is a
private API endpoint used for the following:
- Listing the approval rules on edit merge request form.
- Listing the approval rules on the merge request page.
When approving/unapproving an MR via UI and API, the [Approve Merge Request](../../api/merge_request_approvals.md#approve-merge-request)
API endpoint or the [Unapprove Merge Request](../../api/merge_request_approvals.md#unapprove-merge-request)
API endpoint are requested. They execute `MergeRequests::ApprovalService` and
`MergeRequests::RemoveApprovalService` accordingly.
### `API::ProjectApprovalRules` and `API::MergeRequestApprovalRules`
These APIs are defined in `ee/lib/api/project_approval_rules.rb` and
`ee/lib/api/merge_request_approval_rules.rb`.
Used to list/create/update/delete project and merge request level rules via
[Merge request approvals API](../../api/merge_request_approvals.md).
Executes `ApprovalRules::CreateService`, `ApprovalRules::UpdateService`,
`ApprovalRules::ProjectRuleDestroyService`, and `ApprovalRules::MergeRequestRuleDestroyService`
accordingly.
### `ApprovalRules::ParamsFilteringService`
This service is defined in `ee/app/services/approval_rules/params_filtering_service.rb`.
It is called only when `MergeRequests::CreateService` and
`MergeRequests::UpdateService` are executed.
It is responsible for parsing the `approval_rules_attributes` parameter to:
- Remove it when a user can't update approval rules.
- Filter the user IDs whether they are members of the project or not.
- Filter the group IDs whether they are visible to user.
- Identify the `any_approver` rule.
- Append hidden groups to it when specified.
- Append user defined inapplicable (rules that do not apply to the merge request's target
branch) approval rules.
### `ApprovalRules::CreateService`
This service is defined in `ee/app/services/approval_rules/create_service.rb`.
It is responsible for creating approval rules at either the merge request or project level.
It is called when:
- Creating approval rules at the project level through the UI.
- Creating approval rules at the project level through the [API::ProjectApprovalRules](../../api/merge_request_approvals.md#create-merge-request-level-rule) `/projects/:id/approval_rules` endpoint.
- Creating merge request level rules through [API::MergeRequestApprovalRules](../../api/merge_request_approvals.md#create-project-level-rule) `/projects/:id/merge_requests/:merge_request_iid/approval_rules` endpoint.
Merge request level rules created through the UI do not use this service. See [Projects::MergeRequests::CreationsController](#projectsmergerequestscontroller)
## Flow
These flowcharts should help explain the flow from the controllers down to the
models for different functionalities.
Some CRUD API endpoints are intentionally skipped because they are pretty
straightforward.
### Creating a merge request with approval rules via web UI
```mermaid
%%{init: { "fontFamily": "GitLab Sans" }}%%
graph LR
accTitle: Merge request creation in the UI
accDescr: Flowchart of the creation of a merge request in the web UI, when the merge request contains approval rules
Projects::MergeRequests::CreationsController --> MergeRequests::CreateService
MergeRequests::CreateService --> ApprovalRules::ParamsFilteringService
ApprovalRules::ParamsFilteringService --> MergeRequests::CreateService
MergeRequests::CreateService --> MergeRequest
MergeRequest --> db[(Database)]
MergeRequest --> User
MergeRequest --> Group
MergeRequest --> ApprovalProjectRule
User --> db[(Database)]
Group --> db[(Database)]
ApprovalProjectRule --> db[(Database)]
```
When updating, the same flow is followed but it starts at `Projects::MergeRequestsController`
and executes `MergeRequests::UpdateService` instead.
### Viewing the merge request approval rules on an MR page
```mermaid
%%{init: { "fontFamily": "GitLab Sans" }}%%
graph LR
accTitle: Viewing approval rules on a merge request
accDescr: Flowchart of how the frontend retrieves, then displays, approval rule information on a merge request page
API::MergeRequestApprovals --> MergeRequest
MergeRequest --> ApprovalState
ApprovalState --> id1{approval rules are overridden}
id1{approval rules are overridden} --> |No| ApprovalProjectRule & ApprovalMergeRequestRule
id1{approval rules are overridden} --> |Yes| ApprovalMergeRequestRule
ApprovalState --> ApprovalWrappedRule
ApprovalWrappedRule --> Approval
```
This flow gets initiated by the frontend component. The data returned is
used to display information on the MR widget.
### Approving a merge request
```mermaid
%%{init: { "fontFamily": "GitLab Sans" }}%%
graph LR
accTitle: Approval data flowchart
accDescr: Flowchart of how an approval call to the API reaches the database
API::MergeRequestApprovals --> MergeRequests::ApprovalService
MergeRequests::ApprovalService --> Approval
Approval --> db[(Database)]
```
When unapproving, the same flow is followed but the `MergeRequests::RemoveApprovalService`
is executed instead.
## TODO
1. Add information related to other rule types, such as `code_owner` and `report_approver`.
1. Add information about side effects of approving/unapproving a merge request.
|