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 - resolves CdRollout.rolloutSteps to the rollout's top-level (parent_path nil) steps, ordered; gated behind the existing ai_native_deploy feature flag like every other Cd resolver
  • New Cd::RolloutStepPolicy - delegates to rollout, required for the type's object-level authorize :read_cd_rollout check (GraphQL raised "no policy for Cd::RolloutStep" without it)
  • Cd::RolloutStep gains top_level, nested, for_rollouts scopes and a nested_grouped_by_parent class method
  • Cd::RolloutEnvironment gains environment_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

  1. Run the seed script above in a Rails console - it creates the rollout and its steps in one go.
  2. Run the rolloutSteps query in GraphQL Explorer with the printed GIDs.
  3. Confirm the response is a plain array (not { nodes: [...] }) with the stage node's child nested under its steps field.
  4. Confirm environment is populated on the nested deploy step and null on the stage and wait step.
  5. 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.

Edited by George Koltsov

Merge request reports

Loading
Loading