toolhound-ruby 1.0.4
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 +7 -0
- data/AUTHORS.md +7 -0
- data/CHANGELOG.md +16 -0
- data/Gemfile +10 -0
- data/LICENSE +20 -0
- data/Makefile +15 -0
- data/README.md +208 -0
- data/Rakefile +10 -0
- data/lib/toolhound-ruby.rb +50 -0
- data/lib/toolhound-ruby/authentication.rb +64 -0
- data/lib/toolhound-ruby/base.rb +94 -0
- data/lib/toolhound-ruby/client.rb +113 -0
- data/lib/toolhound-ruby/client/account.rb +14 -0
- data/lib/toolhound-ruby/client/attachments.rb +88 -0
- data/lib/toolhound-ruby/client/bookmarks.rb +39 -0
- data/lib/toolhound-ruby/client/categories.rb +72 -0
- data/lib/toolhound-ruby/client/comments.rb +0 -0
- data/lib/toolhound-ruby/client/companies.rb +76 -0
- data/lib/toolhound-ruby/client/incidents.rb +167 -0
- data/lib/toolhound-ruby/client/notifications.rb +31 -0
- data/lib/toolhound-ruby/client/projects.rb +40 -0
- data/lib/toolhound-ruby/client/users.rb +81 -0
- data/lib/toolhound-ruby/configurable.rb +47 -0
- data/lib/toolhound-ruby/core_ext/string.rb +16 -0
- data/lib/toolhound-ruby/default.rb +57 -0
- data/lib/toolhound-ruby/error.rb +186 -0
- data/lib/toolhound-ruby/incident.rb +75 -0
- data/lib/toolhound-ruby/inventory.rb +19 -0
- data/lib/toolhound-ruby/project.rb +17 -0
- data/lib/toolhound-ruby/version.rb +3 -0
- data/script/bootstrap +5 -0
- data/script/cibuild +5 -0
- data/script/console +15 -0
- data/script/package +7 -0
- data/script/release +16 -0
- data/script/test +0 -0
- data/toolhound-ruby.gemspec +33 -0
- metadata +110 -0
@@ -0,0 +1,31 @@
|
|
1
|
+
module Nearmiss
|
2
|
+
class Client
|
3
|
+
|
4
|
+
# Methods for the Notifications API
|
5
|
+
#
|
6
|
+
module Notifications
|
7
|
+
|
8
|
+
# List notifications for the current user
|
9
|
+
#
|
10
|
+
# If user is not supplied, repositories for the current
|
11
|
+
# authenticated user are returned.
|
12
|
+
#
|
13
|
+
# @note If the user provided is a GitHub organization, only the
|
14
|
+
# organization's public repositories will be listed. For retrieving
|
15
|
+
# organization repositories the {Organizations#organization_repositories}
|
16
|
+
# method should be used instead.
|
17
|
+
# @see https://developer.github.com/v3/repos/#list-your-repositories
|
18
|
+
# @see https://developer.github.com/v3/repos/#list-user-repositories
|
19
|
+
# @param user [Integer, String] Optional GitHub user login or id for which
|
20
|
+
# to list repos.
|
21
|
+
# @return [Array<Sawyer::Resource>] List of projects
|
22
|
+
def notifications(options = {})
|
23
|
+
paginate "notifications", options
|
24
|
+
end
|
25
|
+
alias :list_notifications :notifications
|
26
|
+
|
27
|
+
|
28
|
+
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
@@ -0,0 +1,40 @@
|
|
1
|
+
module Toolhound
|
2
|
+
class Client
|
3
|
+
|
4
|
+
# Methods for the Projects API
|
5
|
+
#
|
6
|
+
module Projects
|
7
|
+
|
8
|
+
# List projects
|
9
|
+
#
|
10
|
+
# @note Shows a list of projects for the users organization aka account
|
11
|
+
#
|
12
|
+
# @return [Array<Sawyer::Resource>] List of projects
|
13
|
+
def projects(options = {})
|
14
|
+
# paginate "projects", options
|
15
|
+
|
16
|
+
end
|
17
|
+
# alias :list_projects :projects
|
18
|
+
|
19
|
+
# Get a single project
|
20
|
+
#
|
21
|
+
# @param project [String] UUID of project to fetch
|
22
|
+
# @return [Sawyer::Resource] Project information
|
23
|
+
#
|
24
|
+
def project(project, options = {})
|
25
|
+
# get "#{project_path(project)}", options
|
26
|
+
end
|
27
|
+
|
28
|
+
private
|
29
|
+
|
30
|
+
# def project_path(id)
|
31
|
+
# if uuid?(id)
|
32
|
+
# "projects/#{id}"
|
33
|
+
# else
|
34
|
+
# "project/#{id}"
|
35
|
+
# end
|
36
|
+
# end
|
37
|
+
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
@@ -0,0 +1,81 @@
|
|
1
|
+
module Toolhound
|
2
|
+
class Client
|
3
|
+
|
4
|
+
# Methods for the Users API
|
5
|
+
#
|
6
|
+
module Users
|
7
|
+
|
8
|
+
# List users
|
9
|
+
#
|
10
|
+
# @note Logged in user must be an admin to see all users
|
11
|
+
#
|
12
|
+
# @return [Array<Sawyer::Resource>] List of projects
|
13
|
+
def users(options = {})
|
14
|
+
paginate "users", options
|
15
|
+
end
|
16
|
+
alias :list_users :users
|
17
|
+
|
18
|
+
# Get a single user
|
19
|
+
#
|
20
|
+
# @param user [String] Nearmiss user email or id.
|
21
|
+
# @return [Sawyer::Resource]
|
22
|
+
# @example
|
23
|
+
# Nearmiss.user("31817811-dce4-48c4-aa5f-f49603c5abee") or Nearmiss.user("m4rkuskk+a@gmail.com")
|
24
|
+
def user(user=nil, options = {})
|
25
|
+
if user.nil?
|
26
|
+
get "me", options
|
27
|
+
else
|
28
|
+
get "users/#{user}", options
|
29
|
+
end
|
30
|
+
# get User.path(user), options
|
31
|
+
end
|
32
|
+
|
33
|
+
# Edit a user
|
34
|
+
#
|
35
|
+
# @param options [Hash] User information.
|
36
|
+
# @option options [String] :email Email of user
|
37
|
+
# @option options [String] :name Name of user
|
38
|
+
# @option options [String] :nickname Nickname of user
|
39
|
+
# @option options [Integer] :role Set to admin or not
|
40
|
+
# @option options [String] :phone_number Phone number of user
|
41
|
+
# @option options [String] :image URL of image of user
|
42
|
+
# @option options [String] :language Code "en", "de", "es"
|
43
|
+
# @return
|
44
|
+
# [Sawyer::Resource] Edited user info
|
45
|
+
# @example Update a user
|
46
|
+
# @client.edit_user('some_id', {
|
47
|
+
# email: "mklooth@webcor.com",
|
48
|
+
# name: "Markus Klooth"
|
49
|
+
# })
|
50
|
+
#
|
51
|
+
def edit_user(user, options = {})
|
52
|
+
patch "update_user/#{user}", options
|
53
|
+
end
|
54
|
+
alias :update_user :edit_user
|
55
|
+
|
56
|
+
def update_email(user, options = {})
|
57
|
+
|
58
|
+
end
|
59
|
+
|
60
|
+
|
61
|
+
def delete_user(user, options = {})
|
62
|
+
delete "users/#{user}", options
|
63
|
+
end
|
64
|
+
|
65
|
+
# Validate user username and password
|
66
|
+
#
|
67
|
+
# @param options [Hash] User credentials
|
68
|
+
# @option options [String] :email Nearmiss login email
|
69
|
+
# @option options [String] :password Nearmiss password
|
70
|
+
# @return [Boolean] True if credentials are valid
|
71
|
+
def validate_credentials(options = {})
|
72
|
+
!self.class.new(options).user.nil?
|
73
|
+
rescue Toolhound::Unauthorized
|
74
|
+
false
|
75
|
+
end
|
76
|
+
|
77
|
+
|
78
|
+
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
@@ -0,0 +1,47 @@
|
|
1
|
+
module Toolhound
|
2
|
+
module Configurable
|
3
|
+
|
4
|
+
# attr_accessor :auto_paginate, :per_page
|
5
|
+
attr_writer :dataserver, :port, :username, :password
|
6
|
+
|
7
|
+
# Define static methods
|
8
|
+
class << self
|
9
|
+
|
10
|
+
def keys
|
11
|
+
@keys ||= [
|
12
|
+
:dataserver,
|
13
|
+
:port,
|
14
|
+
:username,
|
15
|
+
:password
|
16
|
+
]
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
# Set configuration options using a block
|
21
|
+
def configure
|
22
|
+
yield self
|
23
|
+
end
|
24
|
+
|
25
|
+
# Reset configuration options to default values
|
26
|
+
def reset!
|
27
|
+
Toolhound::Configurable.keys.each do |key|
|
28
|
+
instance_variable_set(:"@#{key}", Toolhound::Default.options[key])
|
29
|
+
end
|
30
|
+
self
|
31
|
+
end
|
32
|
+
alias setup reset!
|
33
|
+
|
34
|
+
# def api_endpoint
|
35
|
+
# File.join(@api_endpoint, "")
|
36
|
+
# end
|
37
|
+
|
38
|
+
def options
|
39
|
+
Hash[Toolhound::Configurable.keys.map{|key| [key, instance_variable_get(:"@#{key}")]}]
|
40
|
+
end
|
41
|
+
|
42
|
+
private
|
43
|
+
|
44
|
+
|
45
|
+
|
46
|
+
end
|
47
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
class String
|
2
|
+
|
3
|
+
def underscore()
|
4
|
+
camel_cased_word = self
|
5
|
+
acronym_regex = /#{["REST"].join("|")}/
|
6
|
+
return camel_cased_word unless camel_cased_word =~ /[A-Z-]|::/
|
7
|
+
word = camel_cased_word.to_s.gsub('::'.freeze, '/'.freeze)
|
8
|
+
word.gsub!(/(?:(?<=([A-Za-z\d]))|\b)(#{acronym_regex})(?=\b|[^a-z])/) { "#{$1 && '_'.freeze }#{$2.downcase}" }
|
9
|
+
word.gsub!(/([A-Z\d]+)([A-Z][a-z])/, '\1_\2'.freeze)
|
10
|
+
word.gsub!(/([a-z\d])([A-Z])/, '\1_\2'.freeze)
|
11
|
+
word.tr!("-".freeze, "_".freeze)
|
12
|
+
word.downcase!
|
13
|
+
word
|
14
|
+
end
|
15
|
+
|
16
|
+
end
|
@@ -0,0 +1,57 @@
|
|
1
|
+
# require "bim360/version" unless defined?(Nearmiss::VERSION)
|
2
|
+
# require 'bim360/response/raise_error'
|
3
|
+
|
4
|
+
module Toolhound
|
5
|
+
|
6
|
+
|
7
|
+
# Default configuration options for {Client}
|
8
|
+
module Default
|
9
|
+
|
10
|
+
# Default User Agent header string
|
11
|
+
|
12
|
+
PORT = 1433.freeze
|
13
|
+
|
14
|
+
class << self
|
15
|
+
|
16
|
+
# Configuration options
|
17
|
+
# @return [Hash]
|
18
|
+
def options
|
19
|
+
Hash[Toolhound::Configurable.keys.map{|key| [key, send(key)]}]
|
20
|
+
end
|
21
|
+
|
22
|
+
|
23
|
+
|
24
|
+
def config
|
25
|
+
@config ||= {}
|
26
|
+
end
|
27
|
+
|
28
|
+
# Default API endpoint from ENV or {API_ENDPOINT}
|
29
|
+
# @return [String]
|
30
|
+
def dataserver
|
31
|
+
ENV['TOOLHOUND_DATASERVER'] || config['dataserver']
|
32
|
+
end
|
33
|
+
|
34
|
+
def port
|
35
|
+
ENV['TOOLHOUND_PORT'] || config['port'] || PORT
|
36
|
+
end
|
37
|
+
|
38
|
+
# Default BIM360-Field username for Basic Auth from ENV
|
39
|
+
# @return [String]
|
40
|
+
def username
|
41
|
+
ENV['TOOLHOUND_USERNAME'] || config['username']
|
42
|
+
# ENV['NEARMISS_EMAIL'] || config['email']
|
43
|
+
end
|
44
|
+
|
45
|
+
# Default BIM360-Field password for Basic Auth from ENV
|
46
|
+
# @return [String]
|
47
|
+
def password
|
48
|
+
ENV['TOOLHOUND_PASSWORD'] || config['password']
|
49
|
+
end
|
50
|
+
|
51
|
+
|
52
|
+
|
53
|
+
|
54
|
+
end
|
55
|
+
|
56
|
+
end
|
57
|
+
end
|
@@ -0,0 +1,186 @@
|
|
1
|
+
module Toolhound
|
2
|
+
# Custom error class for rescuing from all GitHub errors
|
3
|
+
class Error < StandardError
|
4
|
+
|
5
|
+
# Returns the appropriate Nearmiss::Error sublcass based
|
6
|
+
# on status and response message
|
7
|
+
#
|
8
|
+
# @param [Hash] response HTTP response
|
9
|
+
# @return [Nearmiss::Error]
|
10
|
+
def self.from_response(response)
|
11
|
+
status = response[:status].to_i
|
12
|
+
body = response[:body].to_s
|
13
|
+
headers = response[:response_headers]
|
14
|
+
|
15
|
+
|
16
|
+
|
17
|
+
if klass = case status
|
18
|
+
when 400 then Nearmiss::BadRequest
|
19
|
+
when 401 then Nearmiss::Unauthorized
|
20
|
+
when 403 then Nearmiss::Forbidden
|
21
|
+
when 404 then Nearmiss::NotFound
|
22
|
+
when 405 then Nearmiss::MethodNotAllowed
|
23
|
+
when 406 then Nearmiss::NotAcceptable
|
24
|
+
when 409 then Nearmiss::Conflict
|
25
|
+
when 415 then Nearmiss::UnsupportedMediaType
|
26
|
+
when 422 then Nearmiss::UnprocessableEntity
|
27
|
+
when 400..499 then Nearmiss::ClientError
|
28
|
+
when 500 then Nearmiss::InternalServerError
|
29
|
+
when 501 then Nearmiss::NotImplemented
|
30
|
+
when 502 then Nearmiss::BadGateway
|
31
|
+
when 503 then Nearmiss::ServiceUnavailable
|
32
|
+
when 500..599 then Nearmiss::ServerError
|
33
|
+
end
|
34
|
+
klass.new(response)
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
def initialize(response=nil)
|
39
|
+
@response = response
|
40
|
+
super(build_error_message)
|
41
|
+
end
|
42
|
+
|
43
|
+
# Array of validation errors
|
44
|
+
# @return [Array<Hash>] Error info
|
45
|
+
def errors
|
46
|
+
if data && data.is_a?(Hash)
|
47
|
+
data[:errors] || []
|
48
|
+
else
|
49
|
+
[]
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
# private
|
54
|
+
|
55
|
+
def data
|
56
|
+
@data ||=
|
57
|
+
if (body = @response[:body]) && !body.empty?
|
58
|
+
if body.is_a?(String) &&
|
59
|
+
@response[:response_headers] &&
|
60
|
+
@response[:response_headers][:content_type] =~ /json/
|
61
|
+
|
62
|
+
# Sawyer::Agent.serializer.decode(body)
|
63
|
+
else
|
64
|
+
body
|
65
|
+
end
|
66
|
+
else
|
67
|
+
nil
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
def response_message
|
72
|
+
case data
|
73
|
+
when Hash
|
74
|
+
data[:message]
|
75
|
+
when String
|
76
|
+
data
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
def response_error
|
81
|
+
"Error: #{data[:error]}" if data.is_a?(Hash) && data[:error]
|
82
|
+
end
|
83
|
+
|
84
|
+
def response_error_summary
|
85
|
+
return nil unless data.is_a?(Hash) && !Array(data[:errors]).empty?
|
86
|
+
|
87
|
+
summary = "\nError summary:\n"
|
88
|
+
# summary << data[:errors].map do |hash|
|
89
|
+
# hash.map { |k,v| " #{k}: #{v}" }
|
90
|
+
# end.join("\n")
|
91
|
+
summary << data[:errors].join("\n")
|
92
|
+
summary
|
93
|
+
end
|
94
|
+
|
95
|
+
def build_error_message
|
96
|
+
return nil if @response.nil?
|
97
|
+
|
98
|
+
|
99
|
+
|
100
|
+
message = "#{@response[:method].to_s.upcase} "
|
101
|
+
message << redact_url(@response[:url].to_s) + ": "
|
102
|
+
message << "#{@response[:status]} - "
|
103
|
+
message << "#{response_message}" unless response_message.nil?
|
104
|
+
message << "#{response_error}" unless response_error.nil?
|
105
|
+
message << "#{response_error_summary}" unless response_error_summary.nil?
|
106
|
+
# message << MultiJson.dump(@response)
|
107
|
+
message
|
108
|
+
end
|
109
|
+
|
110
|
+
def redact_url(url_string)
|
111
|
+
%w[client_secret access_token].each do |token|
|
112
|
+
url_string.gsub!(/#{token}=\S+/, "#{token}=(redacted)") if url_string.include? token
|
113
|
+
end
|
114
|
+
url_string
|
115
|
+
end
|
116
|
+
|
117
|
+
end
|
118
|
+
|
119
|
+
# Raised on errors in the 400-499 range
|
120
|
+
class ClientError < Error; end
|
121
|
+
|
122
|
+
# Raised when Nearmiss-Field returns a 400 HTTP status code
|
123
|
+
class BadRequest < ClientError; end
|
124
|
+
|
125
|
+
# Raised when Nearmiss-Field returns a 401 HTTP status code
|
126
|
+
class Unauthorized < ClientError; end
|
127
|
+
# Raised when Nearmiss-Field returns a 403 HTTP status code
|
128
|
+
class Forbidden < ClientError; end
|
129
|
+
|
130
|
+
# Raised when Nearmiss-Field returns a 403 HTTP status code
|
131
|
+
# and body matches 'rate limit exceeded'
|
132
|
+
class TooManyRequests < Forbidden; end
|
133
|
+
|
134
|
+
# Raised when Nearmiss-Field returns a 403 HTTP status code
|
135
|
+
# and body matches 'login attempts exceeded'
|
136
|
+
class TooManyLoginAttempts < Forbidden; end
|
137
|
+
|
138
|
+
# Raised when Nearmiss-Field returns a 404 HTTP status code
|
139
|
+
class NotFound < ClientError; end
|
140
|
+
|
141
|
+
# Raised when Nearmiss-Field returns a 405 HTTP status code
|
142
|
+
class MethodNotAllowed < ClientError; end
|
143
|
+
|
144
|
+
# Raised when Nearmiss-Field returns a 406 HTTP status code
|
145
|
+
class NotAcceptable < ClientError; end
|
146
|
+
|
147
|
+
# Raised when Nearmiss-Field returns a 409 HTTP status code
|
148
|
+
class Conflict < ClientError; end
|
149
|
+
|
150
|
+
# Raised when Nearmiss-Field returns a 414 HTTP status code
|
151
|
+
class UnsupportedMediaType < ClientError; end
|
152
|
+
|
153
|
+
# Raised when Nearmiss-Field returns a 422 HTTP status code
|
154
|
+
class UnprocessableEntity < ClientError; end
|
155
|
+
|
156
|
+
# Raised on errors in the 500-599 range
|
157
|
+
class ServerError < Error; end
|
158
|
+
|
159
|
+
# Raised when Nearmiss-Field returns a 500 HTTP status code
|
160
|
+
class InternalServerError < ServerError; end
|
161
|
+
|
162
|
+
# Raised when Nearmiss-Field returns a 501 HTTP status code
|
163
|
+
class NotImplemented < ServerError; end
|
164
|
+
|
165
|
+
# Raised when Nearmiss-Field returns a 502 HTTP status code
|
166
|
+
class BadGateway < ServerError; end
|
167
|
+
|
168
|
+
# Raised when Nearmiss-Field returns a 503 HTTP status code
|
169
|
+
class ServiceUnavailable < ServerError; end
|
170
|
+
|
171
|
+
# Raised when client fails to provide valid Content-Type
|
172
|
+
class MissingContentType < ArgumentError; end
|
173
|
+
|
174
|
+
# Raised when a method requires an application client_id
|
175
|
+
# and secret but none is provided
|
176
|
+
class ApplicationCredentialsRequired < StandardError; end
|
177
|
+
|
178
|
+
class MissingParams < Error
|
179
|
+
def initialize(msg)
|
180
|
+
|
181
|
+
msg
|
182
|
+
end
|
183
|
+
end
|
184
|
+
|
185
|
+
|
186
|
+
end
|