peoplegroup-connectors 0.2.4 → 0.3.1

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 8e7d20280252218ea7b8611677e2cb47a9f1fee7c7af7fd06fd0d2cba9b62d33
4
- data.tar.gz: 7f6ea3484cc733e014ca35e8eef0ee7090fd78f3a625129561bb1256642167b7
3
+ metadata.gz: 2188d8525330b4b9f74686e1b07e6debd9d488c91071194747a422e81507bd97
4
+ data.tar.gz: 5f378bc5cf32c4fec762168bc61573c8dfc423695b812377394176c1bf6a46cb
5
5
  SHA512:
6
- metadata.gz: 63c2be0e26a7d273095f50e735ad79594964c37ca8a3531e298b8e001b2d5310a4cb05f68c77ad503f08ff60fea52b4bd7622b679371a96aa4e039502e59f3dd
7
- data.tar.gz: c8b3ba98d40d7cb28a6f679bdfe9cc32d491a7a628b4258b39f2d78459f69c3a2cfd698b7cee8339a983c617e4ba5f52ae5e485620d0d2e93f5afeeec5184fba
6
+ metadata.gz: 188a66b33ede08a2af9e4e2bfd7de4f1905347fd9d63d09e7a2bc77022ebc190da720010c5b9111b25ab6923cb2b3e685876447d2d1dd2d59e745c806ccc6b12
7
+ data.tar.gz: a48bca2c21e59036339b9ac1445e14336c162ced1b497bd1236fe5563b0cdf58aa00f202f9ea7e7f1d727107226b0b1990b6263ff11ac5921013ab36a4a19371
data/README.md CHANGED
@@ -12,11 +12,15 @@ gem 'peoplegroup-connectors'
12
12
 
13
13
  And then execute:
14
14
 
15
- $ bundle install
15
+ ```bash
16
+ bundle install
17
+ ```
16
18
 
17
19
  Or install it yourself as:
18
20
 
19
- $ gem install peoplegroup-connectors
21
+ ```bash
22
+ gem install peoplegroup-connectors
23
+ ```
20
24
 
21
25
  ## Usage
22
26
 
