peoplegroup-connectors 1.0.5 → 2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 71281e48df14d934fff4a9f440e44a53aa416082080afa02735e7675de8359a7
4
- data.tar.gz: e8dec527298a0a87e8e7c2f0524ecd9c691a944c610c7ac66dd3fe638fbeb6ab
3
+ metadata.gz: 561d0cf562f003fd466dd9b9bec4494308228b046ea7e6d1af0a65f44c296acb
4
+ data.tar.gz: c1faf7bfa314dfb8bbbfe36aad665140aa6f357e97efa01a8e378d68e8ef6be8
5
5
  SHA512:
6
- metadata.gz: ec8af3c1b8606214ac38b402eaa60d2467f8201fb3b27ada5a0d4e6e574ca43b8169b807b0d4ced02b9e57d8fb6ce0a4dd194b91b8791cbbfc4b8cd6a7f410f7
7
- data.tar.gz: 65c30b6577487e88dd7a6eb78bfd88bf4528db9eb660a1cd1f54cdf00e9a00176e77d5cbebc9c150dead33cafe7b593b378d238c1ebe354e6e5ecc3f7abebbd8
6
+ metadata.gz: ba0f3f958eabdc082033844c7e3341298606d901333bdd46f6e452e04195ccde1809bc8b388453c4400b60b41febb1ab7e2763ab57c3208ba16b14b86a404fdf
7
+ data.tar.gz: 331d056805178a2335b47a9b4768295da89c4ff403c019edaf5c3bea452cbf6aa5d415a5701350401ff5dacdd0b5057b66ed0ac6d2260461e589092accdccec0
data/README.md CHANGED
@@ -37,6 +37,16 @@ To install this gem onto your local machine, run `bundle exec rake install`. To
37
37
 
38
38
  For local development and manual testing, copy the contents of the `.env.example` file to a new file called `.env` and fill-in the environment variable values. You will then be able to run a REPL by running `bundle exec bin/console` from the root of the repository.
39
39
 
40
+ ### Documentation
41
+
42
+ To view documentation for the current release. You can navigate to https://gemdocs.org/gems/peoplegroup-connectors/latest to view the latest documentation or https://gemdocs.org/gems/peoplegroup-connectors to select documentation for particular tags.
43
+
44
+ If you are developing locally, you can preview documentation changes by starting a yard server in the background:
45
+
46
+ ```console
47
+ yard server --reload
48
+ ```
49
+
40
50
  ## Contributing and Code of Conduct
41
51
 
42
52
  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.
@@ -5,18 +5,28 @@ require 'gitlab'
5
5
  module PeopleGroup
6
6
  module Connectors
7
7
  class GitLab
8
+ # The Gitlab.client instance we wrap around.
8
9
  attr_accessor :client
9
10
 
11
+ # The GitLab instance API url.
10
12
  API_URL = ENV['GITLAB_API_V4_URL'] || 'https://gitlab.com/api/v4'
11
13
 
14
+ # Create a new client instance with the provided token for authentication.
15
+ # @param token [String] The personal access token to use for authentication.
12
16
  def initialize(token: ENV['GITLAB_API_TOKEN'])
13
17
  @client = Gitlab.client(endpoint: API_URL, private_token: token)
14
18
  end
15
19
 
20
+ # Serach for a GitLab user under the gitlab-com group.
16
21
  def find_gitlabber(field, query)
17
22
  find_gitlabber_on(field, query, 'gitlab-com')
18
23
  end
19
24
 
25
+ # Finds a GitLab user by the field.
26
+ # @param field [Symbol] The field to search for.
27
+ # @param query [String] The query to search for.
28
+ # @param group [String|Integer] The group to search in.
29
+ # @return [Gitlab::ObjectifiedHash] The found GitLab user if any.
20
30
  def find_gitlabber_on(field, query, group)
21
31
  return if !query || query.empty?
22
32
 
@@ -28,55 +38,117 @@ module PeopleGroup
28
38
  end
29
39
  end
30
40
 
41
+ # Creates a GitLab group.
42
+ # @param name [String] The name of the group.
43
+ # @param path [String] The path of the group.
44
+ # @param options [Hash] The options to create the group with.
45
+ # @return [Gitlab::ObjectifiedHash] The created group.
46
+ # @see https://docs.gitlab.com/ee/api/groups.html#create-a-group
31
47
  def create_group(name, path, options = {})
32
48
  retry_on_error { @client.create_group(name, path, options) }
33
49
  end
34
50
 
51
+ # Get subgroups of a group.
52
+ # @param group_id [Integer|String] the id of the group to get the subgroups of.
53
+ # @param options [Hash] the options to get the subgroups with.
54
+ # @return [Array<Gitlab::ObjectifiedHash>] the subgroups of the group.
55
+ # @see https://docs.gitlab.com/ee/api/groups.html#list-subgroups
35
56
  def get_subgroups(group_id, options = {})
36
57
  retry_on_error { @client.group_subgroups(group_id, options).auto_paginate }
37
58
  end
38
59
 
60
+ # Get members of a group.
61
+ # @param group_id [Integer|String] the id of the group to get the members of.
62
+ # @param options [Hash] the options to get the members with.
63
+ # @return [Array<Gitlab::ObjectifiedHash>] the members of the group.
64
+ # @see https://docs.gitlab.com/ee/api/groups.html#list-users
39
65
  def get_group_members(group_id, options = {})
40
66
  retry_on_error { @client.group_members(group_id, options).auto_paginate }
41
67
  end
42
68
 
69
+ # Add a group member with a set access level.
70
+ # @param group_id [Integer|String] the id of the group to add the member to.
71
+ # @param user_id [Integer|String] the id of the user to add to the group.
72
+ # @param access_level [Integer] the access level to add the user with.
73
+ # @return [Gitlab::ObjectifiedHash] the created group member.
74
+ # @see https://docs.gitlab.com/ee/api/members.html#add-a-member-to-a-group-or-project
43
75
  def add_group_member(group_id, user_id, access_level)
44
76
  retry_on_error { @client.add_group_member(group_id, user_id, access_level) }
45
77
  end
46
78
 
79
+ # Remove a group member
80
+ # @param group_id [Integer|String] the id of the group to remove the member from.
81
+ # @param user_id [Integer|String] the id of the user to remove from the group.
47
82
  def remove_group_member(group_id, user_id)
48
83
  retry_on_error { @client.remove_group_member(group_id, user_id) }
49
84
  end
50
85
 
86
+ # Get a list of a groups user ids
87
+ # @param group_id [Integer|String] the id of the group to get the members of.
88
+ # @return [Array<Integer>] the user ids of the members of the group.
51
89
  def group_member_ids(group_id)
52
90
  members = retry_on_error { @client.group_members(group_id) }
53
91
  members.map(&:id)
54
92
  end
55
93
 
