Merge group hierarchies when parents are shared
This commit is contained in:
parent
530cf2a266
commit
518216c062
|
@ -77,6 +77,7 @@ class GroupsController < Groups::ApplicationController
|
|||
render json: GroupChildSerializer
|
||||
.new(current_user: current_user)
|
||||
.with_pagination(request, response)
|
||||
.hierarchy_base(parent, open_hierarchy: filter[:filter].present)
|
||||
.represent(@children)
|
||||
end
|
||||
end
|
||||
|
|
|
@ -52,7 +52,7 @@ class GroupChildrenFinder
|
|||
end
|
||||
|
||||
def subgroups_matching_filter
|
||||
all_subgroups.search(params[:filter])
|
||||
all_subgroups.search(params[:filter]).include(:parent)
|
||||
end
|
||||
|
||||
def subgroups
|
||||
|
@ -75,6 +75,7 @@ class GroupChildrenFinder
|
|||
def projects_matching_filter
|
||||
ProjectsFinder.new(current_user: current_user).execute
|
||||
.search(params[:filter])
|
||||
.include(:namespace)
|
||||
.where(namespace: all_subgroups)
|
||||
end
|
||||
|
||||
|
|
|
@ -34,7 +34,6 @@ class GroupProjectsFinder < ProjectsFinder
|
|||
else
|
||||
collection_without_user
|
||||
end
|
||||
|
||||
union(projects)
|
||||
end
|
||||
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
module GroupHierarchy
|
||||
def hierarchy(hierarchy_base = nil)
|
||||
@hierarchy ||= tree_for_child(self, self, hierarchy_base)
|
||||
tree_for_child(self, self, hierarchy_base)
|
||||
end
|
||||
|
||||
def parent
|
||||
|
@ -16,7 +16,7 @@ module GroupHierarchy
|
|||
raise ArgumentError.new('specified base is not part of the tree')
|
||||
end
|
||||
|
||||
if child.parent != hierarchy_base
|
||||
if child.parent && child.parent != hierarchy_base
|
||||
tree_for_child(child.parent,
|
||||
{ child.parent => tree },
|
||||
hierarchy_base)
|
||||
|
@ -24,4 +24,47 @@ module GroupHierarchy
|
|||
tree
|
||||
end
|
||||
end
|
||||
|
||||
def merge_hierarchy(other_element, hierarchy_base = nil)
|
||||
GroupHierarchy.merge_hierarchies([self, other_element], hierarchy_base)
|
||||
end
|
||||
|
||||
def self.merge_hierarchies(hierarchies, hierarchy_base = nil)
|
||||
hierarchies = Array.wrap(hierarchies)
|
||||
return if hierarchies.empty?
|
||||
|
||||
unless hierarchies.all? { |other_base| other_base.is_a?(GroupHierarchy) }
|
||||
raise ArgumentError.new('element is not a hierarchy')
|
||||
end
|
||||
|
||||
first_hierarchy, *other_hierarchies = hierarchies
|
||||
merged = first_hierarchy.hierarchy(hierarchy_base)
|
||||
|
||||
other_hierarchies.each do |child|
|
||||
next_hierarchy = child.hierarchy(hierarchy_base)
|
||||
merged = merge_values(merged, next_hierarchy)
|
||||
end
|
||||
|
||||
merged
|
||||
end
|
||||
|
||||
def self.merge_values(first_child, second_child)
|
||||
# When the first is an array, we need to go over every element to see if
|
||||
# we can merge deeper.
|
||||
if first_child.is_a?(Array)
|
||||
first_child.map do |element|
|
||||
if element.is_a?(Hash) && element.keys.any? { |k| second_child.keys.include?(k) }
|
||||
element.deep_merge(second_child) { |key, first, second| merge_values(first, second) }
|
||||
else
|
||||
element
|
||||
end
|
||||
end
|
||||
# If both of them are hashes, we can deep_merge with the same logic
|
||||
elsif first_child.is_a?(Hash) && second_child.is_a?(Hash)
|
||||
first_child.deep_merge(second_child) { |key, first, second| merge_values(first, second) }
|
||||
# One of them is a root node, we just need to put them next to eachother in an array
|
||||
else
|
||||
Array.wrap(first_child) + Array.wrap(second_child)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -1,5 +1,8 @@
|
|||
class BaseSerializer
|
||||
attr_reader :parameters
|
||||
|
||||
def initialize(parameters = {})
|
||||
@parameters = parameters
|
||||
@request = EntityRequest.new(parameters)
|
||||
end
|
||||
|
||||
|
|
|
@ -1,5 +1,42 @@
|
|||
class GroupChildSerializer < BaseSerializer
|
||||
include WithPagination
|
||||
|
||||
attr_reader :hierarchy_root
|
||||
|
||||
entity GroupChildEntity
|
||||
|
||||
def expand_hierarchy(hierarchy_root)
|
||||
@hierarchy_root = hierarchy_root
|
||||
self
|
||||
end
|
||||
|
||||
def represent(resource, opts = {}, entity_class = nil)
|
||||
if hierarchy_root.present?
|
||||
represent_hierarchies(resource, opts)
|
||||
else
|
||||
super(resource, opts)
|
||||
end
|
||||
end
|
||||
|
||||
def represent_hierarchies(children, opts)
|
||||
if children.is_a?(GroupHierarchy)
|
||||
represent_hierarchy(children.hierarchy(hierarchy_root), opts)
|
||||
else
|
||||
children.map { |child| represent_hierarchy(child.hierarchy(hierarchy_root), opts) }
|
||||
end
|
||||
end
|
||||
|
||||
def represent_hierarchy(hierarchy, opts)
|
||||
serializer = self.class.new(parameters)
|
||||
|
||||
result = if hierarchy.is_a?(Hash)
|
||||
parent = hierarchy.keys.first
|
||||
serializer.represent(parent, opts)
|
||||
.merge(children: [serializer.represent_hierarchy(hierarchy[parent], opts)])
|
||||
else
|
||||
serializer.represent(hierarchy, opts)
|
||||
end
|
||||
|
||||
result
|
||||
end
|
||||
end
|
||||
|
|
|
@ -29,6 +29,40 @@ describe GroupHierarchy, :nested_groups do
|
|||
expect(subsub_group.parent).to eq(subgroup)
|
||||
end
|
||||
end
|
||||
|
||||
describe '#merge_hierarchy' do
|
||||
it 'combines hierarchies' do
|
||||
other_subgroup = create(:group, parent: parent)
|
||||
|
||||
expected_hierarchy = { parent => [{ subgroup => subsub_group }, other_subgroup] }
|
||||
|
||||
expect(subsub_group.merge_hierarchy(other_subgroup)).to eq(expected_hierarchy)
|
||||
end
|
||||
end
|
||||
|
||||
describe '.merge_hierarchies' do
|
||||
it 'combines hierarchies until the top' do
|
||||
other_subgroup = create(:group, parent: parent)
|
||||
other_subsub_group = create(:group, parent: subgroup)
|
||||
|
||||
groups = [other_subgroup, subsub_group, other_subsub_group]
|
||||
|
||||
expected_hierarchy = { parent => [other_subgroup, { subgroup => [subsub_group, other_subsub_group] }] }
|
||||
|
||||
expect(described_class.merge_hierarchies(groups)).to eq(expected_hierarchy)
|
||||
end
|
||||
|
||||
it 'combines upto a given parent' do
|
||||
other_subgroup = create(:group, parent: parent)
|
||||
other_subsub_group = create(:group, parent: subgroup)
|
||||
|
||||
groups = [other_subgroup, subsub_group, other_subsub_group]
|
||||
|
||||
expected_hierarchy = [other_subgroup, { subgroup => [subsub_group, other_subsub_group] }]
|
||||
|
||||
expect(described_class.merge_hierarchies(groups, parent)).to eq(expected_hierarchy)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'for a project' do
|
||||
|
@ -57,5 +91,39 @@ describe GroupHierarchy, :nested_groups do
|
|||
expect(project.parent).to eq(subsub_group)
|
||||
end
|
||||
end
|
||||
|
||||
describe '#merge_hierarchy' do
|
||||
it 'combines hierarchies' do
|
||||
project = create(:project, namespace: parent)
|
||||
|
||||
expected_hierarchy = { parent => [{ subgroup => subsub_group }, project] }
|
||||
|
||||
expect(subsub_group.merge_hierarchy(project)).to eq(expected_hierarchy)
|
||||
end
|
||||
end
|
||||
|
||||
describe '.merge_hierarchies' do
|
||||
it 'combines hierarchies until the top' do
|
||||
other_project = create(:project, namespace: parent)
|
||||
other_subgroup_project = create(:project, namespace: subgroup)
|
||||
|
||||
elements = [other_project, subsub_group, other_subgroup_project]
|
||||
|
||||
expected_hierarchy = { parent => [other_project, { subgroup => [subsub_group, other_subgroup_project] }] }
|
||||
|
||||
expect(described_class.merge_hierarchies(elements)).to eq(expected_hierarchy)
|
||||
end
|
||||
|
||||
it 'combines upto a given parent' do
|
||||
other_project = create(:project, namespace: parent)
|
||||
other_subgroup_project = create(:project, namespace: subgroup)
|
||||
|
||||
elements = [other_project, subsub_group, other_subgroup_project]
|
||||
|
||||
expected_hierarchy = [other_project, { subgroup => [subsub_group, other_subgroup_project] }]
|
||||
|
||||
expect(described_class.merge_hierarchies(elements, parent)).to eq(expected_hierarchy)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -0,0 +1,43 @@
|
|||
require 'spec_helper'
|
||||
|
||||
describe GroupChildSerializer do
|
||||
let(:request) { double('request') }
|
||||
let(:user) { create(:user) }
|
||||
subject(:serializer) { described_class.new(current_user: user) }
|
||||
|
||||
describe '#represent' do
|
||||
context 'for groups' do
|
||||
it 'can render a single group' do
|
||||
expect(serializer.represent(build(:group))).to be_kind_of(Hash)
|
||||
end
|
||||
|
||||
it 'can render a collection of groups' do
|
||||
expect(serializer.represent(build_list(:group, 2))).to be_kind_of(Array)
|
||||
end
|
||||
end
|
||||
|
||||
context 'with a hierarchy' do
|
||||
let(:parent) { create(:group) }
|
||||
|
||||
subject(:serializer) do
|
||||
described_class.new(current_user: user).expand_hierarchy(parent)
|
||||
end
|
||||
|
||||
it 'expands the subgroups' do
|
||||
subgroup = create(:group, parent: parent)
|
||||
subsub_group = create(:group, parent: subgroup)
|
||||
|
||||
json = serializer.represent(subsub_group)
|
||||
subsub_group_json = json[:children].first
|
||||
|
||||
expect(json[:id]).to eq(subgroup.id)
|
||||
expect(subsub_group_json).not_to be_nil
|
||||
expect(subsub_group_json[:id]).to eq(subsub_group.id)
|
||||
end
|
||||
|
||||
it 'can expand multiple trees' do
|
||||
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
Loading…
Reference in New Issue