@@ -35,8 +39,7 @@ For local development and manual testing, copy the contents of the `.env.example
35
39
 
36
40
  ## Contributing and Code of Conduct
37
41
 
38
- Bug reports and pull requests are welcome on GitLab at https://gitlab.com/gitlab-com/people-group/peopleops-eng/connectors-gem. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [Contributor Covenant](http://contributor-covenant.org) code of conduct.
39
-
42
+ Bug reports and pull requests are welcome on GitLab at <https://gitlab.com/gitlab-com/people-group/peopleops-eng/connectors-gem>. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [Contributor Covenant](http://contributor-covenant.org) code of conduct.
40
43
 
41
44
  ## License
42
45
 
@@ -1,6 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require 'bamboozled'
4
+ require_rel 'models/objectified_hash'
4
5
 
5
6
  module PeopleGroup
6
7
  module Connectors
@@ -112,45 +113,6 @@ module PeopleGroup
112
113
  manager
113
114
  end
114
115
 
115
- def people_managers_in_functions(departments: [], divisions: [])
116
- active_and_current_team_members.select do |team_member|
117
- (departments.include?(team_member['department']) || divisions.include?(team_member['division'])) && has_direct_reports?(team_member['employeeNumber'])
118
- end
119
- end
120
-
121
- def all_reports_for(id)
122
- direct_reports_to_check = direct_reports_for(id)
123
- reports_to_return = []
124
- while direct_reports_to_check.present?
125
- current_node = direct_reports_to_check.shift
126
- reports_to_return << current_node
127
- direct_reports_to_check.concat(direct_reports_for(current_node['employeeNumber']))
128
- end
129
- reports_to_return
130
- end
131
-
132
- def direct_reports_for(id)
133
- active_and_current_team_members.select { |team_member| team_member['supervisorId'] == id.to_s }
134
- end
135
-
136
- def has_direct_reports?(id)
137
- active_and_current_team_members.any? { |team_member| team_member['supervisorId'] == id.to_s }
138
- end
139
-
140
- def reports_in_departments(departments, exclude_email)
141
- active_and_current_team_members.select do |team_member|
142
- # exclude_email is for example to filter out the PBP itself in case their department is also the department they manage
143
- departments.include?(team_member['department']) && team_member['workEmail'] != exclude_email
144
- end
145
- end
146
-
147
- def reports_in_divisions(divisions, exclude_email)
148
- active_and_current_team_members.select do |team_member|
149
- # exclude_email is for example to filter out the PBP itself in case their department is also the department they manage
150
- divisions.include?(team_member['division']) && team_member['workEmail'] != exclude_email
151
- end
152
- end
153
-
154
116
  def create_employee(employee_details_hash)
155
117
  invalidate_cache
156
118
  retry_on_error { @client.employee.add(employee_details_hash) }
@@ -164,41 +126,23 @@ module PeopleGroup
164
126
  end
165
127
  alias_method :update_team_member, :update_employee
166
128
 
167
- def departments
168
- meta_fields
169
- .detect { |res| res['name'] == 'Department' }
170
- .dig('options')
171
- .each_with_object([]) { |option, array| array << option['name'] if option['archived'] == 'no' } || []
172
- end
173
-
174
- def divisions
175
- meta_fields
176
- .detect { |res| res['name'] == 'Division' }
177
- .dig('options')
178
- .each_with_object([]) { |option, array| array << option['name'] if option['archived'] == 'no' } || []
179
- end
180
-
181
129
  def locations
182
130
  meta_fields.detect { |res| res['name'] == 'Location' }.dig('options').each_with_object([]) { |option, array| array << option['name'] if option['archived'] == 'no' } || []
183
131
  end
184
132
 
185
- def fields
186
- return @fields if @fields
187
-
188
- meta_field_aliases = retry_on_error { @client.meta.fields }.map { |f| f['alias'] }
189
- @fields = (Bamboozled::API::FieldCollection.all_names + meta_field_aliases).compact.uniq
190
- @fields.delete('flsaCode') # temp fix for problems with BambooHR
191
- @fields
192
- end
193
-
194
133
  def employees
195
- retry_on_error do
134
+ return @employees unless @employees.nil?
135
+
136
+ report = retry_on_error do
196
137
  if @use_report
197
- @employees ||= @client.report.find(@use_report, 'JSON', true).reject { |employee| employee['lastName'] == 'Test-Gitlab' }
138
+ @client.report.find(@use_report, 'JSON', true)
198
139
  else
199
- @employees ||= @client.report.custom(fields, 'JSON').reject { |employee| employee['lastName'] == 'Test-Gitlab' }
140
+ @client.report.custom(fields, 'JSON')
200
141
  end
201
142
  end
143
+
144
+ filtered = report.reject { |team_member| team_member['lastName'] == 'Test-Gitlab' }
145
+ @employees = filtered.map { |team_member| format_team_member(team_member) }
202
146
  end
203
147
  alias_method :team_members, :employees
204
148
 
@@ -220,11 +164,6 @@ module PeopleGroup
220
164
  retry_on_error { @client.employee.add_table_row(id, 'customEquity', data) }
221
165
  end
222
166
 
223
- def stock_options_data(employee_number)
224
- id = bamboo_id!(employee_number)
225
- retry_on_error { @client.employee.table_data(id, 'customEquity') }
226
- end
227
-
228
167
  def add_job_details(employee_number, data)
229
168
  id = bamboo_id!(employee_number)
230
169
  retry_on_error { @client.employee.add_table_row(id, 'jobInfo', data) }
@@ -247,19 +186,10 @@ module PeopleGroup
247
186
  retry_on_error { @client.employee.table_data(id, 'employmentStatus') }
248
187
  end
249
188
 
250
- def time_off_types
251
- @time_off_types ||= retry_on_error { @client.meta.time_off_types }
252
- end
253
-
254
189
  def time_off_type(name)
255
190
  time_off_types['timeOffTypes'].find { |type| type['name'] == name }
256
191
  end
257
192
 
258
- def time_off_estimate(employee_number, end_date = Date.today)
259
- id = bamboo_id!(employee_number)
260
- retry_on_error { @client.employee.time_off_estimate(id, end_date) }
261
- end
262
-
263
193
  def time_off_adjustment(employee_number, options)
264
194
  id = bamboo_id!(employee_number)
265
195
  retry_on_error { @client.employee.time_off_balance_adjustment(id, options) }
@@ -355,11 +285,6 @@ module PeopleGroup
355
285
  retry_on_error { @client.employee.table_data(id, 'employmentStatus') }
356
286
  end
357
287
 
358
- def currency_conversion(employee_number)
359
- id = bamboo_id!(employee_number)
360
- retry_on_error { @client.employee.table_data(id, 'customCurrencyConversion') }
361
- end
362
-
363
288
  def add_bonus(employee_number, comment)
364
289
  team_member = search_employee_by_field(field: 'employeeNumber', value: employee_number)
365
290
  data = {
@@ -372,21 +297,30 @@ module PeopleGroup
372
297
  retry_on_error { @client.employee.add_table_row(team_member['id'], 'customBonus', data) }
373
298
  end
374
299
 
375
- def add_rating_details(team_member_id, performance, potential)
376
- data = {
377
- customEffectiveDate4: Date.today.to_s,
378
- customPerformanceFactor: performance,
379
- customPotentialFactor: potential
380
- }
381
- retry_on_error { @client.employee.add_table_row(team_member_id, 'customPerformanceandPotentialTable', data) }
382
- end
383
-
384
300
  private
385
301
 
302
+ def fields
303
+ return @fields if @fields
304
+
305
+ meta_field_aliases = retry_on_error { @client.meta.fields }.map { |f| f['alias'] }
306
+ @fields = (Bamboozled::API::FieldCollection.all_names + meta_field_aliases).compact.uniq
307
+ @fields.delete('flsaCode') # temp fix for problems with BambooHR
308
+ @fields
309
+ end
310
+
386
311
  def meta_fields
387
312
  @meta_fields ||= retry_on_error { @client.meta.lists }
388
313
  end
389
314
 
315
+ def time_off_types
316
+ @time_off_types ||= retry_on_error { @client.meta.time_off_types }
317
+ end
318
+
319
+ def time_off_estimate(employee_number, end_date = Date.today)
320
+ id = bamboo_id!(employee_number)
321
+ retry_on_error { @client.employee.time_off_estimate(id, end_date) }
322
+ end
323
+
390
324
  def files(employee_number)
391
325
  id = bamboo_id!(employee_number)
392
326
  @files ||= retry_on_error { @client.employee.files(id) }
@@ -399,6 +333,28 @@ module PeopleGroup
399
333
  def invalidate_cache
400
334
  @employees = nil
401
335
  end
336
+
337
+ MALFORMED_FIELDS = [
338
+ 'customExportName/LocationtoTeamPage?',
339
+ 'customI-9Processed',
340
+ 'customJobTitleSpecialty(Multi-Select)'
341
+ ].freeze
342
+
343
+ # Convert BHR team member to Workday.
344
+ def format_team_member(team_member)
345
+ return nil if team_member.nil?
346
+
347
+ formatted = {
348
+ **team_member.except(*MALFORMED_FIELDS),
349
+ 'customExportNameLocationtoTeamPage' => team_member['customExportName/LocationtoTeamPage?'] || 'No',
350
+ 'customJobTitleSpecialtyMultiSelect' => team_member['customJobTitleSpecialty(Multi-Select)'],
351
+ 'customI9Processed' => team_member['customI-9Processed']
352
+ }
353
+
354
+ formatted = PeopleGroup::Connectors::Models::ObjectifiedHash.new(formatted) unless @use_report
355
+
356
+ formatted
357
+ end
402
358
  end
403
359
  end
404
360
  end
@@ -8,11 +8,26 @@ module PeopleGroup
8
8
 
9
9
  def initialize
10
10
  @workday = Workday.new
11
+ @bamboo = Bamboo.new
11
12
  end
12
13
 
13
14
  def employees
14
- @workday.workers.map { |worker| format_worker(worker) }
15
+ @employees ||= @workday.workers
15
16
  end
17
+ alias_method :team_members, :employees
18
+
19
+ def active_employees
20
+ employees.select { |employee| employee['status'] == 'Active' }
21
+ end
22
+ alias_method :active_team_members, :active_employees
23
+
24
+ def active_and_current_employees
25
+ today = Date.current
26
+ employees.select do |employee|
27
+ employee['status'] == 'Active' && Date.parse(employee['hireDate']) <= today
28
+ end
29
+ end
30
+ alias_method :active_and_current_team_members, :active_and_current_employees
16
31
 
17
32
  def get_employee_details(employee_number)
18
33
  employees.find { |emp| emp['employeeNumber'] == employee_number.to_s }
@@ -25,49 +40,233 @@ module PeopleGroup
25
40
 
26
41
  employee_details
27
42
  end
43
+ alias_method :get_team_member_details!, :get_employee_details!
28
44
 
29
- private
45
+ def search_employee(name)
46
+ return if name.empty?
30
47
 
31
- # Convert Workday worker to BambooHR employee
32
- def format_worker(worker)
33
- return nil if worker.nil?
48
+ employees.find do |emp|
49
+ [
50
+ emp['displayName']&.downcase,
51
+ "#{emp['firstName']&.downcase} #{emp['lastName']&.downcase}",
52
+ "#{emp['preferredName']&.downcase} #{emp['lastName']&.downcase}",
53
+ "#{emp['firstName']&.downcase} #{emp['customPreferredLastName']&.downcase}",
54
+ "#{emp['preferredName']&.downcase} #{emp['customPreferredLastName']&.downcase}",
55
+ emp['fullName5']&.downcase # this is firstName middleName lastName
56
+ ].include?(name.downcase)
57
+ end
58
+ end
59
+ alias_method :search_team_member, :search_employee
60
+
61
+ def search_employee!(name)
62
+ employee = search_employee(name)
63
+ raise EmployeeNotFoundError, "No employee found with name #{name}" if employee.nil?
34
64
 
35
- {
36
- 'employeeNumber' => worker.dig(:worker_data, :worker_id),
37
- 'firstName' => worker.dig(:worker_data, :personal_data, :name_data, :legal_name_data, :name_detail_data, :first_name),
38
- 'lastName' => worker.dig(:worker_data, :personal_data, :name_data, :legal_name_data, :name_detail_data, :last_name),
39
- 'preferredName' => worker.dig(:worker_data, :personal_data, :name_data, :preferred_name_data, :name_detail_data, :first_name),
40
- 'customPreferredLastName' => worker.dig(:worker_data, :personal_data, :name_data, :preferred_name_data, :name_detail_data, :last_name),
41
- **format_emails(worker),
42
- 'status' => worker.dig(:worker_data, :employment_data, :worker_status_data, :active) == '1' ? 'Active' : 'Inactive',
43
- 'hireDate' => worker.dig(:worker_data, :employment_data, :worker_status_data, :hire_date).to_s,
44
- 'jobTitle' => worker.dig(:worker_data, :employment_data, :worker_job_data, :position_data, :position_title)
45
- }
65
+ employee
46
66
  end
67
+ alias_method :search_team_member!, :search_employee!
47
68
 
48
- def format_emails(worker)
49
- formatted_emails = {
50
- 'workEmail' => nil,
51
- 'homeEmail' => nil
52
- }
69
+ def search_employee_by_field(field:, value:)
70
+ employees.find { |employee| employee[field] == value.to_s }
71
+ end
72
+ alias_method :search_team_member_by_field, :search_employee_by_field
53
73
 
54
- email_addresses = worker.dig(:worker_data, :personal_data, :contact_data, :email_address_data)
74
+ def search_employee_by_field!(field:, value:)
75
+ employee = search_employee_by_field(field: field, value: value)
76
+ raise EmployeeNotFoundError, "No employee found with #{field}: #{value}" if employee.nil?
55
77
 
56
- return formatted_emails if email_addresses.nil?
78
+ employee
79
+ end
80
+ alias_method :search_team_member_by_field!, :search_employee_by_field!
57
81
 
58
- # If there is only one email, Workday returns a hash
59
- # If there are more than one, Workday returns an array of hashes
60
- # rubocop:disable Style/ArrayCoercion
61
- email_addresses = [email_addresses] unless email_addresses.is_a?(Array)
62
- # rubocop:enable Style/ArrayCoercion
82
+ # Find the associated team member without checking case on their e-mail fields.
83
+ def search_team_member_by_email(email)
84
+ return nil unless email
63
85
 
64
- email_addresses.each do |email_address_data|
65
- email_type = email_address_data.dig(:usage_data, :type_data, :type_reference, :id).second
66
- formatted_emails['workEmail'] = email_address_data[:email_address] if email_type == 'WORK'
67
- formatted_emails['homeEmail'] = email_address_data[:email_address] if email_type == 'HOME'
86
+ team_members.find do |team_member|
87
+ emails = [team_member['workEmail'], team_member['bestEmail'], team_member['homeEmail']]
88
+ emails.compact.any? { |match| email.casecmp(match) === 0 }
68
89
  end
90
+ end
91
+ alias_method :slack_email_lookup_with_fallback, :search_team_member_by_email
92
+
93
+ # Find the team member by email and raise error if not found.
94
+ def search_team_member_by_email!(email)
95
+ team_member = search_team_member_by_email(email)
96
+
97
+ return team_member if team_member
98
+
99
+ raise EmployeeNotFoundError, "Could not find team member with the e-mail: #{email}"
100
+ end
101
+ alias_method :slack_email_lookup_with_fallback!, :search_team_member_by_email!
102
+
103
+ def team_members_by_department(department)
104
+ active_and_current_team_members.select { |team_member| team_member['department'] == department }
105
+ end
106
+
107
+ def team_members_by_division(division)
108
+ active_and_current_team_members.select { |team_member| team_member['division'] == division }
109
+ end
110
+
111
+ def fetch_manager(team_member)
112
+ active_team_members.find { |tm| tm['employeeNumber'] == team_member['supervisorId'] }
113
+ end
114
+
115
+ def fetch_manager!(team_member)
116
+ manager = fetch_manager(team_member)
117
+ raise EmployeeNotFoundError, "Manager not found for employee #{team_member['employeeNumber']}" if manager.nil?
118
+
119
+ manager
120
+ end
121
+
122
+ def fetch_second_level_manager(team_member)
123
+ fetch_manager(fetch_manager(team_member))
124
+ end
125
+
126
+ def fetch_second_level_manager!(team_member)
127
+ manager = fetch_second_level_manager(team_member)
128
+ raise EmployeeNotFoundError, "No second level manager found for employee #{team_member['employeeNumber']}" if manager.nil?
129
+
130
+ manager
131
+ end
132
+
133
+ def create_employee(employee_details_hash)
134
+ @bamboo.create_employee(employee_details_hash)
135
+ end
136
+ alias_method :create_team_member, :create_employee
137
+
138
+ def update_employee(employee_number, employee_details_hash)
139
+ # Only used in activate_team_members.rb, which is not needed in Workday
140
+ @bamboo.update_employee(employee_number, employee_details_hash)
141
+ end
142
+ alias_method :update_team_member, :update_employee
143
+
144
+ def locations
145
+ @workday.locations
146
+ end
147
+
148
+ def add_stock_options(employee_number, data)
149
+ @bamboo.add_stock_options(employee_number, data)
150
+ end
151
+
152
+ def add_job_details(employee_number, data)
153
+ @bamboo.add_job_details(employee_number, data)
154
+ end
155
+
156
+ def update_job_details(employee_number, data)
157
+ @bamboo.update_job_details(employee_number, data)
158
+ end
159
+
160
+ def add_compensation_details(employee_number, data)
161
+ @bamboo.add_compensation_details(employee_number, data)
162
+ end
163
+
164
+ def employment_statuses(employee_number)
165
+ # Terminated/Active in one field already. Those are the statuses we need to care about
166
+ # Look for a hire date that is after the start date (not original hire date) to account for rehires
167
+ # Affected: joining.rb
168
+
169
+ # Probation period, CXC contract ending, temporary contract ending won't be needed
170
+ # We can ignore garden leave (terminate access to systems without pausing benefits/stock vesting)
171
+ # Affected:
172
+ # - anniversary.rb
173
+ # - cxc_contract_renewal_email.rb
174
+ # - extension_netherlands_contract_email.rb
175
+ # - probation_email.rb
176
+ # - probation_status.rb
177
+
178
+ raise NotImplementedError
179
+ end
180
+
181
+ def time_off_type(name)
182
+ # Staying in bamboo for workday first release
183
+ # Will need to have talks with pto by roots admin
184
+ @bamboo.time_off_type(name)
185
+ end
186
+
187
+ def time_off_adjustment(employee_number, options)
188
+ # Staying in bamboo for workday first release
189
+ # Will need to have talks with pto by roots admin
190
+ @bamboo.time_off_adjustment(employee_number, options)
191
+ end
192
+
193
+ def accrued_days(employee_number)
194
+ # Staying in bamboo for workday first release
195
+ # Will need to have talks with pto by roots admin
196
+ @bamboo.accrued_days(employee_number)
197
+ end
198
+
199
+ def time_off_policies
200
+ @bamboo.time_off_policies
201
+ end
202
+
203
+ def employee_time_off_policies(employee_number)
204
+ @bamboo.employee_time_off_policies(employee_number)
205
+ end
206
+ alias_method :team_member_time_off_policies, :employee_time_off_policies
207
+
208
+ def add_time_off_policy(employee_number, time_off_policy_id, accrual_start_date)
209
+ @bamboo.add_time_off_policy(employee_number, time_off_policy_id, accrual_start_date)
210
+ end
211
+
212
+ def remove_time_off_policy(employee_number, time_off_policy_id)
213
+ @bamboo.remove_time_off_policy(employee_number, time_off_policy_id)
214
+ end
215
+
216
+ def job_details(employee_number)
217
+ @workday.manager_history(employee_number)
218
+ end
219
+
220
+ def resumes_folder_id(employee_number)
221
+ @bamboo.resumes_folder_id(employee_number)
222
+ end
223
+
224
+ def contract_folder_id(employee_number)
225
+ @bamboo.contract_folder_id(employee_number)
226
+ end
227
+
228
+ def add_file(employee_number, file_name, file, folder_id)
229
+ @bamboo.add_file(employee_number, file_name, file, folder_id)
230
+ end
231
+
232
+ def add_employment_status(employee_number, data)
233
+ # Only used in parental_pto_to_bamboo.rb
234
+ # That integration adds the following statuses: Parental Leave, End of Parental Leave, Active
235
+ # Asked what to do about this one on Slack: https://gitlab.slack.com/archives/C02FHJ9BYTZ/p1650666853127429
236
+
237
+ raise NotImplementedError
238
+ end
239
+
240
+ def add_currency_conversion(employee_number, data)
241
+ @bamboo.add_currency_conversion(employee_number, data)
242
+ end
243
+
244
+ def add_on_target_earnings(employee_number, data)
245
+ @bamboo.add_on_target_earnings(employee_number, data)
246
+ end
247
+
248
+ def add_signing_bonus(employee_number, data)
249
+ @bamboo.add_signing_bonus(employee_number, data)
250
+ end
251
+
252
+ def add_family_member(employee_number, data)
253
+ @bamboo.add_family_member(employee_number, data)
254
+ end
255
+
256
+ def add_additional_data(employee_number, data)
257
+ @bamboo.add_additional_data(employee_number, data)
258
+ end
259
+
260
+ def additional_data(employee_number)
261
+ @bamboo.additional_data(employee_number)
262
+ end
263
+
264
+ def add_bonus(employee_number, comment)
265
+ @workday.add_bonus(employee_number, comment)
266
+ end
69
267
 
70
- formatted_emails
268
+ def departments
269
+ @workday.departments
71
270
  end
72
271
  end
73
272
  end
@@ -0,0 +1,92 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'json'
4
+
5
+ module PeopleGroup
6
+ module Connectors
7
+ module Models
8
+ # Turns a hash into a Ruby object.
9
+ # This will assign the key of the hash as an instance method, and the value as the return value.
10
+ # This will also convert any nested hashes into another ObjectifiedHash.
11
+ class ObjectifiedHash
12
+ # The raw hash prior to objectification.
13
+ attr_reader :hash
14
+
15
+ # The data with any objectified children.
16
+ attr_reader :data
17
+
18
+ def initialize(hash)
19
+ @hash = hash.transform_keys(&:to_sym)
20
+ @data = hash.transform_keys(&:to_s)
21
+ hash.each do |key, value|
22
+ if value.is_a?(Hash) && value.size.positive?
23
+ value = self.class.new(value)
24
+ elsif value.is_a?(Array) && value.first.is_a?(Hash)
25
+ value = value.map { |data| self.class.new(data) }
26
+ end
27
+
28
+ instance_variable_set("@#{key}", value)
29
+ self.class.send(:attr_reader, key) # rubocop:disable GitlabSecurity/PublicSend
30
+ @data[key] = value
31
+ end
32
+ end
33
+
34
+ # Mimicks the Hash#dig method.
35
+ # @param [Array] Nested keys of the object.
36
+ # @return The value of the key at the nested location.
37
+ def dig(*args)
38
+ @data.dig(*args)
39
+ end
40
+
41
+ # Create a new instance from an array of Hashes
42
+ def self.from_array(array)
43
+ array.map { |data| new(data) if data.is_a?(Hash) }
44
+ end
45
+
46
+ # Maintains normal json data structure
47
+ def [](key)
48
+ @data[key.to_s]
49
+ end
50
+
51
+ # Allows us to override the json data
52
+ def []=(key, value)
53
+ @data[key.to_s] = value
54
+ end
55
+
56
+ # Return the stored raw hash value.
57
+ def to_h
58
+ @hash
59
+ end
60
+
61
+ # Returns the hash with keys converted to strings.
62
+ def to_json # rubocop:disable Lint/ToJSON
63
+ JSON.parse(hash.to_json)
64
+ end
65
+
66
+ # Returns both the hash and json styled keys.
67
+ def keys
68
+ @keys ||= @hash.keys + @data.keys
69
+ end
70
+
71
+ private
72
+
73
+ # For handling Object.respond_to?(:method)
74
+ def respond_to_missing?(method, include_private = false)
75
+ super
76
+ end
77
+
78
+ # Enforces read only access as well as mimicking the return value of a hash.
79
+ # @param method [Symbol] The method to be called.
80
+ # @param args [Array] The arguments attempted to be passed into the method.
81
+ # @raises [NoMethodError] if the method is attempting to set data on the object.
82
+ # @return nil
83
+ def method_missing(method, *args)
84
+ return raise NoMethodError, "This object is read-only." if method.end_with?('=')
85
+ return nil if method.start_with?('[')
86
+
87
+ raise NoMethodError, "Undefined method `#{method}` called for #{inspect}"
88
+ end
89
+ end
90
+ end
91
+ end
92
+ end
@@ -6,12 +6,8 @@ module PeopleGroup
6
6
  module Connectors
