File: source_user.rb

package info (click to toggle)
gitlab 17.6.5-19
  • links: PTS, VCS
  • area: main
  • in suites: sid
  • size: 629,368 kB
  • sloc: ruby: 1,915,304; javascript: 557,307; sql: 60,639; xml: 6,509; sh: 4,567; makefile: 1,239; python: 406
file content (172 lines) | stat: -rw-r--r-- 5,777 bytes parent folder | download
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
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
# frozen_string_literal: true

module Import
  class SourceUser < ApplicationRecord
    include Gitlab::SQL::Pattern
    include EachBatch

    self.table_name = 'import_source_users'

    SORT_ORDERS = {
      source_name_asc: { order_by: 'source_name', sort: 'asc' },
      source_name_desc: { order_by: 'source_name', sort: 'desc' },
      status_asc: { order_by: 'status', sort: 'asc' },
      status_desc: { order_by: 'status', sort: 'desc' }
    }.freeze

    belongs_to :placeholder_user, class_name: 'User', optional: true
    belongs_to :reassign_to_user, class_name: 'User', optional: true
    belongs_to :reassigned_by_user, class_name: 'User', optional: true
    belongs_to :namespace

    validates :namespace_id, :import_type, :source_hostname, :source_user_identifier, :status, presence: true
    validates :source_user_identifier, uniqueness: { scope: [:namespace_id, :source_hostname, :import_type] }
    validates :placeholder_user_id, presence: true, unless: :completed?
    validates :reassignment_token, absence: true, unless: :awaiting_approval?
    validates :reassignment_token, length: { is: 32 }, if: :awaiting_approval?
    validates :reassign_to_user_id, presence: true, if: -> {
                                                          awaiting_approval? || reassignment_in_progress? || completed?
                                                        }
    validates :reassign_to_user_id, absence: true, if: -> { pending_reassignment? || keep_as_placeholder? }
    validates :reassign_to_user_id, uniqueness: {
      scope: [:namespace_id, :source_hostname, :import_type],
      allow_nil: true,
      message: ->(_object, _data) {
        s_('Import|already assigned to another placeholder')
      }
    }
    validate :validate_source_hostname

    scope :for_namespace, ->(namespace_id) { where(namespace_id: namespace_id) }
    scope :by_source_hostname, ->(source_hostname) { where(source_hostname: source_hostname) }
    scope :by_import_type, ->(import_type) { where(import_type: import_type) }
    scope :by_statuses, ->(statuses) { where(status: statuses) }
    scope :awaiting_reassignment, -> { where(status: [0, 1, 2, 3, 4]) }
    scope :reassigned, -> { where(status: [5, 6]) }

    STATUSES = {
      pending_reassignment: 0,
      awaiting_approval: 1,
      reassignment_in_progress: 2,
      rejected: 3,
      failed: 4,
      completed: 5,
      keep_as_placeholder: 6
    }.freeze

    ACCEPTED_STATUSES = %i[reassignment_in_progress completed failed].freeze
    REASSIGNABLE_STATUSES = %i[pending_reassignment rejected].freeze
    CANCELABLE_STATUSES = %i[awaiting_approval rejected].freeze

    state_machine :status, initial: :pending_reassignment do
      STATUSES.each do |status_name, value|
        state status_name, value: value
      end

      before_transition awaiting_approval: any do |source_user|
        source_user.reassignment_token = nil
      end

      before_transition any => :awaiting_approval do |source_user|
        source_user.reassignment_token = SecureRandom.hex
      end

      event :reassign do
        transition REASSIGNABLE_STATUSES => :awaiting_approval
      end

      event :cancel_reassignment do
        transition CANCELABLE_STATUSES => :pending_reassignment
      end

      event :keep_as_placeholder do
        transition REASSIGNABLE_STATUSES => :keep_as_placeholder
      end

      event :accept do
        transition awaiting_approval: :reassignment_in_progress
      end

      event :reject do
        transition awaiting_approval: :rejected
      end

      event :complete do
        transition reassignment_in_progress: :completed
      end

      event :fail_reassignment do
        transition reassignment_in_progress: :failed
      end
    end

    class << self
      def find_source_user(source_user_identifier:, namespace:, source_hostname:, import_type:)
        return unless namespace

        find_by(
          source_user_identifier: source_user_identifier,
          namespace_id: namespace.id,
          source_hostname: source_hostname,
          import_type: import_type
        )
      end

      def search(query)
        return none unless query.is_a?(String)

        fuzzy_search(query, [:source_name, :source_username])
      end

      def sort_by_attribute(method)
        sort_order = SORT_ORDERS[method&.to_sym] || SORT_ORDERS[:source_name_asc]

        reorder(sort_order[:order_by] => sort_order[:sort])
      end

      def namespace_placeholder_user_count(namespace, limit:)
        for_namespace(namespace).distinct.limit(limit).count(:placeholder_user_id) -
          (namespace.namespace_import_user.present? ? 1 : 0)
      end

      def source_users_with_missing_information(namespace:, source_hostname:, import_type:)
        for_namespace(namespace)
          .by_source_hostname(source_hostname)
          .by_import_type(import_type)
          .and(
            where(source_name: nil).or(where(source_username: nil))
          )
      end
    end

    def mapped_user
      accepted_status? ? reassign_to_user : placeholder_user
    end

    def mapped_user_id
      accepted_status? ? reassign_to_user_id : placeholder_user_id
    end

    def accepted_status?
      STATUSES.slice(*ACCEPTED_STATUSES).value?(status)
    end

    def reassignable_status?
      STATUSES.slice(*REASSIGNABLE_STATUSES).value?(status)
    end

    def cancelable_status?
      STATUSES.slice(*CANCELABLE_STATUSES).value?(status)
    end

    def validate_source_hostname
      return unless source_hostname

      uri = Gitlab::Utils.parse_url(source_hostname)

      return if uri && uri.scheme && uri.host && uri.path.blank?

      errors.add(:source_hostname, :invalid, message: 'must contain scheme and host, and not path')
    end
  end
end