bamboozled-gitlab 0.2.9

Sign up to get free protection for your applications and to get access to all the features.
Files changed (75) hide show
  1. checksums.yaml +7 -0
  2. data/.github/ISSUE_TEMPLATE/bug_report.md +39 -0
  3. data/.github/ISSUE_TEMPLATE/feature_request.md +19 -0
  4. data/.github/ISSUE_TEMPLATE/question.md +19 -0
  5. data/.github/main.workflow +38 -0
  6. data/.github/pull_request_template.md +27 -0
  7. data/.gitignore +38 -0
  8. data/.rubocop.yml +39 -0
  9. data/.rubocop_todo.yml +305 -0
  10. data/.travis.yml +18 -0
  11. data/CHANGELOG.md +48 -0
  12. data/CONTRIBUTING.md +91 -0
  13. data/Dockerfile +8 -0
  14. data/Gemfile +16 -0
  15. data/Guardfile +21 -0
  16. data/LICENSE +22 -0
  17. data/README.md +170 -0
  18. data/Rakefile +11 -0
  19. data/bamboozled.gemspec +30 -0
  20. data/examples/employees_over_time.rb +53 -0
  21. data/lib/bamboozled.rb +24 -0
  22. data/lib/bamboozled/api/base.rb +101 -0
  23. data/lib/bamboozled/api/employee.rb +118 -0
  24. data/lib/bamboozled/api/field_collection.rb +107 -0
  25. data/lib/bamboozled/api/meta.rb +25 -0
  26. data/lib/bamboozled/api/report.rb +20 -0
  27. data/lib/bamboozled/api/time_off.rb +34 -0
  28. data/lib/bamboozled/api/time_tracking.rb +24 -0
  29. data/lib/bamboozled/base.rb +31 -0
  30. data/lib/bamboozled/errors.rb +33 -0
  31. data/lib/bamboozled/ext/yesno.rb +11 -0
  32. data/lib/bamboozled/version.rb +3 -0
  33. data/logos/bamboozled_logo_black.png +0 -0
  34. data/logos/bamboozled_logo_green.png +0 -0
  35. data/logos/skookum_mark_black.png +0 -0
  36. data/logos/skookum_mark_black.svg +175 -0
  37. data/relnotes/v0.1.0.md +13 -0
  38. data/spec/fixtures/add_employee_details.json +7 -0
  39. data/spec/fixtures/add_employee_response.json +4 -0
  40. data/spec/fixtures/add_employee_xml.yml +8 -0
  41. data/spec/fixtures/all_employees.json +58 -0
  42. data/spec/fixtures/custom_report.json +38 -0
  43. data/spec/fixtures/employee_emails.json +9 -0
  44. data/spec/fixtures/employee_table_details.json +17 -0
  45. data/spec/fixtures/job_info.xml +22 -0
  46. data/spec/fixtures/last_changed.json +28 -0
  47. data/spec/fixtures/meta_fields.json +5 -0
  48. data/spec/fixtures/meta_lists.json +5 -0
  49. data/spec/fixtures/meta_tables.json +5 -0
  50. data/spec/fixtures/meta_users.json +4 -0
  51. data/spec/fixtures/one_employee.json +9 -0
  52. data/spec/fixtures/time_off_estimate.json +23 -0
  53. data/spec/fixtures/time_tracking_add_200_response.json +7 -0
  54. data/spec/fixtures/time_tracking_add_empty_response.json +9 -0
  55. data/spec/fixtures/time_tracking_adjust_200_response.json +7 -0
  56. data/spec/fixtures/time_tracking_adjust_400_response.json +11 -0
  57. data/spec/fixtures/time_tracking_record_200_response.json +9 -0
  58. data/spec/fixtures/time_tracking_record_400_response.json +11 -0
  59. data/spec/fixtures/time_tracking_record_401_response.json +10 -0
  60. data/spec/fixtures/time_tracking_record_404_response.json +8 -0
  61. data/spec/fixtures/update_employee_details.json +7 -0
  62. data/spec/fixtures/update_employee_response.json +3 -0
  63. data/spec/fixtures/update_employee_table.json +8 -0
  64. data/spec/fixtures/update_employee_table_xml.yml +6 -0
  65. data/spec/fixtures/update_employee_xml.yml +8 -0
  66. data/spec/lib/bamboozled/api/base_spec.rb +18 -0
  67. data/spec/lib/bamboozled/api/employee_spec.rb +186 -0
  68. data/spec/lib/bamboozled/api/field_collection_spec.rb +17 -0
  69. data/spec/lib/bamboozled/api/meta_spec.rb +47 -0
  70. data/spec/lib/bamboozled/api/report_spec.rb +17 -0
  71. data/spec/lib/bamboozled/api/time_tracking_spec.rb +123 -0
  72. data/spec/lib/bamboozled/base_spec.rb +26 -0
  73. data/spec/lib/bamboozled_spec.rb +33 -0
  74. data/spec/spec_helper.rb +32 -0
  75. metadata +237 -0