7
7
  class Slack
8
8
  def initialize
9
- ::Slack::Web::Client.configure do |config|
9
+ ::Slack.configure do |config|
10
10
  config.token = ENV['SLACK_API_TOKEN']
11
-
12
- # configure certificate file
13
- # fixed in slack-ruby-client 2.x
14
- config.ca_file = ENV.fetch('SSL_CERT_FILE', '/usr/lib/ssl/certs/ca-certificates.crt')
15
11
  end
16
12
  @client = ::Slack::Web::Client.new
17
13
  end
@@ -2,6 +2,6 @@
2
2
 
3
3
  module PeopleGroup
4
4
  module Connectors
5
- VERSION = '0.2.4'
5
+ VERSION = '0.3.1'
6
6
  end
7
7
  end
@@ -1,87 +1,110 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require 'savon'
4
+ require_rel 'xml'
4
5
 
5
6
  module PeopleGroup
6
7
  module Connectors
7
8
  class Workday
8
- attr_accessor :client, :subdomain, :tenant
9
+ include XML
10
+ attr_accessor :staffing_client, :compensation_client, :subdomain, :tenant
9
11
 
12
+ # Current Workday API Version
10
13
  API_VERSION = 'v37.0'
11
- MAX_RESULTS_PER_PAGE = 999
12
14
 
