xcal-parktronic 0.0.1 → 1.0.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.
Files changed (56) hide show
  1. checksums.yaml +8 -8
  2. data/.travis.yml +7 -0
  3. data/README.md +73 -7
  4. data/lib/xcal/parktronic.rb +11 -0
  5. data/lib/xcal/parktronic/api_client.rb +8 -5
  6. data/lib/xcal/parktronic/generic_response.rb +18 -2
  7. data/lib/xcal/parktronic/routes.rb +9 -2
  8. data/lib/xcal/parktronic/routes/alarms.rb +115 -34
  9. data/lib/xcal/parktronic/routes/command_notifications.rb +56 -0
  10. data/lib/xcal/parktronic/routes/custom_queries.rb +80 -0
  11. data/lib/xcal/parktronic/routes/event_history_items.rb +36 -0
  12. data/lib/xcal/parktronic/routes/events.rb +67 -0
  13. data/lib/xcal/parktronic/routes/metrics.rb +120 -0
  14. data/lib/xcal/parktronic/routes/nested/alarm_actions.rb +84 -0
  15. data/lib/xcal/parktronic/routes/nested/device_errors.rb +35 -0
  16. data/lib/xcal/parktronic/routes/nested/events.rb +57 -0
  17. data/lib/xcal/parktronic/routes/nested/metric_values.rb +35 -0
  18. data/lib/xcal/parktronic/routes/outages.rb +34 -0
  19. data/lib/xcal/parktronic/routes/stack_changes.rb +53 -0
  20. data/lib/xcal/parktronic/version.rb +1 -1
  21. data/spec/lib/xcal/parktronic/api_client_spec.rb +12 -2
  22. data/spec/lib/xcal/parktronic/routes/alarms_route_spec.rb +16 -1
  23. data/spec/lib/xcal/parktronic/routes/command_notifications_routes_spec.rb +32 -0
  24. data/spec/lib/xcal/parktronic/routes/event_history_items_routes_spec.rb +28 -0
  25. data/spec/lib/xcal/parktronic/routes/events_route_spec.rb +33 -0
  26. data/spec/lib/xcal/parktronic/routes/metrics_routes_spec.rb +92 -49
  27. data/spec/lib/xcal/parktronic/routes/nested/alarm_actions_spec.rb +58 -0
  28. data/spec/lib/xcal/parktronic/routes/nested/events_spec.rb +24 -0
  29. data/spec/lib/xcal/parktronic/routes/outages_routes_spec.rb +25 -0
  30. data/spec/lib/xcal/parktronic/routes/stack_changes_routes_spec.rb +38 -0
  31. data/spec/support/api_mock.rb +147 -2
  32. data/spec/support/fixtures/responses/alarm_action.json +19 -0
  33. data/spec/support/fixtures/responses/alarm_actions.json +40 -0
  34. data/spec/support/fixtures/responses/alarm_events.json +180 -0
  35. data/spec/support/fixtures/responses/alarm_post.json +12 -0
  36. data/spec/support/fixtures/responses/alarm_tags.json +1 -0
  37. data/spec/support/fixtures/responses/command_notification.json +7 -0
  38. data/spec/support/fixtures/responses/command_notifications.json +45 -0
  39. data/spec/support/fixtures/responses/event.json +22 -0
  40. data/spec/support/fixtures/responses/event_tags.json +1 -0
  41. data/spec/support/fixtures/responses/events.json +42 -0
  42. data/spec/support/fixtures/responses/events_history_page_1.json +55 -0
  43. data/spec/support/fixtures/responses/events_history_page_2.json +55 -0
  44. data/spec/support/fixtures/responses/metric.json +8 -0
  45. data/spec/support/fixtures/responses/metric_device_errors.json +610 -0
  46. data/spec/support/fixtures/responses/metric_metric_values.json +586 -0
  47. data/spec/support/fixtures/responses/metric_post.json +12 -0
  48. data/spec/support/fixtures/responses/metrics_page_1.json +90 -0
  49. data/spec/support/fixtures/responses/metrics_page_2.json +90 -0
  50. data/spec/support/fixtures/responses/outages_page_1.json +55 -0
  51. data/spec/support/fixtures/responses/outages_page_2.json +55 -0
  52. data/spec/support/fixtures/responses/stack_changes_page_1.json +80 -0
  53. data/spec/support/fixtures/responses/stack_changes_page_2.json +80 -0
  54. data/spec/support/fixtures/responses/stack_changes_post.json +12 -0
  55. data/xcal-parktronic.gemspec +1 -1
  56. metadata +76 -2
