dogapi-demo 0.1.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 +7 -0
- data/.gitignore +10 -0
- data/.rspec +2 -0
- data/.tailor +106 -0
- data/.travis.yml +8 -0
- data/CHANGELOG.md +157 -0
- data/Gemfile +16 -0
- data/LICENSE +25 -0
- data/README.rdoc +143 -0
- data/Rakefile +50 -0
- data/dogapi-demo.gemspec +32 -0
- data/examples/Capfile +19 -0
- data/examples/custom_event.rb +37 -0
- data/examples/custom_metric.rb +35 -0
- data/lib/capistrano/README.md +13 -0
- data/lib/capistrano/datadog.rb +125 -0
- data/lib/capistrano/datadog/v2.rb +74 -0
- data/lib/capistrano/datadog/v3.rb +64 -0
- data/lib/dogapi-demo.rb +5 -0
- data/lib/dogapi-demo/common.rb +168 -0
- data/lib/dogapi-demo/event.rb +129 -0
- data/lib/dogapi-demo/facade.rb +475 -0
- data/lib/dogapi-demo/metric.rb +34 -0
- data/lib/dogapi-demo/v1.rb +13 -0
- data/lib/dogapi-demo/v1/alert.rb +112 -0
- data/lib/dogapi-demo/v1/comment.rb +62 -0
- data/lib/dogapi-demo/v1/dash.rb +94 -0
- data/lib/dogapi-demo/v1/embed.rb +106 -0
- data/lib/dogapi-demo/v1/event.rb +101 -0
- data/lib/dogapi-demo/v1/metric.rb +118 -0
- data/lib/dogapi-demo/v1/monitor.rb +264 -0
- data/lib/dogapi-demo/v1/screenboard.rb +110 -0
- data/lib/dogapi-demo/v1/search.rb +27 -0
- data/lib/dogapi-demo/v1/service_check.rb +32 -0
- data/lib/dogapi-demo/v1/snapshot.rb +30 -0
- data/lib/dogapi-demo/v1/tag.rb +141 -0
- data/lib/dogapi-demo/v1/user.rb +113 -0
- data/lib/dogapi-demo/version.rb +3 -0
- data/spec/alerts_spec.rb +33 -0
- data/spec/common_spec.rb +37 -0
- data/spec/facade_spec.rb +166 -0
- data/spec/spec_helper.rb +30 -0
- data/spec/support/cassettes/Alerts/create/returns_HTTP_code_200.yml +114 -0
- data/spec/support/cassettes/Alerts/create/returns_a_valid_event_ID.yml +114 -0
- data/spec/support/cassettes/Alerts/create/returns_the_same_query_as_sent.yml +114 -0
- data/spec/support/cassettes/Facade/Events/emits_aggregate_events.yml +193 -0
- data/spec/support/cassettes/Facade/Events/emits_events_and_retrieves_them.yml +100 -0
- data/spec/support/cassettes/Facade/Events/emits_events_with_specified_priority.yml +98 -0
- data/spec/support/cassettes/Facade/Tags/adds_updates_and_detaches_tags.yml +442 -0
- data/tests/test_alerts.rb +38 -0
- data/tests/test_base.rb +30 -0
- data/tests/test_client.rb +23 -0
- data/tests/test_comments.rb +39 -0
- data/tests/test_dashes.rb +85 -0
- data/tests/test_embed.rb +194 -0
- data/tests/test_monitors.rb +192 -0
- data/tests/test_screenboard.rb +90 -0
- data/tests/test_search.rb +20 -0
- data/tests/test_snapshot.rb +28 -0
- data/tests/test_users.rb +65 -0
- metadata +178 -0
@@ -0,0 +1,34 @@
|
|
1
|
+
require 'net/http'
|
2
|
+
|
3
|
+
require 'rubygems'
|
4
|
+
require 'multi_json'
|
5
|
+
|
6
|
+
module DogapiDemo
|
7
|
+
|
8
|
+
# <b>DEPRECATED:</b> Going forward, use the V1 services. This legacy service will be
|
9
|
+
# removed in an upcoming release.
|
10
|
+
class MetricService < DogapiDemo::Service
|
11
|
+
|
12
|
+
API_VERSION = "1.0.0"
|
13
|
+
|
14
|
+
# <b>DEPRECATED:</b> Going forward, use the V1 services. This legacy service will be
|
15
|
+
# removed in an upcoming release.
|
16
|
+
def submit(api_key, scope, metric, points)
|
17
|
+
warn "[DEPRECATION] DogapiDemo::MetricService.submit() has been deprecated in favor of the newer V1 services"
|
18
|
+
series = [{
|
19
|
+
:host => scope.host,
|
20
|
+
:device => scope.device,
|
21
|
+
:metric => metric,
|
22
|
+
:points => points.map { |p| [p[0].to_i, p[1]] }
|
23
|
+
}]
|
24
|
+
|
25
|
+
params = {
|
26
|
+
:api_key => api_key,
|
27
|
+
:api_version => API_VERSION,
|
28
|
+
:series => series.to_json
|
29
|
+
}
|
30
|
+
|
31
|
+
request Net::HTTP::Post, '/series/submit', params
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
@@ -0,0 +1,13 @@
|
|
1
|
+
require 'dogapi-demo/v1/event'
|
2
|
+
require 'dogapi-demo/v1/metric'
|
3
|
+
require 'dogapi-demo/v1/tag'
|
4
|
+
require 'dogapi-demo/v1/comment'
|
5
|
+
require 'dogapi-demo/v1/search'
|
6
|
+
require 'dogapi-demo/v1/dash'
|
7
|
+
require 'dogapi-demo/v1/alert'
|
8
|
+
require 'dogapi-demo/v1/user'
|
9
|
+
require 'dogapi-demo/v1/snapshot'
|
10
|
+
require 'dogapi-demo/v1/embed'
|
11
|
+
require 'dogapi-demo/v1/screenboard'
|
12
|
+
require 'dogapi-demo/v1/monitor'
|
13
|
+
require 'dogapi-demo/v1/service_check'
|
@@ -0,0 +1,112 @@
|
|
1
|
+
require 'dogapi-demo'
|
2
|
+
|
3
|
+
module DogapiDemo
|
4
|
+
class V1 # for namespacing
|
5
|
+
|
6
|
+
class AlertService < DogapiDemo::APIService
|
7
|
+
|
8
|
+
API_VERSION = "v1"
|
9
|
+
|
10
|
+
def alert(query, options = {})
|
11
|
+
begin
|
12
|
+
params = {
|
13
|
+
:api_key => @api_key,
|
14
|
+
:application_key => @application_key
|
15
|
+
}
|
16
|
+
|
17
|
+
body = {
|
18
|
+
'query' => query,
|
19
|
+
}.merge options
|
20
|
+
|
21
|
+
request(Net::HTTP::Post, "/api/#{API_VERSION}/alert", params, body, true)
|
22
|
+
rescue Exception => e
|
23
|
+
suppress_error_if_silent e
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
def update_alert(alert_id, query, options)
|
28
|
+
begin
|
29
|
+
params = {
|
30
|
+
:api_key => @api_key,
|
31
|
+
:application_key => @application_key
|
32
|
+
}
|
33
|
+
|
34
|
+
body = {
|
35
|
+
'query' => query,
|
36
|
+
}.merge options
|
37
|
+
|
38
|
+
request(Net::HTTP::Put, "/api/#{API_VERSION}/alert/#{alert_id}", params, body, true)
|
39
|
+
rescue Exception => e
|
40
|
+
suppress_error_if_silent e
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
def get_alert(alert_id)
|
45
|
+
begin
|
46
|
+
params = {
|
47
|
+
:api_key => @api_key,
|
48
|
+
:application_key => @application_key
|
49
|
+
}
|
50
|
+
|
51
|
+
request(Net::HTTP::Get, "/api/#{API_VERSION}/alert/#{alert_id}", params, nil, false)
|
52
|
+
rescue Exception => e
|
53
|
+
suppress_error_if_silent e
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
def delete_alert(alert_id)
|
58
|
+
begin
|
59
|
+
params = {
|
60
|
+
:api_key => @api_key,
|
61
|
+
:application_key => @application_key
|
62
|
+
}
|
63
|
+
|
64
|
+
request(Net::HTTP::Delete, "/api/#{API_VERSION}/alert/#{alert_id}", params, nil, false)
|
65
|
+
rescue Exception => e
|
66
|
+
suppress_error_if_silent e
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
def get_all_alerts()
|
71
|
+
begin
|
72
|
+
params = {
|
73
|
+
:api_key => @api_key,
|
74
|
+
:application_key => @application_key
|
75
|
+
}
|
76
|
+
|
77
|
+
request(Net::HTTP::Get, "/api/#{API_VERSION}/alert", params, nil, false)
|
78
|
+
rescue Exception => e
|
79
|
+
suppress_error_if_silent e
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
def mute_alerts()
|
84
|
+
begin
|
85
|
+
params = {
|
86
|
+
:api_key => @api_key,
|
87
|
+
:application_key => @application_key
|
88
|
+
}
|
89
|
+
|
90
|
+
request(Net::HTTP::Post, "/api/#{API_VERSION}/mute_alerts", params, nil, false)
|
91
|
+
rescue Exception => e
|
92
|
+
suppress_error_if_silent e
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
96
|
+
def unmute_alerts()
|
97
|
+
begin
|
98
|
+
params = {
|
99
|
+
:api_key => @api_key,
|
100
|
+
:application_key => @application_key
|
101
|
+
}
|
102
|
+
|
103
|
+
request(Net::HTTP::Post, "/api/#{API_VERSION}/unmute_alerts", params, nil, false)
|
104
|
+
rescue Exception => e
|
105
|
+
suppress_error_if_silent e
|
106
|
+
end
|
107
|
+
end
|
108
|
+
|
109
|
+
end
|
110
|
+
|
111
|
+
end
|
112
|
+
end
|
@@ -0,0 +1,62 @@
|
|
1
|
+
require 'dogapi-demo'
|
2
|
+
|
3
|
+
module DogapiDemo
|
4
|
+
class V1 # for namespacing
|
5
|
+
|
6
|
+
class CommentService < DogapiDemo::APIService
|
7
|
+
|
8
|
+
API_VERSION = "v1"
|
9
|
+
|
10
|
+
# Submit a comment.
|
11
|
+
def comment(message, options = {})
|
12
|
+
begin
|
13
|
+
params = {
|
14
|
+
:api_key => @api_key,
|
15
|
+
:application_key => @application_key
|
16
|
+
}
|
17
|
+
|
18
|
+
body = {
|
19
|
+
'message' => message,
|
20
|
+
}.merge options
|
21
|
+
|
22
|
+
request(Net::HTTP::Post, "/api/#{API_VERSION}/comments", params, body, true)
|
23
|
+
rescue Exception => e
|
24
|
+
suppress_error_if_silent e
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
# Update a comment.
|
29
|
+
def update_comment(comment_id, options = {})
|
30
|
+
begin
|
31
|
+
params = {
|
32
|
+
:api_key => @api_key,
|
33
|
+
:application_key => @application_key
|
34
|
+
}
|
35
|
+
|
36
|
+
if options.empty?
|
37
|
+
raise "Must update something."
|
38
|
+
end
|
39
|
+
|
40
|
+
request(Net::HTTP::Put, "/api/#{API_VERSION}/comments/#{comment_id}", params, options, true)
|
41
|
+
rescue Exception => e
|
42
|
+
suppress_error_if_silent e
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
def delete_comment(comment_id)
|
47
|
+
begin
|
48
|
+
params = {
|
49
|
+
:api_key => @api_key,
|
50
|
+
:application_key => @application_key
|
51
|
+
}
|
52
|
+
|
53
|
+
request(Net::HTTP::Delete, "/api/#{API_VERSION}/comments/#{comment_id}", params, nil, false)
|
54
|
+
rescue Exception => e
|
55
|
+
suppress_error_if_silent e
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
end
|
60
|
+
|
61
|
+
end
|
62
|
+
end
|
@@ -0,0 +1,94 @@
|
|
1
|
+
require 'dogapi-demo'
|
2
|
+
|
3
|
+
module DogapiDemo
|
4
|
+
class V1 # for namespacing
|
5
|
+
|
6
|
+
class DashService < DogapiDemo::APIService
|
7
|
+
|
8
|
+
API_VERSION = "v1"
|
9
|
+
|
10
|
+
def create_dashboard(title, description, graphs, template_variables = nil)
|
11
|
+
|
12
|
+
begin
|
13
|
+
params = {
|
14
|
+
:api_key => @api_key,
|
15
|
+
:application_key => @application_key
|
16
|
+
}
|
17
|
+
|
18
|
+
body = {
|
19
|
+
:title => title,
|
20
|
+
:description => description,
|
21
|
+
:graphs => graphs,
|
22
|
+
:template_variables => (template_variables or [])
|
23
|
+
}
|
24
|
+
|
25
|
+
request(Net::HTTP::Post, "/api/#{API_VERSION}/dash", params, body, true)
|
26
|
+
rescue Exception => e
|
27
|
+
suppress_error_if_silent e
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
def update_dashboard(dash_id, title, description, graphs, template_variables=nil)
|
32
|
+
|
33
|
+
begin
|
34
|
+
params = {
|
35
|
+
:api_key => @api_key,
|
36
|
+
:application_key => @application_key
|
37
|
+
}
|
38
|
+
|
39
|
+
body = {
|
40
|
+
:title => title,
|
41
|
+
:description => description,
|
42
|
+
:graphs => graphs,
|
43
|
+
:template_variables => (template_variables or [])
|
44
|
+
}
|
45
|
+
|
46
|
+
request(Net::HTTP::Put, "/api/#{API_VERSION}/dash/#{dash_id}", params, body, true)
|
47
|
+
rescue Exception => e
|
48
|
+
suppress_error_if_silent e
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
def get_dashboard(dash_id)
|
53
|
+
begin
|
54
|
+
params = {
|
55
|
+
:api_key => @api_key,
|
56
|
+
:application_key => @application_key
|
57
|
+
}
|
58
|
+
|
59
|
+
request(Net::HTTP::Get, "/api/#{API_VERSION}/dash/#{dash_id}", params, nil, false)
|
60
|
+
rescue Exception => e
|
61
|
+
suppress_error_if_silent e
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
def get_dashboards
|
66
|
+
begin
|
67
|
+
params = {
|
68
|
+
:api_key => @api_key,
|
69
|
+
:application_key => @application_key
|
70
|
+
}
|
71
|
+
|
72
|
+
request(Net::HTTP::Get, "/api/#{API_VERSION}/dash", params, nil, false)
|
73
|
+
rescue Exception => e
|
74
|
+
suppress_error_if_silent e
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
def delete_dashboard(dash_id)
|
79
|
+
begin
|
80
|
+
params = {
|
81
|
+
:api_key => @api_key,
|
82
|
+
:application_key => @application_key
|
83
|
+
}
|
84
|
+
|
85
|
+
request(Net::HTTP::Delete, "/api/#{API_VERSION}/dash/#{dash_id}", params, nil, false)
|
86
|
+
rescue Exception => e
|
87
|
+
suppress_error_if_silent e
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
91
|
+
end
|
92
|
+
|
93
|
+
end
|
94
|
+
end
|
@@ -0,0 +1,106 @@
|
|
1
|
+
require 'dogapi-demo'
|
2
|
+
|
3
|
+
module DogapiDemo
|
4
|
+
class V1 # for namespacing
|
5
|
+
|
6
|
+
# ================
|
7
|
+
# EMBED API
|
8
|
+
# ================
|
9
|
+
class EmbedService < DogapiDemo::APIService
|
10
|
+
|
11
|
+
API_VERSION = "v1"
|
12
|
+
|
13
|
+
# Get all embeds for the API user's org
|
14
|
+
def get_all_embeds()
|
15
|
+
begin
|
16
|
+
params = {
|
17
|
+
:api_key => @api_key,
|
18
|
+
:application_key => @application_key
|
19
|
+
}
|
20
|
+
|
21
|
+
request(Net::HTTP::Get, "/api/#{API_VERSION}/graph/embed", params, nil, false)
|
22
|
+
rescue Exception => e
|
23
|
+
suppress_error_if_silent e
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
# Get a specific embed
|
28
|
+
#
|
29
|
+
# :embed_id => String: embed token for a specific embed
|
30
|
+
# :size => String: "small", "medium"(defualt), "large", or "xlarge".
|
31
|
+
# :legend => String: "yes" or "no"(default)
|
32
|
+
# :template_vars => String: variable name => variable value (any number of template vars)
|
33
|
+
def get_embed(embed_id, description= {})
|
34
|
+
begin
|
35
|
+
# Initialize parameters and merge with description
|
36
|
+
params = {
|
37
|
+
:api_key => @api_key,
|
38
|
+
:application_key => @application_key,
|
39
|
+
}.merge(description)
|
40
|
+
|
41
|
+
request(Net::HTTP::Get, "/api/#{API_VERSION}/graph/embed/#{embed_id}", params, nil, false)
|
42
|
+
rescue Exception => e
|
43
|
+
suppress_error_if_silent e
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
# Create an embeddable graph
|
48
|
+
#
|
49
|
+
# :graph_json => JSON: graph definition
|
50
|
+
# :timeframe => String: representing the interval of the graph. Default is "1_hour"
|
51
|
+
# :size => String: representing the size of the graph. Default is "medium".
|
52
|
+
# :legend => String: flag representing whether a legend is displayed. Default is "no".
|
53
|
+
# :title => String: represents title of the graph. Default is "Embed created through API."
|
54
|
+
def create_embed(graph_json, description= {})
|
55
|
+
begin
|
56
|
+
params = {
|
57
|
+
:api_key => @api_key,
|
58
|
+
:application_key => @application_key
|
59
|
+
}
|
60
|
+
|
61
|
+
body = {
|
62
|
+
:graph_json => graph_json,
|
63
|
+
}.merge(description)
|
64
|
+
|
65
|
+
request(Net::HTTP::Post, "/api/#{API_VERSION}/graph/embed", params, body, true)
|
66
|
+
rescue Exception => e
|
67
|
+
suppress_error_if_silent e
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
# Enable a specific embed
|
72
|
+
#
|
73
|
+
# :embed_id => String: embed token for a specific embed
|
74
|
+
def enable_embed(embed_id)
|
75
|
+
begin
|
76
|
+
params = {
|
77
|
+
:api_key => @api_key,
|
78
|
+
:application_key => @application_key
|
79
|
+
}
|
80
|
+
|
81
|
+
request(Net::HTTP::Get, "/api/#{API_VERSION}/graph/embed/#{embed_id}/enable", params, nil, false)
|
82
|
+
rescue Exception => e
|
83
|
+
suppress_error_if_silent e
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
# Revoke a specific embed
|
88
|
+
#
|
89
|
+
# :embed_id => String: embed token for a specific embed
|
90
|
+
def revoke_embed(embed_id)
|
91
|
+
begin
|
92
|
+
params = {
|
93
|
+
:api_key => @api_key,
|
94
|
+
:application_key => @application_key
|
95
|
+
}
|
96
|
+
|
97
|
+
request(Net::HTTP::Get, "/api/#{API_VERSION}/graph/embed/#{embed_id}/revoke", params, nil, false)
|
98
|
+
rescue Exception => e
|
99
|
+
suppress_error_if_silent e
|
100
|
+
end
|
101
|
+
end
|
102
|
+
|
103
|
+
end
|
104
|
+
|
105
|
+
end
|
106
|
+
end
|
@@ -0,0 +1,101 @@
|
|
1
|
+
require 'dogapi-demo'
|
2
|
+
|
3
|
+
module DogapiDemo
|
4
|
+
class V1 # for namespacing
|
5
|
+
|
6
|
+
# Event-specific client affording more granular control than the simple DogapiDemo::Client
|
7
|
+
class EventService < DogapiDemo::APIService
|
8
|
+
|
9
|
+
API_VERSION = "v1"
|
10
|
+
MAX_BODY_LENGTH = 4000
|
11
|
+
MAX_TITLE_LENGTH = 100
|
12
|
+
|
13
|
+
# Records an Event with no duration
|
14
|
+
def post(event, scope=nil)
|
15
|
+
begin
|
16
|
+
scope = scope || DogapiDemo::Scope.new()
|
17
|
+
params = {
|
18
|
+
:api_key => @api_key
|
19
|
+
}
|
20
|
+
|
21
|
+
body = event.to_hash.merge({
|
22
|
+
:title => event.msg_title[0..MAX_TITLE_LENGTH - 1],
|
23
|
+
:text => event.msg_text[0..MAX_BODY_LENGTH - 1],
|
24
|
+
:date_happened => event.date_happened.to_i,
|
25
|
+
:host => scope.host,
|
26
|
+
:device => scope.device,
|
27
|
+
:aggregation_key => event.aggregation_key.to_s
|
28
|
+
})
|
29
|
+
|
30
|
+
request(Net::HTTP::Post, '/api/v1/events', params, body, true)
|
31
|
+
rescue Exception => e
|
32
|
+
if @silent
|
33
|
+
warn e
|
34
|
+
return -1, {}
|
35
|
+
else
|
36
|
+
raise e
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
def get(id)
|
42
|
+
begin
|
43
|
+
params = {
|
44
|
+
:api_key => @api_key,
|
45
|
+
:application_key => @application_key
|
46
|
+
}
|
47
|
+
|
48
|
+
request(Net::HTTP::Get, '/api/' + API_VERSION + '/events/' + id.to_s, params, nil, false)
|
49
|
+
rescue Exception => e
|
50
|
+
if @silent
|
51
|
+
warn e
|
52
|
+
return -1, {}
|
53
|
+
else
|
54
|
+
raise e
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
def stream(start, stop, options = {})
|
60
|
+
begin
|
61
|
+
defaults = {
|
62
|
+
:priority => nil,
|
63
|
+
:sources => nil,
|
64
|
+
:tags => nil
|
65
|
+
}
|
66
|
+
options = defaults.merge(options)
|
67
|
+
|
68
|
+
params = {
|
69
|
+
:api_key => @api_key,
|
70
|
+
:application_key => @application_key,
|
71
|
+
|
72
|
+
:start => start.to_i,
|
73
|
+
:end => stop.to_i
|
74
|
+
}
|
75
|
+
|
76
|
+
if options[:priority]
|
77
|
+
params[:priority] = options[:priority]
|
78
|
+
end
|
79
|
+
if options[:sources]
|
80
|
+
params[:sources] = options[:sources]
|
81
|
+
end
|
82
|
+
if options[:tags]
|
83
|
+
tags = options[:tags]
|
84
|
+
params[:tags] = tags.kind_of?(Array) ? tags.join(",") : tags
|
85
|
+
end
|
86
|
+
|
87
|
+
request(Net::HTTP::Get, '/api/' + API_VERSION + '/events', params, nil, false)
|
88
|
+
rescue Exception => e
|
89
|
+
if @silent
|
90
|
+
warn e
|
91
|
+
return -1, {}
|
92
|
+
else
|
93
|
+
raise e
|
94
|
+
end
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
98
|
+
end
|
99
|
+
|
100
|
+
end
|
101
|
+
end
|