13
15
  EmployeeNotFoundError = Class.new(StandardError)
14
16
 
15
17
  def initialize
16
18
  @subdomain = ENV['WORKDAY_SUBDOMAIN']
17
19
  @tenant = ENV['WORKDAY_TENANT']
18
- isu_username = ENV['WORKDAY_ISU_USERNAME']
19
- isu_password = ENV['WORKDAY_ISU_PASSWORD']
20
- soap_debug_logs_enabled = ENV['WORKDAY_SOAP_DEBUG_LOGS'].present?
20
+ @api_base_path = "https://#{@subdomain}.workday.com/ccx/service"
21
+ @isu_username = ENV['WORKDAY_ISU_USERNAME']
22
+ @isu_password = ENV['WORKDAY_ISU_PASSWORD']
21
23
 
22
- @client = Savon.client(
23
- wsdl: "https://#{@subdomain}.workday.com/ccx/service/#{@tenant}/Human_Resources/#{API_VERSION}?wsdl",
24
+ soap_debug_logs_enabled = ENV['WORKDAY_SOAP_DEBUG_LOGS'] == 'true'
25
+ options = {
24
26
  log: soap_debug_logs_enabled,
25
- wsse_auth: ["#{isu_username}@#{@tenant}", isu_password],
26
- pretty_print_xml: true,
27
- convert_request_keys_to: :camelcase,
28
- env_namespace: :env,
29
- namespace_identifier: nil,
30
- namespaces: {
31
- 'xmlns' => nil,
32
- 'xmlns:env' => 'http://schemas.xmlsoap.org/soap/envelope/',
33
- 'xmlns:xsd' => 'http://www.w3.org/2001/XMLSchema',
34
- 'xmlns:wsse' => 'http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd'
35
- }
36
- )
27
+ pretty_print_xml: soap_debug_logs_enabled
28
+ }
29
+
30
+ @staffing_client = client('Staffing', options)
31
+ @compensation_client = client('Compensation', options)
37
32
  end
