debian-mirror-gitlab/app/finders/deployments_finder.rb

229 lines
7.6 KiB
Ruby
Raw Normal View History

2020-01-01 13:55:28 +05:30
# frozen_string_literal: true
2021-03-11 19:13:27 +05:30
# WARNING: This finder does not check permissions!
#
# Arguments:
# params:
# project: Project model - Find deployments for this project
# updated_after: DateTime
# updated_before: DateTime
# finished_after: DateTime
# finished_before: DateTime
2022-10-11 01:57:18 +05:30
# environment: String (name) or Integer (ID)
# status: String or Array<String> (see Deployment.statuses)
2021-03-11 19:13:27 +05:30
# order_by: String (see ALLOWED_SORT_VALUES constant)
# sort: String (asc | desc)
2020-01-01 13:55:28 +05:30
class DeploymentsFinder
2023-05-27 22:25:52 +05:30
include UpdatedAtFilter
2021-03-11 19:13:27 +05:30
attr_reader :params
2020-01-01 13:55:28 +05:30
2021-06-08 01:23:25 +05:30
# Warning:
# These const are directly used in Deployment Rest API, thus
# modifying these values could implicity change the API interface or introduce a breaking change.
# Also, if you add a sort value, make sure that the new query will stay
# performant with the other filtering/sorting parameters.
# The composed query could be significantly slower when the filtering and sorting columns are different.
# See https://gitlab.com/gitlab-org/gitlab/-/issues/325627 for example.
2021-03-11 19:13:27 +05:30
ALLOWED_SORT_VALUES = %w[id iid created_at updated_at ref finished_at].freeze
2021-03-08 18:12:59 +05:30
DEFAULT_SORT_VALUE = 'id'
2020-01-01 13:55:28 +05:30
ALLOWED_SORT_DIRECTIONS = %w[asc desc].freeze
2021-03-08 18:12:59 +05:30
DEFAULT_SORT_DIRECTION = 'asc'
2020-01-01 13:55:28 +05:30
2021-06-08 01:23:25 +05:30
InefficientQueryError = Class.new(StandardError)
2021-03-11 19:13:27 +05:30
def initialize(params = {})
2020-01-01 13:55:28 +05:30
@params = params
2022-10-11 01:57:18 +05:30
@params[:status] = Array(@params[:status]).map(&:to_s) if @params[:status]
2021-06-08 01:23:25 +05:30
validate!
2020-01-01 13:55:28 +05:30
end
def execute
items = init_collection
items = by_updated_at(items)
2021-03-11 19:13:27 +05:30
items = by_finished_at(items)
2020-03-13 15:44:24 +05:30
items = by_environment(items)
items = by_status(items)
2021-03-11 19:13:27 +05:30
items = preload_associations(items)
2021-04-29 21:17:54 +05:30
sort(items)
2020-01-01 13:55:28 +05:30
end
private
2021-06-08 01:23:25 +05:30
def validate!
if filter_by_updated_at? && filter_by_finished_at?
raise InefficientQueryError, 'Both `updated_at` filter and `finished_at` filter can not be specified'
end
# Currently, the inefficient parameters are allowed in order to avoid breaking changes in Deployment API.
# We'll switch to a hard error in https://gitlab.com/gitlab-org/gitlab/-/issues/328500.
2023-07-09 08:55:56 +05:30
if filter_by_updated_at? && !order_by_updated_at?
error = InefficientQueryError.new('`updated_at` filter requires `updated_at` sort')
2022-04-04 11:22:00 +05:30
Gitlab::ErrorTracking.log_exception(error)
2023-07-09 08:55:56 +05:30
# We are adding a Feature Flag to introduce the breaking change indicated in
# https://gitlab.com/gitlab-org/gitlab/-/issues/328500
# We are also adding a way to override this flag for special case users that
# are running into large volume of errors when the flag is enabled.
# These Feature Flags must be removed by 16.1
if Feature.enabled?(:deployments_raise_updated_at_inefficient_error) &&
Feature.disabled?(:deployments_raise_updated_at_inefficient_error_override, params[:project])
raise error
end
2021-06-08 01:23:25 +05:30
end
2022-10-11 01:57:18 +05:30
if filter_by_finished_at? && !order_by_finished_at?
raise InefficientQueryError, '`finished_at` filter requires `finished_at` sort.'
end
if order_by_finished_at? && !(filter_by_finished_at? || filter_by_finished_statuses?)
raise InefficientQueryError,
'`finished_at` sort requires `finished_at` filter or a filter with at least one of the finished statuses.'
2021-06-08 01:23:25 +05:30
end
if filter_by_finished_at? && !filter_by_successful_deployment?
raise InefficientQueryError, '`finished_at` filter must be combined with `success` status filter.'
end
2022-10-11 01:57:18 +05:30
if filter_by_environment_name? && !params[:project].present?
raise InefficientQueryError, '`environment` name filter must be combined with `project` scope.'
end
if filter_by_finished_statuses? && filter_by_upcoming_statuses?
raise InefficientQueryError, 'finished statuses and upcoming statuses must be separately queried.'
2021-06-08 01:23:25 +05:30
end
end
2020-01-01 13:55:28 +05:30
def init_collection
2021-06-08 01:23:25 +05:30
if params[:project].present?
2021-03-11 19:13:27 +05:30
params[:project].deployments
2021-06-08 01:23:25 +05:30
elsif params[:group].present?
::Deployment.for_projects(params[:group].all_projects)
2022-10-11 01:57:18 +05:30
elsif filter_by_environment_id?
::Deployment.for_environment(params[:environment])
2021-03-11 19:13:27 +05:30
else
2021-06-08 01:23:25 +05:30
::Deployment.none
2021-03-11 19:13:27 +05:30
end
2020-01-01 13:55:28 +05:30
end
def sort(items)
2021-06-08 01:23:25 +05:30
sort_params = build_sort_params
optimize_sort_params!(sort_params)
2021-03-11 19:13:27 +05:30
items.order(sort_params) # rubocop: disable CodeReuse/ActiveRecord
2020-01-01 13:55:28 +05:30
end
2021-03-11 19:13:27 +05:30
def by_finished_at(items)
items = items.finished_before(params[:finished_before]) if params[:finished_before].present?
items = items.finished_after(params[:finished_after]) if params[:finished_after].present?
items
end
2020-03-13 15:44:24 +05:30
def by_environment(items)
2022-10-11 01:57:18 +05:30
if params[:project].present? && filter_by_environment_name?
2021-06-08 01:23:25 +05:30
items.for_environment_name(params[:project], params[:environment])
2020-03-13 15:44:24 +05:30
else
items
end
end
def by_status(items)
return items unless params[:status].present?
2022-10-11 01:57:18 +05:30
unless Deployment.statuses.keys.intersection(params[:status]) == params[:status]
2020-03-13 15:44:24 +05:30
raise ArgumentError, "The deployment status #{params[:status]} is invalid"
end
items.for_status(params[:status])
end
2021-06-08 01:23:25 +05:30
def build_sort_params
2020-01-01 13:55:28 +05:30
order_by = ALLOWED_SORT_VALUES.include?(params[:order_by]) ? params[:order_by] : DEFAULT_SORT_VALUE
order_direction = ALLOWED_SORT_DIRECTIONS.include?(params[:sort]) ? params[:sort] : DEFAULT_SORT_DIRECTION
2021-06-08 01:23:25 +05:30
{ order_by => order_direction }
end
def optimize_sort_params!(sort_params)
sort_direction = sort_params.each_value.first
# Implicitly enforce the ordering when filtered by `updated_at` column for performance optimization.
# See https://gitlab.com/gitlab-org/gitlab/-/issues/325627#note_552417509.
# We remove this in https://gitlab.com/gitlab-org/gitlab/-/issues/328500.
2021-09-04 01:27:46 +05:30
if filter_by_updated_at?
2021-06-08 01:23:25 +05:30
sort_params.replace('updated_at' => sort_direction)
2020-01-01 13:55:28 +05:30
end
2021-06-08 01:23:25 +05:30
if sort_params['created_at'] || sort_params['iid']
# Sorting by `id` produces the same result as sorting by `created_at` or `iid`
sort_params.replace(id: sort_direction)
elsif sort_params['updated_at']
# This adds the order as a tie-breaker when multiple rows have the same updated_at value.
# See https://gitlab.com/gitlab-org/gitlab/-/merge_requests/20848.
sort_params.merge!(id: sort_direction)
end
end
def filter_by_updated_at?
params[:updated_before].present? || params[:updated_after].present?
end
def filter_by_finished_at?
params[:finished_before].present? || params[:finished_after].present?
end
def filter_by_successful_deployment?
2022-10-11 01:57:18 +05:30
params[:status].present? && params[:status].count == 1 && params[:status].first.to_s == 'success'
end
def filter_by_finished_statuses?
params[:status].present? && Deployment::FINISHED_STATUSES.map(&:to_s).intersection(params[:status]).any?
end
def filter_by_upcoming_statuses?
params[:status].present? && Deployment::UPCOMING_STATUSES.map(&:to_s).intersection(params[:status]).any?
end
def filter_by_environment_name?
params[:environment].present? && params[:environment].is_a?(String)
end
def filter_by_environment_id?
params[:environment].present? && params[:environment].is_a?(Integer)
2021-06-08 01:23:25 +05:30
end
def order_by_updated_at?
params[:order_by].to_s == 'updated_at'
end
def order_by_finished_at?
params[:order_by].to_s == 'finished_at'
end
2021-03-11 19:13:27 +05:30
# rubocop: disable CodeReuse/ActiveRecord
def preload_associations(scope)
scope.includes(
:user,
environment: [],
deployable: {
job_artifacts: [],
2022-10-11 01:57:18 +05:30
user: [],
2023-03-04 22:38:38 +05:30
metadata: [],
2021-03-11 19:13:27 +05:30
pipeline: {
project: {
route: [],
namespace: :route
}
},
project: {
namespace: :route
}
}
)
end
# rubocop: enable CodeReuse/ActiveRecord
2020-01-01 13:55:28 +05:30
end