@@ -0,0 +1,35 @@
1
+ module Xcal
2
+ module Parktronic
3
+ module Routes
4
+ module Nested
5
+
6
+ module MetricValues
7
+
8
+ # Fetches metric_values for the specific metric
9
+ # Executed as a method chain from the GenericResponse object
10
+ #
11
+ # Call example:
12
+ # metric(id).get_metric_values(page: 2, per_page: 4)
13
+ #
14
+ # Accepted attributes:
15
+ # +page+ page number, defaults to 1
16
+ # +per_page+ per page value, defaults to 100
17
+ def get_paged_metric_values(args = {})
18
+ args.merge!(:access_token => client.access_token)
19
+ response = client.http.get("/#{client.api_version}/metrics/#{id}/metric_values?#{URI.encode_www_form(args)}")
20
+
21
+ generic_response = Xcal::Parktronic::GenericResponse.new(response.body, client)
22
+ if response.code == '200' && generic_response.has_key?(:metric_values)
23
+ generic_response.metric_values.map(&:metric_value)
24
+ else
25
+ generic_response
26
+ end
27
+ end
28
+ alias :get_metric_values :get_paged_metric_values
29
+
30
+ end
31
+
32
+ end
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,34 @@
1
+ module Xcal
2
+ module Parktronic
3
+ module Routes
4
+
5
+ module Outages
6
+
7
+ # Fetches outages
8
+ #
9
+ # ==== Parameters
10
+ # * +page+ page number, defaults to 1
11
+ # * +per_page+ per page value, defaults to 100
12
+ #
13
+ # ==== Examples
14
+ # api.get_paged_outages
15
+ # api.outages
16
+ def get_paged_outages(args = {})
17
+ args.merge!(access_token: access_token)
18
+ response = http.get("/#{api_version}/outages?#{URI.encode_www_form(args)}")
19
+
20
+ generic_response = Xcal::Parktronic::GenericResponse.new(response.body)
21
+ if response.code == '200' && generic_response.has_key?(:high_impact_incidents)
22
+ generic_response.high_impact_incidents.map { |incident| Xcal::Parktronic::GenericResponse.new(incident.high_impact_incident, self) }
23
+ else
24
+ generic_response
25
+ end
26
+
27
+ # TODO Add caching
28
+ end
29
+ alias :outages :get_paged_outages
30
+ end
31
+
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,53 @@
1
+ module Xcal
2
+ module Parktronic
3
+ module Routes
4
+
5
+ module StackChanges
6
+
7
+ # Fetches stack changes
8
+ #
9
+ # ==== Parameters
10
+ # * +element+ stack change element
11
+ # * +time_range+ time range interval
12
+ # * +page+ page number, defaults to 1
13
+ # * +per_page+ per page value, defaults to 100
14
+ #
15
+ # ==== Examples
16
+ # api.get_paged_stack_changes
17
+ # api.stack_changes(element: 'TEST', page: 1, per_page: 5)
18
+ # TODO FIX time_range param, need pass it as json
19
+ def get_paged_stack_changes(args = {})
20
+ args.merge!(access_token: access_token)
21
+ response = http.get("/#{api_version}/stack_changes?#{URI.encode_www_form(args)}")
22
+
23
+ generic_response = Xcal::Parktronic::GenericResponse.new(response.body)
24
+ if response.code == '200' && generic_response.has_key?(:stack_changes)
25
+ generic_response.stack_changes.map { |stack| Xcal::Parktronic::GenericResponse.new(stack.stack_change, self) }
26
+ else
27
+ generic_response
28
+ end
29
+
30
+ # TODO Add caching
31
+ end
32
+ alias :stack_changes :get_paged_stack_changes
33
+
34
+
35
+ # Posts new stack change
36
+ #
37
+ # ==== Parameters
38
+ # * +stack_change+ hash of Stack Change data.
39
+ #
40
+ # ==== Examples
41
+ # api.post_stack_change( ticket_id: 'CHANGE-14725', ticket_summary: 'test', element: 'TEST' )
42
+ def post_stack_change(stack_change = {})
43
+ request = Net::HTTP::Post.new("/#{api_version}/stack_changes", 'Content-Type' => 'application/json')
44
+ request.body = { access_token: access_token, stack_change: stack_change }.to_json
45
+ response = http.start { |net| net.request(request) }
46
+
47
+ Xcal::Parktronic::GenericResponse.new(response.body)
48
+ end
49
+ end
50
+
51
+ end
52
+ end
53
+ end
@@ -1,5 +1,5 @@
1
1
  module Xcal