38
33
 
39
34
  def workers
40
- retry_on_error do
41
- @workers ||= auto_paginated_call(:get_workers, { message_tag: 'wd:Get_Workers_Request', attributes: attributes })
42
- end
35
+ @workers ||= safe_report(report_name: ENV['WORKDAY_WORKERS_REPORT'])
43
36
  end
44
37
 
45
- def get_worker(id)
46
- message = {
47
- 'wd:Request_References' => {
48
- 'wd:Worker_Reference' => {
49
- 'wd:ID' => id,
50
- attributes!: {
51
- 'wd:ID' => {
52
- 'wd:type' => 'Employee_ID'
53
- }
54
- }
55
- }
56
- },
57
- attributes!: {
58
- 'wd:Request_References' => {
59
- 'wd:Skip_Non_Existing_Instances' => 'true',
60
- 'wd:Ignore_Invalid_References' => 'true'
61
- }
62
- }
63
- }
64
-
65
- retry_on_error do
66
- auto_paginated_call(:get_workers, { message_tag: 'wd:Get_Workers_Request', message: message, attributes: attributes })
38
+ # Grabs manager history for the specified team member or all team members.
39
+ # @return [Array<PeopleGroup::Connectors::Models::ObjectifiedHash>]
40
+ # keys:
41
+ # Reports_to - Employee_ID of who they were reporting too at this time.
42
+ # Managed_From_Date - When they became the team members manager.
43
+ # Managed_To_Date - The date they were no longer the team members manager. This field is missing if they are the current manager.
44
+ def manager_history(employee_number = nil)
45
+ options = { report_name: ENV['WORKDAY_MANAGER_REPORT'] }
46
+
47
+ if employee_number
48
+ options[:params] = { 'Employee_ID' => employee_number } unless employee_number.nil?
49
+ options[:nested_key] = 'Manager_Change_group'
67
50
  end