@@ -0,0 +1,53 @@
1
+ # File: employees_over_time.rb
2
+ # Date Created: 2014-08-07
3
+ # Author(s): Mark Rickert (mjar81@gmail.com) / Skookum Digital Works (http://skookum.com)
4
+ #
5
+ # Description: This example script grabs all the historical users in BambooHR
6
+ # and determines how many employees there were at the end of each of the
7
+ # previous 12 months.
8
+ #
9
+ # Run this script with: ruby employees_over_time.rb
10
+
11
+ require '../lib/bamboozled/'
12
+ require 'active_support'
13
+ require 'active_support/core_ext/integer'
14
+ require 'active_support/core_ext/date'
15
+
16
+ @subdomain = 'your_subdomain'
17
+ @api_key = 'your_api_key'
18
+
19
+ def main
20
+ client = Bamboozled.client(subdomain:@subdomain, api_key:@api_key)
21
+
22
+ # Get all users in the system. Even terminated employees.
23
+ # Calling client.employee.all only gets active users.
24
+ employee_data = client.meta.users.map do |e|
25
+ # Sometimes employees are in the system but don't have an emaployee ID.
26
+ # This makes them unqueryable and they're usually a duplicate or admin user.
27
+ next unless e[:employeeId]
28
+
29
+ # Get each employee's start_date and termination date
30
+ client.employee.find(e[:employeeId], %w(displayName department hireDate terminationDate))
31
+ end.compact.reject{|e| e['hireDate'] == '0000-00-00'}
32
+
33
+ # Start from today and go back 12 months and print out how many employees were
34
+ # at the company on that date.
35
+ d = (Date.today - 1.month).end_of_month
36
+ 12.times do
37
+ d = (d - 1.month).end_of_month
38
+ puts "#{d},#{employees_on_date(employee_data, d)}"
39
+ end
40
+
41
+ end
42
+
43
+ # Simple method to compare hire and termination dates and get the count of employees
44
+ # who were with the company on that day.
45
+ def employees_on_date(employees, date)
46
+ employees.map do |e|
47
+ hire_date = Date.parse(e["hireDate"]) rescue nil
48
+ termination_date = Date.parse(e["terminationDate"]) rescue nil
49
+ (hire_date <= date && (termination_date.nil? || termination_date >= date)) ? 1 : 0
50
+ end.inject(:+)
51
+ end
52
+
53
+ main
data/lib/bamboozled.rb ADDED
@@ -0,0 +1,24 @@
1
+ require "httparty"
2
+ require "json"
3
+ require "uri"
4
+
5
+ require "bamboozled/version"
6
+ require "bamboozled/base"
7
+ require "bamboozled/errors"
8
+ require "bamboozled/ext/yesno"
9
+ require "bamboozled/api/base"
10
+ require "bamboozled/api/field_collection"
11
+ require "bamboozled/api/employee"
12
+ require "bamboozled/api/report"
13
+ require "bamboozled/api/time_off"
14
+ require "bamboozled/api/time_tracking"
15
+ require "bamboozled/api/meta"
16
+
17
+ module Bamboozled
18
+ class << self
19
+ # Creates a standard client that will raise all errors it encounters
20
+ def client(subdomain: nil, api_key: nil, httparty_options: {})
21
+ Bamboozled::Base.new(subdomain: subdomain, api_key: api_key, httparty_options: httparty_options)
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,101 @@
1
+ require 'json'
2
+ require "time"
3
+
4
+ module Bamboozled
5
+ module API
6
+ class Base
7
+ attr_reader :subdomain, :api_key
8
+
9
+ def initialize(subdomain, api_key, httparty_options = {})
10
+ @subdomain = subdomain
11
+ @api_key = api_key
12
+ @httparty_options = httparty_options || {}
13
+ end
14
+
15
+ protected
16
+
17
+ def post_file(path, options)
18
+ response = HTTParty.post(
19
+ "#{path_prefix}#{path}",
20
+ multipart: true,
21
+ basic_auth: auth,
22
+ body: options
23
+ )
24
+ parse_response(response, options)
25
+ end
26
+
27
+ def request(method, path, options = {})
28
+ params = {
29
+ path: path,
30
+ options: options,
31
+ method: method
32
+ }
33
+
34
+ httparty_options = @httparty_options.merge({
35
+ query: options[:query],
36
+ body: options[:body],
37
+ format: :plain,
38
+ basic_auth: auth,
39
+ headers: {
40
+ "Accept" => "application/json",
41
+ "User-Agent" => "Bamboozled/#{Bamboozled::VERSION}"
42
+ }.update(options[:headers] || {})
43
+ })
44
+
45
+ response = HTTParty.send(method, "#{path_prefix}#{path}", httparty_options)
46
+ params[:response] = response.inspect.to_s
47
+ parse_response(response, params)
48
+ end
49
+
50
+ private
51
+
52
+ def auth
53
+ { username: api_key, password: "x" }
54
+ end
55
+
56
+ def path_prefix
57
+ "https://api.bamboohr.com/api/gateway.php/#{subdomain}/v1/"
58
+ end
59
+
60
+ def parse_response(response, params)
61
+ case response.code
62
+ when 200..201
63
+ begin
64
+ if response.body.to_s.empty?
65
+ { "headers" => response.headers }
66
+ else
67
+ JSON.parse(response)
68
+ end
69
+ rescue
70
+ typecast = options.fetch(:typecast_values, true)
71
+ MultiXml.parse(response,
72
+ symbolize_keys: true,
73
+ typecast_xml_value: typecast)
74
+ end
75
+ when 400
76
+ raise Bamboozled::BadRequest.new(response, params, 'The request was invalid or could not be understood by the server. Resubmitting the request will likely result in the same error.')
77
+ when 401
78
+ raise Bamboozled::AuthenticationFailed.new(response, params, 'Your API key is missing.')
79
+ when 403
80
+ raise Bamboozled::Forbidden.new(response, params, 'The application is attempting to perform an action it does not have privileges to access. Verify your API key belongs to an enabled user with the required permissions.')
81
+ when 404
82
+ raise Bamboozled::NotFound.new(response, params, 'The resource was not found with the given identifier. Either the URL given is not a valid API, or the ID of the object specified in the request is invalid.')
83
+ when 406
84
+ raise Bamboozled::NotAcceptable.new(response, params, 'The request contains references to non-existent fields.')
85
+ when 409
86
+ raise Bamboozled::Conflict.new(response, params, 'The request attempts to create a duplicate. For employees, duplicate emails are not allowed. For lists, duplicate values are not allowed.')
87
+ when 429
88
+ raise Bamboozled::LimitExceeded.new(response, params, 'The account has reached its employee limit. No additional employees could be added.')
89
+ when 500
90
+ raise Bamboozled::InternalServerError.new(response, params, 'The server encountered an error while processing your request and failed.')
91
+ when 502
92
+ raise Bamboozled::GatewayError.new(response, params, 'The load balancer or web server had trouble connecting to the Bamboo app. Please try the request again.')
93
+ when 503
94
+ raise Bamboozled::ServiceUnavailable.new(response, params, 'The service is temporarily unavailable. Please try the request again.')
95
+ else
96
+ raise Bamboozled::InformBamboo.new(response, params, 'An error occurred that we do not now how to handle. Please contact BambooHR.')
97
+ end
98
+ end
99
+ end
100
+ end
101
+ end
@@ -0,0 +1,118 @@
1
+ module Bamboozled
2
+ module API
3
+ class Employee < Base
4
+
5
+ def all(fields = nil)
6
+ response = request(:get, "employees/directory")
7
+
8
+ if fields.nil? || fields == :default
9
+ Array(response['employees'])
10
+ else
11
+ employees = []
12
+ response['employees'].map{|e| e['id']}.each do |id|
13
+ employees << find(id, fields)
14
+ end
15
+ employees
16
+ end
17
+ end
18
+
19
+ def find(employee_id, fields = nil)
20
+ fields = FieldCollection.wrap(fields).to_csv
21
+
22
+ request(:get, "employees/#{employee_id}?fields=#{fields}&onlyCurrent=false")
23
+ end
24
+
25
+ def last_changed(date = "2011-06-05T00:00:00+00:00", type = nil)
26
+ query = Hash.new
27
+ query[:since] = date.respond_to?(:iso8601) ? date.iso8601 : date
28
+ query[:type] = type unless type.nil?
29
+
30
+ response = request(:get, "employees/changed", query: query)
31
+ response["employees"]
32
+ end
33
+
34
+ # Tabular data
35
+ [:job_info, :employment_status, :compensation, :dependents, :contacts].each do |action|
36
+ define_method(action.to_s) do |argument_id|
37
+ request(:get, "employees/#{argument_id}/tables/#{action.to_s.gsub(/_(.)/) {|e| $1.upcase}}")
38
+ end
39
+ end
40
+
41
+ def add_table_row(employee_id, table_name, table_row_data)
42
+ details = generate_xml(table_row_data)
43
+ options = {body: details}
44
+ request(:post, "employees/#{employee_id}/tables/#{table_name}", options)
45
+ end
46
+
47
+ def update_table_row(employee_id, table_name, row_id, table_row_data)
48
+ details = generate_xml(table_row_data)
49
+ options = {body: details}
50
+ request(:post, "employees/#{employee_id}/tables/#{table_name}/#{row_id}", options)
51
+ end
52
+
53
+ def table_data(employee_id, table_name)
54
+ request(:get, "employees/#{employee_id}/tables/#{table_name}")
55
+ end
56
+
57
+ def time_off_estimate(employee_id, end_date)
58
+ end_date = end_date.strftime("%F") unless end_date.is_a?(String)
59
+ request(:get, "employees/#{employee_id}/time_off/calculator?end=#{end_date}")
60
+ end
61
+
62
+ def photo_binary(employee_id)
63
+ request(:get, "employees/#{employee_id}/photo/small")
64
+ end
65
+
66
+ def photo_url(employee)
67
+ if (Float(employee) rescue false)
68
+ e = find(employee, ['workEmail', 'homeEmail'])
69
+ employee = e['workEmail'].nil? ? e['homeEmail'] : e['workEmail']
70
+ end
71
+
72
+ digest = Digest::MD5.new
73
+ digest.update(employee.strip.downcase)
74
+ "http://#{@subdomain}.bamboohr.com/employees/photos/?h=#{digest}"
75
+ end
76
+
77
+ def add(employee_details)
78
+ details = generate_xml(employee_details)
79
+ options = {body: details}
80
+
81
+ request(:post, "employees/", options)
82
+ end
83
+
84
+ def update(bamboo_id, employee_details)
85
+ details = generate_xml(employee_details)
86
+ options = { body: details }
87
+
88
+ request(:post, "employees/#{bamboo_id}", options)
89
+ end
90
+
91
+ def files(employee_id)
92
+ request(:get, "employees/#{employee_id}/files/view/")
93
+ end
94
+
95
+ def add_file(employee_id, file_details)
96
+ post_file("employees/#{employee_id}/files", file_details)
97
+ end
98
+
99
+ private
100
+
101
+ def generate_xml(employee_details)
102
+ "".tap do |xml|
103
+ xml << "<employee>"
104
+ employee_details.each do |k, v|
105
+ if v.is_a?(Hash)
106
+ value = Integer(v[:value], exception: false) ? v[:value] : v[:value].encode(xml: :text)
107
+ xml << "<field id='#{k}' currency='#{v[:currency]}'>#{value}</field>"
108
+ else
109
+ value = Integer(v, exception: false) ? v : v.encode(xml: :text)
110
+ xml << "<field id='#{k}'>#{value}</field>"
111
+ end
112
+ end
113
+ xml << "</employee>"
114
+ end
115
+ end
116
+ end
117
+ end
118
+ end
@@ -0,0 +1,107 @@
1
+ module Bamboozled
2
+ module API
3
+ class FieldCollection
4
+ def self.wrap(fields)
5
+ fields = all_names if fields == :all
6
+ fields = fields.split(",") if fields.is_a?(String)
7
+ new(fields)
8
+ end
9
+
10
+ def self.all_names # rubocop:disable Metrics/MethodLength
11
+ %w[
12
+ address1
13
+ address2
14
+ age
15
+ bestEmail
16
+ birthday
17
+ bonusAmount
18
+ bonusComment
19
+ bonusDate
20
+ bonusReason
21
+ city
22
+ commisionDate
23
+ commissionAmount
24
+ commissionComment
25
+ commissionDate
26
+ country
27
+ dateOfBirth
28
+ department
29
+ displayName
30
+ division
31
+ eeo
32
+ employeeNumber
33
+ employmentHistoryStatus
34
+ ethnicity
35
+ exempt
36
+ firstName
37
+ flsaCode
38
+ fullName1
39
+ fullName2
40
+ fullName3
41
+ fullName4
42
+ fullName5
43
+ gender
44
+ hireDate
45
+ homeEmail
46
+ homePhone
47
+ id
48
+ includeInPayroll
49
+ isPhotoUploaded
50
+ jobTitle
51
+ lastChanged
52
+ lastName
53
+ location
54
+ maritalStatus
55
+ middleName
56
+ mobilePhone
57
+ originalHireDate
58
+ paidPer
59
+ payChangeReason
60
+ payFrequency
61
+ payGroup
62
+ payGroupId
63
+ payPer
64
+ payRate
65
+ payRateEffectiveDate
66
+ paySchedule
67
+ payScheduleId
68
+ payType
69
+ preferredName
70
+ sin
71
+ ssn
72
+ standardHoursPerWeek
73
+ state
74
+ stateCode
75
+ status
76
+ supervisor
77
+ supervisorEId
78
+ supervisorId
79
+ terminationDate
80
+ workEmail
81
+ workPhone
82
+ workPhoneExtension
83
+ workPhonePlusExtension
84
+ zipcode
85
+ ]
86
+ end
87
+
88
+ def initialize(fields)
89
+ self.fields = fields || []
90
+ end
91
+
92
+ def to_csv
93
+ fields.join(",")
94
+ end
95
+
96
+ def to_xml
97
+ "<fields>" +
98
+ fields.map { |field| "<field id=\"#{field}\" />" }.join +
99
+ "</fields>"
100
+ end
101
+
102
+ private
103
+
104
+ attr_accessor :fields
105
+ end
106
+ end
107
+ end
@@ -0,0 +1,25 @@
1
+ module Bamboozled
2
+ module API
3
+ class Meta < Base
4
+ def users
5
+ request(:get, "meta/users").values
6
+ end
7
+
8
+ def fields
9
+ request(:get, "meta/fields")
10
+ end
11
+
12
+ def lists
13
+ request(:get, "meta/lists")
14
+ end
15
+
16
+ def tables
17
+ request(
18
+ :get, "meta/tables",
19
+ typecast_values: false)
20
+ end
21
+
22
+ end
23
+ end
24
+ end
25
+
@@ -0,0 +1,20 @@
1
+ module Bamboozled
2
+ module API
3
+ class Report < Base
4
+
5
+ def find(number, format = "JSON", fd_param = true)
6
+ response = request(:get, "reports/#{number}?format=#{format.upcase}&fd=#{fd_param.yesno}")
7
+ response["employees"]
8
+ end
9
+
10
+ def custom(fields, format = "JSON", only_current = false)
11
+ options = {
12
+ body: "<report>#{FieldCollection.wrap(fields).to_xml}</report>"
13
+ }
14
+
15
+ response = request(:post, "reports/custom?format=#{format.upcase}&onlyCurrent=#{only_current}", options)
16
+ response["employees"]
17
+ end
18
+ end
19
+ end
20
+ end