summaryrefslogtreecommitdiff
path: root/app/finders/members_finder.rb
blob: f730b015c0a98afc1ca2c0e66fcfa94524becb02 (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
# frozen_string_literal: true

class MembersFinder
  attr_reader :project, :current_user, :group

  def initialize(project, current_user)
    @project = project
    @current_user = current_user
    @group = project.group
  end

  def execute(include_descendants: false, include_invited_groups_members: false)
    project_members = project.project_members
    project_members = project_members.non_invite unless can?(current_user, :admin_project, project)

    union_members = group_union_members(include_descendants, include_invited_groups_members)

    if union_members.any?
      distinct_union_of_members(union_members << project_members)
    else
      project_members
    end
  end

  def can?(*args)
    Ability.allowed?(*args)
  end

  private

  def group_union_members(include_descendants, include_invited_groups_members)
    [].tap do |members|
      members << direct_group_members(include_descendants) if group
      members << project_invited_groups_members if include_invited_groups_members
    end
  end

  def direct_group_members(include_descendants)
    GroupMembersFinder.new(group).execute(include_descendants: include_descendants).non_invite # rubocop: disable CodeReuse/Finder
  end

  def project_invited_groups_members
    invited_groups_ids_including_ancestors = Gitlab::ObjectHierarchy
      .new(project.invited_groups)
      .base_and_ancestors
      .public_or_visible_to_user(current_user)
      .select(:id)

    GroupMember.with_source_id(invited_groups_ids_including_ancestors)
  end

  def distinct_union_of_members(union_members)
    union = Gitlab::SQL::Union.new(union_members, remove_duplicates: false) # rubocop: disable Gitlab/Union
    sql = distinct_on(union)

    Member.includes(:user).from([Arel.sql("(#{sql}) AS #{Member.table_name}")]) # rubocop: disable CodeReuse/ActiveRecord
  end

  def distinct_on(union)
    # We're interested in a list of members without duplicates by user_id.
    # We prefer project members over group members, project members should go first.
    <<~SQL
        SELECT DISTINCT ON (user_id, invite_email) member_union.*
        FROM (#{union.to_sql}) AS member_union
        ORDER BY user_id,
          invite_email,
          CASE
            WHEN type = 'ProjectMember' THEN 1
            WHEN type = 'GroupMember' THEN 2
            ELSE 3
          END
    SQL
  end
end