51
+
52
+ safe_report(**options)
68
53
  end
69
54
 
70
55
  private
71
56
 
57
+ # A generalized report method for Workday.
58
+ # @param! [String] report_name the name of the report.
59
+ # @param [Hash] params key value parameters for the report.
60
+ # @return [Array<PeopleGroup::Models::ObjectifiedHash>]
61
+ def report(report_name:, params: {}, nested_key: nil)
62
+ url = report_url(report_name, params)
63
+ p "Accessing Report: #{url}" if ENV['WORKDAY_SOAP_DEBUG_LOGS']
64
+ response = retry_on_error { RestClient.get(url, basic_auth_header) }
65
+ data = JSON.parse(response.body)['Report_Entry']
66
+ data = data.first[nested_key] if nested_key.is_a?(String) && data.first[nested_key]
67
+ data.map { |obj| XML::ObjectifiedHash.new obj }
68
+ end
69
+
70
+ # A wrapper for the report logic to handle any Workday specific errors.
71
+ # @param [Hash] args
72
+ def safe_report(**args)
73
+ report(**args)
74
+ rescue StandardError => e
75
+ # TODO add common Workday errors here.
76
+ raise e
77
+ end
78
+
79
+ def report_url(report_name, params)
80
+ options = URI.encode_www_form({ format: 'json', **params })
81
+ name = report_name.tr(' ', '_')
82
+ "#{@api_base_path}/customreport2/#{@tenant}/#{@isu_username}/#{name}?#{options}"
83
+ end
84
+
72
85
  def retry_on_error(&block)
73
86
  Utils.retry_on_error(errors: [Net::ReadTimeout], delay: 3, &block)
74
87
  end
75
88
 
76
- def auto_paginated_call(operation_name, locals = {}, &block)
89
+ def client_from_operation(operation_name)
90
+ return @staffing_client if @staffing_client.operations.include?(operation_name)
91
+
92
+ @compensation_client
93
+ end
94
+
95
+ def call(operation_name, options = {}, &block)
96
+ client = client_from_operation(operation_name)
97
+ response = client.call(operation_name, { **options, **attributes }, &block)
98
+ { **response.body, code: response.http.code }
99
+ end
100
+
101
+ def auto_paginated_call(operation_name, options = {}, &block)
102
+ client = client_from_operation(operation_name)
77
103
  data = nil
78
104
  page = 1
79
105
 
80
106
  loop do
81
- message = locals[:message] || {}
82
- message_with_pagination_parameters = { **message, **pagination_parameters(page) }
83
- response = @client.call(operation_name, { **locals, message: message_with_pagination_parameters }, &block)
84
-
107
+ response = client.call(operation_name, { **options, **attributes }, &block)
85
108
  # The response hash contains only one key named after the operation performed
86
109
  # eg. get_workers_response
87
110
  _operation_response_name, operation_result = response.to_hash.first
@@ -107,20 +130,47 @@ module PeopleGroup
107
130
  data
108
131
  end
109
132
 
110
- def pagination_parameters(page = 1, results_per_page = MAX_RESULTS_PER_PAGE)
111
- {
112
- 'wd:Response_Filter' => {
113
- 'wd:Page' => page,
114
- 'wd:Count' => results_per_page
115
- }
116
- }
133
+ # Default Workday specific body paramaters
134
+ # xmlns:wd - The URN of the XMLNS file
135
+ # wd:version - The API Version of workday
136
+ def attributes
137
+ { attributes: { 'xmlns:wd' => 'urn:com.workday/bsvc', 'wd:version' => API_VERSION } }
117
138
  end
118
139
 
