growthforecast 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
data/.gitignore ADDED
@@ -0,0 +1,17 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ Gemfile.lock
7
+ InstalledFiles
8
+ _yardoc
9
+ coverage
10
+ doc/
11
+ lib/bundler/man
12
+ pkg
13
+ rdoc
14
+ spec/reports
15
+ test/tmp
16
+ test/version_tmp
17
+ tmp
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in growthforecast.gemspec
4
+ gemspec
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
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env rake
2
+ require "bundler/gem_tasks"
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,3 @@
1
+ class GrowthForecast
2
+ VERSION = "0.0.1"
3
+ 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: []