halo_msp_api 0.1.0 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 98f866a474dd58a86344301d4265af815a4d6582cabb6de0dfe5a94d572116d8
4
- data.tar.gz: 450ae6c2367fd9acb743fb6b81a183f6218de528868de4dafc63c8893f18cffb
3
+ metadata.gz: 298a5f2396dac7b60b32afb4d7e12af0797c20ea696f06adb5b7cde91a1802e7
4
+ data.tar.gz: a90a79d513e0384e0ac3b3fac4c4f10afa99616b0e4d21f032a21d5958d8dab9
5
5
  SHA512:
6
- metadata.gz: b16bbea790fd1fbc1daf00ba83665261735569d9b0ab4d6fc0ce29562fdad32008220d950e59614712d74a43f476e452ab1e851c5336ea2639362096563c8d47
7
- data.tar.gz: 1b09d2963bed4af41b7c0e680cf814cb05981c8379b333ed67316a1bf589cd6f3da08f6ec134ded8dfa6c6a029dac038ce8e61764b5be28a3ab442f2783cd13d
6
+ metadata.gz: 0fb09a0fca38736575835d473bd52f0836b4f520e1442a9a7c8faa3f0d65e738789a9a52d4c620cccbc9730da822552283afd1f958ca4cf7a90917d029e4b1f8
7
+ data.tar.gz: 20bd7c4acb3661c4240b2749fa15f9b02181d17ba08b40d233e7f4811cea86a78c112fc7b2c547419de56c63629134f1862ba5d561e3c833c0391fe6ac29a009
data/CHANGELOG.md CHANGED
@@ -5,6 +5,23 @@ All notable changes to this project will be documented in this file.
5
5
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
6
6
  and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
7
 
8
+ ## [0.2.0] - 2025-09-15
9
+
10
+ ### Added
11
+ - Products Resource Class
12
+
13
+ ### Fixed
14
+ - Renamed Create/Update/Get/Delete methods in resource classes that caused an infinite recursion error
15
+ - Fixed specs that were failing due to naming mismatched/mocking API requests
16
+
17
+ ### Changed
18
+ - Updated `basic_usage.rb` examples to print specific data for verification
19
+ - Updated project to adhere to rubocop linting rules
20
+ - Removed support for Ruby 2.6 and 2.7 due to dependency incompatibilities
21
+
22
+ ### Deleted
23
+ - Removed `/me/` calls from Clients and Users since those calls returned 500 from the HaloPSA API
24
+
8
25
  ## [0.1.0] - 2025-07-10
9
26
 
10
27
  ### Added
data/Gemfile CHANGED
@@ -1,17 +1,12 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- source "https://rubygems.org"
3
+ source 'https://rubygems.org'
4
4
 
5
5
  # Specify your gem's dependencies in halo_api.gemspec
6
6
  gemspec
7
7
 
8
- gem "rake", "~> 13.0"
8
+ gem 'rake', '~> 13.0'
9
9
 
10
10
  group :development, :test do
11
- gem "rspec", "~> 3.0"
12
- gem "rubocop", "~> 1.21"
13
- gem "yard", "~> 0.9"
14
- gem "webmock", "~> 3.0"
15
- gem "vcr", "~> 6.0"
16
- gem "pry", "~> 0.14"
11
+ gem 'pry'
17
12
  end
data/README.md CHANGED
@@ -1,5 +1,7 @@
1
1
  # HaloMspApi Ruby Gem
2
2
 
3
+ [![Gem Version](https://img.shields.io/gem/v/halo_msp_api)](https://rubygems.org/gems/halo_msp_api)
4
+
3
5
  A comprehensive Ruby API wrapper for the Halo ITSM, HaloPSA and HaloCRM REST API.
4
6
 
5
7
  ## Installation
@@ -185,6 +187,7 @@ The gem provides access to the following Halo API resources:
185
187
  - **Quotations** - `client.quotations` - Quote management, approvals
186
188
  - **Sales Orders** - `client.sales_orders` - Sales order management
187
189
  - **Suppliers** - `client.suppliers` - Supplier and contract management
190
+ - **Products** - `client.products` - Product management
188
191
 
189
192
  ### Service Management
190
193
  - **Appointments** - `client.appointments` - Scheduling and availability
@@ -208,19 +211,19 @@ The gem provides specific error classes for different types of API errors:
208
211
  ```ruby
209
212
  begin
210
213
  ticket = client.tickets.get(999999)
211
- rescue HaloApi::NotFoundError
214
+ rescue HaloMspApi::NotFoundError
212
215
  puts "Ticket not found"
213
- rescue HaloApi::AuthenticationError
216
+ rescue HaloMspApi::AuthenticationError
214
217
  puts "Authentication failed"
215
- rescue HaloApi::AuthorizationError
218
+ rescue HaloMspApi::AuthorizationError
216
219
  puts "Access forbidden"
217
- rescue HaloApi::ValidationError => e
220
+ rescue HaloMspApi::ValidationError => e
218
221
  puts "Validation error: #{e.message}"
219
- rescue HaloApi::RateLimitError
222
+ rescue HaloMspApi::RateLimitError
220
223
  puts "Rate limit exceeded"
221
- rescue HaloApi::ServerError
224
+ rescue HaloMspApi::ServerError
222
225
  puts "Server error"
223
- rescue HaloApi::APIError => e
226
+ rescue HaloMspApi::APIError => e
224
227
  puts "API error: #{e.message} (Status: #{e.status_code})"
225
228
  end
226
229
  ```
@@ -235,6 +238,6 @@ The gem is available as open source under the terms of the [MIT License](https:/
235
238
 
236
239
  ## Development
237
240
 
238
- After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
241
+ After checking out the repo, run `bundle install` to install dependencies. Then, run `bundle exec rspec` to run the tests.
239
242
 
240
243
  To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and the created tag, and push the `.gem` file to [rubygems.org](https://rubygems.org).
data/Rakefile CHANGED
@@ -1,11 +1,11 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "bundler/gem_tasks"
4
- require "rspec/core/rake_task"
3
+ require 'bundler/gem_tasks'
4
+ require 'rspec/core/rake_task'
5
5
 
6
6
  RSpec::Core::RakeTask.new(:spec)
7
7
 
8
- require "rubocop/rake_task"
8
+ require 'rubocop/rake_task'
9
9
 
10
10
  RuboCop::RakeTask.new
11
11
 
@@ -1,70 +1,129 @@
1
1
  #!/usr/bin/env ruby
2
2
  # frozen_string_literal: true
3
3
 
4
- require "bundler/setup"
5
- require "halo_msp_api"
4
+ require 'bundler/setup'
5
+ require 'halo_msp_api'
6
6
 
7
7
  # Configure the HaloMspApi gem
8
8
  HaloMspApi.configure do |config|
9
- config.base_url = ENV["HALO_BASE_URL"] || "https://your-instance.haloitsm.com/api"
10
- config.client_id = ENV["HALO_CLIENT_ID"] || "your_client_id"
11
- config.client_secret = ENV["HALO_CLIENT_SECRET"] || "your_client_secret"
12
- config.tenant = ENV["HALO_TENANT"] # Optional
9
+ config.base_url = ENV['HALO_BASE_URL'] || 'https://your-instance.haloitsm.com/api'
10
+ config.client_id = ENV['HALO_CLIENT_ID'] || 'your_client_id'
11
+ config.client_secret = ENV['HALO_CLIENT_SECRET'] || 'your_client_secret'
12
+ config.tenant = ENV['HALO_TENANT'] # Optional
13
13
  config.timeout = 30
14
14
  config.retries = 3
15
15
  end
16
16
 
17
17
  # Get a client instance
18
- client = HaloMspApi.client
18
+ instance = HaloMspApi.client
19
19
 
20
20
  begin
21
- puts "=== HaloApi Ruby Gem Example Usage ==="
21
+ puts '=== HaloApi Ruby Gem Example Usage ==='
22
22
  puts
23
23
 
24
24
  # Example 1: List tickets
25
- puts "1. Fetching tickets..."
26
- tickets = client.tickets.list(count: 5)
27
- puts "Found #{tickets.dig('tickets')&.length || 0} tickets"
25
+ puts '1. Fetching tickets...'
26
+ tickets = instance.tickets.tickets(count: 5)
27
+ puts "Found #{tickets['tickets']&.length || 0} tickets"
28
+ if tickets['tickets']&.any?
29
+ puts 'Ticket summaries:'
30
+ tickets['tickets'].each do |ticket|
31
+ puts "- #{ticket['summary'] || 'Unknown'}"
32
+ end
33
+ end
28
34
  puts
29
35
 
30
- # Example 2: Get current user
31
- puts "2. Fetching current user..."
32
- current_user = client.users.me
33
- puts "Current user: #{current_user.dig('name') || 'Unknown'}"
36
+ # Example 2: List users
37
+ puts '2. Fetching users...'
38
+ users = instance.users.users(count: 5)
39
+ puts "Found #{users['users']&.length || 0} users"
40
+ if users['users']&.any?
41
+ puts 'User names:'
42
+ users['users'].each do |user|
43
+ puts "- #{user['name'] || 'Unknown'}"
44
+ end
45
+ end
34
46
  puts
35
47
 
36
- # Example 3: List clients
37
- puts "3. Fetching clients..."
38
- clients = client.clients.list(count: 5)
39
- puts "Found #{clients.dig('clients')&.length || 0} clients"
48
+ # Example 3: List companies
49
+ puts '3. Fetching clients...'
50
+ clients = instance.clients.clients(count: 5)
51
+ puts "Found #{clients['clients']&.length || 0} clients"
52
+ if clients['clients']&.any?
53
+ puts 'Client names:'
54
+ clients['clients'].each do |client_item|
55
+ puts "- #{client_item['name'] || 'Unknown'}"
56
+ end
57
+ end
40
58
  puts
41
59
 
42
60
  # Example 4: List assets
43
- puts "4. Fetching assets..."
44
- assets = client.assets.list(count: 5)
45
- puts "Found #{assets.dig('assets')&.length || 0} assets"
61
+ puts '4. Fetching assets...'
62
+ assets = instance.assets.assets(count: 5)
63
+ puts "Found #{assets['assets']&.length || 0} assets"
64
+ if assets['assets']&.any?
65
+ puts 'Asset Inventory Number & Client Association:'
66
+ assets['assets'].each do |asset|
67
+ puts "- #{asset['inventory_number'] || 'Unknown'}: #{asset['client_name'] || 'Unknown'}"
68
+ end
69
+ end
46
70
  puts
47
71
 
48
72
  # Example 5: List invoices
49
- puts "5. Fetching invoices..."
50
- invoices = client.invoices.list(count: 5)
51
- puts "Found #{invoices.dig('invoices')&.length || 0} invoices"
73
+ puts '5. Fetching invoices...'
74
+ invoices = instance.invoices.invoices(count: 5)
75
+ puts "Found #{invoices['invoices']&.length || 0} invoices"
76
+ if invoices['invoices']&.any?
77
+ puts 'Invoice ID and Client Association:'
78
+ invoices['invoices'].each do |invoice|
79
+ puts "- #{invoice['id'] || 'Unknown'}: #{invoice['client_name'] || 'Unknown'}"
80
+ end
81
+ end
52
82
  puts
53
83
 
54
84
  # Example 6: Get reports
55
- puts "6. Fetching reports..."
56
- reports = client.reports.list(count: 5)
57
- puts "Found #{reports.dig('reports')&.length || 0} reports"
85
+ puts '6. Fetching reports...'
86
+ reports = instance.reports.reports(count: 5)
87
+ puts "Found #{reports['reports']&.length || 0} reports"
88
+ if reports['reports']&.any?
89
+ puts 'Report Names:'
90
+ reports['reports'].each do |report|
91
+ puts "- #{report['name'] || 'Unknown'}"
92
+ end
93
+ end
58
94
  puts
59
95
 
60
- puts "=== Example completed successfully! ==="
96
+ # Example 7: Get contracts
97
+ puts '7. Fetching contracts...'
98
+ contracts = instance.clients.contracts(count: 5)
99
+ puts "Found #{contracts['contracts']&.length || 0} contracts"
100
+ if contracts['contracts']&.any?
101
+ puts 'Contract Client & Active:'
102
+ contracts['contracts'].each do |contract|
103
+ puts "- #{contract['client_name'] || 'Unknown'}: #{contract['active'] || 'Unknown'}"
104
+ end
105
+ end
106
+ puts
107
+
108
+ # Example 8: Get products
109
+ puts '8. Fetching products...'
110
+ products = instance.products.products(count: 5)
111
+ puts "Found #{products['items']&.length || 0} products"
112
+ if products['items']&.any?
113
+ puts 'Product Names:'
114
+ products['items'].each do |product|
115
+ puts "- #{product['name'] || 'Unknown'}"
116
+ end
117
+ end
118
+ puts
61
119
 
120
+ puts '=== Example completed successfully! ==='
62
121
  rescue HaloMspApi::AuthenticationError => e
63
122
  puts "Authentication failed: #{e.message}"
64
- puts "Please check your client_id and client_secret"
123
+ puts 'Please check your client_id and client_secret'
65
124
  rescue HaloMspApi::AuthorizationError => e
66
125
  puts "Authorization failed: #{e.message}"
67
- puts "Please check your permissions"
126
+ puts 'Please check your permissions'
68
127
  rescue HaloMspApi::APIError => e
69
128
  puts "API Error: #{e.message}"
70
129
  puts "Status Code: #{e.status_code}" if e.respond_to?(:status_code)
@@ -1,16 +1,18 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "faraday"
4
- require "faraday/retry"
5
- require "json"
3
+ require 'faraday'
4
+ require 'faraday/retry'
5
+ require 'json'
6
6
 
7
7
  module HaloMspApi
8
+ # Client class for Halo MSP API
9
+ # rubocop:disable Metrics/ClassLength
8
10
  class Client
9
11
  attr_reader :configuration, :connection
10
12
 
11
13
  def initialize(configuration = nil)
12
- @configuration = configuration || HaloApi.configuration
13
- raise ConfigurationError, "Configuration is required" unless @configuration&.valid?
14
+ @configuration = configuration || HaloMspApi.configuration
15
+ raise ConfigurationError, 'Configuration is required' unless @configuration&.valid?
14
16
 
15
17
  @connection = build_connection
16
18
  @access_token = nil
@@ -38,6 +40,10 @@ module HaloMspApi
38
40
  @clients ||= Resources::Clients.new(self)
39
41
  end
40
42
 
43
+ def contracts
44
+ @contracts ||= Resources::Contracts.new(self)
45
+ end
46
+
41
47
  def integrations
42
48
  @integrations ||= Resources::Integrations.new(self)
43
49
  end
@@ -54,6 +60,10 @@ module HaloMspApi
54
60
  @organisations ||= Resources::Organisations.new(self)
55
61
  end
56
62
 
63
+ def products
64
+ @products ||= Resources::Products.new(self)
65
+ end
66
+
57
67
  def purchase_orders
58
68
  @purchase_orders ||= Resources::PurchaseOrders.new(self)
59
69
  end
@@ -117,13 +127,15 @@ module HaloMspApi
117
127
 
118
128
  private
119
129
 
130
+ # rubocop:disable Metrics/AbcSize
131
+ # rubocop:disable Metrics/MethodLength
120
132
  def request(method, path, data = {})
121
133
  ensure_authenticated!
122
134
 
123
135
  response = connection.send(method) do |req|
124
136
  req.url path
125
- req.headers["Authorization"] = "Bearer #{@access_token}"
126
- req.headers["Content-Type"] = "application/json"
137
+ req.headers['Authorization'] = "Bearer #{@access_token}"
138
+ req.headers['Content-Type'] = 'application/json'
127
139
 
128
140
  if %i[post put patch].include?(method) && !data.empty?
129
141
  req.body = data.to_json
@@ -134,31 +146,37 @@ module HaloMspApi
134
146
 
135
147
  handle_response(response)
136
148
  rescue Faraday::TimeoutError
137
- raise TimeoutError, "Request timed out"
149
+ raise TimeoutError, 'Request timed out'
138
150
  rescue Faraday::ConnectionFailed
139
- raise ConnectionError, "Connection failed"
151
+ raise ConnectionError, 'Connection failed'
140
152
  end
153
+ # rubocop:enable Metrics/AbcSize
154
+ # rubocop:enable Metrics/MethodLength
141
155
 
156
+ # rubocop:disable Metrics/CyclomaticComplexity
157
+ # rubocop:disable Metrics/MethodLength
142
158
  def handle_response(response)
143
159
  case response.status
144
160
  when 200..299
145
161
  parse_response(response)
146
162
  when 401
147
- raise AuthenticationError, "Authentication failed"
163
+ raise AuthenticationError, 'Authentication failed'
148
164
  when 403
149
- raise AuthorizationError, "Access forbidden"
165
+ raise AuthorizationError, 'Access forbidden'
150
166
  when 404
151
- raise NotFoundError, "Resource not found"
167
+ raise NotFoundError, 'Resource not found'
152
168
  when 422
153
169
  raise ValidationError, "Validation error: #{response.body}"
154
170
  when 429
155
- raise RateLimitError, "Rate limit exceeded"
171
+ raise RateLimitError, 'Rate limit exceeded'
156
172
  when 500..599
157
173
  raise ServerError, "Server error: #{response.status}"
158
174
  else
159
- raise APIError.new("Unexpected response", status_code: response.status, response_body: response.body)
175
+ raise APIError.new('Unexpected response', status_code: response.status, response_body: response.body)
160
176
  end
161
177
  end
178
+ # rubocop:enable Metrics/CyclomaticComplexity
179
+ # rubocop:enable Metrics/MethodLength
162
180
 
163
181
  def parse_response(response)
164
182
  return nil if response.body.nil? || response.body.empty?
@@ -178,32 +196,34 @@ module HaloMspApi
178
196
  @access_token && @token_expires_at && Time.now < @token_expires_at
179
197
  end
180
198
 
199
+ # rubocop:disable Metrics/AbcSize
200
+ # rubocop:disable Metrics/MethodLength
181
201
  def authenticate!
182
202
  auth_params = {
183
- grant_type: "client_credentials",
203
+ grant_type: 'client_credentials',
184
204
  client_id: configuration.client_id,
185
205
  client_secret: configuration.client_secret,
186
- scope: "all"
206
+ scope: 'all'
187
207
  }
188
-
208
+
189
209
  # Include tenant if configured (required for multi-tenant instances)
190
210
  auth_params[:tenant] = configuration.tenant if configuration.tenant
191
-
192
- auth_response = connection.post("/auth/token") do |req|
193
- req.headers["Content-Type"] = "application/x-www-form-urlencoded"
211
+
212
+ auth_response = connection.post('/auth/token') do |req|
213
+ req.headers['Content-Type'] = 'application/x-www-form-urlencoded'
194
214
  req.body = URI.encode_www_form(auth_params)
195
215
  end
196
216
 
197
- if auth_response.status == 200
198
- token_data = JSON.parse(auth_response.body)
199
- @access_token = token_data["access_token"]
200
- @token_expires_at = Time.now + token_data["expires_in"].to_i
201
- else
202
- raise AuthenticationError, "Failed to authenticate: #{auth_response.body}"
203
- end
217
+ raise AuthenticationError, "Failed to authenticate: #{auth_response.body}" unless auth_response.status == 200
218
+
219
+ token_data = JSON.parse(auth_response.body)
220
+ @access_token = token_data['access_token']
221
+ @token_expires_at = Time.now + token_data['expires_in'].to_i
204
222
  rescue JSON::ParserError
205
- raise AuthenticationError, "Invalid authentication response"
223
+ raise AuthenticationError, 'Invalid authentication response'
206
224
  end
225
+ # rubocop:enable Metrics/AbcSize
226
+ # rubocop:enable Metrics/MethodLength
207
227
 
208
228
  def build_connection
209
229
  Faraday.new(url: configuration.base_url) do |conn|
@@ -214,3 +234,4 @@ module HaloMspApi
214
234
  end
215
235
  end
216
236
  end
237
+ # rubocop:enable Metrics/ClassLength
@@ -1,6 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module HaloMspApi
4
+ # HaloMspApi Configuration Class
4
5
  class Configuration
5
6
  attr_accessor :base_url, :client_id, :client_secret, :tenant, :timeout, :retries
6
7
 
@@ -13,6 +13,7 @@ module HaloMspApi
13
13
  class TimeoutError < Error; end
14
14
  class ConnectionError < Error; end
15
15
 
16
+ # Class for handling API Errors
16
17
  class APIError < Error
17
18
  attr_reader :status_code, :response_body
18
19
 
@@ -2,84 +2,85 @@
2
2
 
3
3
  module HaloMspApi
4
4
  module Resources
5
+ # Resource class for Actions
5
6
  class Actions < Base
6
7
  # GET /Actions - List of Actions
7
8
  # Parameters based on swagger.json specification
8
- def list(options = {})
9
+ def actions(options = {})
9
10
  params = build_list_params(options)
10
- get("/Actions", params)
11
+ get('/Actions', params)
11
12
  end
12
13
 
13
14
  # GET /Actions/{id} - Get a specific Action
14
- def get(id, params = {})
15
- get_resource("Actions", id, params)
15
+ def action(id, params = {})
16
+ get_resource('Actions', id, params)
16
17
  end
17
18
 
18
19
  # POST /Actions - Create a new Action
19
- def create(data)
20
- create_resource("Actions", data)
20
+ def create_action(data)
21
+ create_resource('Actions', data)
21
22
  end
22
23
 
23
24
  # PUT /Actions/{id} - Update an Action
24
- def update(id, data)
25
- update_resource("Actions", id, data)
25
+ def update_action(id, data)
26
+ update_resource('Actions', id, data)
26
27
  end
27
28
 
28
29
  # DELETE /Actions/{id} - Delete an Action
29
- def delete(id)
30
- delete_resource("Actions", id)
30
+ def delete_action(id)
31
+ delete_resource('Actions', id)
31
32
  end
32
33
 
33
34
  # Action Reactions methods
34
35
  # GET /ActionReaction - List action reactions
35
36
  def reactions(params = {})
36
- get("/ActionReaction", params)
37
+ get('ActionReaction', params)
37
38
  end
38
39
 
39
40
  # GET /ActionReaction/{id} - Get specific action reaction
40
41
  def reaction(id, params = {})
41
- get("/ActionReaction/#{id}", params)
42
+ get("ActionReaction/#{id}", params)
42
43
  end
43
44
 
44
45
  # POST /ActionReaction - Create action reaction
45
46
  def create_reaction(data)
46
- post("/ActionReaction", data)
47
+ post('ActionReaction', data)
47
48
  end
48
49
 
49
50
  # PUT /ActionReaction/{id} - Update action reaction
50
51
  def update_reaction(id, data)
51
- put("/ActionReaction/#{id}", data)
52
+ put("ActionReaction/#{id}", data)
52
53
  end
53
54
 
54
55
  # DELETE /ActionReaction/{id} - Delete action reaction
55
56
  def delete_reaction(id)
56
- delete("/ActionReaction/#{id}")
57
+ delete("ActionReaction/#{id}")
57
58
  end
58
59
 
59
60
  # Action Review methods
60
61
  # GET /ActionReview - List action reviews
61
62
  def reviews(params = {})
62
- get("/ActionReview", params)
63
+ get('ActionReview', params)
63
64
  end
64
65
 
65
66
  # GET /ActionReview/{id} - Get specific action review
66
67
  def review(id, params = {})
67
- get("/ActionReview/#{id}", params)
68
+ get("ActionReview/#{id}", params)
68
69
  end
69
70
 
70
71
  # POST /ActionReview - Create action review
71
72
  def create_review(data)
72
- post("/ActionReview", data)
73
+ post('ActionReview', data)
73
74
  end
74
75
 
75
76
  # PUT /ActionReview/{id} - Update action review
76
77
  def update_review(id, data)
77
- put("/ActionReview/#{id}", data)
78
+ put("ActionReview/#{id}", data)
78
79
  end
79
80
 
80
81
  # DELETE /ActionReview/{id} - Delete action review
81
82
  def delete_review(id)
82
- delete("/ActionReview/#{id}")
83
+ delete("ActionReview/#{id}")
83
84
  end
84
85
  end
85
86
  end