Commit 1fcd448e authored by Heinrich Lee Yu's avatar Heinrich Lee Yu

Merge branch 'dz-refactor-environments-finder-3' into 'master'

Move environments finders under namespace

See merge request gitlab-org/gitlab!59281
parents d41e6fd0 c8706c80
......@@ -1546,9 +1546,6 @@ Gitlab/NamespacedClass:
- 'app/finders/context_commits_finder.rb'
- 'app/finders/contributed_projects_finder.rb'
- 'app/finders/deployments_finder.rb'
- 'app/finders/environment_names_finder.rb'
- 'app/finders/environments_finder.rb'
- 'app/finders/environments_by_deployments_finder.rb'
- 'app/finders/events_finder.rb'
- 'app/finders/feature_flags_finder.rb'
- 'app/finders/feature_flags_user_lists_finder.rb'
......
......@@ -197,7 +197,7 @@ class GroupsController < Groups::ApplicationController
def unfoldered_environment_names
respond_to do |format|
format.json do
render json: EnvironmentNamesFinder.new(@group, current_user).execute
render json: Environments::EnvironmentNamesFinder.new(@group, current_user).execute
end
end
end
......
......@@ -20,7 +20,7 @@ class Projects::BlameController < Projects::ApplicationController
environment_params = @repository.branch_exists?(@ref) ? { ref: @ref } : { commit: @commit }
environment_params[:find_latest] = true
@environment = EnvironmentsByDeploymentsFinder.new(@project, current_user, environment_params).execute.last
@environment = Environments::EnvironmentsByDeploymentsFinder.new(@project, current_user, environment_params).execute.last
@blame = Gitlab::Blame.new(@blob, @commit)
@blame = Gitlab::View::Presenter::Factory.new(@blame, project: @project, path: @path).fabricate!
......
......@@ -214,7 +214,7 @@ class Projects::BlobController < Projects::ApplicationController
def show_html
environment_params = @repository.branch_exists?(@ref) ? { ref: @ref } : { commit: @commit }
environment_params[:find_latest] = true
@environment = EnvironmentsByDeploymentsFinder.new(@project, current_user, environment_params).execute.last
@environment = Environments::EnvironmentsByDeploymentsFinder.new(@project, current_user, environment_params).execute.last
@last_commit = @repository.last_commit_for_path(@commit.id, @blob.path, literal_pathspec: true)
@code_navigation_path = Gitlab::CodeNavigationPath.new(@project, @blob.commit_id).full_json_path_for(@blob.path)
......
......@@ -167,7 +167,7 @@ class Projects::CommitController < Projects::ApplicationController
@diffs = commit.diffs(opts)
@notes_count = commit.notes.count
@environment = EnvironmentsByDeploymentsFinder.new(@project, current_user, commit: @commit, find_latest: true).execute.last
@environment = Environments::EnvironmentsByDeploymentsFinder.new(@project, current_user, commit: @commit, find_latest: true).execute.last
end
# rubocop: disable CodeReuse/ActiveRecord
......
......@@ -136,7 +136,7 @@ class Projects::CompareController < Projects::ApplicationController
if compare
environment_params = source_project.repository.branch_exists?(head_ref) ? { ref: head_ref } : { commit: compare.commit }
environment_params[:find_latest] = true
@environment = EnvironmentsByDeploymentsFinder.new(source_project, current_user, environment_params).execute.last
@environment = Environments::EnvironmentsByDeploymentsFinder.new(source_project, current_user, environment_params).execute.last
end
end
......
......@@ -58,7 +58,7 @@ module Projects
def environment
strong_memoize(:environment) do
if cluster_params.key?(:environment_name)
EnvironmentsFinder.new(project, current_user, name: cluster_params[:environment_name]).execute.first
::Environments::EnvironmentsFinder.new(project, current_user, name: cluster_params[:environment_name]).execute.first
else
project.default_environment
end
......
......@@ -311,7 +311,7 @@ class ProjectsController < Projects::ApplicationController
def unfoldered_environment_names
respond_to do |format|
format.json do
render json: EnvironmentNamesFinder.new(@project, current_user).execute
render json: Environments::EnvironmentNamesFinder.new(@project, current_user).execute
end
end
end
......
# frozen_string_literal: true
# Finder for obtaining the unique environment names of a project or group.
#
# This finder exists so that the merge requests "environments" filter can be
# populated with a unique list of environment names. If we retrieve _just_ the
# environments, duplicates may be present (e.g. multiple projects in a group
# having a "staging" environment).
#
# In addition, this finder only produces unfoldered environments. We do this
# because when searching for environments we want to exclude review app
# environments.
class EnvironmentNamesFinder
attr_reader :project_or_group, :current_user
def initialize(project_or_group, current_user = nil)
@project_or_group = project_or_group
@current_user = current_user
end
def execute
all_environments.unfoldered.order_by_name.pluck_unique_names
end
def all_environments
if project_or_group.is_a?(Namespace)
namespace_environments
else
project_environments
end
end
def namespace_environments
# We assume reporter access is needed for the :read_environment permission
# here. This expection is also present in
# IssuableFinder::Params#min_access_level, which is used for filtering out
# merge requests that don't have the right permissions.
#
# We use this approach so we don't need to load every project into memory
# just to verify if we can see their environments. Doing so would not be
# efficient, and possibly mess up pagination if certain projects are not
# meant to be visible.
projects = project_or_group
.all_projects
.public_or_visible_to_user(current_user, Gitlab::Access::REPORTER)
Environment.for_project(projects)
end
def project_environments
if Ability.allowed?(current_user, :read_environment, project_or_group)
project_or_group.environments
else
Environment.none
end
end
end
# frozen_string_literal: true
module Environments
# Finder for obtaining the unique environment names of a project or group.
#
# This finder exists so that the merge requests "environments" filter can be
# populated with a unique list of environment names. If we retrieve _just_ the
# environments, duplicates may be present (e.g. multiple projects in a group
# having a "staging" environment).
#
# In addition, this finder only produces unfoldered environments. We do this
# because when searching for environments we want to exclude review app
# environments.
class EnvironmentNamesFinder
attr_reader :project_or_group, :current_user
def initialize(project_or_group, current_user = nil)
@project_or_group = project_or_group
@current_user = current_user
end
def execute
all_environments.unfoldered.order_by_name.pluck_unique_names
end
def all_environments
if project_or_group.is_a?(Namespace)
namespace_environments
else
project_environments
end
end
def namespace_environments
# We assume reporter access is needed for the :read_environment permission
# here. This expection is also present in
# IssuableFinder::Params#min_access_level, which is used for filtering out
# merge requests that don't have the right permissions.
#
# We use this approach so we don't need to load every project into memory
# just to verify if we can see their environments. Doing so would not be
# efficient, and possibly mess up pagination if certain projects are not
# meant to be visible.
projects = project_or_group
.all_projects
.public_or_visible_to_user(current_user, Gitlab::Access::REPORTER)
Environment.for_project(projects)
end
def project_environments
if Ability.allowed?(current_user, :read_environment, project_or_group)
project_or_group.environments
else
Environment.none
end
end
end
end
# frozen_string_literal: true
module Environments
class EnvironmentsByDeploymentsFinder
attr_reader :project, :current_user, :params
def initialize(project, current_user, params = {})
@project = project
@current_user = current_user
@params = params
end
# rubocop: disable CodeReuse/ActiveRecord
def execute
deployments = project.deployments
deployments =
if ref
deployments_query = params[:with_tags] ? 'ref = :ref OR tag IS TRUE' : 'ref = :ref'
deployments.where(deployments_query, ref: ref.to_s)
elsif commit
deployments.where(sha: commit.sha)
else
deployments.none
end
environment_ids = deployments
.group(:environment_id)
.select(:environment_id)
environments = project.environments.available
.where(id: environment_ids)
if params[:find_latest]
find_one(environments.order_by_last_deployed_at_desc)
else
find_all(environments.order_by_last_deployed_at.to_a)
end
end
# rubocop: enable CodeReuse/ActiveRecord
private
def find_one(environments)
[environments.find { |environment| valid_environment?(environment) }].compact
end
def find_all(environments)
environments.select { |environment| valid_environment?(environment) }
end
def valid_environment?(environment)
# Go in order of cost: SQL calls are cheaper than Gitaly calls
return false unless Ability.allowed?(current_user, :read_environment, environment)
return false if ref && params[:recently_updated] && !environment.recently_updated_on_branch?(ref)
return false if ref && commit && !environment.includes_commit?(commit)
true
end
def ref
params[:ref].try(:to_s)
end
def commit
params[:commit]
end
end
end
# frozen_string_literal: true
module Environments
class EnvironmentsFinder
attr_reader :project, :current_user, :params
InvalidStatesError = Class.new(StandardError)
def initialize(project, current_user, params = {})
@project = project
@current_user = current_user
@params = params
end
def execute
environments = project.environments
environments = by_name(environments)
environments = by_search(environments)
# Raises InvalidStatesError if params[:states] contains invalid states.
by_states(environments)
end
private
def by_name(environments)
if params[:name].present?
environments.for_name(params[:name])
else
environments
end
end
def by_search(environments)
if params[:search].present?
environments.for_name_like(params[:search], limit: nil)
else
environments
end
end
def by_states(environments)
if params[:states].present?
environments_with_states(environments)
else
environments
end
end
def environments_with_states(environments)
# Convert to array of strings
states = Array(params[:states]).map(&:to_s)
raise InvalidStatesError, _('Requested states are invalid') unless valid_states?(states)
environments.with_states(states)
end
def valid_states?(states)
valid_states = Environment.valid_states.map(&:to_s)
(states - valid_states).empty?
end
end
end
# frozen_string_literal: true
class EnvironmentsByDeploymentsFinder
attr_reader :project, :current_user, :params
def initialize(project, current_user, params = {})
@project = project
@current_user = current_user
@params = params
end
# rubocop: disable CodeReuse/ActiveRecord
def execute
deployments = project.deployments
deployments =
if ref
deployments_query = params[:with_tags] ? 'ref = :ref OR tag IS TRUE' : 'ref = :ref'
deployments.where(deployments_query, ref: ref.to_s)
elsif commit
deployments.where(sha: commit.sha)
else
deployments.none
end
environment_ids = deployments
.group(:environment_id)
.select(:environment_id)
environments = project.environments.available
.where(id: environment_ids)
if params[:find_latest]
find_one(environments.order_by_last_deployed_at_desc)
else
find_all(environments.order_by_last_deployed_at.to_a)
end
end
# rubocop: enable CodeReuse/ActiveRecord
private
def find_one(environments)
[environments.find { |environment| valid_environment?(environment) }].compact
end
def find_all(environments)
environments.select { |environment| valid_environment?(environment) }
end
def valid_environment?(environment)
# Go in order of cost: SQL calls are cheaper than Gitaly calls
return false unless Ability.allowed?(current_user, :read_environment, environment)
return false if ref && params[:recently_updated] && !environment.recently_updated_on_branch?(ref)
return false if ref && commit && !environment.includes_commit?(commit)
true
end
def ref
params[:ref].try(:to_s)
end
def commit
params[:commit]
end
end
# frozen_string_literal: true
class EnvironmentsFinder
attr_reader :project, :current_user, :params
InvalidStatesError = Class.new(StandardError)
def initialize(project, current_user, params = {})
@project = project
@current_user = current_user
@params = params
end
def execute
environments = project.environments
environments = by_name(environments)
environments = by_search(environments)
# Raises InvalidStatesError if params[:states] contains invalid states.
by_states(environments)
end
private
def by_name(environments)
if params[:name].present?
environments.for_name(params[:name])
else
environments
end
end
def by_search(environments)
if params[:search].present?
environments.for_name_like(params[:search], limit: nil)
else
environments
end
end
def by_states(environments)
if params[:states].present?
environments_with_states(environments)
else
environments
end
end
def environments_with_states(environments)
# Convert to array of strings
states = Array(params[:states]).map(&:to_s)
raise InvalidStatesError, _('Requested states are invalid') unless valid_states?(states)
environments.with_states(states)
end
def valid_states?(states)
valid_states = Environment.valid_states.map(&:to_s)
(states - valid_states).empty?
end
end
......@@ -21,8 +21,8 @@ module Resolvers
def resolve(**args)
return unless project.present?
EnvironmentsFinder.new(project, context[:current_user], args).execute
rescue EnvironmentsFinder::InvalidStatesError => exception
Environments::EnvironmentsFinder.new(project, context[:current_user], args).execute
rescue Environments::EnvironmentsFinder::InvalidStatesError => exception
raise Gitlab::Graphql::Errors::ArgumentError, exception.message
end
end
......
......@@ -1367,11 +1367,11 @@ class MergeRequest < ApplicationRecord
def environments_for(current_user, latest: false)
return [] unless diff_head_commit
envs = EnvironmentsByDeploymentsFinder.new(target_project, current_user,
envs = Environments::EnvironmentsByDeploymentsFinder.new(target_project, current_user,
ref: target_branch, commit: diff_head_commit, with_tags: true, find_latest: latest).execute
if source_project
envs.concat EnvironmentsByDeploymentsFinder.new(source_project, current_user,
envs.concat Environments::EnvironmentsByDeploymentsFinder.new(source_project, current_user,
ref: source_branch, commit: diff_head_commit, find_latest: latest).execute
end
......
......@@ -35,7 +35,7 @@ module Ci
private
def environments
@environments ||= EnvironmentsByDeploymentsFinder
@environments ||= Environments::EnvironmentsByDeploymentsFinder
.new(project, current_user, ref: @ref, recently_updated: true)
.execute
end
......
......@@ -84,7 +84,7 @@ module Prometheus
def environment
strong_memoize(:environment) do
EnvironmentsFinder.new(project, nil, name: 'production').execute.first ||
Environments::EnvironmentsFinder.new(project, nil, name: 'production').execute.first ||
project.environments.first
end
end
......
......@@ -26,7 +26,7 @@ module API
get ':id/environments' do
authorize! :read_environment, user_project
environments = ::EnvironmentsFinder.new(user_project, current_user, params).execute
environments = ::Environments::EnvironmentsFinder.new(user_project, current_user, params).execute
present paginate(environments), with: Entities::Environment, current_user: current_user
end
......
......@@ -130,7 +130,7 @@ module Gitlab
strong_memoize(:environment) do
next unless environment_name
EnvironmentsFinder
::Environments::EnvironmentsFinder
.new(project, nil, { name: environment_name })
.execute
.first
......
......@@ -2,7 +2,7 @@
require 'spec_helper'
RSpec.describe EnvironmentNamesFinder do
RSpec.describe Environments::EnvironmentNamesFinder do
describe '#execute' do
let!(:group) { create(:group) }
let!(:public_project) { create(:project, :public, namespace: group) }
......
......@@ -2,7 +2,7 @@
require 'spec_helper'
RSpec.describe EnvironmentsByDeploymentsFinder do
RSpec.describe Environments::EnvironmentsByDeploymentsFinder do
let(:project) { create(:project, :repository) }
let(:user) { project.creator }
let(:environment) { create(:environment, :available, project: project) }
......
......@@ -2,7 +2,7 @@
require 'spec_helper'
RSpec.describe EnvironmentsFinder do
RSpec.describe Environments::EnvironmentsFinder do
let(:project) { create(:project, :repository) }
let(:user) { project.creator }
let(:environment) { create(:environment, :available, project: project) }
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment