samanage 2.1.08 → 2.1.09

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
  SHA1:
3
- metadata.gz: e376a06968afc85a3f7f74f55999d7e548785b1d
4
- data.tar.gz: 6939690d8c8d2bfcaeb452a7ff072492f2f80fd1
3
+ metadata.gz: 1b27ba531435e04046290dc044b89fc0e2f51125
4
+ data.tar.gz: 203d7b74e6757cc0e8e97344e0e79b7fc97851de
5
5
  SHA512:
6
- metadata.gz: 6e4af5a38e8f3201079f00bec15360a66cad77dc4312dca56aaaa3afb131e7760ca8508a75baf3a0772ed60cd6798dc2d7c5db10542a047d7f79e9c9e5a33d8e
7
- data.tar.gz: 3bf8bc187267a37bdb101a97e0ebd45c9ebe8476851b77ee15ad2ad628a6dbed7273eb709f60c725dd20148ef6824d8683939ddea8c0a60894729be2bb46239b
6
+ metadata.gz: e44d497c9ff424afe653e29344904dae7290cec74af9c6f3695c00ae376db34136b47f179f83ad9025f46c35e82a31e342deee7a9216640618627fef1112da3b
7
+ data.tar.gz: 63f2235a968316f2c4617401c15f5e7fef12cf3e463c00621a7baa8e3d2c9d13b37ba89d4331ed4f3daa8853c4154c18e5164ff696c590d80ca6a4995e331a69
data/Gemfile CHANGED
@@ -2,7 +2,7 @@ source "https://rubygems.org"
2
2
 
3
3
  gem 'rspec'
4
4
 
5
- gem 'httparty', '0.15.7'
5
+ gem 'httparty', '0.16.4'
6
6
  gem 'ffi', '1.9.24'
7
7
  group :development do
8
8
  gem 'guard-rspec', require: false
data/Gemfile.lock CHANGED
@@ -22,7 +22,8 @@ GEM
22
22
  guard (~> 2.1)
23
23
  guard-compat (~> 1.1)
24
24
  rspec (>= 2.99.0, < 4.0)
25
- httparty (0.15.7)
25
+ httparty (0.16.4)
26
+ mime-types (~> 3.0)
26
27
  multi_xml (>= 0.5.2)
27
28
  i18n (1.1.0)
28
29
  concurrent-ruby (~> 1.0)
@@ -32,6 +33,9 @@ GEM
32
33
  ruby_dep (~> 1.2)
33
34
  lumberjack (1.0.13)
34
35
  method_source (0.9.0)
36
+ mime-types (3.2.2)
37
+ mime-types-data (~> 3.2015)
38
+ mime-types-data (3.2018.0812)
35
39
  multi_xml (0.6.0)
36
40
  nenv (0.3.0)
37
41
  notiffany (0.1.1)
@@ -67,7 +71,7 @@ DEPENDENCIES
67
71
  faker
68
72
  ffi (= 1.9.24)
69
73
  guard-rspec
70
- httparty (= 0.15.7)
74
+ httparty (= 0.16.4)
71
75
  rspec
72
76
 
73
77
  BUNDLED WITH
data/changelog.md CHANGED
@@ -1,3 +1,12 @@
1
+ ### 2.1.09
2
+ - Updated HTTParty to 0.16.4
3
+ - Optimize heavy audit+layout incident collection
4
+ - Adding purchase order support
5
+ - Added support for attachments
6
+ - Fix content type inheritance
7
+ - Use body instead of query params
8
+ - Handle HTTPTooManyRequests
9
+
1
10
  ### 2.1.08
2
11
  - Correct Message
3
12
 
data/lib/samanage.rb CHANGED
@@ -16,6 +16,7 @@ require 'samanage/api/incidents'
16
16
  require 'samanage/api/mobiles'
17
17
  require 'samanage/api/other_assets'
18
18
  require 'samanage/api/problems'
19
+ require 'samanage/api/purchase_orders'
19
20
  require 'samanage/api/requester'
20
21
  require 'samanage/api/sites'
21
22
  require 'samanage/api/solutions'
data/lib/samanage/api.rb CHANGED
@@ -7,17 +7,18 @@ module Samanage
7
7
  MAX_RETRIES = 3
