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 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: []