group_descendant.rb 3.42 KB
Newer Older
1
module GroupDescendant
2
3
  def hierarchy(hierarchy_top = nil, preloaded = nil)
    preloaded ||= ancestors_upto(hierarchy_top)
4
    expand_hierarchy_for_child(self, self, hierarchy_top, preloaded)
5
6
  end

7
8
9
  def self.build_hierarchy(descendants, hierarchy_top = nil)
    descendants = Array.wrap(descendants)
    return if descendants.empty?
10

11
    unless descendants.all? { |hierarchy| hierarchy.is_a?(GroupDescendant) }
12
13
14
      raise ArgumentError.new('element is not a hierarchy')
    end

15
    first_descendant, *other_descendants = descendants
16
    merged = first_descendant.hierarchy(hierarchy_top, descendants)
17

18
    other_descendants.each do |descendant|
19
      next_descendant = descendant.hierarchy(hierarchy_top, descendants)
20
      merged = merge_hash_tree(merged, next_descendant)
21
22
23
24
25
    end

    merged
  end

26
27
  private

28
29
30
31
32
33
34
35
36
  def ancestors_upto(hierarchy_top = nil)
    if hierarchy_top
      Gitlab::GroupHierarchy.new(Group.where(id: hierarchy_top)).base_and_descendants
    else
      Gitlab::GroupHierarchy.new(Group.where(id: self)).all_groups
    end
  end

  def expand_hierarchy_for_child(child, hierarchy, hierarchy_top, preloaded)
37
38
39
40
    parent = preloaded.detect { |possible_parent| possible_parent.is_a?(Group) && possible_parent.id == child.parent_id }
    parent ||= child.parent

    if parent.nil? && hierarchy_top.present?
41
      raise ArgumentError.new('specified top is not part of the tree')
42
43
44
45
46
47
48
49
50
51
52
53
54
    end

    if parent && parent != hierarchy_top
      expand_hierarchy_for_child(parent,
                                 { parent => hierarchy },
                                 hierarchy_top,
                                 preloaded)
    else
      hierarchy
    end
  end

  private_class_method def self.merge_hash_tree(first_child, second_child)
55
    # When the first is an array, we need to go over every element to see if
56
57
58
59
60
61
62
63
    # we can merge deeper. If no match is found, we add the element to the array
    #
    # Handled cases:
    # [Array, Hash]
    if first_child.is_a?(Array) && second_child.is_a?(Hash)
      merge_hash_into_array(first_child, second_child)
    elsif first_child.is_a?(Hash) && second_child.is_a?(Array)
      merge_hash_into_array(second_child, first_child)
64
    # If both of them are hashes, we can deep_merge with the same logic
65
66
67
    #
    # Handled cases:
    # [Hash, Hash]
68
    elsif first_child.is_a?(Hash) && second_child.is_a?(Hash)
69
      first_child.deep_merge(second_child) { |key, first, second| merge_hash_tree(first, second) }
70
71
72
73
    # If only one of them is a hash, and one of them is a GroupHierachy-object
    # we can check if its already in the hash. If so, we don't need to do anything
    #
    # Handled cases
74
    # [Hash, GroupDescendant]
75
76
77
78
    elsif first_child.is_a?(Hash) && first_child.keys.include?(second_child)
      first_child
    elsif second_child.is_a?(Hash) && second_child.keys.include?(first_child)
      second_child
79
    # If one or both elements are a GroupDescendant, we wrap create an array
80
81
82
    # combining them.
    #
    # Handled cases:
83
    # [GroupDescendant, Array], [GroupDescendant, GroupDescendant], [Array, Array]
84
85
86
87
    else
      Array.wrap(first_child) + Array.wrap(second_child)
    end
  end
88

89
  private_class_method def self.merge_hash_into_array(array, new_hash)
90
    if mergeable_index = array.index { |element| element.is_a?(Hash) && (element.keys & new_hash.keys).any? }
91
      array[mergeable_index] = merge_hash_tree(array[mergeable_index], new_hash)
92
93
94
95
96
97
    else
      array << new_hash
    end

    array
  end
98
end