8
8
  PATHS = {
9
9
  category: 'categories.json',
10
- contract: 'contracts.json',
11
10
  change: 'changes.json',
11
+ contract: 'contracts.json',
12
12
  custom_fields: 'custom_fields.json',
13
13
  custom_forms: 'custom_forms.json',
14
14
  department: 'departments.json',
15
15
  group: 'groups.json',
16
16
  hardware: 'hardwares.json',
17
- problem: 'problems.json',
18
17
  incident: 'incidents.json',
19
18
  mobile: 'mobiles.json',
20
19
  other_asset: 'other_assets.json',
20
+ problem: 'problems.json',
21
+ purchase_order: 'purchase_orders.json',
21
22
  site: 'sites.json',
22
23
  solution: 'solutions.json',
23
24
  user: 'users.json',
@@ -31,7 +32,7 @@ module Samanage
31
32
  end
32
33
  self.datacenter ||= datacenter.to_s.downcase
33
34
  self.base_url = "https://api#{self.datacenter.to_s.downcase}.samanage.com/"
34
- self.content_type = 'json'
35
+ self.content_type = content_type || 'json'
35
36
  self.admins = []
36
37
  self.max_retries = max_retries
37
38
  if development_mode
@@ -55,9 +56,9 @@ module Samanage
55
56
 
56
57
  # Calling execute without a method defaults to GET
57
58
  def execute(http_method: 'get', path: nil, payload: nil, verbose: nil, headers: {})
58
- if payload.class == String && self.content_type == 'json'
59
+ if payload.class == Hash && self.content_type == 'json'
59
60
  begin
60
- payload = JSON.parse(payload)
61
+ payload = payload.to_json
61
62
  rescue => e
62
63
  puts "Invalid JSON: #{payload.inspect}"
63
64
  raise Samanage::Error.new(error: e, response: nil)
@@ -84,17 +85,17 @@ module Samanage
84
85
  when 'get'
85
86
  api_call = self.class.get(full_path, headers: headers)
86
87
  when 'post'
87
- api_call = self.class.post(full_path, query: payload, headers: headers)
88
+ api_call = self.class.post(full_path, body: payload, headers: headers)
88
89
  when 'put'
89
- api_call = self.class.put(full_path, query: payload, headers: headers)
90
+ api_call = self.class.put(full_path, body: payload, headers: headers)
90
91
  when 'delete'
91
- api_call = self.class.delete(full_path, query: payload, headers: headers)
92
+ api_call = self.class.delete(full_path, body: payload, headers: headers)
92
93
  else
93
94
  raise Samanage::Error.new(response: {response: 'Unknown HTTP method'})
94
95
  end
95
- rescue Errno::ECONNREFUSED, Net::OpenTimeout, Errno::ETIMEDOUT, OpenSSL::SSL::SSLError, Errno::ENETDOWN, Errno::ECONNRESET, Errno::ENOENT, EOFError => e
96
+ rescue Errno::ECONNREFUSED, Net::OpenTimeout, Errno::ETIMEDOUT, OpenSSL::SSL::SSLError, Errno::ENETDOWN, Errno::ECONNRESET, Errno::ENOENT, EOFError, Net::HTTPTooManyRequests => e
96
97
  puts "[Warning] #{e.class}: #{e} - Retry: #{retries}/#{self.max_retries}"
97
- sleep 3
98
+ sleep 5
98
99
  retries += 1
99
100
  retry if retries < self.max_retries
100
101
  error = e
@@ -17,5 +17,26 @@ module Samanage
17
17
  end
18
18
  downloaded_attachment
19
19
  end
20
+
21
+ def create_attachment(filepath: , attachable_type: , attachable_id: )
22
+ if !File.exists?(filepath)
23
+ puts "Cannot find filepath: '#{filepath.inspect}'"
24
+ return
25
+ end
26
+ self.class.post(
27
+ self.base_url + 'attachments.json',
28
+ body: {
29
+ 'file[attachable_type]' => attachable_type,
30
+ 'file[attachable_id]' => attachable_id,
31
+ 'file[attachment]' => File.open(filepath, 'r')
32
+ },
33
+ headers: {
34
+ 'Content-Type' => 'multipart/form-data',
35
+ 'X-Samanage-Authorization' => 'Bearer ' + self.token
36
+ }
37
+ )
38
+ end
39
+
40
+
20
41
  end
21
42
  end
@@ -15,17 +15,17 @@ module Samanage
15
15
  # - layout: 'long'
16
16
  def collect_incidents(options: {})
17
17
  incidents = Array.new
18
- total_pages = self.get_incidents(options: options)[:total_pages]
18
+ total_pages = self.get_incidents(options: options.except(:audit_archives,:audit_archive,:layout))[:total_pages]
19
19
  puts "Pulling Incidents with Audit Archives (this may take a while)" if options[:audit_archives] && options[:verbose]
20
20
  1.upto(total_pages) do |page|
21
21
  puts "Collecting Incidents page: #{page}/#{total_pages}" if options[:verbose]
22
22
  if options[:audit_archives]
23
23
  options[:page] = page
24
- params = URI.encode_www_form(options.except!(:layout,'layout')) # layout not needed as audit only on individual record
24
+ params = URI.encode_www_form(options.except(:audit_archives,:audit_archive,:layout)) # layout not needed as audit only on individual record
25
25
  paginated_path = "incidents.json?" + params
26
26
  paginated_incidents = self.execute(path: paginated_path)[:data]
27
27
  paginated_incidents.map do |incident|
28
- params = self.set_params(options: options)
28
+ params = self.set_params(options: options.except(:audit_archives,:audit_archive,:layout))
29
29
  archive_uri = "incidents/#{incident['id']}.json?layout=long&audit_archive=true"
30
30
  incident_with_archive = self.execute(path: archive_uri)[:data]
31
31
  if block_given?
@@ -0,0 +1,62 @@
1
+ module Samanage
2
+ class Api
3
+
4
+ # Default get purchase_order path
5
+ def get_purchase_orders(path: PATHS[:purchase_order], options: {})
6
+ params = self.set_params(options: options)
7
+ path = 'purchase_orders.json?' + params
8
+ self.execute(path: path)
9
+ end
10
+
11
+
12
+ # Returns all purchase_orders.
13
+ # Options:
14
+ # - audit_archives: true
15
+ # - layout: 'long'
16
+ def collect_purchase_orders(options: {})
17
+ purchase_orders = Array.new
18
+ total_pages = self.get_purchase_orders(options: options)[:total_pages]
19
+ 1.upto(total_pages) do |page|
20
+ options[:page] = page
21
+ params = self.set_params(options: options)
22
+ puts "Collecting purchase_orders page: #{page}/#{total_pages}" if options[:verbose]
23
+ path = "purchase_orders.json?" + params
24
+ request = self.execute(http_method: 'get', path: path)
25
+ request[:data].each do |purchase_order|
26
+ if block_given?
27
+ yield purchase_order
28
+ end
29
+ purchase_orders << purchase_order
30
+ end
31
+ end
32
+ purchase_orders
33
+ end
34
+
35
+
36
+ # Create an purchase_order given json
37
+ def create_purchase_order(payload: nil, options: {})
38
+ self.execute(path: PATHS[:purchase_order], http_method: 'post', payload: payload)
39
+ end
40
+
41
+ # Find purchase_order by ID
42
+ def find_purchase_order(id: , options: {})
43
+ path = "purchase_orders/#{id}.json"
44
+ if options[:layout] == 'long'
45
+ path += '?layout=long'
46
+ end
47
+ self.execute(path: path)
48
+ end
49
+
50
+ # Update an purchase_order given id and json
51
+ def update_purchase_order(payload: , id: , options: {})
52
+ path = "purchase_orders/#{id}.json"
53
+ self.execute(path: path, http_method: 'put', payload: payload)
54
+ end
55
+
56
+ def delete_purchase_order(id: )
57
+ self.execute(path: "purchase_orders/#{id}.json", http_method: 'delete')
58
+ end
59
+
60
+ alias_method :purchase_orders, :collect_purchase_orders
61
+ end
62
+ end
@@ -1,5 +1,7 @@
1
1
  require 'open-uri'
2
2
  require 'fileutils'
3
+
4
+ # API Utils
3
5
  module Samanage
4
6
  class Api
5
7
  def send_activation_email(email: )
@@ -7,5 +9,14 @@ module Samanage
7
9
  raise Samanage::Error.new(error: 'Invalid Email', response: {}) unless user_id
8
10
  self.execute(http_method: 'put', path: "users/#{user_id}.json?send_activation_email=1&add_callbacks=1")
9
11
  end
12
+
13
+ def find_name_from_group_id(group_id: )
14
+ return if [-1,'-1',nil,''].include?(group_id)
15
+ begin
16
+ self.find_group(id: group_id).to_h.dig(:data,'name')
17
+ rescue => e
18
+ return "Unable to find user for group id #{group_id}"
19
+ end
20
+ end
10
21
  end
11
22
  end
@@ -0,0 +1,24 @@
1
+ # Consts and common lookup functions
2
+ module Samanage
3
+ INCIDENT_ORIGIN = {
4
+ 1 => 'Web',
5
+ 2 => 'Portal',
6
+ 3 => 'API',
7
+ 4 => 'Email'
8
+ }
9
+
10
+ def find_custom_field(custom_fields_values: , field_name:, user_type: false, user_resolve: 'email')
11
+ result = custom_fields_values.select{|field| field['name'] == field_name}.first.to_h
12
+ if user_type
13
+ if user_resolve == 'email'
14
+ result_value = result.dig('user','email')
15
+ else
16
+ result_value = result.dig('user','name')
17
+ end
18
+ else
19
+ result_value = result.dig('value')
20
+ end
21
+ return if [-1,'-1',nil,'',{}].include?(result_value)
22
+ result_value
23
+ end
24
+ end
@@ -1,3 +1,3 @@
1
1
  module Samanage
2
- VERSION = '2.1.08'
2
+ VERSION = '2.1.09'
3
3
  end
@@ -51,7 +51,6 @@ describe Samanage::Api do
51
51
  it 'find_change: returns a change card by known id' do
52
52
  sample_id = @changes.sample['id']
53
53
  change = @samanage.find_change(id: sample_id)
54
-
55
54
  expect(change[:data]['id']).to eq(sample_id) # id should match found change
56
55
  expect(change[:data]).to have_key('name')
57
56
  expect(change[:data]).to have_key('requester')
@@ -32,7 +32,7 @@ describe Samanage::Api do
32
32
 
33
33
  expect(department_create[:data]['id']).to be_an(Integer)
34
34
  expect(department_create[:data]['name']).to eq(department_name)
35
- expect(department_create[:code]).to eq(201).or(200)
35
+ expect(department_create[:code]).to eq(200).or(201)
36
36
  end
37
37
  it 'deletes a valid department' do
38
38
  sample_department_id = @departments.sample.dig('id')
@@ -1,3 +1,4 @@
1
+ require 'faker'
1
2
  require 'samanage'
2
3
  describe Samanage::Api do
3
4
  context 'group' do
@@ -46,7 +47,6 @@ describe Samanage::Api do
46
47
  group_name = group['name']
47
48
  group_id = group['id']
48
49
  found_group_id = @samanage.find_group_id_by_name(group: group_name)
49
-
50
50
  expect(group_id).to eq(found_group_id)
51
51
  end
52
52
  it 'returns nil for finding invalid group by name' do
@@ -0,0 +1,82 @@
1
+ require 'samanage'
2
+ require 'faker'
3
+ describe Samanage::Api do
4
+ context 'purchase_orders' do
5
+ describe 'API Functions' do
6
+ before(:all) do
7
+ TOKEN ||= ENV['SAMANAGE_TEST_API_TOKEN']
8
+ @samanage = Samanage::Api.new(token: TOKEN)
9
+ @purchase_orders = @samanage.purchase_orders
10
+ @users = @samanage.get_users[:data]
11
+ end
12
+ it 'get_purchase_orders: it returns API call of purchase_orders' do
13
+ api_call = @samanage.get_purchase_orders
14
+ expect(api_call).to be_a(Hash)
15
+ expect(api_call[:total_count]).to be_an(Integer)
16
+ expect(api_call).to have_key(:response)
17
+ expect(api_call).to have_key(:code)
18
+ end
19
+ it 'collect_purchase_orders: collects array of purchase_orders' do
20
+ purchase_order_count = @samanage.get_purchase_orders[:total_count]
21
+ @purchase_orders = @samanage.purchase_orders
22
+ expect(@purchase_orders).to be_an(Array)
23
+ expect(@purchase_orders.size).to eq(purchase_order_count)
24
+ end
25
+ it 'create_purchase_order(payload: json): creates a purchase_order' do
26
+ users_email = @samanage.collect_users.sample['email']
27
+ purchase_order_name = Faker::Book.title
28
+ json = {
29
+ purchase_order: {
30
+ buyer: {email: users_email},
31
+ name: purchase_order_name,
32
+ vendor: {name: 'Samanage'}
33
+ }
34
+ }
35
+ purchase_order_create = @samanage.create_purchase_order(payload: json)
36
+ expect(purchase_order_create[:data]['id']).to be_an(Integer)
37
+ expect(purchase_order_create[:data]['name']).to eq(purchase_order_name)
38
+ expect(purchase_order_create[:code]).to eq(200).or(201)
39
+ end
40
+ it 'create_purchase_order: fails if no name/title' do
41
+ users_email = @users.sample['email']
42
+ json = {
43
+ :purchase_order => {
44
+ :buyer => {:email => users_email},
45
+ :description => "Description"
46
+ }
47
+ }
48
+ expect{@samanage.create_purchase_order(payload: json)}.to raise_error(Samanage::InvalidRequest)
49
+ end
50
+ it 'find_purchase_order: returns a purchase_order card by known id' do
51
+ sample_id = @purchase_orders.sample['id']
52
+ purchase_order = @samanage.find_purchase_order(id: sample_id)
53
+ expect(purchase_order[:data]['id']).to eq(sample_id) # id should match found purchase_order
54
+ expect(purchase_order[:data]).to have_key('name')
55
+ expect(purchase_order[:data]).to have_key('id')
56
+ end
57
+
58
+ it 'find_purchase_order: returns nothing for an invalid id' do
59
+ sample_id = (0..10).entries.sample
60
+ expect{@samanage.find_purchase_order(id: sample_id)}.to raise_error(Samanage::NotFound) # id should match found purchase_order
61
+ end
62
+ it 'update_purchase_order: update_purchase_order by id' do
63
+ sample_purchase_order = @purchase_orders.reject{|i| ['Closed','Resolved'].include? i['state']}.sample
64
+ sample_id = sample_purchase_order['id']
65
+ description = (0...500).map { ('a'..'z').to_a[rand(26)] }.join
66
+ purchase_order_json = {
67
+ :purchase_order => {
68
+ :description => description
69
+ }
70
+ }
71
+ purchase_order_update = @samanage.update_purchase_order(payload: purchase_order_json, id: sample_id)
72
+ # expect(purchase_order_update[:data]['description']).to eq(description) # purchase_order bug #00044569
73
+ expect(purchase_order_update[:code]).to eq(200).or(201)
74
+ end
75
+ it 'deletes a valid purchase_order' do
76
+ sample_purchase_order_id = @purchase_orders.sample['id']
77
+ purchase_order_delete = @samanage.delete_purchase_order(id: sample_purchase_order_id)
78
+ expect(purchase_order_delete[:code]).to eq(200).or(201)
79
+ end
80
+ end
81
+ end
82
+ end
@@ -1,4 +1,5 @@
1
1
  require 'samanage'
2
+ require 'faker'
2
3
  describe Samanage::Api do
3
4
  context 'Site' do
4
5
  before(:all) do
@@ -34,7 +35,7 @@ describe Samanage::Api do
34
35
 
35
36
  expect(site_create[:data]['id']).to be_an(Integer)
36
37
  expect(site_create[:data]['name']).to eq(site_name)
37
- expect(site_create[:code]).to eq(201).or(200)
38
+ expect(site_create[:code]).to eq(200).or(201)
38
39
  end
39
40
  it 'deletes a valid site' do
40
41
  sample_site_id = @sites.sample['id']
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: samanage
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.1.08
4
+ version: 2.1.09
5
5
  platform: ruby
6
6
  authors:
7
7
  - Chris Walls
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2018-10-24 00:00:00.000000000 Z
11
+ date: 2019-03-28 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: httparty
@@ -67,6 +67,7 @@ files:
67
67
  - lib/samanage/api/mobiles.rb
68
68
  - lib/samanage/api/other_assets.rb
69
69
  - lib/samanage/api/problems.rb
70
+ - lib/samanage/api/purchase_orders.rb
70
71
  - lib/samanage/api/requester.rb
71
72
  - lib/samanage/api/sites.rb
72
73
  - lib/samanage/api/solutions.rb
@@ -76,6 +77,7 @@ files:
76
77
  - lib/samanage/error.rb
77
78
  - lib/samanage/language.rb
78
79
  - lib/samanage/url_builder.rb
80
+ - lib/samanage/utils.rb
79
81
  - lib/samanage/version.rb
80
82
  - samanage.gemspec
81
83
  - sample_file.txt
@@ -93,6 +95,7 @@ files:
93
95
  - spec/api/samanage_mobile_spec.rb
94
96
  - spec/api/samanage_other_asset_spec.rb
95
97
  - spec/api/samanage_problem_spec.rb
98
+ - spec/api/samanage_purchase_order_spec.rb
96
99
  - spec/api/samanage_site_spec.rb
97
100
  - spec/api/samanage_solution_spec.rb
98
101
  - spec/api/samanage_time_tracks_spec.rb