growthforecast 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 +17 -0
- data/Gemfile +4 -0
- data/LICENSE +13 -0
- data/README.md +194 -0
- data/Rakefile +2 -0
- data/example/test1.rb +94 -0
- data/growthforecast.gemspec +17 -0
- data/lib/growthforecast/complex.rb +75 -0
- data/lib/growthforecast/graph.rb +70 -0
- data/lib/growthforecast/path.rb +58 -0
- data/lib/growthforecast/version.rb +3 -0
- data/lib/growthforecast.rb +235 -0
- metadata +58 -0
data/.gitignore
ADDED
data/Gemfile
ADDED
data/LICENSE
ADDED
@@ -0,0 +1,13 @@
|
|
1
|
+
Copyright 2013- TAGOMORI Satoshi
|
2
|
+
|
3
|
+
Licensed under the Apache License, Version 2.0 (the "License");
|
4
|
+
you may not use this file except in compliance with the License.
|
5
|
+
You may obtain a copy of the License at
|
6
|
+
|
7
|
+
http://www.apache.org/licenses/LICENSE-2.0
|
8
|
+
|
9
|
+
Unless required by applicable law or agreed to in writing, software
|
10
|
+
distributed under the License is distributed on an "AS IS" BASIS,
|
11
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
12
|
+
See the License for the specific language governing permissions and
|
13
|
+
limitations under the License.
|
data/README.md
ADDED
@@ -0,0 +1,194 @@
|
|
1
|
+
# GrowthForecast
|
2
|
+
|
3
|
+
Client library to operate GrowthForecast.
|
4
|
+
|
5
|
+
* http://kazeburo.github.com/GrowthForecast/ (Japanese)
|
6
|
+
* https://github.com/kazeburo/growthforecast
|
7
|
+
|
8
|
+
Update graph value, or create/edit/delete basic graphs and complex graphs.
|
9
|
+
|
10
|
+
## Installation
|
11
|
+
|
12
|
+
Add this line to your application's Gemfile:
|
13
|
+
|
14
|
+
gem 'growthforecast'
|
15
|
+
|
16
|
+
And then execute:
|
17
|
+
|
18
|
+
$ bundle
|
19
|
+
|
20
|
+
Or install it yourself as:
|
21
|
+
|
22
|
+
$ gem install growthforecast
|
23
|
+
|
24
|
+
## Usage
|
25
|
+
|
26
|
+
Update graph's value:
|
27
|
+
|
28
|
+
```ruby
|
29
|
+
require 'growthforecast'
|
30
|
+
gf = GrowthForecast.new('your.gf.host.name', 5125)
|
31
|
+
|
32
|
+
gf.post('servicename', 'sectionname', 'graphname', 50)
|
33
|
+
#### or post with graph mode and color
|
34
|
+
# gf.post('servicename', 'sectionname', 'graphname', 50, 'gauge', '#ff00ff')
|
35
|
+
```
|
36
|
+
|
37
|
+
Get graph list and full descripted object:
|
38
|
+
|
39
|
+
```ruby
|
40
|
+
glist = gf.graphs() #=> [] or array of GrowthForecast::Path
|
41
|
+
|
42
|
+
glist[0].id
|
43
|
+
glist[0].service_name
|
44
|
+
glist[0].section_name
|
45
|
+
glist[0].graph_name
|
46
|
+
glist[0].complex? #=> false
|
47
|
+
|
48
|
+
graph = gf.graph(glist[0].id) #=> instance of GrowthForecast::Graph
|
49
|
+
|
50
|
+
graph.id
|
51
|
+
graph.description
|
52
|
+
graph.color
|
53
|
+
graph.type
|
54
|
+
graph.number
|
55
|
+
graph.created_at #=> instance of Time
|
56
|
+
|
57
|
+
### complex
|
58
|
+
clist = gf.complexes() #=> [] or array of GrowthForecast::Path
|
59
|
+
|
60
|
+
clist[0].id
|
61
|
+
clist[0].service_name
|
62
|
+
clist[0].section_name
|
63
|
+
clist[0].graph_name
|
64
|
+
clist[0].complex? #=> true
|
65
|
+
|
66
|
+
complex = gf.complex(clist[0].id) #=> instance of GrowthForecast::Complex
|
67
|
+
|
68
|
+
complex.id
|
69
|
+
complex.description
|
70
|
+
complex.data #=> array of GrowthForecast::Complex::Item
|
71
|
+
|
72
|
+
complex.data[1].graph_id #=> id of GrowthForecast::Graph
|
73
|
+
complex.data[1].stack #=> boolean
|
74
|
+
complex.data[1].gmode #=> 'gauge' or 'subtract'
|
75
|
+
complex.data[1].type #=> 'AREA', 'LINE1' or 'LINE2'
|
76
|
+
|
77
|
+
### get data of elements of complex graph
|
78
|
+
|
79
|
+
complex.data.each do |e|
|
80
|
+
graph = gf.graph(e.graph_id) #=> GrowthForecast::Graph
|
81
|
+
# ...
|
82
|
+
end
|
83
|
+
|
84
|
+
### get full list of both of graph and complex
|
85
|
+
|
86
|
+
list = gf.all() #=> array of GrowthForecast::Graph and GrowthForecast::Complex
|
87
|
+
|
88
|
+
tree = gf.tree()
|
89
|
+
tree['service']['section']['name']
|
90
|
+
#=> instance of GrowthForecast::Graph or GrowthForecast::Complex
|
91
|
+
|
92
|
+
### Or more simply, get one by path name
|
93
|
+
### (heavy operation: consider to cache result of gf.all() or gf.tree() to call multiple times)
|
94
|
+
one = gf.by_name('service', 'section', 'name')
|
95
|
+
#=> instance of GrowthForecast::Graph or GrowthForecast::Complex
|
96
|
+
```
|
97
|
+
|
98
|
+
To edit:
|
99
|
+
|
100
|
+
```ruby
|
101
|
+
target = gf.graph(glist.first.id)
|
102
|
+
|
103
|
+
target.description = 'OK, we are now testing...'
|
104
|
+
target.color = '#000000'
|
105
|
+
target.type = 'LINE2'
|
106
|
+
|
107
|
+
result = gf.edit(target) #=> boolean: success or not
|
108
|
+
|
109
|
+
raise "Something goes wrong..." unless result
|
110
|
+
|
111
|
+
### invert complex graph element order
|
112
|
+
complex = gf.complex(clist.last.id)
|
113
|
+
|
114
|
+
complex.data.reverse!
|
115
|
+
|
116
|
+
gf.edit(complex)
|
117
|
+
|
118
|
+
### add graph into complex graph's data
|
119
|
+
|
120
|
+
item = GrowthForecast::Complex::Item.new({graph_id: g.id, stack: true, gmode: 'gauge', type: 'AREA'})
|
121
|
+
complex.data.push(item)
|
122
|
+
|
123
|
+
gf.edit(complex)
|
124
|
+
|
125
|
+
### delete graph
|
126
|
+
gf.delete(graph) #=> if false, not deleted correctly...
|
127
|
+
gf.delete(complex)
|
128
|
+
```
|
129
|
+
|
130
|
+
To add graph, `add_graph()` and `add()` are available:
|
131
|
+
|
132
|
+
```ruby
|
133
|
+
# add_graph(service, section, graph_name, initial_value=0, color=nil, mode=nil)
|
134
|
+
## color default: nil(random)
|
135
|
+
## mode default: nil('gauge')
|
136
|
+
gf.add_graph('example', 'test', 'graph1')
|
137
|
+
|
138
|
+
# add(spec)
|
139
|
+
## spec: instance of GrowthForecast::Graph
|
140
|
+
spec = GrowthForecast::Graph({service_name: 'example', section_name: 'test', graph_name: 'graph1', color: '#0000ff'})
|
141
|
+
gf.add(spec)
|
142
|
+
```
|
143
|
+
|
144
|
+
As same as graph, to add complex, `add_complex()` and `add()` are available:
|
145
|
+
|
146
|
+
```ruby
|
147
|
+
# add_complex(service, section, graph_name, description, sumup, sort, type, gmode, stack, data_graph_ids)
|
148
|
+
## sumup: true or false
|
149
|
+
## sort: 0-19
|
150
|
+
## type,gmode,stack: specify all options of members of data, with same value
|
151
|
+
## type: 'AREA', 'LINE1', 'LINE2'
|
152
|
+
## gmode: 'gauge', 'subtract'
|
153
|
+
## stack: true or false
|
154
|
+
gf.add_complex('example', 'test', 'summary1', 'testing...', true, 0, 'AREA', 'gauge', true, [graph1.id, graph2.id])
|
155
|
+
|
156
|
+
# add(spec)
|
157
|
+
## spec: instance of GrowthForecast::Complex
|
158
|
+
spec = GrowthForecast::Complex({
|
159
|
+
service_name: 'example', section_name: 'test', graph_name: 'summary1',
|
160
|
+
description: 'testing...', sumup: true,
|
161
|
+
data: graph_id_list.map{|id| GrowthForecast::Complex::Item.new({graph_id: id, type: 'AREA', gmode: 'gauge', stack: true}) }
|
162
|
+
})
|
163
|
+
# hmm, i want not to stack last of data, and want to show by bold line.
|
164
|
+
spec.data.last.stack = false
|
165
|
+
spec.data.last.type = 'LINE2'
|
166
|
+
|
167
|
+
gf.add(spec)
|
168
|
+
```
|
169
|
+
|
170
|
+
`add()` accepts graph/complex instance already exists as template:
|
171
|
+
|
172
|
+
```ruby
|
173
|
+
# copy template complex graph
|
174
|
+
complex_spec = gf.complex(template.id)
|
175
|
+
|
176
|
+
complex_spec.graph_name = 'copy_of_template_1' # you MUST change one of service/section/graph at least
|
177
|
+
complex_spec.description = '....'
|
178
|
+
|
179
|
+
gf.add(complex_spec) # add() ignores 'id' attribute
|
180
|
+
```
|
181
|
+
|
182
|
+
## TODO
|
183
|
+
|
184
|
+
* validations of specs
|
185
|
+
* diff of 2 graph objects
|
186
|
+
* bin/gfclient
|
187
|
+
* tests
|
188
|
+
|
189
|
+
## Copyright
|
190
|
+
|
191
|
+
* Copyright (c) 2013- TAGOMORI Satoshi (tagomoris)
|
192
|
+
* License
|
193
|
+
* Apache License, Version 2.0
|
194
|
+
* see 'LICENSE'
|
data/Rakefile
ADDED
data/example/test1.rb
ADDED
@@ -0,0 +1,94 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require 'minitest/unit'
|
4
|
+
include MiniTest::Assertions
|
5
|
+
|
6
|
+
require 'growthforecast'
|
7
|
+
|
8
|
+
gf = GrowthForecast.new
|
9
|
+
|
10
|
+
start_graphs = gf.all().size
|
11
|
+
|
12
|
+
# value update
|
13
|
+
r = gf.post('example', 'test', 'graph2', Process.pid)
|
14
|
+
g1a = gf.graph(r.id)
|
15
|
+
g1b = gf.post('example', 'test', 'graph2', 0, 'count')
|
16
|
+
assert (g1a.number == g1b.number), "if number changed, something goes wrong..."
|
17
|
+
|
18
|
+
# get graph
|
19
|
+
r = gf.graph(99999) # maybe non-existing graph id
|
20
|
+
assert r.nil?, "may be nil"
|
21
|
+
|
22
|
+
# list check
|
23
|
+
glist = gf.graphs()
|
24
|
+
assert (glist.is_a?(Array) and glist.size >= 0), "lists must be not nil"
|
25
|
+
|
26
|
+
# add graph and edit it
|
27
|
+
r = gf.add_graph('example', 'test', 'graphrb' + Process.pid.to_s + 'a', 0, '#ff0000')
|
28
|
+
g2 = gf.by_name('example', 'test', 'graphrb' + Process.pid.to_s + 'a')
|
29
|
+
|
30
|
+
assert (g2.color == '#ff0000'), "added graph color must be #ffff00"
|
31
|
+
assert (g2.type == 'AREA'), "initial type of graph may be AREA"
|
32
|
+
|
33
|
+
g2.type = 'LINE2'
|
34
|
+
|
35
|
+
r = gf.edit(g2)
|
36
|
+
|
37
|
+
g3 = gf.graph(g2.id)
|
38
|
+
assert (g3.type == 'LINE2'), "type not changed correctly #{g3.type}"
|
39
|
+
|
40
|
+
glist2 = gf.graphs()
|
41
|
+
assert (glist.size + 1 == glist2.size), "graphs not added one: #{glist.size} -> #{glist2.size}"
|
42
|
+
|
43
|
+
# add graph by spec
|
44
|
+
spec = GrowthForecast::Graph.new({
|
45
|
+
'service_name' => 'example', 'section_name' => 'test', 'graph_name' => 'graphrb' + Process.pid.to_s + 'b',
|
46
|
+
'color' => '#00FF00', 'mode' => 'derive',
|
47
|
+
})
|
48
|
+
r = gf.add(spec)
|
49
|
+
|
50
|
+
assert r, "add failed with return value #{r}"
|
51
|
+
|
52
|
+
g4 = gf.by_name('example', 'test', 'graphrb' + Process.pid.to_s + 'b')
|
53
|
+
assert (g4.mode == 'derive'), "mode should be derive, but #{g4.mode}"
|
54
|
+
|
55
|
+
# complex
|
56
|
+
r = gf.complex(99999) # may be non-existing id
|
57
|
+
assert r.nil?, "may be nil"
|
58
|
+
|
59
|
+
clist = gf.complexes()
|
60
|
+
assert (clist.is_a?(Array) and clist.size >= 0), "lists must be not nil"
|
61
|
+
|
62
|
+
r = gf.add_complex('example', 'test', 'graphrb' + Process.pid.to_s + 'c', 'testing now', true, 19, 'LINE1', 'gauge', true, [g1b.id, g2.id, g4.id])
|
63
|
+
|
64
|
+
assert r, "add_complex failed with return value #{r}"
|
65
|
+
c1 = gf.by_name('example', 'test', 'graphrb' + Process.pid.to_s + 'c')
|
66
|
+
assert c1.complex?, "c1 is not complex, why?"
|
67
|
+
|
68
|
+
c2spec = gf.complex(c1) #copy of c1 instance
|
69
|
+
c2spec.graph_name = 'graphrb' + Process.pid.to_s + 'd'
|
70
|
+
c2spec.data = [
|
71
|
+
GrowthForecast::Complex::Item.new(graph_id: g2.id),
|
72
|
+
GrowthForecast::Complex::Item.new(graph_id: g4.id),
|
73
|
+
]
|
74
|
+
|
75
|
+
r = gf.add(c2spec)
|
76
|
+
|
77
|
+
assert r, "add failed with return value #{r}"
|
78
|
+
|
79
|
+
c2 = gf.by_name('example', 'test', 'graphrb' + Process.pid.to_s + 'd')
|
80
|
+
c2.sort = 0
|
81
|
+
|
82
|
+
r = gf.edit(c2)
|
83
|
+
|
84
|
+
c2 = gf.complex(c2)
|
85
|
+
assert (c2.sort == 0), "sort value not updated correctly"
|
86
|
+
|
87
|
+
# delete graphs
|
88
|
+
unless gf.delete(c2) && gf.delete(c1) && gf.delete(g4) && gf.delete(g2) && gf.delete(g1b)
|
89
|
+
raise "failed to delete graphs ..."
|
90
|
+
end
|
91
|
+
|
92
|
+
end_graphs = gf.all().size
|
93
|
+
assert (start_graphs == end_graphs), "start graph nums and end graph nums mismatch: #{start_graphs} -> #{end_graphs}"
|
94
|
+
|
@@ -0,0 +1,17 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
require File.expand_path('../lib/growthforecast/version', __FILE__)
|
3
|
+
|
4
|
+
Gem::Specification.new do |gem|
|
5
|
+
gem.authors = ["TAGOMORI Satoshi"]
|
6
|
+
gem.email = ["tagomoris@gmail.com"]
|
7
|
+
gem.description = %q{Client library and tool to update values, create/edit/delete graphs of GrowthForecast}
|
8
|
+
gem.summary = %q{A client library for GrowthForecast}
|
9
|
+
gem.homepage = "https://github.com/tagomoris/rb-growthforecast"
|
10
|
+
|
11
|
+
gem.files = `git ls-files`.split($\)
|
12
|
+
gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
|
13
|
+
gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
|
14
|
+
gem.name = "growthforecast"
|
15
|
+
gem.require_paths = ["lib"]
|
16
|
+
gem.version = GrowthForecast::VERSION
|
17
|
+
end
|
@@ -0,0 +1,75 @@
|
|
1
|
+
require 'json'
|
2
|
+
require 'time'
|
3
|
+
|
4
|
+
class GrowthForecast::Complex
|
5
|
+
attr_accessor :id, :service_name, :section_name, :graph_name
|
6
|
+
attr_accessor :description, :sort, :sumup
|
7
|
+
attr_accessor :data
|
8
|
+
attr_accessor :number, :data, :created_at, :updated_at
|
9
|
+
|
10
|
+
# TODO strict validations
|
11
|
+
|
12
|
+
def initialize(obj)
|
13
|
+
if obj.is_a?(String)
|
14
|
+
obj = JSON.parse(obj)
|
15
|
+
end
|
16
|
+
obj.keys.each do |k|
|
17
|
+
obj[k.to_sym] = obj.delete(k)
|
18
|
+
end
|
19
|
+
|
20
|
+
if not obj[:complex]
|
21
|
+
raise ArgumentError, "non-complex graph is for GrowthForecast::Complex"
|
22
|
+
end
|
23
|
+
|
24
|
+
@id = obj[:id] ? obj[:id].to_i : nil
|
25
|
+
@service_name = obj[:service_name]
|
26
|
+
@section_name = obj[:section_name]
|
27
|
+
@graph_name = obj[:graph_name]
|
28
|
+
@description = obj[:description]
|
29
|
+
@sort = (obj[:sort] || 19).to_i
|
30
|
+
@sumup = obj[:sumup] ? true : false
|
31
|
+
@data = (obj[:data] || []).map{|d| Item.new(d)}
|
32
|
+
@number = (obj[:number] || 0).to_i
|
33
|
+
@created_at = obj[:created_at] ? Time.strptime(obj[:created_at], GrowthForecast::TIME_FORMAT) : nil
|
34
|
+
@updated_at = obj[:updated_at] ? Time.strptime(obj[:updated_at], GrowthForecast::TIME_FORMAT) : nil
|
35
|
+
end
|
36
|
+
|
37
|
+
def complex?
|
38
|
+
true
|
39
|
+
end
|
40
|
+
|
41
|
+
def to_json
|
42
|
+
{
|
43
|
+
'complex' => true,
|
44
|
+
'id' => @id,
|
45
|
+
'service_name' => @service_name, 'section_name' => @section_name, 'graph_name' => @graph_name,
|
46
|
+
'description' => @description, 'sort' => @sort, 'sumup' => @sumup,
|
47
|
+
'data' => @data.map(&:to_hash),
|
48
|
+
'number' => @number,
|
49
|
+
'created_at' => (@created_at ? @created_at.strftime(GrowthForecast::TIME_FORMAT) : nil),
|
50
|
+
'updated_at' => (@updated_at ? @updated_at.strftime(GrowthForecast::TIME_FORMAT) : nil),
|
51
|
+
}.to_json
|
52
|
+
end
|
53
|
+
|
54
|
+
class Item
|
55
|
+
attr_accessor :graph_id, :gmode, :stack, :type
|
56
|
+
|
57
|
+
def initialize(obj)
|
58
|
+
if obj.is_a?(String)
|
59
|
+
obj = JSON.parse(obj)
|
60
|
+
end
|
61
|
+
obj.keys.each do |k|
|
62
|
+
obj[k.to_sym] = obj.delete(k)
|
63
|
+
end
|
64
|
+
|
65
|
+
@graph_id = obj[:graph_id]
|
66
|
+
@gmode = obj[:gmode] || 'gauge'
|
67
|
+
@stack = (obj[:stack] || obj[:stack].nil?) ? true : false
|
68
|
+
@type = obj[:type] || 'AREA'
|
69
|
+
end
|
70
|
+
|
71
|
+
def to_hash
|
72
|
+
{'graph_id' => @graph_id, 'gmode' => @gmode, 'stack' => @stack, 'type' => @type}
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
@@ -0,0 +1,70 @@
|
|
1
|
+
require 'json'
|
2
|
+
require 'time'
|
3
|
+
|
4
|
+
class GrowthForecast::Graph
|
5
|
+
attr_accessor :id, :service_name, :section_name, :graph_name
|
6
|
+
attr_accessor :description, :mode, :sort, :color, :gmode
|
7
|
+
attr_accessor :type, :ulimit, :llimit, :stype, :sulimit, :sllimit
|
8
|
+
attr_accessor :adjust, :adjustval, :unit
|
9
|
+
attr_accessor :number, :data, :created_at, :updated_at
|
10
|
+
|
11
|
+
# TODO strict validations
|
12
|
+
|
13
|
+
def initialize(obj)
|
14
|
+
if obj.is_a?(String)
|
15
|
+
obj = JSON.parse(obj)
|
16
|
+
end
|
17
|
+
|
18
|
+
obj.keys.each do |k|
|
19
|
+
obj[k.to_sym] = obj.delete(k)
|
20
|
+
end
|
21
|
+
|
22
|
+
if obj[:complex]
|
23
|
+
raise ArgumentError, "complex graph is for GrowthForecast::Complex"
|
24
|
+
end
|
25
|
+
|
26
|
+
@id = obj[:id] ? obj[:id].to_i : nil
|
27
|
+
@service_name = obj[:service_name]
|
28
|
+
@section_name = obj[:section_name]
|
29
|
+
@graph_name = obj[:graph_name]
|
30
|
+
@description = obj[:description]
|
31
|
+
@mode = obj[:mode] || 'gauge'
|
32
|
+
@sort = (obj[:sort] || 19).to_i
|
33
|
+
@color = obj[:color]
|
34
|
+
@gmode = obj[:gmode] || 'gauge'
|
35
|
+
@type = obj[:type] || 'AREA'
|
36
|
+
@ulimit = obj[:ulimit] || 1000000000
|
37
|
+
@llimit = obj[:llimit] || -1000000000
|
38
|
+
@stype = obj[:stype] || 'AREA'
|
39
|
+
@sulimit = obj[:sulimit] || 100000
|
40
|
+
@sllimit = obj[:sllimit] || -100000
|
41
|
+
@adjust = obj[:adjust] || '*'
|
42
|
+
@adjustval = (obj[:adjustval] || 1).to_i
|
43
|
+
@unit = obj[:unit] || ''
|
44
|
+
@number = (obj[:number] || 0).to_i
|
45
|
+
@data = obj[:data] || []
|
46
|
+
@created_at = obj[:created_at] ? Time.strptime(obj[:created_at], GrowthForecast::TIME_FORMAT) : nil
|
47
|
+
@updated_at = obj[:updated_at] ? Time.strptime(obj[:updated_at], GrowthForecast::TIME_FORMAT) : nil
|
48
|
+
end
|
49
|
+
|
50
|
+
def complex?
|
51
|
+
false
|
52
|
+
end
|
53
|
+
|
54
|
+
def to_json
|
55
|
+
if @color.nil?
|
56
|
+
raise RuntimeError, "cannot stringify as json without 'color' parameter"
|
57
|
+
end
|
58
|
+
|
59
|
+
{
|
60
|
+
'complex' => false,
|
61
|
+
'id' => @id,
|
62
|
+
'service_name' => @service_name, 'section_name' => @section_name, 'graph_name' => @graph_name,
|
63
|
+
'description' => @description, 'mode' => @mode, 'sort' => @sort, 'color' => @color, 'gmode' => @gmode,
|
64
|
+
'type' => @type, 'ulimit' => @ulimit, 'llimit' => @llimit, 'stype' => @stype, 'sulimit' => @sulimit, 'sllimit' => @sllimit,
|
65
|
+
'adjust' => @adjust, 'adjustval' => @adjustval, 'unit' => @unit,
|
66
|
+
'number' => @number, 'data' => @data,
|
67
|
+
'created_at' => @created_at.strftime(GrowthForecast::TIME_FORMAT), 'updated_at' => @updated_at.strftime(GrowthForecast::TIME_FORMAT)
|
68
|
+
}.to_json
|
69
|
+
end
|
70
|
+
end
|
@@ -0,0 +1,58 @@
|
|
1
|
+
require 'json'
|
2
|
+
require_relative './graph'
|
3
|
+
require_relative './complex'
|
4
|
+
|
5
|
+
class GrowthForecast::Path
|
6
|
+
attr_accessor :id, :service_name, :section_name, :graph_name
|
7
|
+
attr_accessor :complex
|
8
|
+
|
9
|
+
PATH_KEYS = [:id,:service_name,:section_name,:graph_name,:complex]
|
10
|
+
def self.path?(obj)
|
11
|
+
if obj.is_a?(String)
|
12
|
+
obj = JSON.parse(obj)
|
13
|
+
end
|
14
|
+
keys = obj.keys.size
|
15
|
+
if keys >= 4 && keys <= 5 && obj.keys.map(&:to_sym).reduce(true){|r,k| r && PATH_KEYS.include?(k)}
|
16
|
+
return true
|
17
|
+
end
|
18
|
+
false
|
19
|
+
end
|
20
|
+
|
21
|
+
def initialize(obj)
|
22
|
+
if obj.is_a?(String)
|
23
|
+
obj = JSON.parse(obj)
|
24
|
+
end
|
25
|
+
obj.keys.each do |k|
|
26
|
+
obj[k.to_sym] = obj.delete(k)
|
27
|
+
end
|
28
|
+
|
29
|
+
@id = obj[:id].to_i
|
30
|
+
@service_name = obj[:service_name]
|
31
|
+
@section_name = obj[:section_name]
|
32
|
+
@graph_name = obj[:graph_name]
|
33
|
+
@complex = obj[:complex] ? true : false
|
34
|
+
end
|
35
|
+
|
36
|
+
def complex?
|
37
|
+
@complex
|
38
|
+
end
|
39
|
+
|
40
|
+
def to_json
|
41
|
+
{
|
42
|
+
'id' => @id, 'complex' => @complex,
|
43
|
+
'service_name' => @service_name, 'section_name' => @section_name, 'graph_name' => @graph_name,
|
44
|
+
}.to_json
|
45
|
+
end
|
46
|
+
|
47
|
+
def to_graph
|
48
|
+
if @complex
|
49
|
+
GrowthForecast::Complex.new({
|
50
|
+
complex: true, id: @id, service_name: @service_name, section_name: @section_name, graph_name: @graph_name
|
51
|
+
})
|
52
|
+
else
|
53
|
+
GrowthForecast::Graph.new({
|
54
|
+
complex: false, id: @id, service_name: @service_name, section_name: @section_name, graph_name: @graph_name
|
55
|
+
})
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
@@ -0,0 +1,235 @@
|
|
1
|
+
require "growthforecast/version"
|
2
|
+
|
3
|
+
class GrowthForecast
|
4
|
+
TIME_FORMAT = '%Y/%m/%d %H:%M:%S'
|
5
|
+
end
|
6
|
+
|
7
|
+
require "growthforecast/graph"
|
8
|
+
require "growthforecast/complex"
|
9
|
+
require "growthforecast/path"
|
10
|
+
|
11
|
+
require 'net/http'
|
12
|
+
require 'uri'
|
13
|
+
require 'json'
|
14
|
+
|
15
|
+
class GrowthForecast
|
16
|
+
attr_accessor :host, :port, :prefix, :timeout, :debug
|
17
|
+
|
18
|
+
def initialize(host='localhost', port=5125, prefix=nil, timeout=30, debug=false)
|
19
|
+
@host = host
|
20
|
+
@port = port.to_i
|
21
|
+
@prefix = if prefix && (prefix =~ /^\//) then prefix
|
22
|
+
elsif prefix then '/' + prefix
|
23
|
+
else '/'
|
24
|
+
end
|
25
|
+
@prefix.chop! if @prefix =~ /\/$/
|
26
|
+
@timeout = timeout.to_i
|
27
|
+
@debug = debug ? true : false
|
28
|
+
end
|
29
|
+
|
30
|
+
def debug(mode=nil)
|
31
|
+
if mode.nil?
|
32
|
+
return GrowthForecast.new(@host,@port,@prefix,@timeout,true)
|
33
|
+
end
|
34
|
+
@mode = mode ? true : false
|
35
|
+
self
|
36
|
+
end
|
37
|
+
|
38
|
+
def post(service, section, name, value, mode=nil, color=nil)
|
39
|
+
form = {'number' => value}
|
40
|
+
form.update({'mode' => mode}) if mode
|
41
|
+
form.update({'color' => color}) if color
|
42
|
+
content = URI.encode_www_form(form)
|
43
|
+
|
44
|
+
request('POST', "/api/#{service}/#{section}/#{name}", {}, content)
|
45
|
+
end
|
46
|
+
|
47
|
+
def by_name(service, section, name)
|
48
|
+
((self.tree()[service] || {})[section] || {})[name]
|
49
|
+
end
|
50
|
+
|
51
|
+
def graph(id)
|
52
|
+
if id.respond_to?(:complex?) && (not id.complex?) # accept graph object itself to reload
|
53
|
+
id = id.id
|
54
|
+
end
|
55
|
+
request('GET', "/json/graph/#{id}")
|
56
|
+
end
|
57
|
+
|
58
|
+
def complex(id)
|
59
|
+
if id.respond_to?(:complex?) && id.complex? # accept complex object itself to reload
|
60
|
+
id = id.id
|
61
|
+
end
|
62
|
+
request('GET', "/json/complex/#{id}")
|
63
|
+
end
|
64
|
+
|
65
|
+
def graphs
|
66
|
+
request('GET', "/json/list/graph", {}, '', true)
|
67
|
+
end
|
68
|
+
|
69
|
+
def complexes
|
70
|
+
list = request('GET', "/json/list/complex", {}, '', true)
|
71
|
+
list.each do |path|
|
72
|
+
path.complex = true
|
73
|
+
end
|
74
|
+
list
|
75
|
+
end
|
76
|
+
|
77
|
+
def all
|
78
|
+
request('GET', "/json/list/all", {}, '', true)
|
79
|
+
end
|
80
|
+
|
81
|
+
def tree
|
82
|
+
root = {}
|
83
|
+
self.all().each do |i|
|
84
|
+
root[i.service_name] ||= {}
|
85
|
+
root[i.service_name][i.section_name] ||= {}
|
86
|
+
root[i.service_name][i.section_name][i.graph_name] = i
|
87
|
+
end
|
88
|
+
root
|
89
|
+
end
|
90
|
+
|
91
|
+
def edit(spec)
|
92
|
+
if spec.id.nil?
|
93
|
+
raise ArgumentError, "cannot edit graph without id (get graph data from GrowthForecast at first)"
|
94
|
+
end
|
95
|
+
path = if spec.complex?
|
96
|
+
"/json/edit/complex/#{spec.id}"
|
97
|
+
else
|
98
|
+
"/json/edit/graph/#{spec.id}"
|
99
|
+
end
|
100
|
+
request('POST', path, {}, spec.to_json)
|
101
|
+
end
|
102
|
+
|
103
|
+
def delete(spec)
|
104
|
+
if spec.id.nil?
|
105
|
+
raise ArgumentError, "cannot delete graph without id (get graph data from GrowthForecast at first)"
|
106
|
+
end
|
107
|
+
path = if spec.complex?
|
108
|
+
"/delete_complex/#{spec.id}"
|
109
|
+
else
|
110
|
+
"/delete/#{spec.service_name}/#{spec.section_name}/#{spec.graph_name}"
|
111
|
+
end
|
112
|
+
request('POST', path)
|
113
|
+
end
|
114
|
+
|
115
|
+
ADDITIONAL_PARAMS = ['description' 'sort' 'gmode' 'ulimit' 'llimit' 'sulimit' 'sllimit' 'type' 'stype' 'adjust' 'adjustval' 'unit']
|
116
|
+
def add(spec)
|
117
|
+
unless spec.respond_to?(:complex?)
|
118
|
+
raise ArgumentError, "parameter of add() must be instance of GrowthForecast::Graph or GrowthForecast::Complex (or use add_graph/add_complex)"
|
119
|
+
end
|
120
|
+
if spec.complex?
|
121
|
+
add_complex_spec(spec)
|
122
|
+
else
|
123
|
+
add_graph(spec.service_name, spec.section_name, spec.graph_name, spec.number, spec.color, spec.mode)
|
124
|
+
end
|
125
|
+
end
|
126
|
+
|
127
|
+
def add_graph(service, section, graph_name, initial_value=0, color=nil, mode=nil)
|
128
|
+
if service.empty? || section.empty? || graph_name.empty?
|
129
|
+
raise ArgumentError, "service, section and graph_name must be specified"
|
130
|
+
end
|
131
|
+
if (not color.nil?)
|
132
|
+
unless color =~ /^#[0-9a-fA-F]{6}/
|
133
|
+
raise ArgumentError, "color must be specified like #FFFFFF"
|
134
|
+
end
|
135
|
+
end
|
136
|
+
post(service, section, graph_name, initial_value, mode, color) and true # 'add' and 'add_graph' returns boolean not graph object
|
137
|
+
end
|
138
|
+
|
139
|
+
def add_complex(service, section, graph_name, description, sumup, sort, type, gmode, stack, data_graph_ids)
|
140
|
+
unless data_graph_ids.is_a?(Array) && data_graph_ids.size > 0
|
141
|
+
raise ArgumentError, "To create complex graph, specify 1 or more sub graph ids"
|
142
|
+
end
|
143
|
+
unless sort >= 0 and sort <= 19
|
144
|
+
raise ArgumentError, "sort must be 0..19"
|
145
|
+
end
|
146
|
+
unless type == 'AREA' || type == 'LINE1' || type == 'LINE2'
|
147
|
+
raise ArgumentError, "type must be one of AREA/LINE1/LINE2"
|
148
|
+
end
|
149
|
+
unless gmode == 'gauge' || gmode == 'subtract'
|
150
|
+
raise ArgumentError, "gmode must be one of gauge/subtract"
|
151
|
+
end
|
152
|
+
spec = GrowthForecast::Complex.new({
|
153
|
+
complex: true,
|
154
|
+
service_name: service, section_name: section, graph_name: graph_name,
|
155
|
+
description: description, sumup: sumup, sort: sort,
|
156
|
+
data: data_graph_ids.map{|id| {'graph_id' => id, 'type' => type, 'gmode' => gmode, 'stack' => stack} }
|
157
|
+
})
|
158
|
+
add_complex_spec(spec)
|
159
|
+
end
|
160
|
+
|
161
|
+
private
|
162
|
+
|
163
|
+
def add_complex_spec(spec)
|
164
|
+
request('POST', "/json/create/complex", {}, spec.to_json)
|
165
|
+
end
|
166
|
+
|
167
|
+
def concrete(obj)
|
168
|
+
case
|
169
|
+
when obj.nil?
|
170
|
+
nil
|
171
|
+
when obj.is_a?(TrueClass) || obj.is_a?(FalseClass)
|
172
|
+
obj
|
173
|
+
when obj.is_a?(Array)
|
174
|
+
obj.map{|e| concrete(e)}
|
175
|
+
when GrowthForecast::Path.path?(obj)
|
176
|
+
GrowthForecast::Path.new(obj)
|
177
|
+
when obj['complex']
|
178
|
+
Complex.new(obj)
|
179
|
+
else
|
180
|
+
Graph.new(obj)
|
181
|
+
end
|
182
|
+
end
|
183
|
+
|
184
|
+
def request(method, path, header={}, content='', getlist=false)
|
185
|
+
concrete(http_request(method, path, header, content, getlist))
|
186
|
+
end
|
187
|
+
|
188
|
+
def http_request(method, path, header={}, content='', getlist=false)
|
189
|
+
conn = Net::HTTP.new(@host, @port)
|
190
|
+
conn.open_timeout = conn.read_timeout = @timeout
|
191
|
+
request_path = @prefix + path
|
192
|
+
res = conn.send_request(method, request_path, content, header)
|
193
|
+
|
194
|
+
unless res.is_a?(Net::HTTPSuccess)
|
195
|
+
return [] if getlist and res.code == '404'
|
196
|
+
|
197
|
+
# GrowthForecast returns 200 for validation and other errors. hmm...
|
198
|
+
if @debug
|
199
|
+
warn "GrowthForecast returns response code #{res.code}"
|
200
|
+
warn " request (#{method}) http://#{host}:#{port}#{request_path}"
|
201
|
+
warn " with content #{content}"
|
202
|
+
end
|
203
|
+
return nil
|
204
|
+
end
|
205
|
+
# code 200
|
206
|
+
return true if res.body.length < 1
|
207
|
+
|
208
|
+
obj = begin
|
209
|
+
JSON.parse(res.body)
|
210
|
+
rescue JSON::ParserError => e
|
211
|
+
warn "failed to parse response content as json, with error: #{e.message}"
|
212
|
+
nil
|
213
|
+
end
|
214
|
+
return nil unless obj
|
215
|
+
|
216
|
+
if obj.is_a?(Array) # get valid list
|
217
|
+
return obj
|
218
|
+
end
|
219
|
+
|
220
|
+
# hash obj
|
221
|
+
if obj.has_key?('error') && obj['error'] != 0
|
222
|
+
warn "request ended with error:"
|
223
|
+
(obj['messages'] || {}).each do |key,msg|
|
224
|
+
warn " #{key}: #{msg}"
|
225
|
+
end
|
226
|
+
warn " request (#{method}) http://#{host}:#{port}#{request_path}"
|
227
|
+
warn " with content #{res.body}"
|
228
|
+
nil
|
229
|
+
elsif obj.has_key?('error') && obj.has_key?('data') # valid response, without any errors
|
230
|
+
obj['data']
|
231
|
+
else # bare growthforecast object
|
232
|
+
obj
|
233
|
+
end
|
234
|
+
end
|
235
|
+
end
|
metadata
ADDED
@@ -0,0 +1,58 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: growthforecast
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- TAGOMORI Satoshi
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2013-01-03 00:00:00.000000000 Z
|
13
|
+
dependencies: []
|
14
|
+
description: Client library and tool to update values, create/edit/delete graphs of
|
15
|
+
GrowthForecast
|
16
|
+
email:
|
17
|
+
- tagomoris@gmail.com
|
18
|
+
executables: []
|
19
|
+
extensions: []
|
20
|
+
extra_rdoc_files: []
|
21
|
+
files:
|
22
|
+
- .gitignore
|
23
|
+
- Gemfile
|
24
|
+
- LICENSE
|
25
|
+
- README.md
|
26
|
+
- Rakefile
|
27
|
+
- example/test1.rb
|
28
|
+
- growthforecast.gemspec
|
29
|
+
- lib/growthforecast.rb
|
30
|
+
- lib/growthforecast/complex.rb
|
31
|
+
- lib/growthforecast/graph.rb
|
32
|
+
- lib/growthforecast/path.rb
|
33
|
+
- lib/growthforecast/version.rb
|
34
|
+
homepage: https://github.com/tagomoris/rb-growthforecast
|
35
|
+
licenses: []
|
36
|
+
post_install_message:
|
37
|
+
rdoc_options: []
|
38
|
+
require_paths:
|
39
|
+
- lib
|
40
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
41
|
+
none: false
|
42
|
+
requirements:
|
43
|
+
- - ! '>='
|
44
|
+
- !ruby/object:Gem::Version
|
45
|
+
version: '0'
|
46
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
47
|
+
none: false
|
48
|
+
requirements:
|
49
|
+
- - ! '>='
|
50
|
+
- !ruby/object:Gem::Version
|
51
|
+
version: '0'
|
52
|
+
requirements: []
|
53
|
+
rubyforge_project:
|
54
|
+
rubygems_version: 1.8.21
|
55
|
+
signing_key:
|
56
|
+
specification_version: 3
|
57
|
+
summary: A client library for GrowthForecast
|
58
|
+
test_files: []
|