2
2
  module Parktronic
3
- VERSION = "0.0.1"
3
+ VERSION = '1.0.0'
4
4
  end
5
5
  end
@@ -9,6 +9,16 @@ describe Xcal::Parktronic::ApiClient do
9
9
  let(:api_https_client){ Xcal::Parktronic::ApiClient.new(endpoint: 'https://localhost:3000/v1', access_token: 'fake_access_token') }
10
10
  let(:api_invalid_client){ Xcal::Parktronic::ApiClient.new(endpoint: 'http://', access_token: 'fake_access_token') }
11
11
 
12
+ it 'should initialize correctly initialize' do
13
+ api = Xcal::Parktronic::ApiClient.new(endpoint: 'http://localhost:3000/v1', access_token: 'access_token')
14
+ expect(api.host).to eql('localhost')
15
+ expect(api.access_token).to eql('access_token')
16
+
17
+ api = Xcal::Parktronic::ApiClient.new('endpoint' => 'http://localhost:3000/v1', 'access_token' => 'access_token')
18
+ expect(api.host).to eql('localhost')
19
+ expect(api.access_token).to eql('access_token')
20
+ end
21
+
12
22
  it 'should initialize correctly and provide access to arguments' do
13
23
  expect(api_http_client.host).to eql('localhost')
14
24
  expect(api_http_client.port).to eql(3000)
@@ -25,9 +35,9 @@ describe Xcal::Parktronic::ApiClient do
25
35
  expect(api_https_client.http.use_ssl?).to eql(true)
26
36
 
27
37
  # proxy
28
- expect(api_http_client.http.proxy?).to eql(nil)
38
+ expect([false, nil]).to include(api_http_client.http.proxy?)
29
39
  expect(api_http_proxy_client.http.proxy?).to eql(true)
30
40
  end
31
41
  end
32
42
 
33
- end
43
+ end
@@ -10,7 +10,7 @@ describe Xcal::Parktronic::ApiClient do
10
10
  it 'should not be allowed without access_token' do
11
11
  expect{ api_invalid_client.get_paged_alarms(page: 1, per_page: 10) }.not_to raise_error
12
12
  expect{ api_invalid_client.get_alarm(1) }.not_to raise_error
13
- expect{ api_invalid_client.post_alarm({}, {}) }.not_to raise_error
13
+ expect{ api_invalid_client.post_alarm({}) }.not_to raise_error
14
14
  end
15
15
 
16
16
  context 'getting' do
@@ -53,5 +53,20 @@ describe Xcal::Parktronic::ApiClient do
53
53
  end
54
54
  end
55
55
 
56
+ context 'alarm events' do
57
+ it 'should give an appropriate list of alarms' do
58
+ alarm = api_http_client.get_alarm(2)
59
+
60
+ expect{alarm.get_events.first.id}.not_to raise_error
61
+ expect(alarm.get_events.first.id).to eql(73)
62
+ expect(alarm.get_events.last.id).to eql(370)
63
+ end
64
+ end
65
+
66
+ describe '#get_event_tags' do
67
+ it 'should get tags of alarm' do
68
+ expect{ api_http_client.get_alarm_tags(1) }.not_to raise_error
69
+ end
70
+ end
56
71
  end
57
72
  end