56
- def create_issue(project, title, options = {})
94
+ # Create a new issue.
95
+ # @param project [Integer|String] the id of the project to create the issue in.
96
+ # @param title [String] the title of the issue.
97
+ # @param options [Hash] the options to create the issue with.
98
+ # @return [Gitlab::ObjectifiedHash] the created issue.
99
+ # @see https://docs.gitlab.com/ee/api/issues.html#new-issue
100
+ # @example
101
+ # gitlab = PeopleGroup::Connectors::GitLab.new
102
+ # options = { description: 'This is a new issue', labels: ['bug'] }
103
+ # gitlab.create_issue(123, 'New issue', options)
104
+ def create_issue(project, title, options)
57
105
  retry_on_error { @client.create_issue(project, title, options) }
58
106
  end
59
107
 
108
+ # Create a note on an issue
109
+ # @param project [Integer|String] the id of the project to create the note in.
110
+ # @param id [Integer|String] the id of the issue to create the note on.
111
+ # @param text [String] the text of the note.
112
+ # @return [Gitlab::ObjectifiedHash] the created issue note.
113
+ # @example
114
+ # gitlab = PeopleGroup::Connectors::GitLab.new
115
+ # gitlab.create_issue_note(123, 456, 'This is a new note')
60
116
  def create_issue_note(project, id, text)
61
117
  retry_on_error { @client.create_issue_note(project, id, text) }
62
118
  end
63
119
 
120
+ # List notes on an issue.
121
+ # @param project [Integer|String] the id of the project to list the notes from.
122
+ # @param id [Integer|String] the id of the issue to list the notes from.
123
+ # @param options [Hash] the options to list the notes with.
124
+ # @return [Array<Gitlab::ObjectifiedHash>] a list of notes associated to the issue.
64
125
  def issue_notes(project, id, options = {})
65
126
  retry_on_error { @client.issue_notes(project, id, options) }
66
127
  end
67
128
 
129
+ # Edit an issue.
130
+ # @param project [Integer|String] the id of the project to edit the issue in.
131
+ # @param id [Integer|String] the id of the issue to edit.
132
+ # @param options [Hash] the options to edit the issue with.
133
+ # @return [Gitlab::ObjectifiedHash] the updated issue.
68
134
  def edit_issue(project, id, options)
69
135
  retry_on_error { @client.edit_issue(project, id, options) }
70
136
  end
71
137
 
72
- def get_onboarding_issues(project, args)
73
- retry_on_error { @client.issues(project, args).auto_paginate }
74
- end
75
-
138
+ # Get a list of issues from a project.
139
+ # @param project [Integer|String] the id of the project to search for the issue in.
140
+ # @param args [Hash] the options to search for the issue with.
141
+ # @return [Array<Gitlab::ObjectifiedHash>] the list of issues matching the search criteria.
142
+ # @see https://docs.gitlab.com/ee/api/issues.html#list-project-issues
76
143
  def get_issues(project, args)
77
144
  retry_on_error { @client.issues(project, args).auto_paginate }
78
145
  end
146
+ alias_method :get_onboarding_issues, :get_issues
79
147
 
148
+ # Gets an issue from the associated project.
149
+ # @param project [Integer|String] the id of the project to search for the issue in.
150
+ # @param issue_id [Integer|String] the id of the issue to get.
151
+ # @return [Gitlab::ObjectifiedHash] the issue object.
80
152
  def get_issue(project, issue_id)
81
153
  retry_on_error { @client.issue(project, issue_id) }
82
154
  end
@@ -85,6 +157,11 @@ module PeopleGroup
85
157
  retry_on_error { @client.update_variable(project, key, value, **opts) }
86
158
  end
87
159
 
160
+ # Find or create an epic by title.
161
+ # @param group_id [Integer|String] the id of the group to search for the epic in.
162
+ # @param title [String] the title of the epic.
163
+ # @param options [Hash] the options to create the epic with.
164
+ # @return [Gitlab::ObjectifiedHash] the created or found epic.
88
165
  def find_or_create_epic(group_id, title, options = {})
89
166
  epic = find_epic(group_id, title)
90
167
  reopen_epic(epic) if epic && epic.state == 'closed'
@@ -216,28 +293,75 @@ module PeopleGroup
216
293
  create_merge_request(project_id, commit_message, options)
217
294
  end
218
295
 
296
+ # Create an epic for a group.
297
+ # @param group_id [Integer|String] the id of the group to create the epic for.
298
+ # @param title [String] the title of the epic.
299
+ # @param options [Hash] the options to create the epic with.
300
+ # @return [Gitlab::ObjectifiedHash] the created epic.
301
+ # @see https://docs.gitlab.com/ee/api/epics.html#new-epic
219
302
  def create_epic(group_id, title, options)
220
303
  retry_on_error { @client.create_epic(group_id, title, options) }
221
304
  end
222
305
 
306
+ # Create a note on an epic.
307
+ # @param group_id [Integer|String] the id of the group to search for the epic in.
308
+ # @param epic_id [Integer|String] the id of the epic to create the note for.
309
+ # @param text [String] the text of the note.
223
310
  def create_epic_note(group_id, epic_id, text)
224
311
  retry_on_error { @client.create_epic_note(group_id, epic_id, text) }
225
312
  end
226
313
 
314
+ # Get all epics of a specific group.
315
+ # @param group_id [Integer|String] the id of the group to search for the epics in.
316
+ # @param options [Hash] the options to search for the epics with.
317
+ # @return [Array<Gitlab::ObjectifiedHash>] the list of epics associated
318
+ # @see https://docs.gitlab.com/ee/api/epics.html#list-epics-for-a-group
227
319
  def get_epics(group_id, options = {})
228
320
  retry_on_error { @client.epics(group_id, options).auto_paginate }
229
321
  end
230
322
 
323
+ # Get the the issues for a specific epic.
324
+ # @param group_id [Integer|String] the id of the group to search for the epic in.
325
+ # @param epic_iid [Integer|String] the iid of the epic to search for the issues of.
326
+ # @return [Array<Gitlab::ObjectifiedHash>] the list of issues associated with the epic.
327
+ # @see https://docs.gitlab.com/ee/api/epics.html#get-a-list-of-epic-issues
231
328
  def get_issue_epics(group_id, epic_iid)
232
329
  retry_on_error { @client.epic_issues(group_id, epic_iid) }
233
330
  end
234
331
 
332
+ # Get the file at a specific path in a specific project.
333
+ # @param project_id [Integer|String] the id of the project to search for the file in.
334
+ # @param file_path [String] the files path relative to the project root.
335
+ # @param ref [String] the branch or tag name, or the commit SHA, to retrieve the file from.
336
+ # @return [Gitlab::ObjectifiedHash] The get repository file response.
337
+ # @see https://docs.gitlab.com/ee/api/repository_files.html#get-file-from-repository
338
+ # @example
339
+ # client = PeopleGroup::Connectors::GitLab.new
340
+ # client.get_file(00000, 'path/to/file.txt')
341
+ def get_file(project_id, file_path, ref = 'main')
342
+ retry_on_error { @client.get_file(project_id, file_path, ref) }
343
+ end
344
+
345
+ # Get the contents of the specified file at a specific path in a project.
346
+ # @param project_id [Integer|String] the id of the project to search for the file in.
347
+ # @param file_path [String] the file path relative to the project root.
348
+ # @param ref [String] the branch or tag name, or the commit SHA, to retrieve the file from.
349
+ # @return [String] The contents of the file.
350
+ # @example
351
+ # client = PeopleGroup::Connectors::GitLab.new
352
+ # client.get_file_contents(00000, 'path/to/file.txt')
353
+ def get_file_contents(project_id, file_path, ref = 'main')
354
+ response = get_file(project_id, file_path, ref)
355
+ Base64.decode64(response&.content)
356
+ end
357
+
235
358
  private
