Add CD RolloutStep GraphQL
What does this MR do?
Exposes the Cd::RolloutStep tree over GraphQL as CdRollout.rolloutSteps. The response shape is a plain array, not a paginated Relay connection - the tree is small and bounded, and the frontend consumes it as a plain array. Each node exposes id, path, parentPath, stepType, name, params, environment, state, startedAt, finishedAt, error, and its own nested steps array, also a plain list, populated only for stage-type nodes and empty otherwise. environment resolves to the full CdEnvironment type (id, name, tier, etc.) via the step's rollout_environment link, and is null for steps that target no environment - stage containers, wait steps.
The environment field batches in two lazy hops to avoid an N+1: first a batched pluck from rollout_environment_id to environment_id (new Cd::RolloutEnvironment.environment_ids_by_id class method), then resolves Cd::Environment through the same shared BatchModelLoader batch key that RolloutEnvironmentType#environment already uses elsewhere in the schema - so the two fields' environment lookups merge into one query when they overlap. An earlier version fetched the environment by preloading through Cd::RolloutEnvironment directly; that worked in small test fixtures only because Rails' per-request query cache happened to dedupe an identical single-row query, and it silently produced an extra query once real data diverged - confirmed by diffing raw SQL logs between a small and a larger dataset. The steps field (nested children) batches across every stage-type node in the response in one query, keyed by Cd::RolloutStep.nested_grouped_by_parent.
Changes
- New
Types::Cd::RolloutStepType(CdRolloutStep) - the node type described above - New
Types::Cd::RolloutStepStateEnum(CdRolloutStepState) - mirrors the model's state enum - New
Resolvers::Cd::RolloutStepsResolver- resolvesCdRollout.rolloutStepsto the rollout's top-level (parent_path nil) steps, ordered; gated behind the existingai_native_deployfeature flag like every other Cd resolver - New
Cd::RolloutStepPolicy- delegates torollout, required for the type's object-levelauthorize :read_cd_rolloutcheck (GraphQL raised "no policy for Cd::RolloutStep" without it) Cd::RolloutStepgainstop_level,nested,for_rolloutsscopes and anested_grouped_by_parentclass methodCd::RolloutEnvironmentgainsenvironment_ids_by_id- GraphQL reference docs regenerated (
doc/api/graphql/reference/_index.md), additive-only - three new sections:CdRolloutStep,CdRolloutStepState,CdRolloutStepID
No migrations, no schema changes - this MR is GraphQL/read-layer only, on top of the table and model the prior MR introduced. Gated by the existing ai_native_deploy feature flag, same flag the rest of the CD GraphQL schema already uses, not new. No REST exposure - GraphQL only. Nothing yet updates the tree's state as a rollout runs - there is no KAS event consumer wiring here. This MR is read-only exposure of the tree as it stands at rollout-creation time.
How to test
1. Seed an organization, application, flow definition, rollout, and its steps (Rails console)
Feature.enable(:ai_native_deploy)
org = Organizations::Organization.create!(path: "cd-rollout-steps-graphql-demo", name: "CD RolloutSteps GraphQL Demo")
user = User.find_by(username: "root")
Organizations::OrganizationUser.create!(organization: org, user: user, access_level: Gitlab::Access::OWNER)
app = Cd::Application.create!(organization: org, name: "demo-app")
env = Cd::Environment.create!(organization: org, name: "production", tier: :production)
service = Cd::Service.create!(organization: org, application: app, name: "web")
Cd::EnvironmentDriverBinding.create!(organization: org, environment: env,
driver_ref: "argo-rollouts", driver_config: { "cluster_agent_id" => "1" })
flow_yaml = <<~YAML
steps:
- type: com.gitlab.cd.steps.stage
name: production
steps:
- type: com.gitlab.cd.argo.rolling.deploy
environment: production
services:
- name: web
- type: com.gitlab.cd.steps.wait
seconds: 30
YAML
Cd::ApplicationFlowDefinition.create!(application: app, organization_id: org.id, definition: flow_yaml)
artifact_source = Cd::ArtifactSource.create!(organization: org, service: service,
name: "web-image", source_ref: "registry.example.com/web", source_config: {})
version = Cd::Version.create!(organization: org, artifact_source: artifact_source, name: "v1_0_0")
vset = Cd::VersionSet.create!(organization: org, application: app, name: "1.0.0")
Cd::VersionSetEntry.create!(organization: org, version_set: vset, version: version)
response = Cd::Rollouts::CreateService.new(parent: org, current_user: user, params: { version_set: vset }).execute
rollout = response.payload[:rollout]
puts "organization_gid=#{org.to_global_id}"
puts "rollout_gid=#{rollout.to_global_id}"2. Run the rolloutSteps query (GraphQL Explorer at /-/graphql-explorer)
query {
organization(id: "<organization_gid from step 1>") {
cdRollout(id: "<rollout_gid from step 1>") {
id
rolloutSteps {
id
path
parentPath
stepType
name
state
params
environment { id name tier }
steps {
id
path
stepType
state
params
environment { id name tier }
}
}
}
}
}Expected (verified live against a running GDK): rolloutSteps returns as a plain array (no nodes/edges wrapper), with the stage node's nested steps array containing its one child, and the standalone wait step's steps array empty.
Sample response (rolloutSteps query):
{
"data": {
"organization": {
"cdRollout": {
"id": "gid://gitlab/Cd::Rollout/79",
"rolloutSteps": [
{
"id": "gid://gitlab/Cd::RolloutStep/7",
"path": "0",
"parentPath": null,
"stepType": "com.gitlab.cd.steps.stage",
"name": "production",
"state": "PENDING",
"params": null,
"environment": null,
"steps": [
{
"id": "gid://gitlab/Cd::RolloutStep/8",
"path": "0.0",
"stepType": "com.gitlab.cd.argo.rolling.deploy",
"state": "PENDING",
"params": {
"services": [
{ "name": "web" }
]
},
"environment": {
"id": "gid://gitlab/Cd::Environment/45",
"name": "production",
"tier": "PRODUCTION"
}
}
]
},
{
"id": "gid://gitlab/Cd::RolloutStep/9",
"path": "1",
"parentPath": null,
"stepType": "com.gitlab.cd.steps.wait",
"name": null,
"state": "PENDING",
"params": { "seconds": 30 },
"environment": null,
"steps": []
}
]
}
}
}
}References
https://gitlab.com/gitlab-org/gitlab/-/work_items/610547
- Builds on the RolloutStep model MR (
georgekoltsov/cd-rollout-step), which is not yet merged.
Screenshots or screen recordings
No UI change - backend/GraphQL-layer only.
How to set up and validate locally
- Run the seed script above in a Rails console - it creates the rollout and its steps in one go.
- Run the
rolloutStepsquery in GraphQL Explorer with the printed GIDs. - Confirm the response is a plain array (not
{ nodes: [...] }) with the stage node's child nested under itsstepsfield. - Confirm
environmentis populated on the nested deploy step and null on the stage and wait step. - There's no negative/error case to add here since this MR only adds a read path over existing data - it introduces no new validation.
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.