Loading
Record and expose approval gates on rollouts
What does this MR do?
Records approval gates on continuous deployment rollouts and exposes their state over GraphQL.
ProcessWorkflowEventServicenow journals a request_approval transition when AutoFlow reports a step_started event for the approval step type, guarded against duplicate retries and against rollouts that already reached a terminal state.ResolveGateServicenow requires an open approval gate (derived from the transition journal) instead ofrollout.paused?, and records the resolution against the rollout's actual state instead of a hardcoded paused/paused pair.Cd::Rollout#open_approval_gate?andCd::RolloutTransition.open_gate_rollout_idsderive gate state from the journal.- Adds
CdRollout.awaitingApproval(GraphQL), batch-loaded to avoid N+1 queries.
SQL query plans
Cd::Rollout#open_approval_gate?
Single rollout, used by ResolveGateService and the write-path guard in ProcessWorkflowEventService.
Tested with 300 records seeded on local dev DB.
SELECT "cd_rollout_transitions".*
FROM "cd_rollout_transitions"
WHERE "cd_rollout_transitions"."rollout_id" = $1
AND "cd_rollout_transitions"."event" IN ('request_approval', 'approve', 'reject')
ORDER BY "cd_rollout_transitions"."created_at" DESC, "cd_rollout_transitions"."id" DESC
LIMIT 1;
Limit (cost=1.53..2.65 rows=1 width=147) (actual time=0.018..0.018 rows=1 loops=1)
Buffers: shared hit=12
-> Incremental Sort (cost=1.53..10.51 rows=8 width=147) (actual time=0.018..0.018 rows=1 loops=1)
Sort Key: created_at DESC, id DESC
Presorted Key: created_at
Full-sort Groups: 1 Sort Method: quicksort Average Memory: 25kB Peak Memory: 25kB
Buffers: shared hit=12
-> Index Scan Backward using index_cd_rollout_transitions_on_rollout_id_and_created_at on cd_rollout_transitions (cost=0.28..10.15 rows=8 width=147) (actual time=0.009..0.010 rows=2 loops=1)
Index Cond: (rollout_id = 325)
Filter: (event = ANY ('{request_approval,approve,reject}'::text[]))
Rows Removed by Filter: 4
Buffers: shared hit=3
Planning:
Buffers: shared hit=172
Planning Time: 1.411 ms
Execution Time: 0.028 msCd::RolloutTransition.open_gate_rollout_ids
Batched, used by the CdRollout.awaitingApproval GraphQL field's batch loader.
Tested with 300 records seeded on local dev DB.
SELECT DISTINCT ON (rollout_id) rollout_id, event
FROM "cd_rollout_transitions"
WHERE "cd_rollout_transitions"."event" IN ('request_approval', 'approve', 'reject')
AND "cd_rollout_transitions"."rollout_id" IN ($1, $2, ..., $20)
ORDER BY rollout_id, created_at DESC, id DESC;
Unique (cost=0.69..59.15 rows=131 width=32) (actual time=0.024..0.085 rows=20 loops=1)
Buffers: shared hit=9
-> Incremental Sort (cost=0.69..58.73 rows=169 width=32) (actual time=0.024..0.079 rows=176 loops=1)
Sort Key: rollout_id, created_at DESC, id DESC
Presorted Key: rollout_id
Full-sort Groups: 5 Sort Method: quicksort Average Memory: 27kB Peak Memory: 27kB
Buffers: shared hit=9
-> Index Scan using index_cd_rollout_transitions_on_rollout_id_and_created_at on cd_rollout_transitions (cost=0.28..52.45 rows=169 width=32) (actual time=0.006..0.051 rows=176 loops=1)
Index Cond: (rollout_id = ANY ('{325,326,327,328,329,330,331,332,333,334,335,336,337,338,339,340,341,342,343,344}'::bigint[]))
Filter: (event = ANY ('{request_approval,approve,reject}'::text[]))
Rows Removed by Filter: 224
Buffers: shared hit=9
Planning:
Buffers: shared hit=9
Planning Time: 0.039 ms
Execution Time: 0.092 msGraphQl query
query {
organization(id: "gid://gitlab/Organizations::Organization/1") {
cdRollout(id: "gid://gitlab/Cd::Rollout/1") {
id
state
awaitingApproval
rolloutTransitions {
nodes {
event
principal
createdAt
}
}
}
}
}Validation steps
- create new rollout events in the rails console:
rollout = Cd::Rollout.find(1)
result = Cd::Rollouts::ProcessWorkflowEventService.new(
rollout,
params: {
type: "com.gitlab.cd.step_started",
data: { position: [0, 0], step_type: Cd::RolloutStep::APPROVAL_STEP_TYPE }
}
).execute
- run the query outlined above in the graphql explorer
Related to #607600