236
359
 
237
360
  def retry_on_error(&block)
238
361
  Utils.retry_on_error(errors: retry_errors, delay: 3, &block)
239
362
  end
240
363
 
364
+ # Common errors that we can retry.
241
365
  def retry_errors
242
366
  [Gitlab::Error::InternalServerError, Gitlab::Error::BadGateway, Gitlab::Error::ServiceUnavailable]
243
367
  end
@@ -5,10 +5,17 @@ require 'google_drive'
5
5
  module PeopleGroup
6
6
  module Connectors
7
7
  class GoogleSheets
8
+ # Find a spreadsheet by title.
9
+ # @param title [String] the title of the spreadsheet, must be exact and shared with the service account.
10
+ # @return [GoogleDrive::Spreadsheet|nil] the found spreadsheet or nil if no matches were found.
8
11
  def spreadsheet(title)
9
12
  retry_on_error { session.spreadsheet_by_title(title) }
10
13
  end
11
14
 
15
+ # Find a worksheet by title within a given spreadsheet.
16
+ # @param spreadsheet_title [String] the title of the spreadsheet, must be exact and shared with the service account.
17
+ # @param worksheet_title [String] the title of the worksheet inside the spreadsheet, must be an exact match.
18
+ # @return [GoogleDrive::Worksheet|nil] the found worksheet or nil if no matches were found.
12
19
  def worksheet(spreadsheet_title, worksheet_title)
13
20
  worksheets = retry_on_error { spreadsheet(spreadsheet_title).worksheets }
14
21
  worksheets.find { |worksheet| worksheet.title == worksheet_title }
@@ -5,28 +5,60 @@ require 'greenhouse_io'
5
5
  module PeopleGroup
6
6
  module Connectors
7
7
  class Greenhouse
8
+ # Maximum amount of retries for requests.
8
9
  MAX_RETRIES = 3
9
10
 
11
+ # Error to retry on.
12
+ RETRY_ERRORS = [
13
+ GreenhouseIo::Error,
14
+ Net::OpenTimeout,
15
+ RestClient::InternalServerError
16
+ ].freeze
17
+
18
+ # Time to delay inbetween retries.
19
+ RETRY_DELAY = 3
20
+
21
+ # Create a new Greenhouse client instance.
22
+ # @param use_users_api_key [Boolean] Whether to use the users API key or the default one.
23
+ # @return [PeopleGroup::Connectors::Greenhouse]
24
+ # @example
25
+ # client = PeopleGroup::Connectors::Greenhouse.new
10
26
  def initialize(use_users_api_key: false)
11
27
  api_key = use_users_api_key ? ENV['GREENHOUSE_API_KEY_USERS'] : ENV['GREENHOUSE_API_KEY']
12
28
  @client = GreenhouseIo::Client.new(api_key)
13
29
  end
14
30
 
31
+ # List all offers for an application.
32
+ # @param application_id [Integer|String] The id of the application to get the offers for.
33
+ # @return [Array<Hash>] The offers for the application.
34
+ # @see https://developers.greenhouse.io/harvest.html#get-list-offers-for-application
15
35
  def offer_for_application(application_id)
16
- @client.offers_for_application(application_id)
36
+ retry_on_error { @client.offers_for_application(application_id) }
17
37
  end
18
-
38
+ alias_method :offers_for_application, :offer_for_application
39
+
40
+ # Get the current offer of an application.
41
+ # @param application_id [Integer|String] The id of the application to get the offer for.
42
+ # @return [Hash] The current offer for the application.
43
+ # @example
44
+ # client = PeopleGroup::Connectors::Greenhouse.new
45
+ # client.current_offer_for_application(123)
46
+ # #=> { "id" => 123, "status" => "accepted", "application_id" => 456, ... }
47
+ # @see https://developers.greenhouse.io/harvest.html#get-retrieve-current-offer-for-application
19
48
  def current_offer_for_application(application_id)
20
49
  @client.current_offer_for_application(application_id)
21
50
  end
22
51
 
52
+ # Check for hired candidates based on when they have been updated.
53
+ # @param updated_since [String|DateTime] YYYY-MM-DD format with optional time stamp.
54
+ # @return [Array<Hash>] The hired candidates.
55
+ # @see PeopleGroup::Connectors::Greenhouse#hired_non_active
23
56
  def hired_candidates(updated_since)
24
57
  page = 1
25
58
  candidates = []
26
- on_error = -> { p [updated_since, page] }
27
59
 
28
60
  loop do
29
- results = Utils.retry_on_error(errors: [GreenhouseIo::Error, RestClient::InternalServerError], on_error: on_error) do
61
+ results = Utils.retry_on_error(on_error: -> { p [updated_since, page] }) do
30
62
  @client.candidates(nil, updated_after: updated_since, page: page)
31
63
  end
32
64
 
@@ -42,25 +74,43 @@ module PeopleGroup
42
74
  candidates
43
75
  end
44
76
 
77
+ # Search for a canidate by their candidate id.
78
+ # @param candidate_id [String|Integer] the candidates id.
79
+ # @return [Hash] the candidates details.
80
+ # @see https://developers.greenhouse.io/harvest.html#get-retrieve-candidate
45
81
  def candidate(candidate_id)
46
- @client.candidates(candidate_id)
82
+ retry_on_error { @client.candidates(candidate_id) }
47
83
  end
48
84
 
85
+ # Add a note to the candidates profile in Greenhouse.
86
+ # @param candidate_id [String|Integer] the candidates ID that is being synced.
87
+ # @return [Boolean] whether or not the request was successful.
49
88
  def add_sync_note_to_candidate(candidate_id)
50
89
  note = {
51
90
  user_id: ENV['GREENHOUSE_AUTHOR_ID'],
52
91
  body: "This person was synced at #{Time.now} by the Employee Bot",
53
92
  visibility: 'public'
54
93
  }
55
- @client.create_candidate_note(candidate_id, note, ENV['GREENHOUSE_AUTHOR_ID'])
94
+
95
+ retry_on_error { @client.create_candidate_note(candidate_id, note, ENV['GREENHOUSE_AUTHOR_ID']) }
56
96
  end
57
97
 
98
+ # List candidates by the specified options.
99
+ # @param id [String|Integer] The ID of the candidate to fetch.
100
+ # @param options [Hash] The options to filter the candidates by.
101
+ # @return [Array<Hash>] candidates matching the specified filter.
102
+ # @example
103
+ # greenhouse = PeopleGroup::Connectors::Greenhouse.new
104
+ # greenhouse.candidates(nil, { email: 'john@example.com' })
105
+ # greenhouse.candiates(12345)
106
+ # @see https://developers.greenhouse.io/harvest.html#get-list-candidates
58
107
  def candidates(id = nil, options = {})
