diff options
Diffstat (limited to 'app/models/environment.rb')
-rw-r--r-- | app/models/environment.rb | 76 |
1 files changed, 62 insertions, 14 deletions
diff --git a/app/models/environment.rb b/app/models/environment.rb index 450ed6206d5..9e663b2ee74 100644 --- a/app/models/environment.rb +++ b/app/models/environment.rb @@ -12,7 +12,7 @@ class Environment < ApplicationRecord self.reactive_cache_hard_limit = 10.megabytes self.reactive_cache_work_type = :external_dependency - belongs_to :project, required: true + belongs_to :project, optional: false use_fast_destroy :all_deployments nullify_if_blank :external_url @@ -26,7 +26,7 @@ class Environment < ApplicationRecord has_many :self_managed_prometheus_alert_events, inverse_of: :environment has_many :alert_management_alerts, class_name: 'AlertManagement::Alert', inverse_of: :environment - has_one :last_deployment, -> { success.distinct_on_environment }, class_name: 'Deployment', inverse_of: :environment + has_one :last_deployment, -> { Feature.enabled?(:env_last_deployment_by_finished_at, default_enabled: :yaml) ? success.ordered : success.distinct_on_environment }, class_name: 'Deployment', inverse_of: :environment has_one :last_visible_deployment, -> { visible.distinct_on_environment }, inverse_of: :environment, class_name: 'Deployment' has_one :last_visible_deployable, through: :last_visible_deployment, source: 'deployable', source_type: 'CommitStatus', disable_joins: true has_one :last_visible_pipeline, through: :last_visible_deployable, source: 'pipeline', disable_joins: true @@ -59,17 +59,17 @@ class Environment < ApplicationRecord allow_nil: true, addressable_url: true - delegate :stop_action, :manual_actions, to: :last_deployment, allow_nil: true + delegate :manual_actions, to: :last_deployment, allow_nil: true delegate :auto_rollback_enabled?, to: :project scope :available, -> { with_state(:available) } scope :stopped, -> { with_state(:stopped) } scope :order_by_last_deployed_at, -> do - order(Gitlab::Database.nulls_first_order("(#{max_deployment_id_sql})", 'ASC')) + order(Arel::Nodes::Grouping.new(max_deployment_id_query).asc.nulls_first) end scope :order_by_last_deployed_at_desc, -> do - order(Gitlab::Database.nulls_last_order("(#{max_deployment_id_sql})", 'DESC')) + order(Arel::Nodes::Grouping.new(max_deployment_id_query).desc.nulls_last) end scope :order_by_name, -> { order('environments.name ASC') } @@ -89,13 +89,19 @@ class Environment < ApplicationRecord scope :for_project, -> (project) { where(project_id: project) } scope :for_tier, -> (tier) { where(tier: tier).where.not(tier: nil) } - scope :with_deployment, -> (sha) { where('EXISTS (?)', Deployment.select(1).where('deployments.environment_id = environments.id').where(sha: sha)) } scope :unfoldered, -> { where(environment_type: nil) } scope :with_rank, -> do select('environments.*, rank() OVER (PARTITION BY project_id ORDER BY id DESC)') end scope :for_id, -> (id) { where(id: id) } + scope :with_deployment, -> (sha, status: nil) do + deployments = Deployment.select(1).where('deployments.environment_id = environments.id').where(sha: sha) + deployments = deployments.where(status: status) if status + + where('EXISTS (?)', deployments) + end + scope :stopped_review_apps, -> (before, limit) do stopped .in_review_folder @@ -145,10 +151,11 @@ class Environment < ApplicationRecord find_by(id: id, slug: slug) end - def self.max_deployment_id_sql - Deployment.select(Deployment.arel_table[:id].maximum) - .where(Deployment.arel_table[:environment_id].eq(arel_table[:id])) - .to_sql + def self.max_deployment_id_query + Arel.sql( + Deployment.select(Deployment.arel_table[:id].maximum) + .where(Deployment.arel_table[:environment_id].eq(arel_table[:id])).to_sql + ) end def self.pluck_names @@ -185,6 +192,23 @@ class Environment < ApplicationRecord last_deployment&.deployable end + def last_deployment_pipeline + last_deployable&.pipeline + end + + # This method returns the deployment records of the last deployment pipeline, that successfully executed to this environment. + # e.g. + # A pipeline contains + # - deploy job A => production environment + # - deploy job B => production environment + # In this case, `last_deployment_group` returns both deployments, whereas `last_deployable` returns only B. + def last_deployment_group + return Deployment.none unless last_deployment_pipeline + + successful_deployments.where( + deployable_id: last_deployment_pipeline.latest_builds.pluck(:id)) + end + # NOTE: Below assocation overrides is a workaround for issue https://gitlab.com/gitlab-org/gitlab/-/issues/339908 # It helps to avoid cross joins with the CI database. # Caveat: It also overrides and losses the default AR caching mechanism. @@ -255,8 +279,8 @@ class Environment < ApplicationRecord external_url.gsub(%r{\A.*?://}, '') end - def stop_action_available? - available? && stop_action.present? + def stop_actions_available? + available? && stop_actions.present? end def cancel_deployment_jobs! @@ -269,11 +293,35 @@ class Environment < ApplicationRecord end end - def stop_with_action!(current_user) + def stop_with_actions!(current_user) return unless available? stop! - stop_action&.play(current_user) + + actions = [] + + stop_actions.each do |stop_action| + Gitlab::OptimisticLocking.retry_lock( + stop_action, + name: 'environment_stop_with_actions' + ) do |build| + actions << build.play(current_user) + end + end + + actions + end + + def stop_actions + strong_memoize(:stop_actions) do + if ::Feature.enabled?(:environment_multiple_stop_actions, project, default_enabled: :yaml) + # Fix N+1 queries it brings to the serializer. + # Tracked in https://gitlab.com/gitlab-org/gitlab/-/issues/358780 + last_deployment_group.map(&:stop_action).compact + else + [last_deployment&.stop_action].compact + end + end end def reset_auto_stop |