119
- def attributes
120
- {
121
- 'xmlns:wd' => 'urn:com.workday/bsvc',
122
- 'wd:version' => API_VERSION
140
+ # Returns the client based on the service.
141
+ # @param Service [String] The service to use, ie: Staffing, Compensation
142
+ # @param options [Hash] default options for the client.
143
+ #
144
+ # @return [Savon::Client] The client connected to the specified service.
145
+ def client(service, options = {})
146
+ client_opts = {
147
+ log: false,
148
+ wsse_auth: [
149
+ "#{@isu_username}@#{@tenant}",
150
+ @isu_password
151
+ ],
152
+ pretty_print_xml: false,
153
+ convert_request_keys_to: :camelcase,
154
+ env_namespace: :env,
155
+ namespace_identifier: :wd,
156
+ wsdl: "#{@api_base_path}/#{@tenant}/#{service}/#{API_VERSION}?wsdl",
157
+ namespaces: {
158
+ 'xmlns' => nil,
159
+ 'xmlns:env' => 'http://schemas.xmlsoap.org/soap/envelope/',
160
+ 'xmlns:xsd' => 'http://www.w3.org/2001/XMLSchema',
161
+ 'xmlns:wsse' => 'http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd'
162
+ },
163
+ **options
123
164
  }
165
+
166
+ Savon.client(**client_opts)
167
+ end
168
+
169
+ def basic_auth_header
170
+ return @basic_auth_header unless @basic_auth_header.nil?
171
+
172
+ base_64 = Base64.strict_encode64("#{@isu_username}:#{@isu_password}")
173
+ @basic_auth_header = { Authorization: "Basic #{base_64}" }
124
174
  end
125
175
  end
126
176
  end
@@ -0,0 +1,84 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_rel 'helpers'
4
+
5
+ module PeopleGroup
6
+ module Connectors
7
+ module XML
8
+ # https://community.workday.com/sites/default/files/file-hosting/productionapi/Staffing/v37.2/Get_Organizations.html
9
+ module GetOrganizations
10
+ include Helpers
11
+
12
+ # The Operation for this Module
13
+ OPERATION = :get_organizations
14
+
15
+ # List all of the active departments.
16
+ def departments
17
+ get('Cost_Center')
18
+ end
19
+
20
+ # List all of the active regions.
21
+ def regions
22
+ get('Region')
23
+ end
24
+
25
+ # List all of the active entities.
26
+ def locations
27
+ @locations ||= get('Company').map(&:reference_id)
28
+ end
29
+
30
+ # List all of the active pay groups.
31
+ def pay_groups
32
+ get('Pay_Group')
33
+ end
34
+
35
+ private
36
+
37
+ # Call the specified endpoint.
38
+ # @param type [String] The type of organization to request.
39
+ # @return [Array<PeopleGroup::Connectors::Models::ObjectifiedHash>] An array of the requested organzation types resources.
40
+ def get(type, additional = {})
41
+ options = {
42
+ **org_request_criteria_by_type(type),
43
+ **Helpers.default_response_group,
44
+ **Helpers.pagination_parameters,
45
+ **additional
46
+ }
47
+
48
+ request = Helpers.construct_options(OPERATION, options)
49
+ auto_paginated_call(OPERATION, request).map do |data|
50
+ ObjectifiedHash.new data[:organization_data]
51
+ end
52
+ end
53
+
54
+ # Get the Request_Criteria via type
55
+ # @param type [String] Company, Cost Center, Pay Group, Region
56
+ #
57
+ # @return [Request_Criteria] The Organiztion_Request_Criteria for the specified type.
58
+ def org_request_criteria_by_type(type)
59
+ org_criteria = organization_request_criteria(type: type)
60
+ Helpers.request_criteria(org_criteria)
61
+ end
62
+
63
+ # Get the Organization_Request_Criteria as a hash.
64
+ # @param type [String] The type of Organization to perform the search on.
65
+ # @param include_inactive [boolean] Whether to include inactive organizations, defaults to false.
66
+ # @param field_and_param_data [Hash] A Hash containing the key value pair of Field and Parameter Criteria Data.
67
+ def organization_request_criteria(type:, include_inactive: false, field_and_param_data: nil)
68
+ hash = {
69
+ 'Organization_Type_Reference' => {
70
+ 'ID' => type,
71
+ :attributes! => { 'ID' => { 'wd:type' => 'Organization_Type_ID' } }
72
+ },
73
+ 'Include_Inactive' => include_inactive
74
+ }
75
+
76
+ # Only add parameter criteria if present.
77
+ hash['Field_And_Parameter_Criteria_Data'] = field_and_param_data unless field_and_param_data.nil?
78
+
79
+ hash
80
+ end
81
+ end
82
+ end
83
+ end
84
+ end
@@ -0,0 +1,72 @@
1
+ # frozen_string_literal: true
2
+
3
+ module PeopleGroup
4
+ module Connectors
5
+ module XML
6
+ module Helpers
7
+ MAX_RESULTS_PER_PAGE = 999
8
+
9
+ def self.request_references(refs = {})
10
+ {
11
+ 'Request_References' => refs
12
+ }
13
+ end
14
+
15
+ def self.response_filter(filters = {})
16
+ {
17
+ 'Response_Filter' => filters
18
+ }
19
+ end
20
+
21
+ # Default Request Criteria XML Wrapper
22
+ # @param criteria [Hash] A list of key value pair XML attributes.
23
+ #
24
+ # @return [Hash] - The <wd:Request_Criteria> XML Component containing a set of options.
25
+ def self.request_criteria(criteria = {})
26
+ {
27
+ 'Request_Criteria' => criteria
28
+ }
29
+ end
30
+
31
+ def self.response_group(group = {})
32
+ { 'Response_Group' => group }
33
+ end
34
+
35
+ def self.default_response_group(additional = {})
36
+ groups = { 'Include_Hierarchy_Data' => false, **additional }
37
+ response_group groups
38
+ end
39
+
40
+ # Contructs a hash that we can pass into the #call Savon client method.
41
+ def self.construct_options(operation, message)
42
+ operation = operation.to_s.split('_').collect(&:capitalize).join('_') unless operation.is_a?(String)
43
+ {
44
+ message_tag: "#{operation}_Request",
45
+ message: message
46
+ }
47
+ end
48
+
49
+ def self.pagination_parameters(page = 1, results_per_page = MAX_RESULTS_PER_PAGE)
50
+ response_filter(
51
+ {
52
+ 'Page' => page,
53
+ 'Count' => results_per_page
54
+ }
55
+ )
56
+ end
57
+
58
+ # The <wd:Employee_Reference> object wrapper.
59
+ # @param id [Integer] The employee number of the team member.
60
+ # @return [Hash] The <wd:Employee_Reference> XML wrapper component.
61
+ def self.team_member_reference(id)
62
+ {
63
+ 'Employee_Reference' => {
64
+ 'ID' => id,
65
+ attributes!: { 'ID' => { 'wd:type' => 'Employee_ID' } }
66
+ }
67
+ }
68
+ end
69
+ end
70
+ end
71
+ end
72
+ end
@@ -0,0 +1,103 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_rel 'helpers'
4
+
5
+ module PeopleGroup
6
+ module Connectors
7
+ module XML
8
+ # https://community.workday.com/sites/default/files/file-hosting/productionapi/Compensation/v37.0/Request_One-Time_Payment.html
9
+ module RequestOneTimePayment
10
+ include Helpers
11
+
12
+ # The Operation for this Module
13
+ OPERATION = :request_one_time_payment
14
+ OPERATION_AS_PARAM = 'Request_One-Time_Payment'
15
+
16
+ # The amount in USD to give for bonuses, must be a decimal.
17
+ DISCRETIONARY_BONUS = 1000.0
18
+
19
+ # Adds a bonus to the team members
20
+ # @param employee_number [Integer] The Employee Number of the team member receiving the bonus.
21
+ # @param comment [String] The comment to leave on the business process.
22
+ def add_bonus(employee_number, comment)
23
+ options = {
24
+ **business_process_params(comment),
25
+ **one_time_payment_data(employee_number, comment)
26
+ }
27
+
28
+ request = Helpers.construct_options(OPERATION_AS_PARAM, options)
29
+ call(OPERATION, request)
30
+ end
31
+
32
+ private
33
+
34
+ # https://community.workday.com/sites/default/files/file-hosting/productionapi/Compensation/v37.0/Request_One-Time_Payment.html#Business_Process_ParametersType
35
+ # @param comment [String] The comment associated with the bonus.
36
+ # @param run_now: [Boolean] Defaults to true to process this transaction immediatley.
37
+ # @param auto_complete: [Boolean] Defaults to true to apply this bonus without needing approval.
38
+ #
39
+ # @return [Hash] The <wd:Business_Process_Parameters> for this request.
40
+ def business_process_params(comment, run_now: true, auto_complete: true)
41
+ {
42
+ 'Business_Process_Parameters' => {
43
+ 'Auto_Complete' => auto_complete,
44
+ 'Run_Now' => run_now,
45
+
46
+ # Comment Data
47
+ # https://community.workday.com/sites/default/files/file-hosting/productionapi/Compensation/v37.0/Request_One-Time_Payment.html#Business_Process_Comment_DataType
48
+ 'Comment_Data' => {
49
+ 'Comment' => comment
50
+ }
51
+ }
52
+ }
53
+ end
54
+
55
+ # The Effective Date parameter for when to apply the payment, defaults to today.
56
+ # @param date [Date] The day to apply the payment.
57
+ # @return [Hash] The <wd:Effective_Date> parameter set to the specified date.
58
+ def effective_date(date = Date.today)
59
+ {
60
+ 'Effective_Date' => date.to_s
61
+ }
62
+ end
63
+
64
+ # The payment specific data for what should be applied.
65
+ # @param comment [String] The comment for the discretionary bonus.
66
+ # @param amount [Double] The amount to include in the bonus with decimal accuracy.
67
+ def one_time_payment_sub_data(comment, amount = DISCRETIONARY_BONUS)
68
+ {
69
+ 'One-Time_Payment_Sub_Data' => {
70
+ **payment_plan_reference,
71
+ 'Amount' => amount,
72
+ 'Comment' => comment
73
+ }
74
+ }
75
+ end
76
+
77
+ # The type of one time payment we want to group this under.
78
+ def payment_plan_reference
79
+ {
80
+ 'One_Time_Payment_Plan_Reference' => {
81
+ 'ID' => 'Discretionary Bonus',
82
+ attributes!: { 'ID' => { 'wd:type' => 'One-Time_Payment_Plan_ID' } }
83
+ }
84
+ }
85
+ end
86
+
87
+ # https://community.workday.com/sites/default/files/file-hosting/productionapi/Compensation/v37.0/Request_One-Time_Payment.html#Request_One-Time_Payment_DataType
88
+ # @param employee_number [Integer] The team member's employee number.
89
+ # @param comment [String] A comment describing why the payment is being requested.
90
+ # @return [Hash] The <wd:One-Time_Payment_Data> component.
91
+ def one_time_payment_data(employee_number, comment)
92
+ {
93
+ 'One-Time_Payment_Data' => {
94
+ **Helpers.team_member_reference(employee_number),
95
+ **effective_date,
96
+ **one_time_payment_sub_data(comment)
97
+ }
98
+ }
99
+ end
100
+ end
101
+ end
102
+ end
103
+ end
@@ -0,0 +1,14 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_rel 'xml'
4
+ require_rel 'models/objectified_hash'
5
+
6
+ module PeopleGroup::Connectors
7
+ module XML
8
+ include GetOrganizations
9
+ include RequestOneTimePayment
10
+
11
+ # Rename Objectified hash for ease of use.
12
+ ObjectifiedHash = PeopleGroup::Connectors::Models::ObjectifiedHash
13
+ end
14
+ end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: peoplegroup-connectors
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.4
4
+ version: 0.3.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - lien van den steen
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2024-04-02 00:00:00.000000000 Z
11
+ date: 2022-06-08 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: gitlab
@@ -94,6 +94,20 @@ dependencies:
94
94
  - - "~>"
95
95
  - !ruby/object:Gem::Version
96
96
  version: '2.5'
97
+ - !ruby/object:Gem::Dependency
98
+ name: rest-client
99
+ requirement: !ruby/object:Gem::Requirement
100
+ requirements:
101
+ - - "~>"
102
+ - !ruby/object:Gem::Version
103
+ version: '2.1'
104
+ type: :runtime
105
+ prerelease: false
106
+ version_requirements: !ruby/object:Gem::Requirement
107
+ requirements:
108
+ - - "~>"
109
+ - !ruby/object:Gem::Version
110
+ version: '2.1'
97
111
  - !ruby/object:Gem::Dependency
98
112
  name: savon
99
113
  requirement: !ruby/object:Gem::Requirement
@@ -234,6 +248,20 @@ dependencies:
234
248
  - - "~>"
235
249
  - !ruby/object:Gem::Version
236
250
  version: '0.21'
251
+ - !ruby/object:Gem::Dependency
252
+ name: simplecov-cobertura
253
+ requirement: !ruby/object:Gem::Requirement
254
+ requirements:
255
+ - - "~>"
256
+ - !ruby/object:Gem::Version
257
+ version: '2.1'
258
+ type: :development
259
+ prerelease: false
260
+ version_requirements: !ruby/object:Gem::Requirement
261
+ requirements:
262
+ - - "~>"
263
+ - !ruby/object:Gem::Version
264
+ version: '2.1'
237
265
  - !ruby/object:Gem::Dependency
238
266
  name: byebug
239
267
  requirement: !ruby/object:Gem::Requirement
@@ -263,10 +291,15 @@ files:
263
291
  - lib/peoplegroup/connectors/google_sheets.rb
264
292
  - lib/peoplegroup/connectors/greenhouse.rb
265
293
  - lib/peoplegroup/connectors/hris.rb
294
+ - lib/peoplegroup/connectors/models/objectified_hash.rb
266
295
  - lib/peoplegroup/connectors/pto_roots.rb
267
296
  - lib/peoplegroup/connectors/slack.rb
268
297
  - lib/peoplegroup/connectors/version.rb
269
298
  - lib/peoplegroup/connectors/workday.rb
299
+ - lib/peoplegroup/connectors/xml.rb
300
+ - lib/peoplegroup/connectors/xml/get_organizations.rb
301
+ - lib/peoplegroup/connectors/xml/helpers.rb
302
+ - lib/peoplegroup/connectors/xml/request_one_time_payment.rb
270
303
  - lib/peoplegroup/utils.rb
271
304
  homepage: https://gitlab.com/gitlab-com/people-group/peopleops-eng/connectors-gem
272
305
  licenses:
@@ -283,14 +316,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
283
316
  requirements:
284
317
  - - ">="
285
318
  - !ruby/object:Gem::Version
286
- version: '2.3'
319
+ version: '3.0'
287
320
  required_rubygems_version: !ruby/object:Gem::Requirement
288
321
  requirements:
289
322
  - - ">="
290
323
  - !ruby/object:Gem::Version
291
324
  version: '0'
292
325
  requirements: []
293
- rubygems_version: 3.4.19
326
+ rubygems_version: 3.2.33
294
327
  signing_key:
295
328
  specification_version: 4
296
329
  summary: Library for our shared connectors.