59
- Utils.retry_on_error(errors: [GreenhouseIo::Error, Net::OpenTimeout]) do
60
- @client.candidates(id, options)
61
- end
108
+ retry_on_error { @client.candidates(id, options) }
62
109
  end
63
110
 
111
+ # Paginate through a list of users in Greenhouse.
112
+ # @return [Array<Hash>] The users in Greenhouse.
113
+ # @see https://developers.greenhouse.io/harvest.html#get-list-users
64
114
  def users
65
115
  page = 1
66
116
  users = []
@@ -76,18 +126,40 @@ module PeopleGroup
76
126
  users
77
127
  end
78
128
 
129
+ # Find an application by id.
130
+ # @param application_id [String|Integer] The ID of the application to fetch.
131
+ # @return [Hash] The application data.
132
+ # @see https://developers.greenhouse.io/harvest.html#get-retrieve-application
133
+ # @example
134
+ # greenhouse = PeopleGroup::Connectors::Greenhouse.new
135
+ # greenhouse.application(12345)
79
136
  def application(application_id)
80
- @client.applications(application_id)
137
+ retry_on_error { @client.applications(application_id) }
81
138
  end
82
139
 
140
+ # Check if the individual has any active applications.
141
+ # @param work_email [String] the individuals email address.
142
+ # @return [Boolean] true if the individual has any active applications, false if not.
83
143
  def has_active_application?(work_email)
84
- candidates(nil, { email: work_email })&.[]('applications')&.any? { |application| application['status'] == 'active' }
144
+ # Find the canidate by email.
145
+ candidate = candidates(nil, { email: work_email })
146
+
147
+ # Return false unless they could be found or have no applications.
148
+ return false unless candidate && candidate['applications'].size.positive?
149
+
150
+ # Check all applications for any with a status of 'active'
151
+ candidate['applications'].any? { |application| application['status'] == 'active' }
85
152
  rescue RestClient::NotFound
86
- nil # return nil if candidate could not be found
153
+ false # return false if candidate could not be found
87
154
  end
88
155
 
89
156
  private
90
157
 
158
+ # Check if the candidate is hired and inactive.
159
+ # - The candidate has any application that is active, we don't sync.
160
+ # - Check if candidate is hired for at least one of their applications
161
+ # @param candidate [Hash] The candidate data.
162
+ # @return [Boolean] whether or not the candidate is hired but not active.
91
163
  def hired_non_active?(candidate)
92
164
  # If the candidate has any application that is active, we don't sync.
93
165
  return false if candidate['applications'].any? { |application| application['status'] == 'active' }
@@ -95,6 +167,10 @@ module PeopleGroup
95
167
  # Check if candidate is hired for at least one of their applications
96
168
  candidate['applications'].any? { |application| application['status'] == 'hired' }
97
169
  end
170
+
171
+ def retry_on_error(on_error: -> {}, &block)
172
+ Utils.retry_on_error(errors: RETRY_ERRORS, delay: RETRY_DELAY, max_attempts: MAX_RETRIES, on_error: on_error, &block)
173
+ end
98
174
  end
99
175
  end
100
176
  end
@@ -8,9 +8,13 @@ module PeopleGroup
8
8
 
9
9
  def initialize
10
10
  @workday = Workday::Client.new unless ENV.fetch('WORKDAY_SERVICE_URL', nil).nil?
11
- @bamboo = Bamboo.new
12
11
  end
13
12
 
13
+ # List all team members from the WORKDAY_WORKERS_REPORT.
14
+ # @return [Array<Hash>]
15
+ # hris = PeopleGroup::Connectors::Hris.new
16
+ # hris.team_members
17
+ # #=> [{ "workEmail"=>"tanuki@gitlab.com" }, ...]
14
18
  def employees
