summaryrefslogtreecommitdiff
path: root/app/finders/group_descendants_finder.rb
blob: 3f231bd8b100756c078d456542201a0258327914 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
class GroupDescendantsFinder
  attr_reader :current_user, :parent_group, :params

  def initialize(current_user: nil, parent_group:, params: {})
    @current_user = current_user
    @parent_group = parent_group
    @params = params.reverse_merge(non_archived: true)
  end

  def execute
    # The children array might be extended with the ancestors of projects when
    # filtering. In that case, take the maximum so the aray does not get limited
    # Otherwise, allow paginating through all results
    #
    all_required_elements = children
    all_required_elements |= ancestors_for_projects if params[:filter]
    total_count = [all_required_elements.size, paginator.total_count].max

    Kaminari.paginate_array(all_required_elements, total_count: total_count)
  end

  def subgroup_count
    @subgroup_count ||= subgroups.count
  end

  def project_count
    @project_count ||= projects.count
  end

  private

  def children
    @children ||= paginator.paginate(params[:page])
  end

  def collections
    [subgroups.with_selects_for_list, projects]
  end

  def paginator
    @paginator ||= Gitlab::MultiCollectionPaginator.new(*collections, per_page: params[:per_page])
  end

  def direct_child_groups
    GroupsFinder.new(current_user,
                     parent: parent_group,
                     all_available: true).execute
  end

  def all_visible_descendant_groups
    groups_table = Group.arel_table
    visible_to_user = groups_table[:visibility_level]
                      .in(Gitlab::VisibilityLevel.levels_for_user(current_user))
    if current_user
      authorized_groups = GroupsFinder.new(current_user,
                                           all_available: false)
                            .execute.as('authorized')
      authorized_to_user = groups_table.project(1).from(authorized_groups)
                             .where(authorized_groups[:id].eq(groups_table[:id]))
                             .exists
      visible_to_user = visible_to_user.or(authorized_to_user)
    end

    hierarchy_for_parent
      .descendants
      .where(visible_to_user)
  end

  def subgroups_matching_filter
    all_visible_descendant_groups
      .search(params[:filter])
  end

  # When filtering we want all to preload all the ancestors upto the specified
  # parent group.
  #
  # - root
  #   - subgroup
  #     - nested-group
  #       - project
  #
  # So when searching 'project', on the 'subgroup' page we want to preload
  # 'nested-group' but not 'subgroup' or 'root'
  def ancestors_for_groups(base_for_ancestors)
    Gitlab::GroupHierarchy.new(base_for_ancestors)
      .base_and_ancestors(upto: parent_group.id)
  end

  def ancestors_for_projects
    projects_to_load_ancestors_of = projects.where.not(namespace: parent_group)
    groups_to_load_ancestors_of = Group.where(id: projects_to_load_ancestors_of.select(:namespace_id))
    ancestors_for_groups(groups_to_load_ancestors_of)
      .with_selects_for_list
  end

  def subgroups
    return Group.none unless Group.supports_nested_groups?

    # When filtering subgroups, we want to find all matches withing the tree of
    # descendants to show to the user
    groups = if params[:filter]
               ancestors_for_groups(subgroups_matching_filter)
             else
               direct_child_groups
             end
    groups.order_by(sort)
  end

  def projects_for_user
    Project.public_or_visible_to_user(current_user).non_archived
  end

  def direct_child_projects
    projects_for_user.where(namespace: parent_group)
  end

  # Finds all projects nested under `parent_group` or any of it's descendant
  # groups
  def projects_matching_filter
    projects_for_user.search(params[:filter])
      .where(namespace_id: hierarchy_for_parent.base_and_descendants.select(:id))
  end

  def projects
    projects = if params[:filter]
                 projects_matching_filter
               else
                 direct_child_projects
               end
    projects.with_route.order_by(sort)
  end

  def sort
    params.fetch(:sort, 'id_asc')
  end

  def hierarchy_for_parent
    @hierarchy ||= Gitlab::GroupHierarchy.new(Group.where(id: parent_group.id))
  end
end