Commit 3ac4f84d authored by Ash McKenzie's avatar Ash McKenzie

Merge branch 'environments-dashboard-limits-and-optimization' into 'master'

Environments Dashboard Limits and Optimization

See merge request gitlab-org/gitlab!19775
parents e8fb4ef7 7f6a6383
......@@ -12,7 +12,7 @@ class Environment < ApplicationRecord
has_one :last_deployment, -> { success.order('deployments.id DESC') }, class_name: 'Deployment'
has_one :last_deployable, through: :last_deployment, source: 'deployable', source_type: 'CommitStatus'
has_one :last_pipeline, through: :last_deployable, source: 'pipeline'
has_one :last_visible_deployment, -> { visible.distinct_on_environment }, class_name: 'Deployment'
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'
has_one :last_visible_pipeline, through: :last_visible_deployable, source: 'pipeline'
......@@ -66,6 +66,9 @@ class Environment < ApplicationRecord
scope :for_project, -> (project) { where(project_id: project) }
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
state_machine :state, initial: :available do
event :start do
......
......@@ -287,7 +287,7 @@ class Project < ApplicationRecord
has_many :variables, class_name: 'Ci::Variable'
has_many :triggers, class_name: 'Ci::Trigger'
has_many :environments
has_many :unfoldered_environments, -> { unfoldered.available }, class_name: 'Environment'
has_many :environments_for_dashboard, -> { from(with_rank.unfoldered.available, :environments).where('rank <= 3') }, class_name: 'Environment'
has_many :deployments
has_many :pipeline_schedules, class_name: 'Ci::PipelineSchedule'
has_many :project_deploy_tokens
......
......@@ -12,11 +12,15 @@ class DashboardEnvironmentEntity < Grape::Entity
expose :external_url
expose :last_visible_deployment, as: :last_deployment, expose_nil: false do |environment|
DeploymentEntity.represent(environment.last_visible_deployment, options.merge(request: request_with_project))
DeploymentEntity.represent(environment.last_visible_deployment,
options.merge(request: request_with_project,
except: unnecessary_deployment_fields))
end
expose :last_visible_pipeline, as: :last_pipeline, expose_nil: false do |environment|
PipelineDetailsEntity.represent(environment.last_visible_pipeline, options.merge(request: request_with_project))
PipelineDetailsEntity.represent(environment.last_visible_pipeline,
options.merge(request: request_with_project,
only: required_pipeline_fields))
end
private
......@@ -29,4 +33,17 @@ class DashboardEnvironmentEntity < Grape::Entity
project: environment.project
)
end
def unnecessary_deployment_fields
[:deployed_by, :manual_actions, :scheduled_actions, :cluster]
end
def required_pipeline_fields
[
:id,
{ details: :detailed_status },
{ triggered_by: { details: :detailed_status } },
{ triggered: { details: :detailed_status } }
]
end
end
......@@ -13,5 +13,5 @@ class DashboardEnvironmentsProjectEntity < Grape::Entity
end
expose :namespace, using: API::Entities::NamespaceBasic
expose :unfoldered_environments, as: :environments, using: DashboardEnvironmentEntity
expose :environments_for_dashboard, as: :environments, using: DashboardEnvironmentEntity
end
......@@ -3,6 +3,8 @@
module Dashboard
module Environments
class ListService
MAX_NUM_PROJECTS = 7
def initialize(user)
@user = user
end
......@@ -15,11 +17,35 @@ module Dashboard
attr_reader :user
# rubocop: disable CodeReuse/ActiveRecord
def load_projects(user)
::Dashboard::Operations::ProjectsService
projects = ::Dashboard::Operations::ProjectsService
.new(user)
.execute(user.ops_dashboard_projects)
.execute(user.ops_dashboard_projects, limit: MAX_NUM_PROJECTS)
ActiveRecord::Associations::Preloader.new.preload(projects, [
:route,
environments_for_dashboard: [
last_visible_pipeline: [
:user,
project: [:route, :group, :project_feature, namespace: :route]
],
last_visible_deployment: [
deployable: [
:metadata,
:pipeline,
project: [:project_feature, :group, :route, namespace: :route]
],
project: [:route, namespace: :route]
],
project: [:project_feature, :group, namespace: :route]
],
namespace: [:route, :owner]
])
projects
end
# rubocop: enable CodeReuse/ActiveRecord
end
end
end
......@@ -7,11 +7,12 @@ module Dashboard
@user = user
end
def execute(project_ids, include_unavailable: false)
def execute(project_ids, include_unavailable: false, limit: nil)
return [] unless License.feature_available?(:operations_dashboard)
projects = find_projects(user, project_ids).to_a
projects = available_projects(projects) unless include_unavailable
projects = limit ? projects.first(limit) : projects
projects
end
......
......@@ -4,21 +4,54 @@ require 'spec_helper'
describe Dashboard::Environments::ListService do
describe '#execute' do
def setup
user = create(:user)
project = create(:project)
project.add_developer(user)
user.update!(ops_dashboard_projects: [project])
[user, project]
end
before do
stub_licensed_features(operations_dashboard: true)
end
it 'returns a list of projects' do
user = create(:user)
project = create(:project)
project.add_developer(user)
user.update!(ops_dashboard_projects: [project])
user, project = setup
projects_with_environments = described_class.new(user).execute
expect(projects_with_environments).to eq([project])
end
it 'preloads only relevant ci_builds' do
user, project = setup
ci_build_a = create(:ci_build, project: project)
ci_build_b = create(:ci_build, project: project)
ci_build_c = create(:ci_build, project: project)
environment_a = create(:environment, project: project)
environment_b = create(:environment, project: project)
create(:deployment, :success, project: project, environment: environment_a, deployable: ci_build_a)
create(:deployment, :success, project: project, environment: environment_a, deployable: ci_build_b)
create(:deployment, :success, project: project, environment: environment_b, deployable: ci_build_c)
expect(CommitStatus).to receive(:instantiate)
.with(a_hash_including("id" => ci_build_b.id), anything)
.at_least(:once)
.and_call_original
expect(CommitStatus).to receive(:instantiate)
.with(a_hash_including("id" => ci_build_c.id), anything)
.at_least(:once)
.and_call_original
described_class.new(user).execute
end
context 'when unlicensed' do
before do
stub_licensed_features(operations_dashboard: false)
......
......@@ -340,7 +340,7 @@ project:
- triggers
- pipeline_schedules
- environments
- unfoldered_environments
- environments_for_dashboard
- deployments
- project_feature
- auto_devops
......
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