peoplegroup-connectors 1.0.5 → 2.1.0

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: 71281e48df14d934fff4a9f440e44a53aa416082080afa02735e7675de8359a7
4
- data.tar.gz: e8dec527298a0a87e8e7c2f0524ecd9c691a944c610c7ac66dd3fe638fbeb6ab
3
+ metadata.gz: 53e75844f42c51cd533f4a52159f7ff5f37579fbbfe61c709e115e20b2ce45e7
4
+ data.tar.gz: 9d082f640c3458e8b6c65781fafeb87011b942fab5f91b28bd15f894f20410c6
5
5
  SHA512:
6
- metadata.gz: ec8af3c1b8606214ac38b402eaa60d2467f8201fb3b27ada5a0d4e6e574ca43b8169b807b0d4ced02b9e57d8fb6ce0a4dd194b91b8791cbbfc4b8cd6a7f410f7
7
- data.tar.gz: 65c30b6577487e88dd7a6eb78bfd88bf4528db9eb660a1cd1f54cdf00e9a00176e77d5cbebc9c150dead33cafe7b593b378d238c1ebe354e6e5ecc3f7abebbd8
6
+ metadata.gz: 5c863185fbee4c1d453c8f7d46611eb6e1e8c26ddb31f75ff27d43ea1a007047c4b61476141b3aec02ce9777a886ed448886a3ba8566b0a2b6bcfdcf20e9f1a8
7
+ data.tar.gz: af47b688c53efddae132ee96907b25f53e86b7e997e96879d86f578460a6aa9f8d97ad9e83bebc513b76dae4de761b7b669a9350acd6e8e914e4dd8aef37d864
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,68 @@ 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)
108
+ retry_on_error { @client.candidates(id, options) }
109
+ end
110
+
111
+ # https://developer.greenhouse.io/harvest.html#get-list-scorecards
112
+ # @param id [String|Integer|nil] The ID of the candidate to fetch, if nil fetches all scorecards.
113
+ # @param options [Hash] The options to filter the candidates by.
114
+ # skip_count whether or not to skip the count of records. Defaults to true.
115
+ # created_before Return only scorecards that were created before this timestamp. Timestamp must be in in ISO-8601 format.
116
+ # created_after Return only scorecards that were created at or after this timestamp. Timestamp must be in in ISO-8601 format.
117
+ # updated_before Return only scorecards that were updated before this timestamp. Timestamp must be in in ISO-8601 format.
118
+ # updated_after Return only scorecards that were updated at or after this timestamp. Timestamp must be in in ISO-8601 format.
119
+ # @return [Array<Hash>] candidates matching the specified filter.
120
+ def scorecards(id = nil, options = {})
121
+ options[:skip_count] ||= true
122
+ options[:page] ||= 1
123
+ scorecards = []
124
+
125
+ loop do
126
+ results = retry_on_error { @client.all_scorecards(id, **options) }
127
+ break if results.empty?
128
+
129
+ scorecards += results
130
+ options[:page] += 1
61
131
  end
132
+
133
+ scorecards
62
134
  end
63
135
 
136
+ # Paginate through a list of users in Greenhouse.
137
+ # @return [Array<Hash>] The users in Greenhouse.
138
+ # @see https://developers.greenhouse.io/harvest.html#get-list-users
64
139
  def users
65
140
  page = 1
66
141
  users = []
@@ -76,18 +151,45 @@ module PeopleGroup
76
151
  users
77
152
  end
78
153
 
154
+ # https://developers.greenhouse.io/harvest.html#get-retrieve-job
155
+ def jobs(id = nil, options = {})
156
+ retry_on_error { @client.jobs(id, options) }
157
+ end
158
+
159
+ # Find an application by id.
160
+ # @param application_id [String|Integer] The ID of the application to fetch.
161
+ # @return [Hash] The application data.
162
+ # @see https://developers.greenhouse.io/harvest.html#get-retrieve-application
163
+ # @example
164
+ # greenhouse = PeopleGroup::Connectors::Greenhouse.new
165
+ # greenhouse.application(12345)
79
166
  def application(application_id)
80
- @client.applications(application_id)
167
+ retry_on_error { @client.applications(application_id) }
81
168
  end
82
169
 
170
+ # Check if the individual has any active applications.
171
+ # @param work_email [String] the individuals email address.
172
+ # @return [Boolean] true if the individual has any active applications, false if not.
83
173
  def has_active_application?(work_email)
84
- candidates(nil, { email: work_email })&.[]('applications')&.any? { |application| application['status'] == 'active' }
174
+ # Find the canidate by email.
175
+ candidate = candidates(nil, { email: work_email })
176
+
177
+ # Return false unless they could be found or have no applications.
178
+ return false unless candidate && candidate['applications'].size.positive?
179
+
180
+ # Check all applications for any with a status of 'active'
181
+ candidate['applications'].any? { |application| application['status'] == 'active' }
85
182
  rescue RestClient::NotFound
86
- nil # return nil if candidate could not be found
183
+ false # return false if candidate could not be found
87
184
  end
88
185
 
89
186
  private
90
187
 
188
+ # Check if the candidate is hired and inactive.
189
+ # - The candidate has any application that is active, we don't sync.
190
+ # - Check if candidate is hired for at least one of their applications
191
+ # @param candidate [Hash] The candidate data.
192
+ # @return [Boolean] whether or not the candidate is hired but not active.
91
193
  def hired_non_active?(candidate)
92
194
  # If the candidate has any application that is active, we don't sync.
93
195
  return false if candidate['applications'].any? { |application| application['status'] == 'active' }
@@ -95,6 +197,10 @@ module PeopleGroup
95
197
  # Check if candidate is hired for at least one of their applications
96
198
  candidate['applications'].any? { |application| application['status'] == 'hired' }
97
199
  end
200
+
201
+ def retry_on_error(on_error: -> {}, &block)
202
+ Utils.retry_on_error(errors: RETRY_ERRORS, delay: RETRY_DELAY, max_attempts: MAX_RETRIES, on_error: on_error, &block)
203
+ end
98
204
  end
99
205
  end
100
206
  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.1.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.1.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-24 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