15
19
  @employees ||= Workday::Report.call(
16
20
  url: ENV.fetch('WORKDAY_WORKERS_REPORT', nil),
@@ -19,11 +23,15 @@ module PeopleGroup
19
23
  end
20
24
  alias_method :team_members, :employees
21
25
 
26
+ # List team members with the satus of 'Active'.
27
+ # @return [Array<Hash] team members with a status of 'Active'.
22
28
  def active_employees
23
29
  employees.select { |employee| employee['status'] == 'Active' }
24
30
  end
25
31
  alias_method :active_team_members, :active_employees
26
32
 
33
+ # List team members with the satus of 'Active' and a hireDate that is less than or equal to today.
34
+ # @return [Array<Hash>] team members with a status of 'Active' and a hireDate that is less than or equal to today.
27
35
  def active_and_current_employees
28
36
  today = Date.current
29
37
  employees.select do |employee|
@@ -32,11 +40,22 @@ module PeopleGroup
32
40
  end
33
41
  alias_method :active_and_current_team_members, :active_and_current_employees
34
42
 
43
+ # Get the details of a team member from the WORKDAY_WORKERS_REPORT.
44
+ # @param employee_number [String|Integer] the team member's employee number.
45
+ # @return [Hash] the team member's details.
46
+ # @example
47
+ # hris = PeopleGroup::Connectors::Hris.new
48
+ # hris.get_employee_details(12345)
49
+ # #=> { "workEmail"=>"tanuki@gitlab.com", ... }
35
50
  def get_employee_details(employee_number)
36
51
  employees.find { |emp| emp['employeeNumber'] == employee_number.to_s }
37
52
  end
38
53
  alias_method :get_team_member_details, :get_employee_details
39
54
 
55
+ # Get the details of a team member from the WORKDAY_WORKERS_REPORT.
56
+ # @param employee_number [String|Integer]
57
+ # @raise [EmployeeNotFoundError] if the team member could not be found.
58
+ # @see [PeopleGroup::Connectors::Hris#get_employee_details]
40
59
  def get_employee_details!(employee_number)
41
60
  employee_details = get_employee_details(employee_number)
42
61
  raise EmployeeNotFoundError, "No team member found with employee number #{employee_number}" if employee_details.nil?
@@ -45,6 +64,12 @@ module PeopleGroup
45
64
  end
46
65
  alias_method :get_team_member_details!, :get_employee_details!
47
66
 
67
+ # Search a team member by name.
68
+ # @param name [String] the team member's name.
69
+ # @return [Hash|nil] the team member's details if found or nil.
70
+ # @example
71
+ # hris = PeopleGroup::Connectors::Hris.new
72
+ # hris.search_employee('Jane Doe')
48
73
  def search_employee(name)
49
74
  return if name.empty?
50
75
 
@@ -61,6 +86,10 @@ module PeopleGroup
61
86
  end
62
87
  alias_method :search_team_member, :search_employee
63
88
 
89
+ # Search team member by name.
90
+ # @param name [String] the team member's name or display name.
91
+ # @raise [EmployeeNotFoundError] if the team member could not be found.
92
+ # @see [PeopleGroup::Connectors::Hris#search_employee]
64
93
  def search_employee!(name)
65
94
  employee = search_employee(name)
66
95
  raise EmployeeNotFoundError, "No employee found with name #{name}" if employee.nil?
@@ -69,11 +98,24 @@ module PeopleGroup
69
98
  end
70
99
  alias_method :search_team_member!, :search_employee!
71
100
 
101
+ # Search a team meber by field.
102
+ # @param field [String] the field to search on.
103
+ # @param value [String|Integer] the value to search for.
104
+ # @return [Hash|nil] the team member's details if found or nil.
105
+ # @example
106
+ # hris = PeopleGroup::Connectors::Hris.new
107
+ # hris.search_employee_by_field('workEmail', 'tanuki@gitlab.com')
108
+ # #=> { "workEmail"=>"tanuki@gitlab.com", ... }
72
109
  def search_employee_by_field(field:, value:)
73
110
  employees.find { |employee| employee[field] == value.to_s }
74
111
  end
75
112
  alias_method :search_team_member_by_field, :search_employee_by_field
76
113
 
114
+ # Search a team member by field, and raise error if not found.
115
+ # @param field [String] the field to search on.
116
+ # @param value [String|Integer] the value to search for.
117
+ # @raise [EmployeeNotFoundError] if the team member could not be found.
118
+ # @see [PeopleGroup::Connectors::Hris#search_employee_by_field]
77
119
  def search_employee_by_field!(field:, value:)
78
120
  employee = search_employee_by_field(field: field, value: value)
79
121
  raise EmployeeNotFoundError, "No employee found with #{field}: #{value}" if employee.nil?
@@ -83,6 +125,11 @@ module PeopleGroup
83
125
  alias_method :search_team_member_by_field!, :search_employee_by_field!
84
126
 
85
127
  # Find the associated team member without checking case on their e-mail fields.
128
+ # 1. Look for a match on their workEmail field.
129
+ # 2. Look for a match on their bestEmail field.
130
+ # 3. Look for a match on their homeEmail field.
131
+ # @param email [String] the team member's e-mail address.
132
+ # @return [Hash|nil] the team member's details if found or nil.
86
133
  def search_team_member_by_email(email)
87
134
  return nil unless email
88
135
 
@@ -94,6 +141,8 @@ module PeopleGroup
94
141
  alias_method :slack_email_lookup_with_fallback, :search_team_member_by_email
95
142
 
96
143
  # Find the team member by email and raise error if not found.
144
+ # @param email [String] the team members e-mail address.
145
+ # @see [PeopleGroup::Connectors::Hris#search_team_member_by_email]
97
146
  def search_team_member_by_email!(email)
98
147
  team_member = search_team_member_by_email(email)
99
148
 
@@ -103,18 +152,38 @@ module PeopleGroup
103
152
  end
104
153
  alias_method :slack_email_lookup_with_fallback!, :search_team_member_by_email!
105
154
 
155
+ # Team members filtered by department
156
+ # @param department [String] the department name.
157
+ # @return [Array<Hash>] the team members that belong to that department
158
+ # @example
159
+ # hris = PeopleGroup::Connectors::Hris.new
160
+ # hris.team_members_by_department('People Operations')
161
+ # #=> [{ "workEmail"=>"tanuki@gitlab.com", ... }, ...]
106
162
  def team_members_by_department(department)
107
163
  active_and_current_team_members.select { |team_member| team_member['department'] == department }
108
164
  end
109
165
 
166
+ # Team members filtered by division
167
+ # @param division [String] the division name.
168
+ # @return [Array<Hash>] the team members that belong to that division
169
+ # @example
170
+ # hris = PeopleGroup::Connectors::Hris.new
171
+ # hris.team_members_by_division('People Group')
110
172
  def team_members_by_division(division)
111
173
  active_and_current_team_members.select { |team_member| team_member['division'] == division }
112
174
  end
113
175
 
176
+ # Find the manager of a team member.
177
+ # @param team_member [Hash] the team member's details.
178
+ # @return [Hash|nil] the manager's details
114
179
  def fetch_manager(team_member)
115
180
  active_team_members.find { |tm| tm['employeeNumber'] == team_member['supervisorId'] }
116
181
  end
117
182
 
183
+ # Find the manager of a team member and raise error if not found.
184
+ # @param team_member [Hash] the team member's details.
185
+ # @raise [EmployeeNotFoundError] if the manager could not be found.
186
+ # @see [PeopleGroup::Connectors::Hris#fetch_manager]
118
187
  def fetch_manager!(team_member)
119
188
  manager = fetch_manager(team_member)
120
189
  raise EmployeeNotFoundError, "Manager not found for employee #{team_member['employeeNumber']}" if manager.nil?
@@ -122,10 +191,20 @@ module PeopleGroup
122
191
  manager
123
192
  end
124
193
 
194
+ # Find the second level manager of a team member.
195
+ # 1. Find the team member's manager.
196
+ # 2. Find the manager's manager.
197
+ # @param team_member [Hash] the team member's details.
198
+ # @return [Hash|nil] the second level manager's details or nil.
125
199
  def fetch_second_level_manager(team_member)
126
200
  fetch_manager(fetch_manager(team_member))
127
201
  end
128
202
 
203
+ # Find the second level manager of a team member, raise error if not found.
204
+ # @param team_member [Hash] the team member's details.
205
+ # @return [Hash|nil] the second level manager's details or nil.
206
+ # @raise [EmployeeNotFoundError] if the second level manager could not be found.
207
+ # @see [PeopleGroup::Connectors::Hris#fetch_second_level_manager]
129
208
  def fetch_second_level_manager!(team_member)
130
209
  manager = fetch_second_level_manager(team_member)
131
210
  raise EmployeeNotFoundError, "No second level manager found for employee #{team_member['employeeNumber']}" if manager.nil?
@@ -133,37 +212,12 @@ module PeopleGroup
133
212
  manager
134
213
  end
135
214
 
136
- def create_employee(employee_details_hash)
137
- @bamboo.create_employee(employee_details_hash)
138
- end
139
- alias_method :create_team_member, :create_employee
140
-
141
- def update_employee(employee_number, employee_details_hash)
142
- # Only used in activate_team_members.rb, which is not needed in Workday
143
- @bamboo.update_employee(employee_number, employee_details_hash)
144
- end
145
- alias_method :update_team_member, :update_employee
146
-
215
+ # List entities from Workday.
216
+ # @return [Array<Hash>] list of entities from Workday.
147
217
  def locations
148
218
  @workday.locations
149
219
  end
150
220
 
151
- def add_stock_options(employee_number, data)
152
- @bamboo.add_stock_options(employee_number, data)
153
- end
154
-
155
- def add_job_details(employee_number, data)
156
- @bamboo.add_job_details(employee_number, data)
157
- end
158
-
159
- def update_job_details(employee_number, data)
160
- @bamboo.update_job_details(employee_number, data)
161
- end
162
-
163
- def add_compensation_details(employee_number, data)
164
- @bamboo.add_compensation_details(employee_number, data)
165
- end
166
-
167
221
  def employment_statuses(employee_number)
168
222
  # Terminated/Active in one field already. Those are the statuses we need to care about
169
223
  # Look for a hire date that is after the start date (not original hire date) to account for rehires
@@ -181,98 +235,26 @@ module PeopleGroup
181
235
  raise NotImplementedError
182
236
  end
183
237
 
184
- def time_off_type(name)
185
- # Staying in bamboo for workday first release
186
- # Will need to have talks with pto by roots admin
187
- @bamboo.time_off_type(name)
188
- end
189
-
190
- def time_off_adjustment(employee_number, options)
191
- # Staying in bamboo for workday first release
192
- # Will need to have talks with pto by roots admin
193
- @bamboo.time_off_adjustment(employee_number, options)
194
- end
195
-
196
- def accrued_days(employee_number)
197
- # Staying in bamboo for workday first release
198
- # Will need to have talks with pto by roots admin
199
- @bamboo.accrued_days(employee_number)
200
- end
201
-
202
- def time_off_policies
203
- @bamboo.time_off_policies
204
- end
205
-
206
- def employee_time_off_policies(employee_number)
207
- @bamboo.employee_time_off_policies(employee_number)
208
- end
209
- alias_method :team_member_time_off_policies, :employee_time_off_policies
210
-
211
- def add_time_off_policy(employee_number, time_off_policy_id, accrual_start_date)
212
- @bamboo.add_time_off_policy(employee_number, time_off_policy_id, accrual_start_date)
213
- end
214
-
215
- def remove_time_off_policy(employee_number, time_off_policy_id)
216
- @bamboo.remove_time_off_policy(employee_number, time_off_policy_id)
217
- end
218
-
219
238
  def job_details(employee_number)
220
239
  warn '[DEPRECATED] PeopleGroup::Connectors::Hris#job_details, use a custom workday report instead.'
221
240
  Workday::Report.call(url: ENV.fetch('WORKDAY_MANAGER_REPORT', nil))
222
241
  end
223
242
 
224
- def resumes_folder_id(employee_number)
225
- @bamboo.resumes_folder_id(employee_number)
226
- end
227
-
228
- def contract_folder_id(employee_number)
229
- @bamboo.contract_folder_id(employee_number)
230
- end
231
-
232
- def add_file(employee_number, file_name, file, folder_id)
233
- @bamboo.add_file(employee_number, file_name, file, folder_id)
234
- end
235
-
236
- def add_employment_status(employee_number, data)
237
- # Only used in parental_pto_to_bamboo.rb
238
- # That integration adds the following statuses: Parental Leave, End of Parental Leave, Active
239
- # Asked what to do about this one on Slack: https://gitlab.slack.com/archives/C02FHJ9BYTZ/p1650666853127429
240
-
241
- raise NotImplementedError
242
- end
243
-
244
- def add_currency_conversion(employee_number, data)
245
- @bamboo.add_currency_conversion(employee_number, data)
246
- end
247
-
248
- def add_on_target_earnings(employee_number, data)
249
- @bamboo.add_on_target_earnings(employee_number, data)
250
- end
251
-
252
- def add_signing_bonus(employee_number, data)
253
- @bamboo.add_signing_bonus(employee_number, data)
254
- end
255
-
256
- def add_family_member(employee_number, data)
257
- @bamboo.add_family_member(employee_number, data)
258
- end
259
-
260
- def add_additional_data(employee_number, data)
261
- @bamboo.add_additional_data(employee_number, data)
262
- end
263
-
264
- def additional_data(employee_number)
265
- @bamboo.additional_data(employee_number)
266
- end
267
-
268
243
  def add_bonus(employee_number, comment)
269
244
  @workday.add_bonus(employee_number, comment)
270
245
  end
271
246
 
247
+ # List active departments from Workday.
248
+ # @return [Array<Hash>] list of departments from Workday.
272
249
  def departments
273
250
  @workday.departments
274
251
  end
275
252
 
253
+ # Check team member eligibility status based on salary ranges.
254
+ # @param eid [String|Integer] the team members employee number
255
+ # @param salary_min [Integer] the minimum salary.
256
+ # @param salary_max [Integer] the maximum salary.
257
+ # @return [Hash] details regarding their eligibility status.
276
258
  def eligibility_status(eid, salary_min, salary_max)
277
259
  @workday.eligibility_status(eid, salary_min, salary_max)
278
260
  end
@@ -2,6 +2,6 @@
2
2
 
3
3
  module PeopleGroup
4
4
  module Connectors
5
- VERSION = '1.0.5'
5
+ VERSION = '2.0.0'
6
6
  end
7
7
  end
@@ -20,9 +20,9 @@ module PeopleGroup
20
20
  # Adds a bonus to the team members
21
21
  # @param employee_number [Integer] The Employee Number of the team member receiving the bonus.
22
22
  # @param comment [String] The comment to leave on the business process.
23
- def add_bonus(employee_number, comment)
23
+ def add_bonus(employee_number, comment: '', auto_complete: false)
24
24
  options = {
25
- **business_process_params(comment),
25
+ **business_process_params(comment: comment, auto_complete: auto_complete),
26
26
  **one_time_payment_data(employee_number, comment)
27
27
  }
28
28
 
@@ -38,7 +38,7 @@ module PeopleGroup
38
38
  # @param auto_complete: [Boolean] Defaults to true to apply this bonus without needing approval.
39
39
  #
40
40
  # @return [Hash] The <wd:Business_Process_Parameters> for this request.
41
- def business_process_params(comment, run_now: true, auto_complete: true)
41
+ def business_process_params(comment:, run_now: true, auto_complete: true)
42
42
  {
43
43
  'Business_Process_Parameters' => {
44
44
  'Auto_Complete' => auto_complete,
metadata CHANGED
@@ -1,29 +1,15 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: peoplegroup-connectors
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.5
4
+ version: 2.0.0
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-09-19 00:00:00.000000000 Z
11
+ date: 2025-01-17 00:00:00.000000000 Z
12
12
  dependencies:
13
- - !ruby/object:Gem::Dependency
14
- name: bamboozled-gitlab
15
- requirement: !ruby/object:Gem::Requirement
16
- requirements:
17
- - - "~>"
18
- - !ruby/object:Gem::Version
19
- version: 0.2.14
20
- type: :runtime
21
- prerelease: false
22
- version_requirements: !ruby/object:Gem::Requirement
23
- requirements:
24
- - - "~>"
25
- - !ruby/object:Gem::Version
26
- version: 0.2.14
27
13
  - !ruby/object:Gem::Dependency
28
14
  name: gitlab
29
15
  requirement: !ruby/object:Gem::Requirement
@@ -122,7 +108,7 @@ dependencies:
122
108
  - - "~>"
123
109
  - !ruby/object:Gem::Version
124
110
  version: '0.1'
125
- description: Avoid repeating methods in different projects for our connectos.
111
+ description: Avoid repeating methods in different projects for our connectors.
126
112
  email:
127
113
  - lvandensteen@gitlab.com
128
114
  executables: []
@@ -132,7 +118,6 @@ files:
132
118
  - LICENSE.txt
133
119
  - README.md
134
120
  - lib/peoplegroup/connectors.rb
135
- - lib/peoplegroup/connectors/bamboo.rb
136
121
  - lib/peoplegroup/connectors/gitlab.rb
137
122
  - lib/peoplegroup/connectors/google_sheets.rb
138
123
  - lib/peoplegroup/connectors/greenhouse.rb
@@ -171,7 +156,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
171
156
  - !ruby/object:Gem::Version
172
157
  version: '0'
173
158
  requirements: []
174
- rubygems_version: 3.5.11
159
+ rubygems_version: 3.5.23
175
160
  signing_key:
176
161
  specification_version: 4
177
162
  summary: Library for our shared connectors.
@@ -1,355 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require 'bamboozled'
4
-
5
- module PeopleGroup
6
- module Connectors
7
- class Bamboo
8
- attr_accessor :client
9
-
10
- EmployeeNotFoundError = Class.new(StandardError)
11
-
12
- def initialize(use_report: false)
13
- @use_report = use_report
14
- @client = Bamboozled.client(subdomain: 'gitlab', api_key: ENV['BAMBOO_API_KEY'])
15
- end
16
-
17
- def get_employee_details(id)
18
- employees.find { |emp| emp['employeeNumber'] == id.to_s }
19
- end
20
- alias_method :get_team_member_details, :get_employee_details
21
-
22
- def get_employee_details!(id)
23
- employee_details = get_employee_details(id)
24
- raise EmployeeNotFoundError, "No team member found with employee number #{id}" if employee_details.nil?
25
-
26
- employee_details
27
- end
28
- alias_method :get_team_member_details!, :get_employee_details!
29
-
30
- def bamboo_id!(employee_number)
31
- get_team_member_details!(employee_number)['id']
32
- end
33
-
34
- def search_employee(name)
35
- return if name.empty?
36
-
37
- employees.find do |emp|
38
- [
39
- emp['displayName']&.downcase,
40
- "#{emp['firstName']&.downcase} #{emp['lastName']&.downcase}",
41
- "#{emp['preferredName']&.downcase} #{emp['lastName']&.downcase}",
42
- "#{emp['firstName']&.downcase} #{emp['customPreferredLastName']&.downcase}",
43
- "#{emp['preferredName']&.downcase} #{emp['customPreferredLastName']&.downcase}",
44
- emp['fullName5']&.downcase # this is firstName middleName lastName
45
- ].include?(name.downcase)
46
- end
47
- end
48
- alias_method :search_team_member, :search_employee
49
-
50
- def search_employee!(name)
51
- employee = search_employee(name)
52
- raise EmployeeNotFoundError, "No employee found with name #{name}" if employee.nil?
53
-
54
- employee
55
- end
56
- alias_method :search_team_member!, :search_employee!
57
-
58
- def search_employee_by_field(field:, value:)
59
- employees.find { |employee| employee[field] == value.to_s }
60
- end
61
- alias_method :search_team_member_by_field, :search_employee_by_field
62
-
63
- def search_employee_by_field!(field:, value:)
64
- employee = search_employee_by_field(field: field, value: value)
65
- raise EmployeeNotFoundError, "No employee found with #{field}: #{value}" if employee.nil?
66
-
67
- employee
68
- end
69
- alias_method :search_team_member_by_field!, :search_employee_by_field!
70
-
71
- # Find the associated team member without checking case on the 'workEmail' field.
72
- def search_team_member_by_email(email)
73
- team_members.find { |team_member| team_member['workEmail']&.downcase == email&.downcase }
74
- end
75
- alias_method :slack_email_lookup_with_fallback, :search_team_member_by_email
76
-
77
- def search_team_member_by_email!(email)
78
- team_member = search_team_member_by_email(email)
79
- raise EmployeeNotFoundError, "No team member found with email #{email}." unless team_member
80
-
81
- team_member
82
- end
83
- alias_method :slack_email_lookup_with_fallback!, :search_team_member_by_email!
84
-
85
- def team_members_by_department(department)
86
- active_and_current_team_members.select { |team_member| team_member['department'] == department }
87
- end
88
-
89
- def team_members_by_division(division)
90
- active_and_current_team_members.select { |team_member| team_member['division'] == division }
91
- end
92
-
93
- def fetch_manager(team_member)
94
- active_team_members.find { |tm| tm['employeeNumber'] == team_member['supervisorId'] }
95
- end
96
-
97
- def fetch_manager!(team_member)
98
- manager = fetch_manager(team_member)
99
- raise EmployeeNotFoundError, "Manager not found for employee #{team_member['employeeNumber']}" if manager.nil?
100
-
101
- manager
102
- end
103
-
104
- def fetch_second_level_manager(team_member)
105
- fetch_manager(fetch_manager(team_member))
106
- end
107
-
108
- def fetch_second_level_manager!(team_member)
109
- manager = fetch_second_level_manager(team_member)
110
- raise EmployeeNotFoundError, "No second level manager found for employee #{team_member['employeeNumber']}" if manager.nil?
111
-
112
- manager
113
- end
114
-
115
- def create_employee(employee_details_hash)
116
- invalidate_cache
117
- retry_on_error { @client.employee.add(employee_details_hash) }
118
- end
119
- alias_method :create_team_member, :create_employee
120
-
121
- def update_employee(employee_number, employee_details_hash)
122
- id = bamboo_id!(employee_number)
123
- invalidate_cache
124
- retry_on_error { @client.employee.update(id, employee_details_hash) }
125
- end
126
- alias_method :update_team_member, :update_employee
127
-
128
- def locations
129
- meta_fields.detect { |res| res['name'] == 'Location' }['options'].each_with_object([]) { |option, array| array << option['name'] if option['archived'] == 'no' } || []
130
- end
131
-
132
- def employees
133
- return @employees unless @employees.nil?
134
-
135
- report = retry_on_error do
136
- if @use_report
137
- @client.report.find(@use_report, 'JSON', true)
138
- else
139
- @client.report.custom(fields, 'JSON')
140
- end
141
- end
142
-
143
- filtered = report.reject { |team_member| team_member['lastName'] == 'Test-Gitlab' }
144
- @employees = filtered.map { |team_member| format_team_member(team_member) }
145
- end
146
- alias_method :team_members, :employees
147
-
148
- def active_employees
149
- employees.select { |employee| employee['status'] == 'Active' }
150
- end
151
- alias_method :active_team_members, :active_employees
152
-
153
- def active_and_current_employees
154
- today = Date.current
155
- employees.select do |employee|
156
- employee['status'] == 'Active' && Date.parse(employee['hireDate']) <= today
157
- end
158
- end
159
- alias_method :active_and_current_team_members, :active_and_current_employees
160
-
161
- def add_stock_options(employee_number, data)
162
- id = bamboo_id!(employee_number)
163
- retry_on_error { @client.employee.add_table_row(id, 'customEquity', data) }
164
- end
165
-
166
- def add_job_details(employee_number, data)
167
- id = bamboo_id!(employee_number)
168
- retry_on_error { @client.employee.add_table_row(id, 'jobInfo', data) }
169
- end
170
-
171
- def update_job_details(employee_number, data)
172
- id = bamboo_id!(employee_number)
173
- current_data = job_details(employee_number) # it should only be one row as we just created this user
174
- row_id = current_data.first['id']
175
- retry_on_error { @client.employee.update_table_row(id, 'jobInfo', row_id, data) }
176
- end
177
-
178
- def add_compensation_details(employee_number, data)
179
- id = bamboo_id!(employee_number)
180
- retry_on_error { @client.employee.add_table_row(id, 'compensation', data) }
181
- end
182
-
183
- def employment_statuses(employee_number)
184
- id = bamboo_id!(employee_number)
185
- retry_on_error { @client.employee.table_data(id, 'employmentStatus') }
186
- end
187
-
188
- def time_off_type(name)
189
- time_off_types['timeOffTypes'].find { |type| type['name'] == name }
190
- end
191
-
192
- def time_off_adjustment(employee_number, options)
193
- id = bamboo_id!(employee_number)
194
- retry_on_error { @client.employee.time_off_balance_adjustment(id, options) }
195
- end
196
-
197
- def accrued_days(employee_number)
198
- employee_accruals_type = time_off_type('Employee Accruals')
199
- total_accruals = time_off_estimate(employee_number).find { |type| type['timeOffType'] == employee_accruals_type['id'] }
200
-
201
- total_accruals['balance'].to_f
202
- end
203
-
204
- def time_off_policies
205
- @time_off_policies ||= retry_on_error { @client.meta.time_off_policies }
206
- end
207
-
208
- def employee_time_off_policies(employee_number)
209
- id = bamboo_id!(employee_number)
210
- retry_on_error { @client.employee.time_off_policies(id) }
211
- end
212
- alias_method :team_member_time_off_policies, :employee_time_off_policies
213
-
214
- def add_time_off_policy(employee_number, time_off_policy_id, accrual_start_date)
215
- policies = [{ timeOffPolicyId: time_off_policy_id, accrualStartDate: accrual_start_date }]
216
- id = bamboo_id!(employee_number)
217
-
218
- retry_on_error { @client.employee.add_time_off_policies(id, policies) }
219
- end
220
-
221
- def remove_time_off_policy(employee_number, time_off_policy_id)
222
- # A nil accrual start date removes a policy assignment
223
- # Reference: https://documentation.bamboohr.com/reference#time-off-assign-time-off-policies-for-an-employee
224
- add_time_off_policy(employee_number, time_off_policy_id, nil)
225
- end
226
-
227
- def job_details(employee_number)
228
- id = bamboo_id!(employee_number)
229
- retry_on_error { @client.employee.table_data(id, 'jobInfo') }
230
- end
231
-
232
- def resumes_folder_id(employee_number)
233
- @resumes_folder_id ||= files(employee_number)['categories'].find { |folder| folder['name'] == 'Resumes and Applications' }['id']
234
- end
235
-
236
- def contract_folder_id(employee_number)
237
- @contract_folder_id ||= files(employee_number)['categories'].find { |folder| folder['name'] == 'Contracts & Changes' }['id']
238
- end
239
-
240
- def add_file(employee_number, file_name, file, folder_id)
241
- id = bamboo_id!(employee_number)
242
-
243
- options = {
244
- category: folder_id,
245
- fileName: file_name,
246
- share: 'yes',
247
- file: file
248
- }
249
- retry_on_error { @client.employee.add_file(id, options) }
250
- end
251
-
252
- def add_employment_status(employee_number, data)
253
- id = bamboo_id!(employee_number)
254
- retry_on_error { @client.employee.add_table_row(id, 'employmentStatus', data) }
255
- end
256
-
257
- def add_currency_conversion(employee_number, data)
258
- id = bamboo_id!(employee_number)
259
- retry_on_error { @client.employee.add_table_row(id, 'customCurrencyConversion', data) }
260
- end
261
-
262
- def add_on_target_earnings(employee_number, data)
263
- id = bamboo_id!(employee_number)
264
- retry_on_error { @client.employee.add_table_row(id, 'customOnTargetEarnings', data) }
265
- end
266
-
267
- def add_signing_bonus(employee_number, data)
268
- id = bamboo_id!(employee_number)
269
- retry_on_error { @client.employee.add_table_row(id, 'customBonus', data) }
270
- end
271
-
272
- def add_family_member(employee_number, data)
273
- id = bamboo_id!(employee_number)
274
- retry_on_error { @client.employee.add_table_row(id, 'customFamilyMember', data) }
275
- end
276
-
277
- def add_additional_data(employee_number, data)
278
- id = bamboo_id!(employee_number)
279
- retry_on_error { @client.employee.add_table_row(id, 'customAdditionalInformation1', data) }
280
- end
281
-
282
- def additional_data(employee_number)
283
- id = bamboo_id!(employee_number)
284
- retry_on_error { @client.employee.table_data(id, 'employmentStatus') }
285
- end
286
-
287
- def add_bonus(employee_number, comment)
288
- team_member = search_employee_by_field(field: 'employeeNumber', value: employee_number)
289
- data = {
290
- customBonusdate: Date.today.to_s,
291
- customBonusamount: { value: 1_000, currency: 'USD' },
292
- customBonustype: 'Discretionary Bonus',
293
- customBonuscomments: comment
294
- # customNominatedBy: 'TODO'
295
- }
296
- retry_on_error { @client.employee.add_table_row(team_member['id'], 'customBonus', data) }
297
- end
298
-
299
- private
300
-
301
- def fields
302
- return @fields if @fields
303
-
304
- meta_field_aliases = retry_on_error { @client.meta.fields }.map { |f| f['alias'] }
305
- @fields = (Bamboozled::API::FieldCollection.all_names + meta_field_aliases).compact.uniq
306
- @fields.delete('flsaCode') # temp fix for problems with BambooHR
307
- @fields
308
- end
309
-
310
- def meta_fields
311
- @meta_fields ||= retry_on_error { @client.meta.lists }
312
- end
313
-
314
- def time_off_types
315
- @time_off_types ||= retry_on_error { @client.meta.time_off_types }
316
- end
317
-
318
- def time_off_estimate(employee_number, end_date = Date.today)
319
- id = bamboo_id!(employee_number)
320
- retry_on_error { @client.employee.time_off_estimate(id, end_date) }
321
- end
322
-
323
- def files(employee_number)
324
- id = bamboo_id!(employee_number)
325
- @files ||= retry_on_error { @client.employee.files(id) }
326
- end
327
-
328
- def retry_on_error(&block)
329
- Utils.retry_on_error(errors: [Net::ReadTimeout, Bamboozled::GatewayError, Bamboozled::ServiceUnavailable], delay: 3, &block)
330
- end
331
-
332
- def invalidate_cache
333
- @employees = nil
334
- end
335
-
336
- MALFORMED_FIELDS = [
337
- 'customExportName/LocationtoTeamPage?',
338
- 'customI-9Processed',
339
- 'customJobTitleSpecialty(Multi-Select)'
340
- ].freeze
341
-
342
- # Convert BHR team member to Workday.
343
- def format_team_member(team_member)
344
- return nil if team_member.nil?
345
-
346
- {
347
- **team_member.except(*MALFORMED_FIELDS),
348
- 'customExportNameLocationtoTeamPage' => team_member['customExportName/LocationtoTeamPage?'] || 'No',
349
- 'customJobTitleSpecialtyMultiSelect' => team_member['customJobTitleSpecialty(Multi-Select)'],
350
- 'customI9Processed' => team_member['customI-9Processed']
351
- }
352
- end
353
- end
354
- end
355
- end