growthforecast-client 0.0.1
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.
- data/.gitignore +11 -0
- data/.rdebugrc +4 -0
- data/.rspec +2 -0
- data/.travis.yml +7 -0
- data/Gemfile +5 -0
- data/LICENSE +22 -0
- data/README.md +25 -0
- data/Rakefile +15 -0
- data/VERSION +1 -0
- data/examples/complex_graph.rb +37 -0
- data/examples/edit_graph.rb +36 -0
- data/growthforecast-client.gemspec +30 -0
- data/lib/growthforecast-client.rb +1 -0
- data/lib/growthforecast/client.rb +323 -0
- data/spec/growthforecast/client_spec.rb +154 -0
- data/spec/spec_helper.rb +16 -0
- data/spec/support/mock.rb +150 -0
- metadata +199 -0
data/.gitignore
ADDED
data/.rdebugrc
ADDED
data/.rspec
ADDED
data/.travis.yml
ADDED
data/Gemfile
ADDED
data/LICENSE
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2013 Naotoshi SEO
|
2
|
+
|
3
|
+
MIT License
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
6
|
+
a copy of this software and associated documentation files (the
|
7
|
+
"Software"), to deal in the Software without restriction, including
|
8
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
9
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
10
|
+
permit persons to whom the Software is furnished to do so, subject to
|
11
|
+
the following conditions:
|
12
|
+
|
13
|
+
The above copyright notice and this permission notice shall be
|
14
|
+
included in all copies or substantial portions of the Software.
|
15
|
+
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
19
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
20
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
21
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
22
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,25 @@
|
|
1
|
+
# GrowthForecast Client [](http://travis-ci.org/sonots/growthforecast-client) [](https://gemnasium.com/sonots/growthforecast-client)
|
2
|
+
|
3
|
+
testing ruby: 1.9.3; GrowthForecast: > 0.39
|
4
|
+
|
5
|
+
## About GrowthForecast Client
|
6
|
+
|
7
|
+
growthforecast-client is a ruby client library for GrowthForecast API where [GrowthForecast](http://kazeburo.github.com/GrowthForecast/) is a visualization graph tool.
|
8
|
+
|
9
|
+
## USAGE
|
10
|
+
|
11
|
+
gem install growthforecast-client
|
12
|
+
|
13
|
+
See [examples](examples) directory.
|
14
|
+
|
15
|
+
## Contributing
|
16
|
+
|
17
|
+
1. Fork it
|
18
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
19
|
+
3. Commit your changes (`git commit -am 'Add some feature'`)
|
20
|
+
4. Push to the branch (`git push origin my-new-feature`)
|
21
|
+
5. Create new [Pull Request](../../pull/new/master)
|
22
|
+
|
23
|
+
## Copyright
|
24
|
+
|
25
|
+
Copyright (c) 2013 Naotoshi SEO. See [LICENSE](LICENSE) for details.
|
data/Rakefile
ADDED
@@ -0,0 +1,15 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
require "bundler/gem_tasks"
|
3
|
+
|
4
|
+
require 'rspec/core'
|
5
|
+
require 'rspec/core/rake_task'
|
6
|
+
RSpec::Core::RakeTask.new(:spec) do |spec|
|
7
|
+
spec.pattern = FileList['spec/**/*_spec.rb']
|
8
|
+
end
|
9
|
+
task :default => :spec
|
10
|
+
|
11
|
+
desc 'Open an irb session preloaded with the gem library'
|
12
|
+
task :console do
|
13
|
+
sh 'irb -rubygems -I lib -r growthforecast-client.rb'
|
14
|
+
end
|
15
|
+
task :c => :console
|
data/VERSION
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
0.0.1
|
@@ -0,0 +1,37 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
require 'growthforecast-client'
|
3
|
+
|
4
|
+
# Create a GrowthForecast Client, given he base URI of GrowthForecast
|
5
|
+
uri = 'http://localhost:5125'
|
6
|
+
client = GrowthForecast::Client.new(uri)
|
7
|
+
|
8
|
+
# Apply for all services/sections
|
9
|
+
sections = client.list_section
|
10
|
+
sections.each do |service_name, sections|
|
11
|
+
sections.each do |section_name|
|
12
|
+
# Make a complex graph from these graphs
|
13
|
+
from_graphs= [
|
14
|
+
{:path => "#{service_name}/#{section_name}/<1sec_count", :gmode => 'gauge', :stack => true, :type => 'AREA'},
|
15
|
+
{:path => "#{service_name}/#{section_name}/<2sec_count", :gmode => 'gauge', :stack => true, :type => 'AREA'},
|
16
|
+
{:path => "#{service_name}/#{section_name}/<3sec_count", :gmode => 'gauge', :stack => true, :type => 'AREA'},
|
17
|
+
{:path => "#{service_name}/#{section_name}/<4sec_count", :gmode => 'gauge', :stack => true, :type => 'AREA'},
|
18
|
+
{:path => "#{service_name}/#{section_name}/>=4sec_count", :gmode => 'gauge', :stack => true, :type => 'AREA'},
|
19
|
+
]
|
20
|
+
|
21
|
+
# The propety of a complex graph to create, e.g., path
|
22
|
+
to_complex = {
|
23
|
+
:path => "#{service_name}/#{section_name}/response_count",
|
24
|
+
:description => 'response time count',
|
25
|
+
:sort => 10,
|
26
|
+
}
|
27
|
+
|
28
|
+
begin
|
29
|
+
puts "Setup #{to_complex[:path]}"
|
30
|
+
client.create_complex(from_graphs, to_complex)
|
31
|
+
rescue GrowthForecast::AlreadyExists => e
|
32
|
+
puts "\tclass:#{e.class}\t#{e.message}"
|
33
|
+
rescue GrowthForecast::NotFound => e
|
34
|
+
puts "\tclass:#{e.class}\t#{e.message}"
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
require 'growthforecast-client'
|
3
|
+
|
4
|
+
# Create a GrowthForecast Client, given he base URI of GrowthForecast
|
5
|
+
uri = 'http://localhost:5125'
|
6
|
+
client = GrowthForecast::Client.new(uri)
|
7
|
+
|
8
|
+
# configure colors of graphs whose names are as belows:
|
9
|
+
graph_colors = {
|
10
|
+
'<1sec_count' => '#1111cc',
|
11
|
+
'<2sec_count' => '#11cc11',
|
12
|
+
'<3sec_count' => '#cc7711',
|
13
|
+
'<4sec_count' => '#cccc11',
|
14
|
+
'>=4sec_count' => '#cc1111',
|
15
|
+
}
|
16
|
+
# Apply for all services/sections
|
17
|
+
sections = client.list_section
|
18
|
+
sections.each do |service_name, sections|
|
19
|
+
sections.each do |section_name|
|
20
|
+
graph_colors.keys.each do |graph_name|
|
21
|
+
data = {
|
22
|
+
'color' => graph_colors[graph_name],
|
23
|
+
'unit' => 'count',
|
24
|
+
'sort' => 1, # order to display, 19 is the top
|
25
|
+
'adjust' => '/',
|
26
|
+
'adjustval' => '1',
|
27
|
+
}
|
28
|
+
begin
|
29
|
+
puts "Setup /#{service_name}/#{section_name}/#{graph_name}"
|
30
|
+
client.edit_graph(service_name, section_name, graph_name, data)
|
31
|
+
rescue GrowthForecast::NotFound => e
|
32
|
+
puts "\tclass:#{e.class}\t#{e.message}"
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
#! /usr/bin/env gem build
|
2
|
+
# encoding: utf-8
|
3
|
+
|
4
|
+
Gem::Specification.new do |gem|
|
5
|
+
gem.name = 'growthforecast-client'
|
6
|
+
gem.version = File.read(File.expand_path('VERSION', File.dirname(__FILE__))).chomp
|
7
|
+
gem.authors = ["Naotoshi Seo"]
|
8
|
+
gem.email = ["sonots@gmail.com"]
|
9
|
+
gem.homepage = "https://github.com/sonots/growthforecast-client"
|
10
|
+
gem.summary = "A Ruby Client Library for GrowthForecast API"
|
11
|
+
gem.description = gem.summary
|
12
|
+
|
13
|
+
gem.files = `git ls-files`.split($\)
|
14
|
+
gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
|
15
|
+
gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
|
16
|
+
gem.require_paths = ["lib"]
|
17
|
+
|
18
|
+
gem.add_runtime_dependency "httpclient"
|
19
|
+
|
20
|
+
# for testing
|
21
|
+
gem.add_development_dependency "rake"
|
22
|
+
gem.add_development_dependency "rspec", "~> 2.11"
|
23
|
+
gem.add_development_dependency "webmock"
|
24
|
+
|
25
|
+
# for debug
|
26
|
+
gem.add_development_dependency "pry"
|
27
|
+
gem.add_development_dependency "pry-debugger"
|
28
|
+
gem.add_development_dependency "rb-readline"
|
29
|
+
gem.add_development_dependency "tapp"
|
30
|
+
end
|
@@ -0,0 +1 @@
|
|
1
|
+
require 'growthforecast/client'
|
@@ -0,0 +1,323 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
require 'httpclient'
|
3
|
+
require 'json'
|
4
|
+
require 'uri'
|
5
|
+
require 'pp'
|
6
|
+
|
7
|
+
module GrowthForecast
|
8
|
+
class Error < StandardError; end
|
9
|
+
class NotFound < Error; end
|
10
|
+
class AlreadyExists < Error; end
|
11
|
+
|
12
|
+
class Client
|
13
|
+
attr_accessor :debug
|
14
|
+
# @param [String] base_uri The base uri of GrowthForecast
|
15
|
+
def initialize(base_uri = 'http://127.0.0.1:5125')
|
16
|
+
@base_uri = base_uri
|
17
|
+
end
|
18
|
+
|
19
|
+
# GET the JSON API
|
20
|
+
# @param [String] path
|
21
|
+
# @return [Hash] response body
|
22
|
+
def get_json(path)
|
23
|
+
res = client.get("#{@base_uri}#{path}")
|
24
|
+
handle_error(res)
|
25
|
+
JSON.parse(res.body)
|
26
|
+
end
|
27
|
+
|
28
|
+
# POST the JSON API
|
29
|
+
# @param [String] path
|
30
|
+
# @param [Hash] data
|
31
|
+
# @return [Hash] response body
|
32
|
+
def post_json(path, data = {})
|
33
|
+
pp data if @debug
|
34
|
+
json = JSON.generate(data)
|
35
|
+
res = client.post("#{@base_uri}#{path}", json)
|
36
|
+
handle_error(res)
|
37
|
+
JSON.parse(res.body)
|
38
|
+
end
|
39
|
+
|
40
|
+
# POST the non-JSON API
|
41
|
+
# @param [String] path
|
42
|
+
# @param [Hash] data
|
43
|
+
# @return [String] response body
|
44
|
+
def post_query(path, data = {})
|
45
|
+
pp data if @debug
|
46
|
+
res = client.post("#{@base_uri}#{path}", data)
|
47
|
+
handle_error(res)
|
48
|
+
JSON.parse(res.body)
|
49
|
+
end
|
50
|
+
|
51
|
+
# Get the list of graphs, /json/list/graph
|
52
|
+
# @return [Hash] list of graphs
|
53
|
+
# @example
|
54
|
+
# [
|
55
|
+
# {"service_name"=>"test",
|
56
|
+
# "graph_name"=>"<2sec_count",
|
57
|
+
# "section_name"=>"hostname",
|
58
|
+
# "id"=>4},
|
59
|
+
# {"service_name"=>"test",
|
60
|
+
# "graph_name"=>"<1sec_count",
|
61
|
+
# "section_name"=>"hostname",
|
62
|
+
# "id"=>3},
|
63
|
+
# ]
|
64
|
+
def list_graph
|
65
|
+
get_json('/json/list/graph')
|
66
|
+
end
|
67
|
+
|
68
|
+
# A Helper: Get the list of section
|
69
|
+
# @return [Hash] list of sections
|
70
|
+
# @example
|
71
|
+
# {
|
72
|
+
# "service_name1" => [
|
73
|
+
# "section_name1",
|
74
|
+
# "section_name2",
|
75
|
+
# ],
|
76
|
+
# "service_name2" => [
|
77
|
+
# "section_name1",
|
78
|
+
# "section_name2",
|
79
|
+
# ],
|
80
|
+
# }
|
81
|
+
def list_section
|
82
|
+
graphs = list_graph
|
83
|
+
services = {}
|
84
|
+
graphs.each do |graph|
|
85
|
+
service_name, section_name = graph['service_name'], graph['section_name']
|
86
|
+
services[service_name] ||= {}
|
87
|
+
services[service_name][section_name] ||= true
|
88
|
+
end
|
89
|
+
Hash[services.map {|service_name, sections| [service_name, sections.keys] }]
|
90
|
+
end
|
91
|
+
|
92
|
+
# A Helper: Get the list of services
|
93
|
+
# @return [Array] list of services
|
94
|
+
# @example
|
95
|
+
# [
|
96
|
+
# "service_name1",
|
97
|
+
# "service_name2",
|
98
|
+
# ]
|
99
|
+
def list_service
|
100
|
+
graphs = list_graph
|
101
|
+
services = {}
|
102
|
+
graphs.each do |graph|
|
103
|
+
service_name = graph['service_name']
|
104
|
+
services[service_name] ||= true
|
105
|
+
end
|
106
|
+
services.keys
|
107
|
+
end
|
108
|
+
|
109
|
+
# Get the propety of a graph, GET /api/:service_name/:section_name/:graph_name
|
110
|
+
# @param [String] service_name
|
111
|
+
# @param [String] section_name
|
112
|
+
# @param [String] graph_name
|
113
|
+
# @return [Hash] the graph property
|
114
|
+
# @example
|
115
|
+
#{
|
116
|
+
# "number"=>1,
|
117
|
+
# "llimit"=>-1000000000,
|
118
|
+
# "mode"=>"gauge",
|
119
|
+
# "stype"=>"AREA",
|
120
|
+
# "adjustval"=>"1",
|
121
|
+
# "meta"=>"",
|
122
|
+
# "service_name"=>"test",
|
123
|
+
# "gmode"=>"gauge",
|
124
|
+
# "color"=>"#cc6633",
|
125
|
+
# "created_at"=>"2013/02/02 00:41:11",
|
126
|
+
# "section_name"=>"hostname",
|
127
|
+
# "ulimit"=>1000000000,
|
128
|
+
# "id"=>21,
|
129
|
+
# "graph_name"=>"<4sec_count",
|
130
|
+
# "description"=>"",
|
131
|
+
# "sulimit"=>100000,
|
132
|
+
# "unit"=>"",
|
133
|
+
# "sort"=>0,
|
134
|
+
# "updated_at"=>"2013/02/02 02:32:10",
|
135
|
+
# "adjust"=>"*",
|
136
|
+
# "type"=>"AREA",
|
137
|
+
# "sllimit"=>-100000,
|
138
|
+
# "md5"=>"3c59dc048e8850243be8079a5c74d079"}
|
139
|
+
def get_graph(service_name, section_name, graph_name)
|
140
|
+
get_json("/api/#{service_name}/#{section_name}/#{graph_name}")
|
141
|
+
end
|
142
|
+
|
143
|
+
# Get the propety of a graph, /json/graph/:id
|
144
|
+
# @param [String] id
|
145
|
+
# @return [Hash] the graph property
|
146
|
+
# @example
|
147
|
+
# {"llimit"=>-1000000000,
|
148
|
+
# "number"=>48778224,
|
149
|
+
# "stype"=>"AREA",
|
150
|
+
# "mode"=>"count",
|
151
|
+
# "complex"=>false,
|
152
|
+
# "adjustval"=>"1",
|
153
|
+
# "created_at"=>"2013/02/01 16:01:17",
|
154
|
+
# "color"=>"#cc3366",
|
155
|
+
# "service_name"=>"test",
|
156
|
+
# "gmode"=>"gauge",
|
157
|
+
# "ulimit"=>1000000000,
|
158
|
+
# "section_name"=>"all",
|
159
|
+
# "id"=>1,
|
160
|
+
# "graph_name"=>"response_time_max",
|
161
|
+
# "description"=>"",
|
162
|
+
# "sort"=>0,
|
163
|
+
# "unit"=>"",
|
164
|
+
# "sulimit"=>100000,
|
165
|
+
# "updated_at"=>"2013/02/04 18:26:49",
|
166
|
+
# "adjust"=>"*",
|
167
|
+
# "sllimit"=>-100000,
|
168
|
+
# "type"=>"AREA"}
|
169
|
+
def get_graph_by_id(id)
|
170
|
+
get_json("/json/graph/#{id}")
|
171
|
+
end
|
172
|
+
|
173
|
+
# Post parameters to a graph, POST /api/:service_name/:section_name/:graph_name
|
174
|
+
# @param [String] service_name
|
175
|
+
# @param [String] section_name
|
176
|
+
# @param [String] graph_name
|
177
|
+
# @param [Hash] params The POST parameters. See #get_graph
|
178
|
+
def post_graph(service_name, section_name, graph_name, params)
|
179
|
+
post_query("/api/#{service_name}/#{section_name}/#{graph_name}", params)
|
180
|
+
end
|
181
|
+
|
182
|
+
# Delete a graph, POST /delete/:service_name/:section_name/:graph_name
|
183
|
+
# @param [String] service_name
|
184
|
+
# @param [String] section_name
|
185
|
+
# @param [String] graph_name
|
186
|
+
def delete_graph(service_name, section_name, graph_name)
|
187
|
+
post_query("/delete/#{service_name}/#{section_name}/#{graph_name}")
|
188
|
+
end
|
189
|
+
|
190
|
+
# Update the property of a graph, /json/edit/graph/:id
|
191
|
+
# @param [String] service_name
|
192
|
+
# @param [String] section_name
|
193
|
+
# @param [String] graph_name
|
194
|
+
# @param [Hash] params
|
195
|
+
# All of parameters given by #get_graph are available except `number` and `mode`.
|
196
|
+
# @return [Hash] error response
|
197
|
+
# @example
|
198
|
+
# {"error"=>0} #=> Success
|
199
|
+
# {"error"=>1} #=> Error
|
200
|
+
def edit_graph(service_name, section_name, graph_name, params)
|
201
|
+
data = get_graph(service_name, section_name, graph_name)
|
202
|
+
id = data['id']
|
203
|
+
updates = handle_update_params(data, params)
|
204
|
+
post_json("/json/edit/graph/#{id}", updates)
|
205
|
+
end
|
206
|
+
|
207
|
+
# Get the list of complex graphs, /json/list/complex
|
208
|
+
# @return [Hash] list of complex graphs
|
209
|
+
# @example
|
210
|
+
# [
|
211
|
+
# {"service_name"=>"test",
|
212
|
+
# "graph_name"=>"<2sec_count",
|
213
|
+
# "section_name"=>"hostname",
|
214
|
+
# "id"=>4},
|
215
|
+
# {"service_name"=>"test",
|
216
|
+
# "graph_name"=>"<1sec_count",
|
217
|
+
# "section_name"=>"hostname",
|
218
|
+
# "id"=>3},
|
219
|
+
# ]
|
220
|
+
def list_complex
|
221
|
+
get_json('/json/list/complex')
|
222
|
+
end
|
223
|
+
|
224
|
+
# Create a complex graph
|
225
|
+
#
|
226
|
+
# @param [Array] from_graphs Array of graph properties whose keys are
|
227
|
+
# ["service_name", "section_name", "graph_name", "gmode", "stack", "type"]
|
228
|
+
# @param [Hash] to_complex Property of Complex Graph, whose keys are like
|
229
|
+
# ["service_name", "section_name", "graph_name", "description", "sort"]
|
230
|
+
def create_complex(from_graphs, to_complex)
|
231
|
+
graph_data = []
|
232
|
+
from_graphs.each do |from_graph|
|
233
|
+
graph = get_graph(from_graph["service_name"], from_graph["section_name"], from_graph["graph_name"])
|
234
|
+
graph_id = graph['id']
|
235
|
+
|
236
|
+
graph_data << {
|
237
|
+
:gmode => from_graph["gmode"],
|
238
|
+
:stack => from_graph["stack"],
|
239
|
+
:type => from_graph["type"],
|
240
|
+
:graph_id => graph_id
|
241
|
+
}
|
242
|
+
end
|
243
|
+
|
244
|
+
post_params = {
|
245
|
+
:service_name => to_complex["service_name"],
|
246
|
+
:section_name => to_complex["section_name"],
|
247
|
+
:graph_name => to_complex["graph_name"],
|
248
|
+
:description => to_complex["description"],
|
249
|
+
:sort => to_complex["sort"],
|
250
|
+
:data => graph_data
|
251
|
+
}
|
252
|
+
|
253
|
+
post_json('/json/create/complex', post_params)
|
254
|
+
end
|
255
|
+
|
256
|
+
# Delete a complex graph
|
257
|
+
#
|
258
|
+
# This is a helper method of GrowthForecast API to find complex graph id and call delete_complex_by_id
|
259
|
+
#
|
260
|
+
# @param [String] service_name
|
261
|
+
# @param [String] section_name
|
262
|
+
# @param [String] graph_name
|
263
|
+
# @return [Hash] error response
|
264
|
+
# @example
|
265
|
+
# {"error"=>0} #=> Success
|
266
|
+
# {"error"=>1} #=> Error
|
267
|
+
def delete_complex(service_name, section_name, graph_name)
|
268
|
+
complex_graphs = list_complex
|
269
|
+
complex = complex_graphs.select {|g| g["service_name"] == service_name and g["section_name"] == section_name and g["graph_name"] == graph_name }
|
270
|
+
raise NotFound if complex.empty?
|
271
|
+
delete_complex_by_id(complex.first["id"])
|
272
|
+
end
|
273
|
+
|
274
|
+
# Delete a complex graph, /delete_complex/:complex_id
|
275
|
+
#
|
276
|
+
# @param [String] id of a complex graph
|
277
|
+
# @return [Hash] error response
|
278
|
+
# @example
|
279
|
+
# {"error"=>0} #=> Success
|
280
|
+
# {"error"=>1} #=> Error
|
281
|
+
def delete_complex_by_id(complex_id)
|
282
|
+
post_query("/delete_complex/#{complex_id}")
|
283
|
+
end
|
284
|
+
|
285
|
+
private
|
286
|
+
|
287
|
+
def client
|
288
|
+
@client ||= HTTPClient.new
|
289
|
+
end
|
290
|
+
|
291
|
+
def handle_error(res)
|
292
|
+
case res.status
|
293
|
+
when 200
|
294
|
+
when 404
|
295
|
+
raise NotFound.new(error_message(res))
|
296
|
+
when 409
|
297
|
+
raise AlreadyExists.new(error_message(res))
|
298
|
+
else
|
299
|
+
raise Error.new(error_message(res))
|
300
|
+
end
|
301
|
+
end
|
302
|
+
|
303
|
+
def error_message(res)
|
304
|
+
"status:#{res.status}\turi:#{res.http_header.request_uri.to_s}"
|
305
|
+
end
|
306
|
+
|
307
|
+
# GrowthForecast's /json/edit/graph API requires all parameters to update, thus
|
308
|
+
# we have to merge the original graph parameters and parameters which we want to update. Sucks!
|
309
|
+
#
|
310
|
+
# @param [Hash] graph_data the current graph property data
|
311
|
+
# @param [Hash[ params the parameters which we want to update
|
312
|
+
# @return [Hash] merged parameters
|
313
|
+
def handle_update_params(graph_data, params)
|
314
|
+
updates = graph_data.merge(params)
|
315
|
+
# `meta` field is automatically added when we call get_graph.
|
316
|
+
# If we post `meta` data to update graph, `meta` is constructed circularly. Sucks!
|
317
|
+
# Thus, I remove the `meta` here.
|
318
|
+
updates['meta'] = '' if !params.has_key?('meta')
|
319
|
+
updates
|
320
|
+
end
|
321
|
+
end
|
322
|
+
end
|
323
|
+
|
@@ -0,0 +1,154 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe GrowthForecast::Client do
|
4
|
+
id_keys = %w[id service_name section_name graph_name]
|
5
|
+
graph_keys = %w[number llimit mode stype adjustval gmode color created_at ulimit description
|
6
|
+
sulimit unit sort updated_at adjust type sllimit meta md5]
|
7
|
+
|
8
|
+
before(:all) { @client = GrowthForecast::Client.new('http://localhost:5125') }
|
9
|
+
|
10
|
+
include_context "stub_list_graph" if ENV['MOCK'] == 'on'
|
11
|
+
let(:graphs) { @client.list_graph }
|
12
|
+
let(:graph) { graphs.first }
|
13
|
+
|
14
|
+
include_context "stub_post_graph" if ENV['MOCK'] == 'on'
|
15
|
+
include_context "stub_delete_graph" if ENV['MOCK'] == 'on'
|
16
|
+
before(:all) {
|
17
|
+
@client.delete_graph("app_name", "hostname", "<1sec_count") rescue nil
|
18
|
+
@client.delete_graph("app_name", "hostname", "<2sec_count") rescue nil
|
19
|
+
@client.post_graph("app_name", "hostname", "<1sec_count", { 'number' => 0 }) rescue nil
|
20
|
+
@client.post_graph("app_name", "hostname", "<2sec_count", { 'number' => 0 }) rescue nil
|
21
|
+
}
|
22
|
+
after(:all) {
|
23
|
+
@client.delete_graph("app_name", "hostname", "<1sec_count") rescue nil
|
24
|
+
@client.delete_graph("app_name", "hostname", "<2sec_count") rescue nil
|
25
|
+
}
|
26
|
+
|
27
|
+
context "#list_graph" do
|
28
|
+
include_context "stub_list_graph" if ENV['MOCK'] == 'on'
|
29
|
+
subject { graphs }
|
30
|
+
its(:size) { should > 0 }
|
31
|
+
id_keys.each {|key| its(:first) { should have_key(key) } }
|
32
|
+
end
|
33
|
+
|
34
|
+
context "#list_section" do
|
35
|
+
include_context "stub_list_graph" if ENV['MOCK'] == 'on'
|
36
|
+
subject { @client.list_section }
|
37
|
+
its(:size) { should > 0 }
|
38
|
+
its(:class) { should == Hash }
|
39
|
+
it { subject.each {|service_name, sections| sections.size.should > 0 } }
|
40
|
+
end
|
41
|
+
|
42
|
+
context "#list_service" do
|
43
|
+
include_context "stub_list_graph" if ENV['MOCK'] == 'on'
|
44
|
+
subject { @client.list_service }
|
45
|
+
its(:size) { should > 0 }
|
46
|
+
its(:class) { should == Array }
|
47
|
+
end
|
48
|
+
|
49
|
+
context "#get_graph" do
|
50
|
+
include_context "stub_get_graph" if ENV['MOCK'] == 'on'
|
51
|
+
subject { @client.get_graph(graph["service_name"], graph["section_name"], graph["graph_name"]) }
|
52
|
+
id_keys.each {|key| it { subject[key].should == graph[key] } }
|
53
|
+
graph_keys.each {|key| it { subject.should have_key(key) } }
|
54
|
+
end
|
55
|
+
|
56
|
+
context "#get_graph_by_id" do
|
57
|
+
include_context "stub_get_graph_by_id" if ENV['MOCK'] == 'on'
|
58
|
+
subject { @client.get_graph_by_id(graph["id"]) }
|
59
|
+
id_keys.each {|key| it { subject[key].should == graph[key] } }
|
60
|
+
# this is the behavior of GrowthForecast API
|
61
|
+
(graph_keys - %w[meta md5]).each {|key| it { subject.should have_key(key) } }
|
62
|
+
end
|
63
|
+
|
64
|
+
context "#post_graph" do
|
65
|
+
include_context "stub_post_graph" if ENV['MOCK'] == 'on'
|
66
|
+
include_context "stub_get_graph" if ENV['MOCK'] == 'on'
|
67
|
+
params = {
|
68
|
+
'number' => 0,
|
69
|
+
}
|
70
|
+
subject { @client.post_graph(graph["service_name"], graph["section_name"], graph["graph_name"], params) }
|
71
|
+
it { subject["error"].should == 0 }
|
72
|
+
params.keys.each {|key| it { subject["data"][key].should == params[key] } }
|
73
|
+
end
|
74
|
+
|
75
|
+
context "#delete_graph" do
|
76
|
+
include_context "stub_post_graph" if ENV['MOCK'] == 'on'
|
77
|
+
include_context "stub_delete_graph" if ENV['MOCK'] == 'on'
|
78
|
+
let(:graph) {
|
79
|
+
{
|
80
|
+
"service_name" => "app_name",
|
81
|
+
"section_name" => "hostname",
|
82
|
+
"graph_name" => "<1sec_count",
|
83
|
+
}
|
84
|
+
}
|
85
|
+
before { @client.post_graph(graph['service_name'], graph['section_name'], graph['graph_name'], { 'number' => 0 }) }
|
86
|
+
subject { @client.delete_graph(graph['service_name'], graph['section_name'], graph['graph_name']) }
|
87
|
+
it { subject["error"].should == 0 }
|
88
|
+
end
|
89
|
+
|
90
|
+
context "#edit_graph" do
|
91
|
+
context "normal" do
|
92
|
+
include_context "stub_edit_graph" if ENV['MOCK'] == 'on'
|
93
|
+
params = {
|
94
|
+
'sort' => 19,
|
95
|
+
'adjust' => '/',
|
96
|
+
'adjustval' => '1000000',
|
97
|
+
'unit' => 'sec',
|
98
|
+
'color' => "#000000"
|
99
|
+
}
|
100
|
+
before(:all) do
|
101
|
+
@before = @client.get_graph(graph["service_name"], graph["section_name"], graph["graph_name"])
|
102
|
+
@response = @client.edit_graph(graph["service_name"], graph["section_name"], graph["graph_name"], params)
|
103
|
+
@after = @client.get_graph(graph["service_name"], graph["section_name"], graph["graph_name"])
|
104
|
+
end
|
105
|
+
it { @response["error"].should == 0 }
|
106
|
+
# @todo: how to stub @after?
|
107
|
+
unless ENV['MOCK'] == 'on'
|
108
|
+
(id_keys + graph_keys - params.keys - %w[meta md5]).each {|key| it { @after[key].should == @before[key] } }
|
109
|
+
params.keys.each {|key| it { @after[key].should == params[key] } }
|
110
|
+
end
|
111
|
+
end
|
112
|
+
|
113
|
+
# this is the behavior of GrowthForecast API
|
114
|
+
context "number and mode does not affect" do
|
115
|
+
include_context "stub_edit_graph" if ENV['MOCK'] == 'on'
|
116
|
+
params = {
|
117
|
+
'number' => 0,
|
118
|
+
'mode' => 'count',
|
119
|
+
}
|
120
|
+
before(:all) do
|
121
|
+
@before = @client.get_graph(graph["service_name"], graph["section_name"], graph["graph_name"])
|
122
|
+
@response = @client.edit_graph(graph["service_name"], graph["section_name"], graph["graph_name"], params)
|
123
|
+
@after = @client.get_graph(graph["service_name"], graph["section_name"], graph["graph_name"])
|
124
|
+
end
|
125
|
+
params.keys.each {|key| it { @after[key].should == @before[key] } }
|
126
|
+
end
|
127
|
+
end
|
128
|
+
|
129
|
+
context "#create_complex" do
|
130
|
+
include_context "stub_create_complex" if ENV['MOCK'] == 'on'
|
131
|
+
include_context "stub_delete_complex" if ENV['MOCK'] == 'on'
|
132
|
+
context "normal" do
|
133
|
+
let(:from_graphs) do
|
134
|
+
[
|
135
|
+
graphs[0],
|
136
|
+
graphs[1],
|
137
|
+
]
|
138
|
+
end
|
139
|
+
let(:to_complex) do
|
140
|
+
{
|
141
|
+
"service_name" => graphs.first["service_name"],
|
142
|
+
"section_name" => graphs.first["section_name"],
|
143
|
+
"graph_name" => "complex_graph_test",
|
144
|
+
"description" => "complex graph test",
|
145
|
+
"sort" => 10
|
146
|
+
}
|
147
|
+
end
|
148
|
+
subject { @client.create_complex(from_graphs, to_complex) }
|
149
|
+
it { subject["error"].should == 0 }
|
150
|
+
after { @client.delete_complex(to_complex["service_name"], to_complex["section_name"], to_complex["graph_name"]) }
|
151
|
+
end
|
152
|
+
end
|
153
|
+
end
|
154
|
+
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,16 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
require "bundler/setup"
|
3
|
+
|
4
|
+
ENV['MOCK'] ||= 'on'
|
5
|
+
require "pry"
|
6
|
+
require 'debugger'
|
7
|
+
require 'growthforecast/client'
|
8
|
+
require 'webmock/rspec' if ENV['MOCK'] == 'on'
|
9
|
+
|
10
|
+
ROOT = File.dirname(__FILE__)
|
11
|
+
Dir[File.expand_path("support/**/*.rb", ROOT)].each {|f| require f }
|
12
|
+
|
13
|
+
RSpec.configure do |config|
|
14
|
+
config.treat_symbols_as_metadata_keys_with_true_values = true
|
15
|
+
config.run_all_when_everything_filtered = true
|
16
|
+
end
|
@@ -0,0 +1,150 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
|
3
|
+
base_uri = 'http://localhost:5125'
|
4
|
+
|
5
|
+
shared_context "stub_list_graph" do
|
6
|
+
let(:list_graph_example) {
|
7
|
+
[
|
8
|
+
{"service_name"=>"app_name",
|
9
|
+
"section_name"=>"hostname",
|
10
|
+
"graph_name"=>"<1sec_count",
|
11
|
+
"id"=>1},
|
12
|
+
{"service_name"=>"app_name",
|
13
|
+
"section_name"=>"hostname",
|
14
|
+
"graph_name"=>"<2sec_count",
|
15
|
+
"id"=>2},
|
16
|
+
]
|
17
|
+
}
|
18
|
+
|
19
|
+
proc = Proc.new do
|
20
|
+
# WebMock.allow_net_connect!
|
21
|
+
stub_request(:get, "#{base_uri}/json/list/graph").to_return(:status => 200, :body => list_graph_example.to_json)
|
22
|
+
end
|
23
|
+
before(:each, &proc)
|
24
|
+
before(:all, &proc)
|
25
|
+
end
|
26
|
+
|
27
|
+
shared_context "stub_get_graph" do
|
28
|
+
let(:graph_example) {
|
29
|
+
{
|
30
|
+
"number"=>0,
|
31
|
+
"llimit"=>-1000000000,
|
32
|
+
"mode"=>"gauge",
|
33
|
+
"stype"=>"AREA",
|
34
|
+
"adjustval"=>"1",
|
35
|
+
"meta"=>"",
|
36
|
+
"service_name"=>"app_name",
|
37
|
+
"gmode"=>"gauge",
|
38
|
+
"color"=>"#cc6633",
|
39
|
+
"created_at"=>"2013/02/02 00:41:11",
|
40
|
+
"section_name"=>"hostname",
|
41
|
+
"ulimit"=>1000000000,
|
42
|
+
"id"=>1,
|
43
|
+
"graph_name"=>"<1sec_count",
|
44
|
+
"description"=>"",
|
45
|
+
"sulimit"=>100000,
|
46
|
+
"unit"=>"",
|
47
|
+
"sort"=>0,
|
48
|
+
"updated_at"=>"2013/02/02 02:32:10",
|
49
|
+
"adjust"=>"*",
|
50
|
+
"type"=>"AREA",
|
51
|
+
"sllimit"=>-100000,
|
52
|
+
"md5"=>"3c59dc048e8850243be8079a5c74d079"
|
53
|
+
}
|
54
|
+
}
|
55
|
+
|
56
|
+
proc = Proc.new do
|
57
|
+
stub_request(:get, "#{base_uri}/api/#{graph['service_name']}/#{graph['section_name']}/#{graph['graph_name']}").
|
58
|
+
to_return(:status => 200, :body => graph_example.to_json)
|
59
|
+
end
|
60
|
+
before(:each, &proc)
|
61
|
+
before(:all, &proc)
|
62
|
+
end
|
63
|
+
|
64
|
+
shared_context "stub_get_graph_by_id" do
|
65
|
+
include_context "stub_get_graph"
|
66
|
+
|
67
|
+
proc = Proc.new do
|
68
|
+
stub_request(:get, "#{base_uri}/json/graph/#{graph['id']}").
|
69
|
+
to_return(:status => 200, :body => graph_example.to_json)
|
70
|
+
end
|
71
|
+
before(:each, &proc)
|
72
|
+
before(:all, &proc)
|
73
|
+
end
|
74
|
+
|
75
|
+
shared_context "stub_post_graph" do
|
76
|
+
include_context "stub_get_graph"
|
77
|
+
proc = Proc.new do
|
78
|
+
stub_request(:post, "#{base_uri}/api/#{graph['service_name']}/#{graph['section_name']}/#{graph['graph_name']}").
|
79
|
+
to_return(:status => 200, :body => { "error" => 0, "data" => graph_example }.to_json)
|
80
|
+
end
|
81
|
+
before(:each, &proc)
|
82
|
+
before(:all, &proc)
|
83
|
+
end
|
84
|
+
|
85
|
+
shared_context "stub_delete_graph" do
|
86
|
+
proc = Proc.new do
|
87
|
+
stub_request(:post, "#{base_uri}/delete/#{graph['service_name']}/#{graph['section_name']}/#{graph['graph_name']}").
|
88
|
+
to_return(:status => 200, :body => { "error" => 0 }.to_json)
|
89
|
+
end
|
90
|
+
before(:each, &proc)
|
91
|
+
before(:all, &proc)
|
92
|
+
end
|
93
|
+
|
94
|
+
shared_context "stub_edit_graph" do
|
95
|
+
include_context "stub_get_graph"
|
96
|
+
|
97
|
+
proc = Proc.new do
|
98
|
+
stub_request(:post, "#{base_uri}/json/edit/graph/#{graph['id']}").
|
99
|
+
to_return(:status => 200, :body => { "error" => 0 }.to_json)
|
100
|
+
end
|
101
|
+
before(:each, &proc)
|
102
|
+
before(:all, &proc)
|
103
|
+
end
|
104
|
+
|
105
|
+
shared_context "stub_list_complex" do
|
106
|
+
let(:list_complex_example) {
|
107
|
+
[
|
108
|
+
{"service_name"=>"app_name",
|
109
|
+
"section_name"=>"hostname",
|
110
|
+
"graph_name"=>"complex_graph_test",
|
111
|
+
"id"=>1},
|
112
|
+
]
|
113
|
+
}
|
114
|
+
let(:complex_example) {
|
115
|
+
list_complex_example.first
|
116
|
+
}
|
117
|
+
|
118
|
+
proc = Proc.new do
|
119
|
+
stub_request(:get, "#{base_uri}/json/list/complex").
|
120
|
+
to_return(:status => 200, :body => list_complex_example.to_json)
|
121
|
+
end
|
122
|
+
before(:each, &proc)
|
123
|
+
before(:all, &proc)
|
124
|
+
end
|
125
|
+
|
126
|
+
shared_context "stub_delete_complex" do
|
127
|
+
proc = Proc.new do
|
128
|
+
stub_request(:post, "#{base_uri}/delete_complex/#{complex_example['id']}").
|
129
|
+
to_return(:status => 200, :body => { "error" => 0 }.to_json)
|
130
|
+
end
|
131
|
+
before(:each, &proc)
|
132
|
+
before(:all, &proc)
|
133
|
+
end
|
134
|
+
|
135
|
+
shared_context "stub_create_complex" do
|
136
|
+
include_context "stub_list_complex"
|
137
|
+
|
138
|
+
proc = Proc.new do
|
139
|
+
list_graph_example.each do |graph|
|
140
|
+
stub_request(:get, "#{base_uri}/api/#{graph['service_name']}/#{graph['section_name']}/#{graph['graph_name']}").
|
141
|
+
to_return(:status => 200, :body => graph.to_json)
|
142
|
+
end
|
143
|
+
|
144
|
+
stub_request(:post, "#{base_uri}/json/create/complex").
|
145
|
+
to_return(:status => 200, :body => { "error" => 0 }.to_json)
|
146
|
+
end
|
147
|
+
before(:each, &proc)
|
148
|
+
before(:all, &proc)
|
149
|
+
end
|
150
|
+
|
metadata
ADDED
@@ -0,0 +1,199 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: growthforecast-client
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Naotoshi Seo
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2013-02-20 00:00:00.000000000 Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: httpclient
|
16
|
+
requirement: !ruby/object:Gem::Requirement
|
17
|
+
none: false
|
18
|
+
requirements:
|
19
|
+
- - ! '>='
|
20
|
+
- !ruby/object:Gem::Version
|
21
|
+
version: '0'
|
22
|
+
type: :runtime
|
23
|
+
prerelease: false
|
24
|
+
version_requirements: !ruby/object:Gem::Requirement
|
25
|
+
none: false
|
26
|
+
requirements:
|
27
|
+
- - ! '>='
|
28
|
+
- !ruby/object:Gem::Version
|
29
|
+
version: '0'
|
30
|
+
- !ruby/object:Gem::Dependency
|
31
|
+
name: rake
|
32
|
+
requirement: !ruby/object:Gem::Requirement
|
33
|
+
none: false
|
34
|
+
requirements:
|
35
|
+
- - ! '>='
|
36
|
+
- !ruby/object:Gem::Version
|
37
|
+
version: '0'
|
38
|
+
type: :development
|
39
|
+
prerelease: false
|
40
|
+
version_requirements: !ruby/object:Gem::Requirement
|
41
|
+
none: false
|
42
|
+
requirements:
|
43
|
+
- - ! '>='
|
44
|
+
- !ruby/object:Gem::Version
|
45
|
+
version: '0'
|
46
|
+
- !ruby/object:Gem::Dependency
|
47
|
+
name: rspec
|
48
|
+
requirement: !ruby/object:Gem::Requirement
|
49
|
+
none: false
|
50
|
+
requirements:
|
51
|
+
- - ~>
|
52
|
+
- !ruby/object:Gem::Version
|
53
|
+
version: '2.11'
|
54
|
+
type: :development
|
55
|
+
prerelease: false
|
56
|
+
version_requirements: !ruby/object:Gem::Requirement
|
57
|
+
none: false
|
58
|
+
requirements:
|
59
|
+
- - ~>
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '2.11'
|
62
|
+
- !ruby/object:Gem::Dependency
|
63
|
+
name: webmock
|
64
|
+
requirement: !ruby/object:Gem::Requirement
|
65
|
+
none: false
|
66
|
+
requirements:
|
67
|
+
- - ! '>='
|
68
|
+
- !ruby/object:Gem::Version
|
69
|
+
version: '0'
|
70
|
+
type: :development
|
71
|
+
prerelease: false
|
72
|
+
version_requirements: !ruby/object:Gem::Requirement
|
73
|
+
none: false
|
74
|
+
requirements:
|
75
|
+
- - ! '>='
|
76
|
+
- !ruby/object:Gem::Version
|
77
|
+
version: '0'
|
78
|
+
- !ruby/object:Gem::Dependency
|
79
|
+
name: pry
|
80
|
+
requirement: !ruby/object:Gem::Requirement
|
81
|
+
none: false
|
82
|
+
requirements:
|
83
|
+
- - ! '>='
|
84
|
+
- !ruby/object:Gem::Version
|
85
|
+
version: '0'
|
86
|
+
type: :development
|
87
|
+
prerelease: false
|
88
|
+
version_requirements: !ruby/object:Gem::Requirement
|
89
|
+
none: false
|
90
|
+
requirements:
|
91
|
+
- - ! '>='
|
92
|
+
- !ruby/object:Gem::Version
|
93
|
+
version: '0'
|
94
|
+
- !ruby/object:Gem::Dependency
|
95
|
+
name: pry-debugger
|
96
|
+
requirement: !ruby/object:Gem::Requirement
|
97
|
+
none: false
|
98
|
+
requirements:
|
99
|
+
- - ! '>='
|
100
|
+
- !ruby/object:Gem::Version
|
101
|
+
version: '0'
|
102
|
+
type: :development
|
103
|
+
prerelease: false
|
104
|
+
version_requirements: !ruby/object:Gem::Requirement
|
105
|
+
none: false
|
106
|
+
requirements:
|
107
|
+
- - ! '>='
|
108
|
+
- !ruby/object:Gem::Version
|
109
|
+
version: '0'
|
110
|
+
- !ruby/object:Gem::Dependency
|
111
|
+
name: rb-readline
|
112
|
+
requirement: !ruby/object:Gem::Requirement
|
113
|
+
none: false
|
114
|
+
requirements:
|
115
|
+
- - ! '>='
|
116
|
+
- !ruby/object:Gem::Version
|
117
|
+
version: '0'
|
118
|
+
type: :development
|
119
|
+
prerelease: false
|
120
|
+
version_requirements: !ruby/object:Gem::Requirement
|
121
|
+
none: false
|
122
|
+
requirements:
|
123
|
+
- - ! '>='
|
124
|
+
- !ruby/object:Gem::Version
|
125
|
+
version: '0'
|
126
|
+
- !ruby/object:Gem::Dependency
|
127
|
+
name: tapp
|
128
|
+
requirement: !ruby/object:Gem::Requirement
|
129
|
+
none: false
|
130
|
+
requirements:
|
131
|
+
- - ! '>='
|
132
|
+
- !ruby/object:Gem::Version
|
133
|
+
version: '0'
|
134
|
+
type: :development
|
135
|
+
prerelease: false
|
136
|
+
version_requirements: !ruby/object:Gem::Requirement
|
137
|
+
none: false
|
138
|
+
requirements:
|
139
|
+
- - ! '>='
|
140
|
+
- !ruby/object:Gem::Version
|
141
|
+
version: '0'
|
142
|
+
description: A Ruby Client Library for GrowthForecast API
|
143
|
+
email:
|
144
|
+
- sonots@gmail.com
|
145
|
+
executables: []
|
146
|
+
extensions: []
|
147
|
+
extra_rdoc_files: []
|
148
|
+
files:
|
149
|
+
- .gitignore
|
150
|
+
- .rdebugrc
|
151
|
+
- .rspec
|
152
|
+
- .travis.yml
|
153
|
+
- Gemfile
|
154
|
+
- LICENSE
|
155
|
+
- README.md
|
156
|
+
- Rakefile
|
157
|
+
- VERSION
|
158
|
+
- examples/complex_graph.rb
|
159
|
+
- examples/edit_graph.rb
|
160
|
+
- growthforecast-client.gemspec
|
161
|
+
- lib/growthforecast-client.rb
|
162
|
+
- lib/growthforecast/client.rb
|
163
|
+
- spec/growthforecast/client_spec.rb
|
164
|
+
- spec/spec_helper.rb
|
165
|
+
- spec/support/mock.rb
|
166
|
+
homepage: https://github.com/sonots/growthforecast-client
|
167
|
+
licenses: []
|
168
|
+
post_install_message:
|
169
|
+
rdoc_options: []
|
170
|
+
require_paths:
|
171
|
+
- lib
|
172
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
173
|
+
none: false
|
174
|
+
requirements:
|
175
|
+
- - ! '>='
|
176
|
+
- !ruby/object:Gem::Version
|
177
|
+
version: '0'
|
178
|
+
segments:
|
179
|
+
- 0
|
180
|
+
hash: -2138731048266880490
|
181
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
182
|
+
none: false
|
183
|
+
requirements:
|
184
|
+
- - ! '>='
|
185
|
+
- !ruby/object:Gem::Version
|
186
|
+
version: '0'
|
187
|
+
segments:
|
188
|
+
- 0
|
189
|
+
hash: -2138731048266880490
|
190
|
+
requirements: []
|
191
|
+
rubyforge_project:
|
192
|
+
rubygems_version: 1.8.23
|
193
|
+
signing_key:
|
194
|
+
specification_version: 3
|
195
|
+
summary: A Ruby Client Library for GrowthForecast API
|
196
|
+
test_files:
|
197
|
+
- spec/growthforecast/client_spec.rb
|
198
|
+
- spec/spec_helper.rb
|
199
|
+
- spec/support/mock.rb
|