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

  # rubocop: disable CodeReuse/ActiveRecord
  def execute(include_descendants: false)
    project_members = project.project_members
    project_members = project_members.non_invite unless can?(current_user, :admin_project, project)

    if group
      group_members = GroupMembersFinder.new(group).execute(include_descendants: include_descendants) # rubocop: disable CodeReuse/Finder
      group_members = group_members.non_invite

      union = Gitlab::SQL::Union.new([project_members, group_members], remove_duplicates: false) # rubocop: disable Gitlab/Union

      sql = distinct_on(union)

      Member.includes(:user).from("(#{sql}) AS #{Member.table_name}")
    else
      project_members
    end
  end
  # rubocop: enable CodeReuse/ActiveRecord

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

  private

  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.
    if Gitlab::Database.postgresql?
      <<~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
    else
      # Older versions of MySQL do not support window functions (and DISTINCT ON is postgres-specific).
      <<~SQL
          SELECT t1.*
          FROM (#{union.to_sql}) AS t1
          JOIN (
            SELECT
              COALESCE(user_id, -1) AS user_id,
              COALESCE(invite_email, 'NULL') AS invite_email,
              MIN(CASE WHEN type = 'ProjectMember' THEN 1 WHEN type = 'GroupMember' THEN 2 ELSE 3 END) AS type_number
            FROM
            (#{union.to_sql}) AS t3
            GROUP BY COALESCE(user_id, -1), COALESCE(invite_email, 'NULL')
          ) AS t2 ON COALESCE(t1.user_id, -1) = t2.user_id
                 AND COALESCE(t1.invite_email, 'NULL') = t2.invite_email
                 AND CASE WHEN t1.type = 'ProjectMember' THEN 1 WHEN t1.type = 'GroupMember' THEN 2 ELSE 3 END = t2.type_number
      SQL
    end
  end
end