Commit e218a8df authored by Heinrich Lee Yu's avatar Heinrich Lee Yu

Remove code related to object hierarchy in MySQL

These are not required because MySQL is not
supported anymore
parent 5715586d
......@@ -421,7 +421,7 @@ class ApplicationController < ActionController::Base
end
def manifest_import_enabled?
Group.supports_nested_objects? && Gitlab::CurrentSettings.import_sources.include?('manifest')
Gitlab::CurrentSettings.import_sources.include?('manifest')
end
def phabricator_import_enabled?
......
......@@ -32,18 +32,14 @@ module GroupTree
def filtered_groups_with_ancestors(groups)
filtered_groups = groups.search(params[:filter]).page(params[:page])
if Group.supports_nested_objects?
# We find the ancestors by ID of the search results here.
# Otherwise the ancestors would also have filters applied,
# which would cause them not to be preloaded.
#
# Pagination needs to be applied before loading the ancestors to
# make sure ancestors are not cut off by pagination.
Gitlab::ObjectHierarchy.new(Group.where(id: filtered_groups.select(:id)))
.base_and_ancestors
else
filtered_groups
end
# We find the ancestors by ID of the search results here.
# Otherwise the ancestors would also have filters applied,
# which would cause them not to be preloaded.
#
# Pagination needs to be applied before loading the ancestors to
# make sure ancestors are not cut off by pagination.
Gitlab::ObjectHierarchy.new(Group.where(id: filtered_groups.select(:id)))
.base_and_ancestors
end
# rubocop: enable CodeReuse/ActiveRecord
end
......@@ -132,8 +132,6 @@ class GroupDescendantsFinder
end
def subgroups
return Group.none unless Group.supports_nested_objects?
# When filtering subgroups, we want to find all matches within the tree of
# descendants to show to the user
groups = if params[:filter]
......
......@@ -14,11 +14,9 @@ module Types
group.avatar_url(only_path: false)
end
if ::Group.supports_nested_objects?
field :parent, GroupType,
null: true,
resolve: -> (obj, _args, _ctx) { Gitlab::Graphql::Loaders::BatchModelLoader.new(Group, obj.parent_id).find }
end
field :parent, GroupType,
null: true,
resolve: -> (obj, _args, _ctx) { Gitlab::Graphql::Loaders::BatchModelLoader.new(Group, obj.parent_id).find }
end
end
......
......@@ -127,10 +127,6 @@ module GroupsHelper
groups.to_json
end
def supports_nested_groups?
Group.supports_nested_objects?
end
private
def get_group_sidebar_links
......
# frozen_string_literal: true
module Descendant
extend ActiveSupport::Concern
class_methods do
def supports_nested_objects?
Gitlab::Database.postgresql?
end
end
end
......@@ -10,7 +10,6 @@ class Group < Namespace
include Referable
include SelectForProjectAuthorization
include LoadedInGroupList
include Descendant
include GroupDescendant
include TokenAuthenticatable
include WithUploads
......
......@@ -332,8 +332,6 @@ class Namespace < ApplicationRecord
end
def force_share_with_group_lock_on_descendants
return unless Group.supports_nested_objects?
# We can't use `descendants.update_all` since Rails will throw away the WITH
# RECURSIVE statement. We also can't use WHERE EXISTS since we can't use
# different table aliases, hence we're just using WHERE IN. Since we have a
......
......@@ -16,8 +16,6 @@ class GroupPolicy < BasePolicy
condition(:maintainer) { access_level >= GroupMember::MAINTAINER }
condition(:reporter) { access_level >= GroupMember::REPORTER }
condition(:nested_groups_supported, scope: :global) { Group.supports_nested_objects? }
condition(:has_parent, scope: :subject) { @subject.has_parent? }
condition(:share_with_group_locked, scope: :subject) { @subject.share_with_group_lock? }
condition(:parent_share_with_group_locked, scope: :subject) { @subject.parent&.share_with_group_lock? }
......@@ -108,8 +106,8 @@ class GroupPolicy < BasePolicy
enable :read_nested_project_resources
end
rule { owner & nested_groups_supported }.enable :create_subgroup
rule { maintainer & maintainer_can_create_group & nested_groups_supported }.enable :create_subgroup
rule { owner }.enable :create_subgroup
rule { maintainer & maintainer_can_create_group }.enable :create_subgroup
rule { public_group | logged_in_viewable }.enable :view_globally
......
......@@ -18,10 +18,6 @@ module Groups
return namespace
end
if group_path.include?('/') && !Group.supports_nested_objects?
raise 'Nested groups are not supported on MySQL'
end
create_group_path
end
......
......@@ -43,7 +43,6 @@ module Groups
def ensure_allowed_transfer
raise_transfer_error(:group_is_already_root) if group_is_already_root?
raise_transfer_error(:database_not_supported) unless Group.supports_nested_objects?
raise_transfer_error(:same_parent_as_current) if same_parent?
raise_transfer_error(:invalid_policies) unless valid_policies?
raise_transfer_error(:namespace_with_same_path) if namespace_with_same_path?
......
......@@ -31,7 +31,7 @@ module Members
return unless member.is_a?(GroupMember) && member.user && member.group
delete_project_members(member)
delete_subgroup_members(member) if Group.supports_nested_objects?
delete_subgroup_members(member)
end
def delete_project_members(member)
......
......@@ -102,13 +102,7 @@ module Users
end
def fresh_authorizations
klass = if Group.supports_nested_objects?
Gitlab::ProjectAuthorizations::WithNestedGroups
else
Gitlab::ProjectAuthorizations::WithoutNestedGroups
end
klass.new(user).calculate
Gitlab::ProjectAuthorizations.new(user).calculate
end
end
end
......@@ -23,20 +23,19 @@
= f.submit 'Change group path', class: 'btn btn-warning'
- if supports_nested_groups?
.sub-section
%h4.warning-title Transfer group
= form_for @group, url: transfer_group_path(@group), method: :put do |f|
.form-group
= dropdown_tag('Select parent group', options: { toggle_class: 'js-groups-dropdown', title: 'Parent Group', filter: true, dropdown_class: 'dropdown-open-top dropdown-group-transfer', placeholder: 'Search groups', data: { data: parent_group_options(@group) } })
= hidden_field_tag 'new_parent_group_id'
.sub-section
%h4.warning-title Transfer group
= form_for @group, url: transfer_group_path(@group), method: :put do |f|
.form-group
= dropdown_tag('Select parent group', options: { toggle_class: 'js-groups-dropdown', title: 'Parent Group', filter: true, dropdown_class: 'dropdown-open-top dropdown-group-transfer', placeholder: 'Search groups', data: { data: parent_group_options(@group) } })
= hidden_field_tag 'new_parent_group_id'
%ul
%li Be careful. Changing a group's parent can have unintended #{link_to 'side effects', 'https://docs.gitlab.com/ce/user/project/index.html#redirects-when-changing-repository-paths', target: 'blank'}.
%li You can only transfer the group to a group you manage.
%li You will need to update your local repositories to point to the new location.
%li If the parent group's visibility is lower than the group current visibility, visibility levels for subgroups and projects will be changed to match the new parent group's visibility.
= f.submit 'Transfer group', class: 'btn btn-warning'
%ul
%li Be careful. Changing a group's parent can have unintended #{link_to 'side effects', 'https://docs.gitlab.com/ce/user/project/index.html#redirects-when-changing-repository-paths', target: 'blank'}.
%li You can only transfer the group to a group you manage.
%li You will need to update your local repositories to point to the new location.
%li If the parent group's visibility is lower than the group current visibility, visibility levels for subgroups and projects will be changed to match the new parent group's visibility.
= f.submit 'Transfer group', class: 'btn btn-warning'
.sub-section
%h4.danger-title= _('Remove group')
......
......@@ -25,7 +25,6 @@ export default {
'canUpdate',
'canDestroy',
'canAdmin',
'subepicsSupported',
'initialTitleHtml',
'initialTitleText',
'initialDescriptionHtml',
......@@ -64,7 +63,7 @@ export default {
/>
</div>
<related-items
v-if="subepicsSupported && !isEpicTreeEnabled"
v-if="!isEpicTreeEnabled"
:endpoint="epicLinksEndpoint"
:can-admin="canAdmin"
:can-reorder="canAdmin"
......
......@@ -21,7 +21,6 @@ export default () => ({
canUpdate: false,
canDestroy: false,
canAdmin: false,
subepicsSupported: false,
// Epic Information
epicId: 0,
......
......@@ -3,8 +3,6 @@
class Groups::EpicLinksController < Groups::ApplicationController
include EpicRelations
before_action :check_nested_support!
def update
result = EpicLinks::UpdateService.new(child_epic, current_user, params[:epic]).execute
......@@ -30,8 +28,4 @@ class Groups::EpicLinksController < Groups::ApplicationController
def child_epic
@child_epic ||= Epic.find(params[:id])
end
def check_nested_support!
render_404 unless Epic.supports_nested_objects?
end
end
......@@ -21,7 +21,6 @@ module EE
if parent.is_a?(Group)
data[:issueLinksEndpoint] = group_epic_issues_path(parent, issuable)
data[:epicLinksEndpoint] = group_epic_links_path(parent, issuable)
data[:subepicsSupported] = ::Epic.supports_nested_objects?
data[:fullPath] = parent.full_path
end
......
......@@ -12,7 +12,6 @@ module EE
include Referable
include Awardable
include LabelEventable
include Descendant
include RelativePositioning
enum state: { opened: 1, closed: 2 }
......@@ -164,8 +163,6 @@ module EE
# epic2 - parent: epic1
# Returns: 2
def deepest_relationship_level
return unless supports_nested_objects?
::Gitlab::ObjectHierarchy.new(self.where(parent_id: nil)).max_descendants_depth
end
......
......@@ -82,7 +82,7 @@ module EE
end
def action_allowed?
::Epic.supports_nested_objects? && quick_action_target.group&.feature_available?(:epics) &&
quick_action_target.group&.feature_available?(:epics) &&
current_user.can?(:"admin_#{quick_action_target.to_ability_name}", quick_action_target)
end
end
......
......@@ -136,12 +136,9 @@ module EE
projects_with_packages: count(::Packages::Package.select('distinct project_id')),
projects_with_prometheus_alerts: count(PrometheusAlert.distinct_projects),
projects_with_tracing_enabled: count(ProjectTracingSetting)
}).merge(service_desk_counts).merge(security_products_usage)
# MySql does not support recursive queries so we can't retrieve epics relationship depth
if ::Group.supports_nested_objects?
usage_data[:counts] = usage_data[:counts].merge(epics_deepest_relationship_level)
end
}).merge(service_desk_counts)
.merge(security_products_usage)
.merge(epics_deepest_relationship_level)
usage_data
end
......
# frozen_string_literal: true
require 'spec_helper'
describe Admin::Geo::NodesController, :postgresql do
describe Admin::Geo::NodesController do
shared_examples 'unlicensed geo action' do
it 'redirects to the license page' do
expect(response).to redirect_to(admin_license_path)
......
......@@ -93,7 +93,7 @@ describe EE::RoutableActions::SsoEnforcementRedirect do
it_behaves_like 'a routable SSO url'
end
context 'with a nested group', :nested_groups do
context 'with a nested group' do
subject { described_class.new(nested_group) }
it_behaves_like 'a routable SSO url'
......@@ -105,7 +105,7 @@ describe EE::RoutableActions::SsoEnforcementRedirect do
it_behaves_like 'a routable SSO url'
end
context 'with a nested project', :nested_groups do
context 'with a nested project' do
subject { described_class.new(nested_project) }
it_behaves_like 'a routable SSO url'
......
......@@ -58,7 +58,7 @@ describe RoutableActions do
include_examples 'sso redirects'
end
describe 'for a nested group', :nested_groups do
describe 'for a nested group' do
let(:routable) { create(:group, :private, parent: root_group) }
include_examples 'sso redirects'
......@@ -70,7 +70,7 @@ describe RoutableActions do
include_examples 'sso redirects'
end
describe 'for a nested project', :nested_groups do
describe 'for a nested project' do
let(:routable) { create(:project, :private, group: create(:group, :private, parent: root_group)) }
include_examples 'sso redirects'
......
......@@ -2,7 +2,7 @@
require 'spec_helper'
describe Groups::EpicLinksController, :postgresql do
describe Groups::EpicLinksController do
let(:group) { create(:group, :public) }
let(:parent_epic) { create(:epic, group: group) }
let(:epic1) { create(:epic, group: group) }
......
......@@ -96,7 +96,7 @@ describe Groups::SamlProvidersController do
expect(assigns(:scim_token_url)).to eq("http://localhost/api/scim/v2/groups/#{group.full_path}")
end
context 'not on a top level group', :nested_groups do
context 'not on a top level group' do
let(:group) { create(:group, :nested) }
it_behaves_like '404 status'
......
......@@ -158,7 +158,7 @@ describe 'Billing plan pages', :feature do
end
end
context 'on sub-group', :nested_groups do
context 'on sub-group' do
let(:user2) { create(:user) }
let(:user3) { create(:user) }
let(:group) { create(:group, plan: :bronze_plan) }
......
......@@ -55,7 +55,7 @@ describe 'Epic Issues', :js do
expect(page).not_to have_selector('.related-items-tree-container .js-add-issues-button')
end
it 'user cannot add new epics to the epic', :postgresql do
it 'user cannot add new epics to the epic' do
expect(page).not_to have_selector('.related-items-tree-container .js-add-epics-button')
end
end
......@@ -109,7 +109,7 @@ describe 'Epic Issues', :js do
end
end
it 'user can see all epics of the group and delete the associations', :postgresql do
it 'user can see all epics of the group and delete the associations' do
within('.related-items-tree-container ul.related-items-list') do
expect(page).to have_selector('li.js-item-type-epic', count: 2)
expect(page).to have_content(nested_epics[0].title)
......@@ -146,7 +146,7 @@ describe 'Epic Issues', :js do
end
end
it 'user can add new epics to the epic', :postgresql do
it 'user can add new epics to the epic' do
references = "#{epic_to_add.to_reference(full: true)}"
add_epics(references)
......
......@@ -148,7 +148,7 @@ describe 'Edit group settings' do
end
end
context 'when custom_project_templates feature', :postgresql do
context 'when custom_project_templates feature' do
let!(:subgroup) { create(:group, :public, parent: group) }
let!(:subgroup_1) { create(:group, :public, parent: subgroup) }
......
require 'spec_helper'
describe 'Labels Hierarchy', :js, :nested_groups do
describe 'Labels Hierarchy', :js do
let!(:user) { create(:user) }
let!(:grandparent) { create(:group) }
let!(:parent) { create(:group, parent: grandparent) }
......
......@@ -104,7 +104,7 @@ describe 'Project > Members > Invite group and members', :js do
end
end
context 'for a project in a subgroup', :nested_groups do
context 'for a project in a subgroup' do
let(:root_group) { create(:group) }
let(:subgroup) { create(:group, parent: root_group) }
let(:project) { create(:project, namespace: subgroup) }
......
......@@ -177,7 +177,7 @@ describe 'New project' do
end
end
context 'Group-level project templates', :js, :postgresql do
context 'Group-level project templates', :js do
def visit_create_from_group_template_tab
visit url
click_link 'Create from template'
......
......@@ -18,7 +18,7 @@ describe Boards::MilestonesFinder do
let(:user) { create(:user) }
let(:finder) { described_class.new(board, user) }
context 'when project board', :nested_groups do
context 'when project board' do
let(:board) { create(:board, project: nested_group_project, group: nil) }
it 'returns milestones from board project and ancestors groups' do
......@@ -32,7 +32,7 @@ describe Boards::MilestonesFinder do
end
end
context 'when group board', :nested_groups do
context 'when group board' do
let(:board) { create(:board, project: nil, group: nested_group) }
it 'returns milestones from board group and its ancestors' do
......
......@@ -110,7 +110,7 @@ describe EpicsFinder do
end
end
context 'when subgroups are supported', :nested_groups do
context 'when subgroups are supported' do
let(:subgroup) { create(:group, :private, parent: group) }
let(:subgroup2) { create(:group, :private, parent: subgroup) }
let!(:subepic1) { create(:epic, group: subgroup) }
......
require 'spec_helper'
describe Geo::RepositoryVerificationFinder, :postgresql do
describe Geo::RepositoryVerificationFinder do
set(:project) { create(:project) }
describe '#find_failed_repositories' do
......
......@@ -60,7 +60,7 @@ describe Resolvers::EpicResolver do
end
end
context 'with subgroups', :nested_groups do
context 'with subgroups' do
let(:sub_group) { create(:group, parent: group) }
let(:iids) { [epic1, epic2].map(&:iid) }
let!(:epic3) { create(:epic, group: sub_group, iid: epic1.iid) }
......
......@@ -35,8 +35,7 @@ describe IssuablesHelper do
initialTitleText: epic.title,
initialDescriptionHtml: '<p dir="auto">epic text</p>',
initialDescriptionText: 'epic text',
initialTaskStatus: '0 of 0 tasks completed',
subepicsSupported: Gitlab::Database.postgresql?
initialTaskStatus: '0 of 0 tasks completed'
}
expect(helper.issuable_initial_data(epic)).to eq(expected_data)
end
......
# frozen_string_literal: true
require 'spec_helper'
describe EE::NamespacesHelper, :postgresql do
describe EE::NamespacesHelper do
let!(:admin) { create(:admin) }
let!(:admin_project_creation_level) { nil }
let!(:admin_group) do
......
require 'spec_helper'
describe EE::API::Entities::GeoNodeStatus, :postgresql do
describe EE::API::Entities::GeoNodeStatus do
include ::EE::GeoHelpers
let(:geo_node_status) { build(:geo_node_status) }
......
......@@ -83,7 +83,7 @@ describe Gitlab::CustomFileTemplates do
end
end
context 'in a subgroup', :nested_groups do
context 'in a subgroup' do
set(:subgroup) { create(:group, parent: group) }
set(:subproject) { create(:project, namespace: subgroup) }
set(:subgroup_template_project) { create(:project, :custom_repo, namespace: subgroup, files: template_files('subgroup')) }
......@@ -151,7 +151,7 @@ describe Gitlab::CustomFileTemplates do
end
end
context 'in a subgroup', :nested_groups do
context 'in a subgroup' do
let(:subgroup) { create(:group, parent: group) }
let(:subproject) { create(:project, namespace: subgroup) }
let(:subgroup_template_project) { create(:project, :custom_repo, namespace: subgroup, files: template_files('subgroup')) }
......
require 'spec_helper'
describe Gitlab::Database::LoadBalancing::Host, :postgresql do
describe Gitlab::Database::LoadBalancing::Host do
let(:load_balancer) do
Gitlab::Database::LoadBalancing::LoadBalancer.new(%w[localhost])
end
......
require 'spec_helper'
describe Gitlab::Geo::LogCursor::Daemon, :postgresql, :clean_gitlab_redis_shared_state do
describe Gitlab::Geo::LogCursor::Daemon, :clean_gitlab_redis_shared_state do
include ::EE::GeoHelpers
include ExclusiveLeaseHelpers
......
require 'spec_helper'
describe Gitlab::Geo::LogCursor::EventLogs, :postgresql, :clean_gitlab_redis_shared_state do
describe Gitlab::Geo::LogCursor::EventLogs, :clean_gitlab_redis_shared_state do
subject { described_class.new }
describe '#fetch_in_batches' do
......
......@@ -2,7 +2,7 @@
require 'spec_helper'
describe Gitlab::Geo::LogCursor::Events::CacheInvalidationEvent, :postgresql, :clean_gitlab_redis_shared_state do
describe Gitlab::Geo::LogCursor::Events::CacheInvalidationEvent, :clean_gitlab_redis_shared_state do
let(:logger) { Gitlab::Geo::LogCursor::Logger.new(described_class, Logger::INFO) }
let(:event_log) { create(:geo_event_log, :cache_invalidation_event) }
let!(:event_log_state) { create(:geo_event_log_state, event_id: event_log.id - 1) }
......
require 'spec_helper'
describe Gitlab::Geo::LogCursor::Events::HashedStorageAttachmentsEvent, :postgresql, :clean_gitlab_redis_shared_state do
describe Gitlab::Geo::LogCursor::Events::HashedStorageAttachmentsEvent, :clean_gitlab_redis_shared_state do
let(:logger) { Gitlab::Geo::LogCursor::Logger.new(described_class, Logger::INFO) }
let(:event_log) { create(:geo_event_log, :hashed_storage_attachments_event) }
let!(:event_log_state) { create(:geo_event_log_state, event_id: event_log.id - 1) }
......
require 'spec_helper'
describe Gitlab::Geo::LogCursor::Events::HashedStorageMigratedEvent, :postgresql, :clean_gitlab_redis_shared_state do
describe Gitlab::Geo::LogCursor::Events::HashedStorageMigratedEvent, :clean_gitlab_redis_shared_state do
let(:logger) { Gitlab::Geo::LogCursor::Logger.new(described_class, Logger::INFO) }
let(:event_log) { create(:geo_event_log, :hashed_storage_migration_event) }
let!(:event_log_state) { create(:geo_event_log_state, event_id: event_log.id - 1) }
......
require 'spec_helper'
describe Gitlab::Geo::LogCursor::Events::JobArtifactDeletedEvent, :postgresql, :clean_gitlab_redis_shared_state do
describe Gitlab::Geo::LogCursor::Events::JobArtifactDeletedEvent, :clean_gitlab_redis_shared_state do
let(:logger) { Gitlab::Geo::LogCursor::Logger.new(described_class, Logger::INFO) }
let(:event_log) { create(:geo_event_log, :job_artifact_deleted_event) }
let!(:event_log_state) { create(:geo_event_log_state, event_id: event_log.id - 1) }
......
require 'spec_helper'
describe Gitlab::Geo::LogCursor::Events::LfsObjectDeletedEvent, :postgresql, :clean_gitlab_redis_shared_state do
describe Gitlab::Geo::LogCursor::Events::LfsObjectDeletedEvent, :clean_gitlab_redis_shared_state do
let(:logger) { Gitlab::Geo::LogCursor::Logger.new(described_class, Logger::INFO) }
let(:event_log) { create(:geo_event_log, :lfs_object_deleted_event) }
let!(:event_log_state) { create(:geo_event_log_state, event_id: event_log.id - 1) }
......
require 'spec_helper'
describe Gitlab::Geo::LogCursor::Events::RepositoriesChangedEvent, :postgresql, :clean_gitlab_redis_shared_state do
describe Gitlab::Geo::LogCursor::Events::RepositoriesChangedEvent, :clean_gitlab_redis_shared_state do
include ::EE::GeoHelpers
let(:logger) { Gitlab::Geo::LogCursor::Logger.new(described_class, Logger::INFO) }
......
require 'spec_helper'
describe Gitlab::Geo::LogCursor::Events::RepositoryCreatedEvent, :postgresql, :clean_gitlab_redis_shared_state do
describe Gitlab::Geo::LogCursor::Events::RepositoryCreatedEvent, :clean_gitlab_redis_shared_state do
let(:logger) { Gitlab::Geo::LogCursor::Logger.new(described_class, Logger::INFO) }
let(:project) { create(:project) }
let(:repository_created_event) { create(:geo_repository_created_event, project: project) }
......
require 'spec_helper'
describe Gitlab::Geo::LogCursor::Events::RepositoryDeletedEvent, :postgresql, :clean_gitlab_redis_shared_state do
describe Gitlab::Geo::LogCursor::Events::RepositoryDeletedEvent, :clean_gitlab_redis_shared_state do
let(:logger) { Gitlab::Geo::LogCursor::Logger.new(described_class, Logger::INFO) }
let(:event_log) { create(:geo_event_log, :deleted_event) }
let!(:event_log_state) { create(:geo_event_log_state, event_id: event_log.id - 1) }
......
require 'spec_helper'
describe Gitlab::Geo::LogCursor::Events::RepositoryRenamedEvent, :postgresql, :clean_gitlab_redis_shared_state do
describe Gitlab::Geo::LogCursor::Events::RepositoryRenamedEvent, :clean_gitlab_redis_shared_state do
let(:logger) { Gitlab::Geo::LogCursor::Logger.new(described_class, Logger::INFO) }
let(:event_log) { create(:geo_event_log, :renamed_event) }
let!(:event_log_state) { create(:geo_event_log_state, event_id: event_log.id - 1) }
......
require 'spec_helper'
describe Gitlab::Geo::LogCursor::Events::RepositoryUpdatedEvent, :postgresql, :clean_gitlab_redis_shared_state do
describe Gitlab::Geo::LogCursor::Events::RepositoryUpdatedEvent, :clean_gitlab_redis_shared_state do
include ::EE::GeoHelpers
let(:logger) { Gitlab::Geo::LogCursor::Logger.new(described_class, Logger::INFO) }
......
......@@ -2,7 +2,7 @@
require 'spec_helper'
describe Gitlab::Geo::LogCursor::Events::ResetChecksumEvent, :postgresql, :clean_gitlab_redis_shared_state do
describe Gitlab::Geo::LogCursor::Events::ResetChecksumEvent, :clean_gitlab_redis_shared_state do
let(:logger) { Gitlab::Geo::LogCursor::Logger.new(described_class, Logger::INFO) }
let(:event_log) { create(:geo_event_log, :reset_checksum_event) }
let!(:event_log_state) { create(:geo_event_log_state, event_id: event_log.id - 1) }
......
require 'spec_helper'
describe Gitlab::Geo::LogCursor::Events::UploadDeletedEvent, :postgresql, :clean_gitlab_redis_shared_state do
describe Gitlab::Geo::LogCursor::Events::UploadDeletedEvent, :clean_gitlab_redis_shared_state do
let(:logger) { Gitlab::Geo::LogCursor::Logger.new(described_class, Logger::INFO) }
let(:project) { create(:project) }
let(:upload_deleted_event) { create(:geo_upload_deleted_event, project: project) }
......
......@@ -166,7 +166,7 @@ RSpec.describe Gitlab::Insights::Finders::IssuableFinder do
end
end
context 'for a group with subgroups', :nested_groups do
context 'for a group with subgroups' do
include_examples 'group tests' do
let(:project) { create(:project, :public, group: create(:group, parent: entity)) }
end
......
......@@ -88,7 +88,7 @@ describe Gitlab::UsageData do
expect(count_data[:feature_flags]).to eq(1)
end
it 'gathers deepest epic relationship level', :postgresql do
it 'gathers deepest epic relationship level' do
expect(count_data.keys).to include(:epics_deepest_relationship_level)
end
......
......@@ -169,7 +169,7 @@ describe Burndown do
let(:parent_group_milestone) { create(:milestone, project: nil, group: parent_group, start_date: start_date, due_date: due_date) }
let(:group_milestone) { create(:milestone, group: group, start_date: start_date, due_date: due_date) }
context 'when nested group milestone', :nested_groups do
context 'when nested group milestone' do
before do
parent_group.add_developer(user)
end
......
......@@ -74,7 +74,7 @@ describe Project, :elastic do
end
end
it 'indexes only projects under the group', :nested_groups do
it 'indexes only projects under the group' do
Sidekiq::Testing.inline! do
create :project, name: 'test1', group: create(:group, parent: group)
create :project, name: 'test2', description: 'awesome project'
......
......@@ -118,7 +118,7 @@ describe Epic do
expect(epic.valid_parent?).to be_falsey
end
it 'returns false when level is too deep', :nested_groups do
it 'returns false when level is too deep' do
epic1 = create(:epic, group: group)
epic2 = create(:epic, group: group, parent: epic1)
epic3 = create(:epic, group: group, parent: epic2)
......@@ -142,7 +142,7 @@ describe Epic do
expect(epic.valid_parent?).to be_truthy
end
it 'returns false when total depth after adding would exceed limit', :nested_groups do
it 'returns false when total depth after adding would exceed limit' do
child_epic2 = create(:epic, group: group, parent: child_epic1)
child_epic3 = create(:epic, group: group, parent: child_epic2)
child_epic4 = create(:epic, group: group, parent: child_epic3)
......@@ -166,7 +166,7 @@ describe Epic do
expect(epic.valid_parent?).to be_truthy
end
it 'returns false when total depth after adding would exceed limit', :nested_groups do
it 'returns false when total depth after adding would exceed limit' do
root_epic.update(parent: create(:epic, group: group))
create(:epic, group: group, parent: child_epic1)
......@@ -185,14 +185,14 @@ describe Epic do
expect(epic.valid_parent?).to be_falsey
end
it 'returns false when child epic is parent of the given parent', :nested_groups do
it 'returns false when child epic is parent of the given parent' do
epic1 = create(:epic, group: group, parent: epic)
epic.parent = epic1
expect(epic.valid_parent?).to be_falsey
end
it 'returns false when child epic is an ancestor of the given parent', :nested_groups do
it 'returns false when child epic is an ancestor of the given parent' do
epic1 = create(:epic, group: group, parent: epic)
epic2 = create(:epic, group: group, parent: epic1)
epic.parent = epic2
......@@ -207,7 +207,7 @@ describe Epic do
let(:epic2) { create(:epic, group: group, parent: epic1) }
let(:epic3) { create(:epic, group: group, parent: epic2) }
describe '#ancestors', :nested_groups do
describe '#ancestors' do
it 'returns all ancestors for an epic' do
expect(epic3.ancestors).to eq [epic2, epic1]
end
......@@ -217,7 +217,7 @@ describe Epic do
end
end
describe '#descendants', :nested_groups do
describe '#descendants' do
it 'returns all descendants for an epic' do
expect(epic1.descendants).to match_array([epic2, epic3])
end
......@@ -576,7 +576,7 @@ describe Epic do
end
end
describe '.deepest_relationship_level', :postgresql do
describe '.deepest_relationship_level' do
context 'when there are no epics' do
it 'returns nil' do
expect(described_class.deepest_relationship_level).to be_nil
......
......@@ -48,14 +48,14 @@ describe GitlabSubscription do
expect(gitlab_subscription.seats_in_use).to eq(1)
end
it 'also counts users from subgroups', :postgresql do
it 'also counts users from subgroups' do
group.add_developer(user_1)
subgroup_1.add_developer(user_2)
expect(gitlab_subscription.seats_in_use).to eq(2)
end
it 'does not count duplicated members', :postgresql do
it 'does not count duplicated members' do
group.add_developer(user_1)
subgroup_1.add_developer(user_2)
subgroup_2.add_developer(user_2)
......
......@@ -213,13 +213,11 @@ describe Namespace do
end
end
if Group.supports_nested_objects?
context 'when license is applied to parent group' do
let(:child_group) { create :group, parent: group }
context 'when license is applied to parent group' do
let(:child_group) { create :group, parent: group }
it 'child group has feature available' do
expect(child_group.feature_available?(feature)).to be_truthy
end
it 'child group has feature available' do
expect(child_group.feature_available?(feature)).to be_truthy
end
end
end
......@@ -460,7 +458,7 @@ describe Namespace do
it { is_expected.to be_truthy }
context 'when is subgroup', :nested_groups do
context 'when is subgroup' do
before do
namespace.parent = build(:group)
end
......@@ -478,7 +476,7 @@ describe Namespace do
describe '#shared_runners_enabled?' do
subject { namespace.shared_runners_enabled? }
context 'subgroup with shared runners enabled project', :nested_groups do
context 'subgroup with shared runners enabled project' do
let(:subgroup) { create(:group, parent: namespace) }
let!(:subproject) { create(:project, namespace: subgroup, shared_runners_enabled: true) }
......@@ -776,7 +774,7 @@ describe Namespace do
end
end
describe '#membership_lock with subgroups', :nested_groups do
describe '#membership_lock with subgroups' do
context 'when creating a subgroup' do
let(:subgroup) { create(:group, parent: root_group) }
......
require 'spec_helper'
describe PgReplicationSlot, :postgresql do
describe PgReplicationSlot do
if Gitlab::Database.replication_slots_supported?
describe 'with replication slot support' do
it '#max_replication_slots' do
expect(described_class.max_replication_slots).to be >= 0
end
skip_examples = PgReplicationSlot.max_replication_slots <= PgReplicationSlot.count
skip_examples = described_class.max_replication_slots <= described_class.count
context 'with enough slots available' do
before(:all) do
skip('max_replication_slots too small') if skip_examples
......@@ -37,11 +37,11 @@ describe PgReplicationSlot, :postgresql do
end
it '#max_retained_wal' do
expect(PgReplicationSlot.max_retained_wal).not_to be_nil
expect(described_class.max_retained_wal).not_to be_nil
end
it '#slots_retained_bytes' do
slot = PgReplicationSlot.slots_retained_bytes.find {|x| x['slot_name'] == 'test_slot' }
slot = described_class.slots_retained_bytes.find {|x| x['slot_name'] == 'test_slot' }
expect(slot).not_to be_nil
expect(slot['retained_bytes']).to be_nil
......
......@@ -598,7 +598,7 @@ describe Project do
subject { project.root_namespace }
context 'when namespace has parent group', :nested_groups do
context 'when namespace has parent group' do
let(:root_ancestor) { create(:group) }
let(:parent) { create(:group, parent: root_ancestor) }
......@@ -616,7 +616,7 @@ describe Project do
end
end
describe '#shared_runners_limit_namespace', :nested_groups do
describe '#shared_runners_limit_namespace' do
set(:root_ancestor) { create(:group) }
set(:group) { create(:group, parent: root_ancestor) }
let(:project) { create(:project, namespace: group) }
......
......@@ -329,7 +329,7 @@ describe User do
end
end
describe '#available_subgroups_with_custom_project_templates', :postgresql do
describe '#available_subgroups_with_custom_project_templates' do
let(:user) { create(:user) }
context 'without Groups with custom project templates' do
......
......@@ -348,7 +348,7 @@ describe GroupPolicy do
end
end
describe 'private nested group use the highest access level from the group and inherited permissions', :nested_groups do
describe 'private nested group use the highest access level from the group and inherited permissions' do
let(:nested_group) { create(:group, :private, parent: group) }
before do
......
......@@ -29,7 +29,7 @@ describe EpicPresenter do
expect(presenter.show_data.keys).to match_array([:initial, :meta])
end
it 'has correct ancestors', :nested_groups do
it 'has correct ancestors' do
metadata = JSON.parse(presenter.show_data[:meta])
ancestor_url = metadata['ancestors'].first['url']
......@@ -48,7 +48,7 @@ describe EpicPresenter do
expect(data[:meta]).to match_schema('epic_meta_data', dir: 'ee')
end
it 'avoids N+1 database queries', :nested_groups do
it 'avoids N+1 database queries' do
group1 = create(:group)
group2 = create(:group, parent: group1)
epic1 = create(:epic, group: group1)
......
......@@ -244,7 +244,7 @@ describe API::Geo do
end
end
describe 'POST /geo/status', :postgresql do
describe 'POST /geo/status' do
let(:geo_base_request) { Gitlab::Geo::BaseRequest.new(scope: ::Gitlab::Geo::API_SCOPE) }
let(:data) do
......
......@@ -308,7 +308,7 @@ describe API::V3::Github do
end
end
context 'nested group namespace', :nested_groups do
context 'nested group namespace' do
let(:group) { create(:group, :nested) }
let!(:parent_group_project) { create(:project, group: group.parent, name: 'parent_group_project') }
let!(:child_group_project) { create(:project, group: group, name: 'child_group_project') }
......
......@@ -91,7 +91,7 @@ describe GroupsController, type: :request do
end
end
context 'subgroup', :nested_groups do
context 'subgroup' do
let(:group) { create(:group, :nested) }
it 'does not create ip restriction' do
......
require 'spec_helper'
describe GeoProjectRegistryEntity, :postgresql do
describe GeoProjectRegistryEntity do
let(:registry) { create(:geo_project_registry, :synced) }
let(:entity) do
......
require 'spec_helper'
describe StorageShardEntity, :postgresql do
describe StorageShardEntity do
let(:entity) { described_class.new(StorageShard.new, request: double) }
subject { entity.as_json }
......
......@@ -86,7 +86,7 @@ describe Ci::RegisterJobService do
end
end
context 'when group is subgroup', :nested_groups do
context 'when group is subgroup' do
let!(:root_ancestor) { create(:group) }
let!(:group) { create(:group, parent: root_ancestor) }
let!(:project) { create :project, shared_runners_enabled: true, group: group }
......
......@@ -152,7 +152,7 @@ describe EpicIssues::CreateService do
include_examples 'returns success'
end
context 'when a link of an issue in a subgroup is given', :nested_groups do
context 'when a link of an issue in a subgroup is given' do
let(:subgroup) { create(:group, parent: group) }
let(:project2) { create(:project, group: subgroup) }
let(:issue) { create(:issue, project: project2) }
......
......@@ -2,7 +2,7 @@
require 'spec_helper'
describe EpicLinks::CreateService, :postgresql do
describe EpicLinks::CreateService do
describe '#execute' do
let(:group) { create(:group) }
let(:user) { create(:user) }
......
......@@ -2,7 +2,7 @@
require 'spec_helper'
describe EpicLinks::DestroyService, :postgresql do
describe EpicLinks::DestroyService do
describe '#execute' do
let(:group) { create(:group) }
let(:user) { create(:user) }
......
......@@ -2,7 +2,7 @@
require 'spec_helper'
describe EpicLinks::ListService, :postgresql do
describe EpicLinks::ListService do
let(:user) { create :user }
let(:group) { create(:group, :public) }
let(:parent_epic) { create(:epic, group: group) }
......
......@@ -54,7 +54,7 @@ describe Groups::AutocompleteService do
end
end
describe '#issues', :nested_groups do
describe '#issues' do
let(:project) { create(:project, group: group) }
let(:sub_group_project) { create(:project, group: sub_group) }
......@@ -67,7 +67,7 @@ describe Groups::AutocompleteService do
end
end
describe '#merge_requests', :nested_groups do
describe '#merge_requests' do
let(:project) { create(:project, :repository, group: group) }
let(:sub_group_project) { create(:project, :repository, group: sub_group) }
......@@ -104,10 +104,10 @@ describe Groups::AutocompleteService do
end
it 'returns available commands' do
available_commands = [:todo, :unsubscribe, :award, :shrug, :tableflip, :cc, :title, :close]
if ::Epic.supports_nested_objects?
available_commands += [:child_epic, :remove_child_epic, :parent_epic, :remove_parent_epic]
end
available_commands = [
:todo, :unsubscribe, :award, :shrug, :tableflip, :cc, :title, :close,
:child_epic, :remove_child_epic, :parent_epic, :remove_parent_epic
]
expect(subject.commands(epic).map { |c| c[:name] }).to match_array(available_commands)
end
......@@ -131,7 +131,7 @@ describe Groups::AutocompleteService do
subgroup_milestone.update(group: public_subgroup)
end
it 'returns milestones from groups and subgroups', :nested_groups do
it 'returns milestones from groups and subgroups' do
subject = described_class.new(public_subgroup, user)
expect(subject.milestones.map(&:iid)).to contain_exactly(group_milestone.iid, subgroup_milestone.iid)
......@@ -144,14 +144,14 @@ describe Groups::AutocompleteService do
expect(subject.milestones.map(&:title)).to contain_exactly(group_milestone.title)
end
it 'returns milestones from groups and subgroups', :nested_groups do
it 'returns milestones from groups and subgroups' do
milestones = described_class.new(sub_group, user).milestones
expect(milestones.map(&:iid)).to contain_exactly(group_milestone.iid, subgroup_milestone.iid)
expect(milestones.map(&:title)).to contain_exactly(group_milestone.title, subgroup_milestone.title)
end
it 'returns only milestones that user can read', :nested_groups do
it 'returns only milestones that user can read' do
user = create(:user)
sub_group.add_guest(user)
......
......@@ -47,7 +47,7 @@ describe Groups::ParticipantsService do
end
end
describe '#group_members', :nested_groups do
describe '#group_members' do
let(:parent_group) { create(:group) }
let(:group) { create(:group, parent: parent_group) }
let(:subgroup) { create(:group_with_members, parent: group) }
......
......@@ -108,7 +108,7 @@ describe Groups::UpdateService, '#execute' do
expect(group.errors[:file_template_project_id]).to include('is invalid')
end
context 'in a subgroup', :nested_groups do
context 'in a subgroup' do
let(:parent_group) { create(:group) }
let(:hidden_project) { create(:project, :private, namespace: parent_group) }
let(:group) { create(:group, parent: parent_group) }
......
......@@ -113,7 +113,7 @@ describe Projects::CreateFromTemplateService do
it_behaves_like 'creates project from custom template', nil
it_behaves_like 'creates project from custom template', ''
describe 'creating project from a Group project template', :postgresql do
describe 'creating project from a Group project template' do
let(:project_name) { project_template.name }
let(:group_with_project_templates_id) { subgroup_1_2.id }
let(:group2) { create(:group) }
......
......@@ -298,7 +298,7 @@ describe QuickActions::InterpretService do
end
end
context 'child_epic command', :nested_groups do
context 'child_epic command' do
let(:subgroup) { create(:group, parent: group) }
let(:another_group) { create(:group) }
let(:merge_request) { create(:merge_request, source_project: project) }
......@@ -427,7 +427,7 @@ describe QuickActions::InterpretService do
end
end
context 'remove_child_epic command', :nested_groups do
context 'remove_child_epic command' do
let(:subgroup) { create(:group, parent: group) }
let(:another_group) { create(:group) }
let(:merge_request) { create(:merge_request, source_project: project) }
......
......@@ -43,7 +43,7 @@ describe UpdateBuildMinutesService do
end
end
context 'when namespace is subgroup', :nested_groups do
context 'when namespace is subgroup' do
let(:root_ancestor) { create(:group, shared_runners_minutes_limit: 100) }
let(:namespace) { create(:namespace, parent: root_ancestor) }
......
......@@ -31,7 +31,7 @@ shared_examples_for 'member validations' do
expect(member.errors.messages[:user]).to eq(['is not linked to a SAML account'])
end
context 'subgroups', :nested_groups do
context 'subgroups' do
let!(:subgroup) { create(:group, parent: group) }
before do
......
......@@ -10,7 +10,7 @@ describe 'admin/groups/_form' do
allow(view).to receive(:visibility_level) { group.visibility_level }
end
context 'when sub group is used', :nested_groups do
context 'when sub group is used' do
let(:root_ancestor) { create(:group) }
let(:group) { build(:group, parent: root_ancestor) }
......
......@@ -33,7 +33,7 @@ describe 'groups/edit.html.haml' do
end
end
context 'subgroup', :nested_groups do
context 'subgroup' do
let(:group) { create(:group, :nested) }
before do
......
......@@ -48,7 +48,7 @@ describe ClearSharedRunnersMinutesWorker do
end
end
context 'when namespace has extra shared runner minutes', :postgresql do
context 'when namespace has extra shared runner minutes' do
let!(:namespace) do
create(:namespace, shared_runners_minutes_limit: 100, extra_shared_runners_minutes_limit: 10 )
end
......
require 'spec_helper'
describe Geo::RepositoryVerification::Primary::BatchWorker, :postgresql, :clean_gitlab_redis_cache do
describe Geo::RepositoryVerification::Primary::BatchWorker, :clean_gitlab_redis_cache do
include ::EE::GeoHelpers
set(:healthy_not_verified) { create(:project) }
......
require 'spec_helper'
describe Geo::RepositoryVerification::Primary::ShardWorker, :postgresql, :clean_gitlab_redis_cache do
describe Geo::RepositoryVerification::Primary::ShardWorker, :clean_gitlab_redis_cache do
include ::EE::GeoHelpers
include ExclusiveLeaseHelpers
......
require 'spec_helper'
describe Geo::RepositoryVerification::Primary::SingleWorker, :postgresql, :clean_gitlab_redis_cache do
describe Geo::RepositoryVerification::Primary::SingleWorker, :clean_gitlab_redis_cache do
include ::EE::GeoHelpers
include ExclusiveLeaseHelpers
......
require 'spec_helper'
describe Geo::RepositoryVerification::Secondary::SchedulerWorker, :postgresql, :clean_gitlab_redis_cache do
describe Geo::RepositoryVerification::Secondary::SchedulerWorker, :clean_gitlab_redis_cache do
include ::EE::GeoHelpers
set(:healthy_not_verified) { create(:project) }
......
require 'spec_helper'
describe Geo::RepositoryVerification::Secondary::SingleWorker, :postgresql, :clean_gitlab_redis_cache do
describe Geo::RepositoryVerification::Secondary::SingleWorker, :clean_gitlab_redis_cache do
include ::EE::GeoHelpers
include ExclusiveLeaseHelpers
......
require 'spec_helper'
describe UpdateMaxSeatsUsedForGitlabComSubscriptionsWorker, :postgresql do
describe UpdateMaxSeatsUsedForGitlabComSubscriptionsWorker do
subject { described_class.new }
let!(:user) { create(:user) }
......
......@@ -366,10 +366,7 @@ module API
end
expose :request_access_enabled
expose :full_name, :full_path
if ::Group.supports_nested_objects?
expose :parent_id
end
expose :parent_id
expose :custom_attributes, using: 'API::Entities::CustomAttribute', if: :with_custom_attributes
......
......@@ -114,10 +114,7 @@ module API
params do
requires :name, type: String, desc: 'The name of the group'
requires :path, type: String, desc: 'The path of the group'
if ::Group.supports_nested_objects?
optional :parent_id, type: Integer, desc: 'The parent group id for creating nested group'
end
optional :parent_id, type: Integer, desc: 'The parent group id for creating nested group'
use :optional_params
end
......
......@@ -32,11 +32,6 @@ module Gitlab
# Returns the maximum depth starting from the base
# A base object with no children has a maximum depth of `1`
def max_descendants_depth
unless hierarchy_supported?
# This makes the return value consistent with the case where hierarchy is supported
return descendants_base.exists? ? 1 : nil
end
base_and_descendants(with_depth: true).maximum(DEPTH_COLUMN)
end
......@@ -66,8 +61,6 @@ module Gitlab
# each parent.
# rubocop: disable CodeReuse/ActiveRecord
def base_and_ancestors(upto: nil, hierarchy_order: nil)
return ancestors_base unless hierarchy_supported?
recursive_query = base_and_ancestors_cte(upto, hierarchy_order).apply_to(model.all)
recursive_query = recursive_query.order(depth: hierarchy_order) if hierarchy_order
......@@ -81,10 +74,6 @@ module Gitlab
# When `with_depth` is `true`, a `depth` column is included where it starts with `1` for the base objects
# and incremented as we go down the descendant tree
def base_and_descendants(with_depth: false)
unless hierarchy_supported?
return with_depth ? descendants_base.select("1 as #{DEPTH_COLUMN}", objects_table[Arel.star]) : descendants_base
end
read_only(base_and_descendants_cte(with_depth: with_depth).apply_to(model.all))
end
......@@ -112,8 +101,6 @@ module Gitlab
# If nested objects are not supported, ancestors_base is returned.
# rubocop: disable CodeReuse/ActiveRecord
def all_objects
return ancestors_base unless hierarchy_supported?
ancestors = base_and_ancestors_cte
descendants = base_and_descendants_cte
......@@ -135,10 +122,6 @@ module Gitlab
private
def hierarchy_supported?
Gitlab::Database.postgresql?
end
# rubocop: disable CodeReuse/ActiveRecord
def base_and_ancestors_cte(stop_id = nil, hierarchy_order = nil)
cte = SQL::RecursiveCTE.new(:base_and_ancestors)
......
# frozen_string_literal: true
# This class relies on Common Table Expressions to efficiently get all data,
# including data for nested groups.
module Gitlab
class ProjectAuthorizations
attr_reader :user
# user - The User object for which to calculate the authorizations.
def initialize(user)
@user = user
end
def calculate
cte = recursive_cte
cte_alias = cte.table.alias(Group.table_name)
projects = Project.arel_table
links = ProjectGroupLink.arel_table
relations = [
# The project a user has direct access to.
user.projects.select_for_project_authorization,
# The personal projects of the user.
user.personal_projects.select_as_maintainer_for_project_authorization,
# Projects that belong directly to any of the groups the user has
# access to.
Namespace
.unscoped
.select([alias_as_column(projects[:id], 'project_id'),
cte_alias[:access_level]])
.from(cte_alias)
.joins(:projects),
# Projects shared with any of the namespaces the user has access to.
Namespace
.unscoped
.select([
links[:project_id],
least(cte_alias[:access_level], links[:group_access], 'access_level')
])
.from(cte_alias)
.joins('INNER JOIN project_group_links ON project_group_links.group_id = namespaces.id')
.joins('INNER JOIN projects ON projects.id = project_group_links.project_id')
.joins('INNER JOIN namespaces p_ns ON p_ns.id = projects.namespace_id')
.where('p_ns.share_with_group_lock IS FALSE')
]
ProjectAuthorization
.unscoped
.with
.recursive(cte.to_arel)
.select_from_union(relations)
end
private
# Builds a recursive CTE that gets all the groups the current user has
# access to, including any nested groups.
def recursive_cte
cte = Gitlab::SQL::RecursiveCTE.new(:namespaces_cte)
members = Member.arel_table
namespaces = Namespace.arel_table
# Namespaces the user is a member of.
cte << user.groups
.select([namespaces[:id], members[:access_level]])
.except(:order)
# Sub groups of any groups the user is a member of.
cte << Group.select([
namespaces[:id],
greatest(members[:access_level], cte.table[:access_level], 'access_level')
])
.joins(join_cte(cte))
.joins(join_members)
.except(:order)
cte
end
# Builds a LEFT JOIN to join optional memberships onto the CTE.
def join_members
members = Member.arel_table
namespaces = Namespace.arel_table
cond = members[:source_id]
.eq(namespaces[:id])
.and(members[:source_type].eq('Namespace'))
.and(members[:requested_at].eq(nil))
.and(members[:user_id].eq(user.id))
Arel::Nodes::OuterJoin.new(members, Arel::Nodes::On.new(cond))
end
# Builds an INNER JOIN to join namespaces onto the CTE.
def join_cte(cte)
namespaces = Namespace.arel_table
cond = cte.table[:id].eq(namespaces[:parent_id])
Arel::Nodes::InnerJoin.new(cte.table, Arel::Nodes::On.new(cond))
end
def greatest(left, right, column_alias)
sql_function('GREATEST', [left, right], column_alias)
end
def least(left, right, column_alias)
sql_function('LEAST', [left, right], column_alias)
end
def sql_function(name, args, column_alias)
alias_as_column(Arel::Nodes::NamedFunction.new(name, args), column_alias)
end
def alias_as_column(value, alias_to)
Arel::Nodes::As.new(value, Arel::Nodes::SqlLiteral.new(alias_to))
end
end
end
# frozen_string_literal: true
module Gitlab
module ProjectAuthorizations
# Calculating new project authorizations when supporting nested groups.
#
# This class relies on Common Table Expressions to efficiently get all data,
# including data for nested groups. As a result this class can only be used
# on PostgreSQL.
class WithNestedGroups
attr_reader :user
# user - The User object for which to calculate the authorizations.
def initialize(user)
@user = user
end
def calculate
cte = recursive_cte
cte_alias = cte.table.alias(Group.table_name)
projects = Project.arel_table
links = ProjectGroupLink.arel_table
relations = [
# The project a user has direct access to.
user.projects.select_for_project_authorization,
# The personal projects of the user.
user.personal_projects.select_as_maintainer_for_project_authorization,
# Projects that belong directly to any of the groups the user has
# access to.
Namespace
.unscoped
.select([alias_as_column(projects[:id], 'project_id'),
cte_alias[:access_level]])
.from(cte_alias)
.joins(:projects),
# Projects shared with any of the namespaces the user has access to.
Namespace
.unscoped
.select([links[:project_id],
least(cte_alias[:access_level],
links[:group_access],
'access_level')])
.from(cte_alias)
.joins('INNER JOIN project_group_links ON project_group_links.group_id = namespaces.id')
.joins('INNER JOIN projects ON projects.id = project_group_links.project_id')
.joins('INNER JOIN namespaces p_ns ON p_ns.id = projects.namespace_id')
.where('p_ns.share_with_group_lock IS FALSE')
]
ProjectAuthorization
.unscoped
.with
.recursive(cte.to_arel)
.select_from_union(relations)
end
private
# Builds a recursive CTE that gets all the groups the current user has
# access to, including any nested groups.
def recursive_cte
cte = Gitlab::SQL::RecursiveCTE.new(:namespaces_cte)
members = Member.arel_table
namespaces = Namespace.arel_table
# Namespaces the user is a member of.
cte << user.groups
.select([namespaces[:id], members[:access_level]])
.except(:order)
# Sub groups of any groups the user is a member of.
cte << Group.select([namespaces[:id],
greatest(members[:access_level],
cte.table[:access_level], 'access_level')])
.joins(join_cte(cte))
.joins(join_members)
.except(:order)
cte
end
# Builds a LEFT JOIN to join optional memberships onto the CTE.
def join_members
members = Member.arel_table
namespaces = Namespace.arel_table
cond = members[:source_id]
.eq(namespaces[:id])
.and(members[:source_type].eq('Namespace'))
.and(members[:requested_at].eq(nil))
.and(members[:user_id].eq(user.id))
Arel::Nodes::OuterJoin.new(members, Arel::Nodes::On.new(cond))
end
# Builds an INNER JOIN to join namespaces onto the CTE.
def join_cte(cte)
namespaces = Namespace.arel_table
cond = cte.table[:id].eq(namespaces[:parent_id])
Arel::Nodes::InnerJoin.new(cte.table, Arel::Nodes::On.new(cond))
end
def greatest(left, right, column_alias)
sql_function('GREATEST', [left, right], column_alias)
end
def least(left, right, column_alias)
sql_function('LEAST', [left, right], column_alias)
end
def sql_function(name, args, column_alias)
alias_as_column(Arel::Nodes::NamedFunction.new(name, args), column_alias)
end
def alias_as_column(value, alias_to)
Arel::Nodes::As.new(value, Arel::Nodes::SqlLiteral.new(alias_to))
end
end
end
end
# frozen_string_literal: true
module Gitlab
module ProjectAuthorizations
# Calculating new project authorizations when not supporting nested groups.
class WithoutNestedGroups
attr_reader :user
# user - The User object for which to calculate the authorizations.
def initialize(user)
@user = user
end
def calculate
relations = [
# Projects the user is a direct member of
user.projects.select_for_project_authorization,
# Personal projects
user.personal_projects.select_as_maintainer_for_project_authorization,
# Projects of groups the user is a member of
user.groups_projects.select_for_project_authorization,
# Projects shared with groups the user is a member of
user.groups.joins(:shared_projects).select_for_project_authorization
]
ProjectAuthorization
.unscoped
.select_from_union(relations)
end
end
end
end
......@@ -85,7 +85,7 @@ describe Boards::IssuesController do
expect { list_issues(user: user, board: group_board, list: list3) }.not_to exceed_query_limit(control_count + (2 * 8 - 1))
end
it 'avoids N+1 database queries when adding a subgroup, project, and issue', :nested_groups do
it 'avoids N+1 database queries when adding a subgroup, project, and issue' do
create(:project, group: sub_group_1)
create(:labeled_issue, project: project, labels: [development])
control_count = ActiveRecord::QueryRecorder.new { list_issues(user: user, board: group_board, list: list3) }.count
......
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
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