Implements MR approved flow trigger
What does this MR do and why?
Addresses #592456 (closed). This MR implements merge_request_approved cloud event trigger to automate workflows when an MR is fully approved by reviewers.
Scenarios/Edge Cases Covered as per product requirement
Decision / Discussion [link]
| # | Question | Decision |
|---|---|---|
| 1 | When does the trigger fire? | When the MR transitions from not-fully-approved to fully-approved, based on set approval rules in Merge Requests setting in admin. |
| 2 | Force-push resets approvals: does the trigger fire on subsequent re-approval? | Yes. |
| 3 | Approval revoked then re-added: does the trigger fire again? | Yes. |
| 4 | Do non-human (i.e. service accounts, bots) approvals fire the trigger? | Yes. |
| 5 | Concurrent approvals | Only the latest approval (actual threshold-crosser) fires. |
Feature flag
- Name:
merge_request_approved_flow_trigger - Type:
gitlab_com_derisk - Scoped to: Project
- Default: disabled
- Rollout issue: #599843
Architectural Note / Follow-up Work
Merge request related event types will be consolidated under a single "Merge request" event type, with the specific action (approved, unapproved, conflict, etc.) configured via the trigger's filter field. This aims to improve scalability and UX of triggers and is tracked in this issue.
This MR lays the groundwork for that by using FilterEvaluator to route events based on the action stored in the filter. When a user selects "Merge request approved", the filter { merge_request: { action: 'approved' } } is automatically set. Future actions will follow the same pattern. This pattern is also extensible to other future event categories with multiple event types (e.g. Issue, Work Items, etc.).
This also means that this trigger will need to be refactored to use the configuration component once the work is complete.
How to set up and validate locally (see screen recording for UI navigation)
Pre-requisites:
- Duo Enterprise enabled
- GDK running with all services up
- Checkout this branch
592456/sq/mr_approved-trigger - In Rails console:
- Get your duo enterprise enabled project, e.g:
proj = Project.find(1000000) - Create two users that you'll request MR reviews from (see scripts in details below)
- Enable the feature flag:
Feature.enable(:merge_request_approved_flow_trigger, proj)
- Get your duo enterprise enabled project, e.g:
- In GitLab UI:
- Create a custom flow in the GDK UI following the docs instructions:
- Go to:
http://172.16.123.1:3000/explore/ai-catalog/flows/ -
- Add a flow named
MR APPROVED TEST FLOW
- Add a flow named
- Select a "Managed by" project, e.g.
GitLab Duo / Test - Add a description and you can leave the yaml configuration as is
-
- Create flow
- Go to:
- Enable the newly created flow in the GDK UI following the docs instructions:
- Deselect all triggers and add the
Merge request approvedtrigger - Enable flow
- Deselect all triggers and add the
- Trigger the event:
- Create or navigate to an existing MR in the project
- Request review from the reviewer previously created
- Open two new windows (e.g. Incognito in chrome and a Safari window) with the two approvers logged in each
- Approve the MR with the two reviewers
- Create a custom flow in the GDK UI following the docs instructions:
- Verify:
- All scenario / edge cases above. (Note: scenario 4 & 5 is easier to verify using rails console -- see scripts below for how-to)
- The custom workflow should trigger, with a system note appearing in the MR timeline (see screenshot)
- Note: the MR example in the screen recording requires minimum of two approvals but feel free to play around with approval rules
- Frontend (feature flag gating)
- Disable feature flag on the project:
Feature.disable(:merge_request_approved_flow_trigger, proj) - Confirm that
Merge request approvedevent_type is not available in the configuration UI (http://gdk.test:8080/gitlab-duo/test/-/automate/triggers/new).
- Disable feature flag on the project:
- All scenario / edge cases above. (Note: scenario 4 & 5 is easier to verify using rails console -- see scripts below for how-to)
Scripts
1. Create two users for an MR that requires two approvers (add password & confirmation) and assign the review to them in the MR after creation
approver1 = User.new(
username: 'approver1',
name: 'Approver One',
email: 'approver1@example.com',
password: <insert>,
password_confirmation: <insert>',
confirmed_at: Time.current
)
approver1.build_namespace(
path: approver1.username,
name: approver1.name,
organization: Organizations::Organization.default_organization
)
approver1.save!
approver2 = User.new(
username: 'approver2',
name: 'Approver Two',
email: 'approver2@example.com',
password: <insert>,
password_confirmation: <insert>',
confirmed_at: Time.current
)
approver2.build_namespace(
path: approver2.username,
name: approver2.name,
organization: Organizations::Organization.default_organization
)
approver2.save!2. Verify non-human approval that fires the trigger
# Reset existing approvals if any
mr = MergeRequest.find(YOUR_MR_ID)
mr.approvals.destroy_all
mr.reset
# Use an existing service account (like the one from your flow trigger OR create two new bots with service account IDs)
bot_user_1 = User.find(YOUR_BOT_SERVICE_ACCOUNT_ID)
bot_user_2 = User.find(YOUR_BOT_SERVICE_ACCOUNT_ID)
# Approve the MR with the two bots
a1 = mr.approvals.create!(user: bot_user_1, patch_id_sha: mr.current_patch_id_sha)
a2 = mr.approvals.create!(user: bot_user_2, patch_id_sha: mr.current_patch_id_sha)
puts "approved? #{mr.reset.approval_state.approved?}"
puts "user_1_type: #{bot_user_1.user_type}" # should be service account
puts "user_2_type: #{bot_user_2.user_type}" # should be service account
# Publish the event with the bots to trigger the events
Gitlab::EventStore.publish(
MergeRequests::ApprovedCloudEvent.build(
merge_request: mr, current_user: bot_user_1, approval: a1
)
)
Gitlab::EventStore.publish(
MergeRequests::ApprovedCloudEvent.build(
merge_request: mr, current_user: bot_user_2, approval: a2
)
)
# Expected Result: Go the MR UI and verify the trigger was fired and the workflow started3. Verify concurrent approvals
# Reset existing approvals if any
mr = project.merge_requests.find_by(iid: YOUR_MR_IID)
mr.approvals.destroy_all
mr.reset
# Approve MRs with two reviewers
user_a = User.find_by(username: 'approver1')
user_b = User.find_by(username: 'approver2')
# Create two approvals with close timestamps
a1 = mr.approvals.create!(user: user_a, patch_id_sha: mr.current_patch_id_sha)
a2 = mr.approvals.create!(user: user_b, patch_id_sha: mr.current_patch_id_sha)
# Verify sub-second precision
puts "a1: #{a1.created_at.iso8601(6)}"
puts "a2: #{a2.created_at.iso8601(6)}"
puts "approved? #{mr.reset.approval_state.approved?}"
# Publish both events to fire the trigger
[user_a, user_b].zip([a1, a2]).each do |user, approval|
Gitlab::EventStore.publish(
MergeRequests::ApprovedCloudEvent.build(
merge_request: mr, current_user: user, approval: approval
)
)
end
# Expected Result: only one trigger fires (for user_b, the later timestamp)References
Screenshots or screen recordings
System notes example of triggered flow
UI Flow
| Before | After |
|---|---|
MR acceptance checklist
Evaluate this MR against the MR acceptance checklist. It helps you analyze changes to reduce risks in quality, performance, reliability, security, and maintainability.
