Stamp deploy timestamps on CD rollout environments

What does this MR do and why?

The organization CD environments page (route /o/:organization_path/-/deploy/environments) rendered placeholder dashes for the Release, Last deploy, and By fields on every environment card, even after rollouts completed.

The cause: the cd_rollout_environments columns started_at and finished_at were never written by any code path. This MR adds timestamp stamping when a rollout environment changes state, mirroring what Cd::Rollout and Cd::Deployment already do.

This is behind the existing ai_native_deploy experimental feature flag. No new flag, no migration.

Root cause analysis

  1. The environment card (ee/app/assets/javascripts/cd/components/environment_card.vue) only attributes deploy metadata to rollout environments that have finishedAt set. With none set, it renders the placeholder for all three fields.
  2. Querying /api/graphql exactly as the page does confirmed the API returned the release name (versionSet.name) and deploying user correctly, but finishedAt: null on every rollout environment. This ruled out the frontend.
  3. In the database, completed cd_rollout_environments rows had NULL started_at and finished_at, while the parent Cd::Rollout row and the child Cd::Deployment rows both had timestamps. The gap was exactly this one model.
  4. Cd::Rollout and Cd::Deployment stamp their timestamps in state_machines before_transition callbacks. Cd::RolloutEnvironment uses a plain Rails enum, and nothing in the codebase wrote its timestamp columns.
  5. Both of the model's state writers only wrote state. The EnvironmentTransition class in the AutoFlow workflow-event path (added by !250702 (merged)) called update!(state: target_state), and the model's own sync_state_from_deployments! did the same.
  6. !249670 (merged) (moves latest-finished selection to the backend) selects on the same finished_at column (scope :finished, -> { where.not(finished_at: nil) }), so it returns nothing until this column is populated. Its specs pass because factories set finished_at explicitly.

Solution

  • New Cd::RolloutEnvironment#transition_state! method: no-op when state is unchanged, sets started_at ||= Time.current on entering in_progress, sets finished_at = Time.current on entering a terminal state (completed, failed, cancelled). This mirrors the before_transition callbacks on Cd::Rollout and Cd::Deployment.
  • Both state writers now route through it: sync_state_from_deployments! and Cd::Rollouts::WorkflowEvents::EnvironmentTransition (previously update!(state: target_state)).
  • Specs cover started_at stamping, started_at preservation across re-entry, finished_at for each terminal state, no-op on unchanged state, and both writer paths.
  • Deliberately no backfill. Rows finished before this change keep NULL finished_at, and their cards keep the placeholder until the next rollout. The feature is behind the experimental ai_native_deploy flag. If reviewers think existing data warrants it, a post-deployment data migration could derive finished_at from each row's deployments or parent rollout.

References

In chronological order of the findings:

  1. !244679 (merged) introduced the environment card with client-side latest-finished selection filtering on finishedAt.
  2. !250702 (merged) (merged) added the AutoFlow workflow-event transitions that update rollout environment state but not timestamps, which is where the gap lives.
  3. https://gitlab.com/gitlab-org/gitlab/-/work_items/617189 reported environment data not rendering on the environments index page during the CD demo on 2026-08-14.
  4. !249670 (merged) (open) moves the latest-finished selection to the backend using the same finished_at criterion, so it depends on this fix to return data.

How to set up and validate locally

  1. Enable the ai_native_deploy feature flag: Feature.enable(:ai_native_deploy)
  2. Set up a deploy driver and seed data. For example, use the simulated driver from !250187 (closed), or the Argo Rollouts driver with a real cluster.
  3. Run a rollout to completion.
  4. Check the rollout environment rows: Cd::Rollout.last.rollout_environments.pluck(:state, :started_at, :finished_at) shows timestamps.
  5. Open the organization environments page and confirm the environment cards show Release, Last deploy, and By for the deployed environments.

Merge request reports

Loading
Loading