Commit f8820e15 authored by Jason Goodman's avatar Jason Goodman Committed by Douglas Barbosa Alexandre

Return only environments not in folders in Environment Dashboard

Delete EnvironmentFolder class
parent 7c89f8e0
......@@ -65,6 +65,7 @@ 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) }
state_machine :state, initial: :available do
event :start do
......
......@@ -286,6 +286,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 :deployments
has_many :pipeline_schedules, class_name: 'Ci::PipelineSchedule'
has_many :project_deploy_tokens
......
# frozen_string_literal: true
class EnvironmentFolder
attr_reader :last_environment, :size
delegate :project, to: :last_environment
def self.find_for_projects(projects)
environments = ::Environment.where(project: projects).available
t = ::Environment.arel_table
folder_data = environments
.group('COALESCE(environment_type, name), project_id')
.pluck(t[:id].maximum, t[:id].count)
environments_by_id = environments
.id_in(folder_data.map { |(env_id, _)| env_id })
.includes(:project, last_visible_deployment: [:project, deployable: :user])
.index_by(&:id)
folders = folder_data.map do |(environment_id, count)|
environment = environments_by_id[environment_id]
next unless environment
new(environments_by_id[environment_id], count)
end
projects_with_folders = folders.compact.group_by(&:project)
projects.map { |p| { p => [] } }.reduce({}, :merge).merge(projects_with_folders)
end
def initialize(last_environment, size)
@last_environment = last_environment
@size = size
end
def within_folder?
last_environment.environment_type.present? || size > 1
end
end
# frozen_string_literal: true
class DashboardEnvironmentsFolderEntity < Grape::Entity
expose :last_environment, merge: true, using: DashboardEnvironmentEntity
expose :size
expose :within_folder?, as: :within_folder
end
......@@ -13,7 +13,5 @@ class DashboardEnvironmentsProjectEntity < Grape::Entity
end
expose :namespace, using: API::Entities::NamespaceBasic
expose :environments, using: DashboardEnvironmentsFolderEntity do |_project, options|
options[:folders]
end
expose :unfoldered_environments, as: :environments, using: DashboardEnvironmentEntity
end
......@@ -2,10 +2,4 @@
class DashboardEnvironmentsSerializer < BaseSerializer
entity DashboardEnvironmentsProjectEntity
def represent(projects_with_folders, opts = {}, entity_class = nil)
projects_with_folders.map do |project, folders|
super(project, opts.merge(folders: folders), entity_class)
end
end
end
......@@ -16,11 +16,9 @@ module Dashboard
attr_reader :user
def load_projects(user)
projects = ::Dashboard::Operations::ProjectsService
::Dashboard::Operations::ProjectsService
.new(user)
.execute(user.ops_dashboard_projects)
EnvironmentFolder.find_for_projects(projects)
end
end
end
......
......@@ -344,9 +344,9 @@ describe OperationsController do
expect(project2_json['environments'].map { |e| e['id'] }).to eq([environment3.id])
end
it 'groups like environments together in a folder' do
it 'does not return environments that would be grouped into a folder' do
create(:environment, project: project, name: 'review/test-feature')
environment = create(:environment, project: project, name: 'review/another-feature')
create(:environment, project: project, name: 'review/another-feature')
get :environments_list
......@@ -355,14 +355,11 @@ describe OperationsController do
project_json = json_response['projects'].first
expect(project_json['environments'].count).to eq(1)
expect(project_json['environments'].first['id']).to eq(environment.id)
expect(project_json['environments'].first['size']).to eq(2)
expect(project_json['environments'].first['within_folder']).to eq(true)
expect(project_json['environments'].count).to eq(0)
end
it 'returns an environment not in a folder' do
environment = create(:environment, project: project, name: 'production')
it 'does not return environments that would be grouped into a folder even when there is only a single environment' do
create(:environment, project: project, name: 'staging/test-feature')
get :environments_list
......@@ -371,52 +368,11 @@ describe OperationsController do
project_json = json_response['projects'].first
expect(project_json['environments'].count).to eq(1)
expect(project_json['environments'].first['id']).to eq(environment.id)
expect(project_json['environments'].first['size']).to eq(1)
expect(project_json['environments'].first['within_folder']).to eq(false)
expect(project_json['environments'].count).to eq(0)
end
it 'returns true for within_folder when a folder contains only a single environment' do
environment = create(:environment, project: project, name: 'review/test-feature')
get :environments_list
expect(response).to have_gitlab_http_status(:ok)
expect(response).to match_response_schema('dashboard/operations/environments_list', dir: 'ee')
project_json = json_response['projects'].first
expect(project_json['environments'].count).to eq(1)
expect(project_json['environments'].first['id']).to eq(environment.id)
expect(project_json['environments'].first['size']).to eq(1)
expect(project_json['environments'].first['within_folder']).to eq(true)
end
it 'counts only available environments' do
create(:environment, project: project, name: 'review/test-feature', state: :available)
environment = create(:environment, project: project, name: 'review/another-feature', state: :available)
create(:environment, project: project, name: 'review/great-feature', state: :stopped)
get :environments_list
expect(response).to have_gitlab_http_status(:ok)
expect(response).to match_response_schema('dashboard/operations/environments_list', dir: 'ee')
project_json = json_response['projects'].first
expect(project_json['environments'].count).to eq(1)
expect(project_json['environments'].first['size']).to eq(2)
expect(project_json['environments'].first['within_folder']).to eq(true)
expect(project_json['environments'].first['id']).to eq(environment.id)
end
it "excludes environments with the same folder name for other projects" do
project2 = create(:project)
create(:environment, project: project, name: 'review/test')
create(:environment, project: project2, name: 'review/test')
environment = create(:environment, project: project, name: 'review/something')
user.update!(ops_dashboard_projects: [project])
it 'returns an environment not in a folder' do
environment = create(:environment, project: project, name: 'production')
get :environments_list
......@@ -426,29 +382,6 @@ describe OperationsController do
project_json = json_response['projects'].first
expect(project_json['environments'].count).to eq(1)
expect(project_json['environments'].first['size']).to eq(2)
expect(project_json['environments'].first['within_folder']).to eq(true)
expect(project_json['environments'].first['id']).to eq(environment.id)
end
it "groups environments scoped to projects for multiple projects included in the user's ops dashboard" do
project2 = create(:project)
project2.add_developer(user)
environment = create(:environment, project: project, name: 'review/test')
create(:environment, project: project2, name: 'review/test')
create(:environment, project: project2, name: 'review/thing')
user.update!(ops_dashboard_projects: [project, project2])
get :environments_list
expect(response).to have_gitlab_http_status(:ok)
expect(response).to match_response_schema('dashboard/operations/environments_list', dir: 'ee')
project_json = json_response['projects'].find { |p| p['id'] == project.id }
expect(project_json['environments'].count).to eq(1)
expect(project_json['environments'].first['size']).to eq(1)
expect(project_json['environments'].first['within_folder']).to eq(true)
expect(project_json['environments'].first['id']).to eq(environment.id)
end
......
......@@ -55,16 +55,12 @@
"required": [
"id",
"name",
"size",
"within_folder",
"external_url",
"environment_path"
],
"properties": {
"id": { "type": "integer" },
"name": { "type": "string" },
"size": { "type": "integer" },
"within_folder": { "type": "boolean" },
"external_url": { "type": "string" },
"environment_path": { "type": "string" },
"last_deployment": { "$ref": "../../../../../../../spec/fixtures/api/schemas/deployment.json" }
......
......@@ -17,8 +17,6 @@ describe('Environment Header', () => {
environment_path: '/enivronment/1',
name: 'staging',
external_url: 'http://example.com',
size: 1,
within_folder: false,
},
};
});
......
# frozen_string_literal: true
require 'spec_helper'
describe EnvironmentFolder do
describe '.find_for_projects' do
it 'returns an environment within a folder when the last environment does not have an environment_type' do
project = create(:project)
create(:environment, project: project, name: 'production/azure')
last_environment = create(:environment, project: project, name: 'production')
projects_with_environment_folders = described_class.find_for_projects([project])
environment_folder = projects_with_environment_folders[project].first
expect(environment_folder.last_environment.id).to eq(last_environment.id)
expect(environment_folder.within_folder?).to eq(true)
end
it 'returns an environment outside a folder' do
project = create(:project)
create(:environment, project: project, name: 'production')
projects_with_environment_folders = described_class.find_for_projects([project])
environment_folder = projects_with_environment_folders[project].first
expect(environment_folder.within_folder?).to eq(false)
end
it 'returns a project without any environments' do
project = create(:project)
projects_with_environment_folders = described_class.find_for_projects([project])
expect(projects_with_environment_folders).to eq({ project => [] })
end
it 'returns a project without any available environments' do
project = create(:project)
create(:environment, project: project, state: :stopped)
projects_with_environment_folders = described_class.find_for_projects([project])
expect(projects_with_environment_folders).to eq({ project => [] })
end
it 'returns multiple projects' do
project1 = create(:project)
project2 = create(:project)
create(:environment, project: project1, state: :stopped)
environment = create(:environment, project: project2, state: :available)
projects_with_environment_folders = described_class.find_for_projects([project1, project2])
expect(projects_with_environment_folders[project1]).to eq([])
expect(projects_with_environment_folders[project2].count).to eq(1)
environment_folder = projects_with_environment_folders[project2].first
expect(environment_folder.last_environment).to eq(environment)
end
it 'preloads only relevant ci_builds' do
project = create(:project)
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)
.and_call_original
expect(CommitStatus).to receive(:instantiate)
.with(a_hash_including("id" => ci_build_c.id), anything)
.and_call_original
described_class.find_for_projects([project])
end
end
end
# frozen_string_literal: true
require 'spec_helper'
describe DashboardEnvironmentsFolderEntity do
describe '.as_json' do
it 'includes folder and environment attributes' do
user = create(:user)
environment = create(:environment)
create(:deployment, project: environment.project, environment: environment, status: :success)
size = 1
environment_folder = EnvironmentFolder.new(environment, size)
request = EntityRequest.new(current_user: user)
result = described_class.new(environment_folder, request: request).as_json
expect(result.keys.sort).to eq([:environment_path, :external_url, :id, :last_deployment,
:last_pipeline, :name, :size, :within_folder])
end
end
end
......@@ -8,11 +8,9 @@ describe DashboardEnvironmentsProjectEntity do
current_user = create(:user)
project = create(:project)
environment = create(:environment)
size = 1
environment_folder = EnvironmentFolder.new(environment, size)
entity_request = EntityRequest.new(current_user: current_user)
result = described_class.new(project, { folders: [environment_folder], request: entity_request }).as_json
result = described_class.new(project, { environments: [environment], request: entity_request }).as_json
expect(result.keys.sort).to eq([:avatar_url, :environments, :id, :name, :namespace, :remove_path, :web_url])
end
......
......@@ -6,9 +6,9 @@ describe DashboardEnvironmentsSerializer do
describe '.represent' do
it 'returns an empty array when there are no projects' do
current_user = create(:user)
projects_with_folders = {}
projects = []
result = described_class.new(current_user: current_user).represent(projects_with_folders)
result = described_class.new(current_user: current_user).represent(projects)
expect(result).to eq([])
end
......@@ -16,12 +16,10 @@ describe DashboardEnvironmentsSerializer do
it 'includes project attributes' do
current_user = create(:user)
project = create(:project)
environment = create(:environment)
size = 1
environment_folder = EnvironmentFolder.new(environment, size)
projects_with_folders = { project => [environment_folder] }
create(:environment, project: project, state: :available)
projects = [project]
result = described_class.new(current_user: current_user).represent(projects_with_folders)
result = described_class.new(current_user: current_user).represent(projects)
expect(result.first.keys.sort).to eq([:avatar_url, :environments, :id, :name, :namespace, :remove_path, :web_url])
end
......
......@@ -14,9 +14,9 @@ describe Dashboard::Environments::ListService do
project.add_developer(user)
user.update!(ops_dashboard_projects: [project])
projects_with_folders = described_class.new(user).execute
projects_with_environments = described_class.new(user).execute
expect(projects_with_folders).to eq({ project => [] })
expect(projects_with_environments).to eq([project])
end
context 'when unlicensed' do
......@@ -24,15 +24,15 @@ describe Dashboard::Environments::ListService do
stub_licensed_features(operations_dashboard: false)
end
it 'returns an empty hash' do
it 'returns an empty array' do
user = create(:user)
project = create(:project)
project.add_developer(user)
user.update!(ops_dashboard_projects: [project])
projects_with_folders = described_class.new(user).execute
projects_with_environments = described_class.new(user).execute
expect(projects_with_folders).to eq({})
expect(projects_with_environments).to eq([])
end
end
end
......
......@@ -339,6 +339,7 @@ project:
- triggers
- pipeline_schedules
- environments
- unfoldered_environments
- 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