@@ -0,0 +1,32 @@
1
+ require 'spec_helper'
2
+
3
+ describe Xcal::Parktronic::ApiClient do
4
+
5
+ let(:api_http_client){ Xcal::Parktronic::ApiClient.new(endpoint: 'http://api.mock', access_token: 'access_token') }
6
+ let(:api_invalid_client){ Xcal::Parktronic::ApiClient.new(endpoint: 'http://api.mock') }
7
+
8
+ context 'remote commands routes' do
9
+
10
+ it 'is not allowed without access_token' do
11
+ expect{ api_invalid_client.get_paged_command_notifications(page: 1, per_page: 5) }.not_to raise_error
12
+ expect{ api_invalid_client.update_command_notification(1) }.not_to raise_error
13
+ end
14
+
15
+ context 'getting' do
16
+ it 'responds with the correct set of remote commands' do
17
+ command_notifications = api_http_client.get_paged_command_notifications(page: 1, per_page: 5)
18
+
19
+ expect(command_notifications.first.command_id.to_s).to eql('1')
20
+ expect(command_notifications.last.command_id.to_s).to eql('5')
21
+ end
22
+ end
23
+
24
+ context 'setting' do
25
+ it 'should update command notification by id' do
26
+ expect{ api_http_client.update_command_notification() }.to raise_error
27
+ expect{ api_http_client.update_command_notification(1) }.not_to raise_error
28
+ end
29
+ end
30
+
31
+ end
32
+ end
@@ -0,0 +1,28 @@
1
+ require 'spec_helper'
2
+
3
+ describe Xcal::Parktronic::ApiClient do
4
+
5
+ let(:api_http_client){ Xcal::Parktronic::ApiClient.new(endpoint: 'http://api.mock', access_token: 'access_token') }
6
+ let(:api_invalid_client){ Xcal::Parktronic::ApiClient.new(endpoint: 'http://api.mock') }
7
+
8
+ context 'events history routes' do
9
+
10
+ it 'is not allowed without access_token' do
11
+ expect{ api_invalid_client.get_paged_event_history_items(page: 1, per_page: 5) }.not_to raise_error
12
+ end
13
+
14
+ context 'getting' do
15
+ it 'responds with the correct set of events history' do
16
+ event_history_items = api_http_client.get_paged_event_history_items(page: 1, per_page: 5)
17
+ expect(event_history_items.first.event_id.to_s).to eql('1')
18
+ expect(event_history_items.first.incident_status).to eql('Resolved')
19
+ expect(event_history_items.first.initiated_at).to eql('2013-12-30T21:03:39.000Z')
20
+
21
+
22
+ expect(event_history_items.last.event_id.to_s).to eql('2')
23
+ expect(event_history_items.last.incident_status).to eql('ACKNOWLEDGED')
24
+ expect(event_history_items.last.initiated_at).to eql('2013-12-26T21:03:39.000Z')
25
+ end
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,33 @@
1
+ require 'spec_helper'
2
+
3
+ describe Xcal::Parktronic::ApiClient do
4
+
5
+ let(:api_http_client) { Xcal::Parktronic::ApiClient.new(endpoint: 'http://api.mock', access_token: 'access_token') }
6
+ let(:api_invalid_client){ Xcal::Parktronic::ApiClient.new(endpoint: 'http://api.mock') }
7
+
8
+ context 'events route' do
9
+
10
+ describe '#get_event' do
11
+ it 'should get event by id' do
12
+ expect{ api_http_client.get_event(1) }.not_to raise_error
13
+
14
+ event = api_http_client.get_event(1)
15
+ expect(event.service_impacted).to eql('Redis Hitrate')
16
+ end
17
+ end
18
+
19
+ describe '#update_event' do
20
+ it 'should update event by id' do
21
+ expect{ api_http_client.update_event() }.to raise_error
22
+ expect{ api_http_client.update_event(1, service_impacted: 'test') }.not_to raise_error
23
+ end
24
+ end
25
+
26
+ describe '#get_event_tags' do
27
+ it 'should get tags of event' do
28
+ expect{ api_http_client.get_event_tags(1) }.not_to raise_error
29
+ end
30
+ end
31
+ end
32
+
33
+ end
@@ -1,49 +1,92 @@
1
- #require 'spec_helper'
2
- #
3
- #describe Xcal::Parktronic::ApiClient do
4
- #
5
- # let(:api_http_client){ Xcal::Parktronic::ApiClient.new(endpoint: 'http://api.mock', access_token: 'fake_access_token') }
6
- # let(:api_invalid_client){ Xcal::Parktronic::ApiClient.new(endpoint: 'http://api.mock') }
7
- #
8
- # context 'alarms route' do
9
- #
10
- # it 'should not be allowed without access_token' do
11
- # response = api_invalid_client.get_paged_alarms(page: 1, per_page: 10)
12
- #
13
- # expect(response.code).to eql('403')
14
- # expect{ JSON.load(response.body) }.not_to raise_error
15
- # end
16
- #
17
- # it 'should respond with the correct set of alarms' do
18
- # response = api_http_client.get_paged_alarms(page: 1, per_page: 10)
19
- #
20
- # expect(response.code).to eql('200')
21
- # expect{ JSON.load(response.body) }.not_to raise_error
22
- # end
23
- #
24
- # context 'posting' do
25
- # let(:alarm) do
26
- # { :name => 'alarm name', :originating_system => 'Source System', :impact_level => 'low', :tag_list => %w(taga tagb) }
27
- # end
28
- # let(:event) do
29
- # {
30
- # :subject => 'EventSubj',
31
- # :description => 'EventDesc',
32
- # :host_impacted => 'host',
33
- # :initiated_at => '2013-11-22T01:00:24Z',
34
- # :service_impacted => 'EventSvc',
35
- # :incident_status => 'CRITICAL'
36
- # }
37
- # end
38
- #
39
- # it 'should post alarms successfully' do
40
- # response = api_http_client.post_alarm({ :alarm => alarm, :events => [event] })
41
- #
42
- # expect(response.code).to eql('201')
43
- # expect{ JSON.load(response.body) }.not_to raise_error
44
- # end
45
- # end
46
- #
47
- #
48
- # end
49
- #end
1
+ require 'spec_helper'
2
+
3
+ describe Xcal::Parktronic::ApiClient do
4
+
5
+ let(:api_http_client){ Xcal::Parktronic::ApiClient.new(endpoint: 'http://api.mock', access_token: 'access_token') }
6
+ let(:api_invalid_client){ Xcal::Parktronic::ApiClient.new(endpoint: 'http://api.mock') }
7
+
8
+ context 'metrics route' do
9
+
10
+ it 'should not be allowed without access_token' do
11
+ expect{ api_invalid_client.get_paged_metrics(page: 1, per_page: 10) }.not_to raise_error
12
+ expect{ api_invalid_client.get_metric(1) }.not_to raise_error
13
+ expect{ api_invalid_client.post_metric({}) }.not_to raise_error
14
+ end
15
+
16
+ context 'getting' do
17
+ it 'should respond with the correct set of metrics' do
18
+ metrics = nil
19
+ expect{ metrics = api_http_client.get_paged_metrics(page: 1, per_page: 10) }.not_to raise_error
20
+ expect(metrics.first.id.to_s).to eql('7')
21
+ expect(metrics.first.name).to eql('Metric 1')
22
+
23
+ expect(metrics.last.id.to_s).to eql('16')
24
+ expect(metrics.last.name).to eql('Metric 10')
25
+ end
26
+
27
+ it 'should respond with single metric' do
28
+ metric = nil
29
+ expect{ metric = api_http_client.get_metric(2) }.not_to raise_error
30
+
31
+ expect(metric.id.to_s).to eql('7')
32
+ end
33
+ end
34
+
35
+ context 'posting' do
36
+ let(:metric) do
37
+ { :name => 'metric name', :originating_system => 'Source System', :impact_level => 'low', :tag_list => %w(taga tagb) }
38
+ end
39
+ let(:metric_value) do
40
+ {
41
+ value: 22,
42
+ created_at: '2013-07-24T13:21:16Z',
43
+ custom_timestamp: nil,
44
+ }
45
+ end
46
+ let(:device_error) do
47
+ {
48
+ value: 5,
49
+ created_at: '2013-07-24T13:21:16Z',
50
+ custom_timestamp: nil,
51
+ value_groups: [
52
+ {
53
+ group_type: 'Code',
54
+ group_value: 33
55
+ },
56
+ {
57
+ group_type: 'software_stack',
58
+ group_value: 'stack'
59
+ }
60
+ ]
61
+ }
62
+ end
63
+
64
+ it 'should post metrics successfully' do
65
+ send_data = { :metric => metric, :metric_values => [metric_value], :device_error => device_error }
66
+ expect{ api_http_client.post_metric(send_data) }.not_to raise_error
67
+ expect{ api_invalid_client.post_metric(send_data) }.not_to raise_error
68
+ end
69
+ end
70
+
71
+ context 'metric metric_values' do
72
+ it 'should give an appropriate list of alarms' do
73
+ metric = api_http_client.get_metric(2)
74
+
75
+ expect{metric.get_metric_values.first.value}.not_to raise_error
76
+ expect(metric.get_metric_values.first.value).to eql('5.339')
77
+ expect(metric.get_metric_values.last.value).to eql('4.880')
78
+ end
79
+ end
80
+
81
+ context 'metric device_errors' do
82
+ it 'should give an appropriate list of alarms' do
83
+ metric = api_http_client.get_metric(2)
84
+
85
+ expect{metric.get_device_errors.first.value}.not_to raise_error
86
+ expect(metric.get_device_errors.first.value).to eql(53)
87
+ expect(metric.get_device_errors.last.value).to eql(32)
88
+ end
89
+ end
90
+
91
+ end
92
+ end
@@ -0,0 +1,58 @@
1
+ require 'spec_helper'
2
+
3
+ describe Xcal::Parktronic::ApiClient do
4
+
5
+ let(:api_http_client){ Xcal::Parktronic::ApiClient.new(endpoint: 'http://api.mock', access_token: 'access_token') }
6
+ let(:api_invalid_client){ Xcal::Parktronic::ApiClient.new(endpoint: 'http://api.mock') }
7
+
8
+ context 'alarms route' do
9
+ describe '#get_alarm_actions' do
10
+ it 'should return alarm_actions for alarm' do
11
+ expect{ api_http_client.get_alarm(2).get_alarm_actions}.not_to raise_error
12
+ expect{ api_http_client.get_alarm(22).get_alarm_actions}.not_to raise_error
13
+ end
14
+ end
15
+
16
+ describe '#get_alarm_actions' do
17
+ it 'should return alarm_action for alarm' do
18
+ expect{ api_http_client.get_alarm(2).get_alarm_action(2)}.not_to raise_error
19
+
20
+ alarm_action = api_http_client.get_alarm(2).get_alarm_action(2)
21
+ expect(alarm_action.action_id).to eql(1)
22
+ expect(alarm_action.rule.attribute).to eql('aggregated_count')
23
+ expect(alarm_action.rule.operator).to eql('NOT IN')
24
+ expect(alarm_action.rule.value).to eql('OPEN')
25
+ end
26
+ end
27
+
28
+ describe '#post_alarm_action' do
29
+ it 'should create alarm_action for alarm' do
30
+ alarm_action = api_http_client.get_alarm(2).get_alarm_action(2)
31
+ alarm_action_params = {alarm_id: 2, action_id: 1,rule: {:attribute=>"aggregated_count", :operator=>"NOT IN", :value=>"OPEN"}}
32
+
33
+ expect{ api_http_client.get_alarm(2).post_alarm_action(alarm_action_params)}.not_to raise_error
34
+ expect{ alarm_action.post_alarm_action(alarm_action_params)}.not_to raise_error
35
+ end
36
+ end
37
+
38
+ describe '#update_alarm_action' do
39
+ it 'should update alarm_action' do
40
+ alarm_action = api_http_client.get_alarm(2).get_alarm_action(2)
41
+ alarm_action_params = {alarm_id: 2, action_id: 1,rule: {:attribute=>"aggregated_count", :operator=>"IN", :value=>"NEW"}}
42
+
43
+ expect{ api_http_client.get_alarm(2).update_alarm_action(1, alarm_action_params) }.not_to raise_error
44
+ expect{ alarm_action.update_alarm_action(1, alarm_action_params) }.not_to raise_error
45
+ end
46
+ end
47
+
48
+ describe '#set_position' do
49
+ it 'should update position for alarm_action' do
50
+ alarm_action = api_http_client.get_alarm(2).get_alarm_action(2)
51
+
52
+ expect{ api_http_client.get_alarm(2).set_position(1, 10) }.not_to raise_error
53
+ expect{ alarm_action.set_position(1, 10) }.not_to raise_error
54
+ end
55
+ end
56
+
57
+ end
58
+ end