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
|
# frozen_string_literal: true
class CustomerRelations::Contact < ApplicationRecord
include Gitlab::SQL::Pattern
include Sortable
include StripAttribute
self.table_name = "customer_relations_contacts"
belongs_to :group, -> { where(type: Group.sti_name) }, foreign_key: 'group_id'
belongs_to :organization, optional: true
has_many :issue_contacts, inverse_of: :contact
has_many :issues, through: :issue_contacts, inverse_of: :customer_relations_contacts
strip_attributes! :phone, :first_name, :last_name
enum state: {
inactive: 0,
active: 1
}
validates :group, presence: true
validates :phone, length: { maximum: 32 }
validates :first_name, presence: true, length: { maximum: 255 }
validates :last_name, presence: true, length: { maximum: 255 }
validates :email, length: { maximum: 255 }
validates :description, length: { maximum: 1024 }
validates :email, uniqueness: { case_sensitive: false, scope: :group_id }
validate :validate_email_format
validate :validate_root_group
scope :order_scope_asc, ->(field) { order(arel_table[field].asc.nulls_last) }
scope :order_scope_desc, ->(field) { order(arel_table[field].desc.nulls_last) }
scope :order_by_organization_asc, -> { includes(:organization).order("customer_relations_organizations.name ASC NULLS LAST") }
scope :order_by_organization_desc, -> { includes(:organization).order("customer_relations_organizations.name DESC NULLS LAST") }
SAFE_ATTRIBUTES = %w[
created_at
description
first_name
group_id
id
last_name
organization_id
state
updated_at
].freeze
def hook_attrs
attributes.slice(*SAFE_ATTRIBUTES)
end
def self.reference_prefix
'[contact:'
end
def self.reference_prefix_quoted
'["contact:'
end
def self.reference_postfix
']'
end
# Searches for contacts with a matching first name, last name, email or description.
#
# This method uses ILIKE on PostgreSQL
#
# query - The search query as a String
#
# Returns an ActiveRecord::Relation.
def self.search(query)
fuzzy_search(query, [:first_name, :last_name, :email, :description], use_minimum_char_limit: false)
end
def self.search_by_state(state)
where(state: state)
end
def self.sort_by_field(field, direction)
if direction == :asc
order_scope_asc(field)
else
order_scope_desc(field)
end
end
def self.sort_by_organization(direction)
if direction == :asc
order_by_organization_asc
else
order_by_organization_desc
end
end
def self.sort_by_name
order(Gitlab::Pagination::Keyset::Order.build(
[
Gitlab::Pagination::Keyset::ColumnOrderDefinition.new(
attribute_name: 'last_name',
order_expression: arel_table[:last_name].asc,
distinct: false
),
Gitlab::Pagination::Keyset::ColumnOrderDefinition.new(
attribute_name: 'first_name',
order_expression: arel_table[:first_name].asc,
distinct: false
),
Gitlab::Pagination::Keyset::ColumnOrderDefinition.new(
attribute_name: 'id',
order_expression: arel_table[:id].asc
)
]))
end
def self.find_ids_by_emails(group, emails)
raise ArgumentError, "Cannot lookup more than #{MAX_PLUCK} emails" if emails.length > MAX_PLUCK
where(group: group).where('lower(email) in (?)', emails.map(&:downcase)).pluck(:id)
end
def self.exists_for_group?(group)
return false unless group
exists?(group: group)
end
def self.move_to_root_group(group)
update_query = <<~SQL
UPDATE #{CustomerRelations::IssueContact.table_name}
SET contact_id = new_contacts.id
FROM #{table_name} AS existing_contacts
JOIN #{table_name} AS new_contacts ON new_contacts.group_id = :old_group_id AND LOWER(new_contacts.email) = LOWER(existing_contacts.email)
WHERE existing_contacts.group_id = :new_group_id AND contact_id = existing_contacts.id
SQL
connection.execute(sanitize_sql([update_query, old_group_id: group.root_ancestor.id, new_group_id: group.id]))
dupes_query = <<~SQL
DELETE FROM #{table_name} AS existing_contacts
USING #{table_name} AS new_contacts
WHERE existing_contacts.group_id = :new_group_id AND new_contacts.group_id = :old_group_id AND LOWER(new_contacts.email) = LOWER(existing_contacts.email)
SQL
connection.execute(sanitize_sql([dupes_query, old_group_id: group.root_ancestor.id, new_group_id: group.id]))
where(group: group).update_all(group_id: group.root_ancestor.id)
end
def self.counts_by_state
group(:state).count
end
private
def validate_email_format
return unless email
self.errors.add(:email, I18n.t(:invalid, scope: 'valid_email.validations.email')) unless ValidateEmail.valid?(self.email)
end
def validate_root_group
return if group&.root?
self.errors.add(:base, _('contacts can only be added to root groups'))
end
end
|