graffable 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: e68f6c232ecbb7293cc71a1abc7435e98b6e3b0f
4
+ data.tar.gz: d2059c65cfa780aec4164843ecc664d046cd9c69
5
+ SHA512:
6
+ metadata.gz: 54b73fd518fe5249903dd32b7dcd6c5ed47b645d3ad9a45b0b9002e12ffd964667a3e88e17b750a7c789fe4ed4a3e52cc0ff707ce5e249c3401cc57e31d28777
7
+ data.tar.gz: 9d1c0e3058e8c2d35e62b4b882f6ea06004557d88bb39682281b326b84a3721018d250bff26036678e85330947f13f727e64cbcf1915dd7cdca9a680192cf8d8
data/.gitignore ADDED
@@ -0,0 +1,7 @@
1
+ .*.swp
2
+ /.env
3
+ /db/*.sqlite3
4
+ /db/seed.rb
5
+ /Gemfile.lock
6
+ /out.txt
7
+ /pkg
data/CHANGES.md ADDED
@@ -0,0 +1,8 @@
1
+
2
+ Graffable Changes
3
+ =================
4
+
5
+ 2014-01-29 Graffable v0.0.1
6
+ ---------------------------
7
+ First release of Sinatra-based data charting application.
8
+
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in graffable.gemspec
4
+ gemspec
data/LICENSE.txt ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2014 blair christensen
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,89 @@
1
+ # Graffable
2
+
3
+ Sinatra-based data charting application
4
+
5
+ ## Installation
6
+
7
+ Add this line to your application's Gemfile:
8
+
9
+ gem 'graffable'
10
+
11
+ And then execute:
12
+
13
+ $ bundle
14
+
15
+ Or install it yourself as:
16
+
17
+ $ gem install graffable
18
+
19
+ ## Usage
20
+
21
+ Set environment variables:
22
+
23
+ % export GRAFFABLE_DATABASE_URL=sqlite://path/to/db.sqlite3
24
+ % export GRAFFABLE_SEED_FILE=/path/to/seed.rb
25
+
26
+ Create seed file:
27
+
28
+ require 'graffable'
29
+
30
+ DB = Graffable::Database.connect
31
+
32
+ # Report Groups
33
+ [
34
+ [ 'a', 'Service A' ],
35
+ [ 'b', 'Host B', ]
36
+ ].each do |tuple|
37
+ DB[:groups].insert name: tuple.first, description: tuple.last
38
+ end
39
+
40
+ # Reports
41
+ {
42
+ 'a' => {
43
+ 'report-a1' => [ 'Name of report', :sum ],
44
+ 'report-a2' => [ 'Name of report', :avg ],
45
+ },
46
+ 'b' => {
47
+ 'report-b1' => [ 'Name of report', :sum ],
48
+ 'report-b2' => [ 'Name of report', :avg ],
49
+ }
50
+ }.each_pair do |group_name, reports|
51
+ group = DB[:groups][ name: group_name ]
52
+ raise "ERROR: unknown report group '#{ group_name }'" if group.nil?
53
+
54
+ reports.each_pair do |name, values|
55
+ DB[:reports].insert group_id: group[:id], name: name, description: values.first, aggregate: values.last.to_s
56
+ end
57
+ end
58
+
59
+
60
+ Add tasks to `Rakefile`:
61
+
62
+ % cat Rakefile
63
+ ...
64
+ require 'graffable/migration_task'
65
+ Graffable::MigrationTask.new
66
+
67
+ require 'graffable/seed_task'
68
+ Graffable::SeedTask.new
69
+ ...
70
+
71
+
72
+ Migrate database and load seed data:
73
+
74
+ % rake graffable:migrate:reset graffable:seed
75
+
76
+ Create `config.ru` and launch application:
77
+
78
+ % cat config.ru
79
+ require 'graffable'
80
+ run Graffable::App
81
+ % rackup
82
+
83
+ ## Contributing
84
+
85
+ 1. Fork it
86
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
87
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
88
+ 4. Push to the branch (`git push origin my-new-feature`)
89
+ 5. Create new Pull Request
data/Rakefile ADDED
@@ -0,0 +1,11 @@
1
+ require 'bundler/gem_tasks'
2
+
3
+ require './lib/graffable.rb'
4
+ ENV['GRAFFABLE_DATABASE_URL'] ||= 'sqlite://db/development.sqlite3'
5
+
6
+ require './lib/graffable/migration_task.rb'
7
+ Graffable::MigrationTask.new
8
+
9
+ require './lib/graffable/seed_task.rb'
10
+ Graffable::SeedTask.new
11
+
data/TODO.md ADDED
@@ -0,0 +1,9 @@
1
+ Graffable To Do
2
+ ===============
3
+ # `GET /groups.json`
4
+ # `GET /groups`
5
+ # `GET /:group.json`
6
+ # `GET /:group`
7
+ # `GET /:group/:report.json`
8
+ # `GET /:group/:report`
9
+
data/config.ru ADDED
@@ -0,0 +1,8 @@
1
+
2
+ $LOAD_PATH << './lib'
3
+
4
+ require 'graffable'
5
+ run Graffable::App
6
+
7
+ # vim: syntax=ruby
8
+
@@ -0,0 +1,14 @@
1
+
2
+ Sequel.migration do
3
+ transaction
4
+ change do
5
+ create_table(:groups) do
6
+ primary_key :id
7
+ String :name, null: false
8
+ String :description
9
+ index :name
10
+ unique :name
11
+ end
12
+ end
13
+ end
14
+
@@ -0,0 +1,17 @@
1
+
2
+ Sequel.migration do
3
+ transaction
4
+ change do
5
+ create_table(:reports) do
6
+ primary_key :id
7
+ foreign_key :group_id, :groups
8
+ String :name, null: false
9
+ String :description
10
+ String :aggregate, null: false
11
+ index :group_id
12
+ index :name
13
+ unique [ :group_id, :name ]
14
+ end
15
+ end
16
+ end
17
+
@@ -0,0 +1,25 @@
1
+
2
+ Sequel.migration do
3
+ transaction
4
+
5
+ change do
6
+ create_table(:numbers) do
7
+ primary_key :id
8
+ foreign_key :report_id, :reports
9
+ String :value, null: false
10
+ String :year, size: 4
11
+ String :month, size: 2
12
+ String :day, size: 2
13
+ String :hour, size: 2
14
+ String :label
15
+ index :report_id
16
+ index :year
17
+ index :month
18
+ index :day
19
+ index :hour
20
+ unique [ :report_id, :year, :month, :day, :hour, :label ]
21
+ end
22
+ end
23
+
24
+ end
25
+
data/graffable.gemspec ADDED
@@ -0,0 +1,31 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'graffable/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = 'graffable'
8
+ spec.version = Graffable::VERSION
9
+ spec.authors = ["blair christensen."]
10
+ spec.email = ["blair.christensen@gmail.com"]
11
+ spec.description = %q{Sinatra-based data charting application}
12
+ spec.summary = %q{Sinatra-based data charting application}
13
+ spec.homepage = 'https://github.com/blairc/graffable'
14
+ spec.license = "MIT"
15
+
16
+ spec.files = `git ls-files`.split($/)
17
+ spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
18
+ spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
19
+ spec.require_paths = ["lib"]
20
+
21
+ spec.add_dependency 'haml'
22
+ spec.add_dependency 'json'
23
+ spec.add_dependency 'sequel'
24
+ spec.add_dependency 'sinatra'
25
+ spec.add_dependency 'sinatra-flash'
26
+
27
+ spec.add_development_dependency 'bundler', '~> 1.3'
28
+ spec.add_development_dependency 'rake'
29
+ spec.add_development_dependency 'sqlite3'
30
+ end
31
+
data/lib/graffable.rb ADDED
@@ -0,0 +1,7 @@
1
+ require 'graffable/app'
2
+ require 'graffable/database'
3
+ require 'graffable/version'
4
+
5
+ module Graffable
6
+ end
7
+
@@ -0,0 +1,231 @@
1
+ require 'csv'
2
+ require 'date'
3
+ require 'haml'
4
+ require 'json'
5
+ require 'sinatra/base'
6
+ require 'sinatra/flash'
7
+
8
+
9
+ module Graffable
10
+
11
+ class App < Sinatra::Base
12
+
13
+ configure do
14
+ set :root, File.dirname(__FILE__)
15
+ set :haml, { format: :html5 }
16
+ set :static, true
17
+ enable :sessions
18
+
19
+ register Sinatra::Flash
20
+ end
21
+
22
+
23
+ before do
24
+ @db = Graffable::Database.connect # Sequel.connect settings.database_url
25
+ @groups = @db[:groups].order(:name)
26
+ end
27
+
28
+ # /:group/:report/:year/:month.json
29
+ get %r{/(.+)/(.+)/(\d{4})/(\d{2}).(csv|json)} do |group_name, report_name, year, month, extension|
30
+ group = assert_group group_name
31
+ report = assert_report group, report_name
32
+
33
+ # TODO DRY?
34
+ query = "SELECT year,month,day,label,#{ report[:aggregate].to_s.upcase }(value) AS value " +
35
+ "FROM numbers " +
36
+ "WHERE report_id=:report_id " +
37
+ "AND year=:year " +
38
+ "AND month=:month " +
39
+ "GROUP BY year,month,day,label " +
40
+ "ORDER BY year,month,day,label"
41
+ dataset = @db.fetch( query, month: month, report_id: report[:id], year: year )
42
+
43
+ case extension
44
+ when 'csv'
45
+ return_csv dataset
46
+ when 'json'
47
+ return_json dataset, date_formatter: lambda { |y,m,d,h| d }
48
+ end
49
+ end
50
+
51
+ # /:group/:report/:year.json
52
+ get %r{/(.+)/(.+)/(\d{4}).(csv|json)} do |group_name, report_name, year, extension|
53
+ group = assert_group group_name
54
+ report = assert_report group, report_name
55
+
56
+ # TODO DRY?
57
+ query = "SELECT year,month,label,#{ report[:aggregate].to_s.upcase }(value) AS value " +
58
+ "FROM numbers " +
59
+ "WHERE report_id=:report_id " +
60
+ "AND year=:year " +
61
+ "GROUP BY year,month,label " +
62
+ "ORDER BY year,month,label"
63
+ dataset = @db.fetch( query, report_id: report[:id], year: year )
64
+
65
+ case extension
66
+ when 'csv'
67
+ return_csv dataset
68
+ when 'json'
69
+ return_json dataset, date_formatter: lambda { |y,m,d,h| "#{y}-#{m}" }
70
+ end
71
+ end
72
+
73
+ # /:group/:report.json
74
+ get %r{/(.+)/(.+).(csv|json)} do |group_name, report_name, extension|
75
+ group = assert_group group_name
76
+ report = assert_report group, report_name
77
+
78
+ # TODO DRY?
79
+ query = "SELECT year,month,day,label,#{ report[:aggregate].to_s.upcase }(value) AS value " +
80
+ "FROM numbers " +
81
+ "WHERE report_id=:report_id " +
82
+ "GROUP BY year,month,day,label " +
83
+ "ORDER BY year,month,day,label"
84
+ dataset = @db.fetch( query, report_id: report[:id] )
85
+ max = 15
86
+
87
+ case extension
88
+ when 'csv'
89
+ return_csv dataset, max: max
90
+ when 'json'
91
+ return_json dataset, date_formatter: lambda { |y,m,d,h| Date.parse("#{y}-#{m}-#{d}").strftime("%b %d") }, max: max
92
+ end
93
+ end
94
+
95
+ # TODO DRY
96
+ get %r{/(.+)/(.+)/(\d{4})/(\d{2})} do |group_name, report_name, year, month|
97
+ @group = @groups.detect { |g| group_name == g[:name] }
98
+ redirect_with_warning( '/', "Report group not found: #{group_name}" ) unless @group
99
+
100
+ @reports = @db[:reports].where( group_id: @group[:id] ).all
101
+ @report = @reports.detect { |r| report_name == r[:name] }
102
+ redirect_with_warning( "/#{group_name}", "Report not found: #{report_name}" ) unless @report
103
+
104
+ @data_url = "/#{group_name}/#{report_name}/#{year}/#{month}.json"
105
+ @interval = "#{year}-#{month}"
106
+
107
+ date = Date.parse( "#{year}-#{month}-01" )
108
+ @previous = "/#{group_name}/#{report_name}/#{ date.prev_month.year }/#{ date.prev_month.strftime('%m') }"
109
+ @next = "/#{group_name}/#{report_name}/#{ date.next_month.year }/#{ date.next_month.strftime('%m') }"
110
+
111
+ haml :report
112
+ end
113
+
114
+ # TODO DRY
115
+ get %r{/(.+)/(.+)/(\d{4})} do |group_name, report_name, year|
116
+ @group = @groups.detect { |g| group_name == g[:name] }
117
+ redirect_with_warning( '/', "Report group not found: #{group_name}" ) unless @group
118
+
119
+ @reports = @db[:reports].where( group_id: @group[:id] ).all
120
+ @report = @reports.detect { |r| report_name == r[:name] }
121
+ redirect_with_warning( "/#{group_name}", "Report not found: #{report_name}" ) unless @report
122
+
123
+ @data_url = "/#{group_name}/#{report_name}/#{year}.json"
124
+ @interval = year
125
+
126
+ date = Date.parse( "#{year}-01-01" )
127
+ @previous = "/#{group_name}/#{report_name}/#{ date.prev_year.year }"
128
+ @next = "/#{group_name}/#{report_name}/#{ date.next_year.year }"
129
+
130
+ haml :report
131
+ end
132
+
133
+ # TODO DRY
134
+ get '/:group/:report' do
135
+ @group = @groups.detect { |g| params[:group] == g[:name] }
136
+ redirect_with_warning( '/', "Report group not found: #{ params[:group] }" ) unless @group
137
+
138
+ @reports = @db[:reports].where( group_id: @group[:id] ).all
139
+ @report = @reports.detect { |r| params[:report] == r[:name] }
140
+ redirect_with_warning( "/#{ params[:group] }", "Report not found: #{ params[:report] }" ) unless @report
141
+
142
+ @data_url = "/#{ params[:group] }/#{ params[:report] }.json"
143
+ @interval = 'Recent'
144
+
145
+ haml :report
146
+ end
147
+
148
+ get '/:group' do
149
+ @group = @groups.detect{ |g| params[:group] == g[:name] }
150
+ redirect_with_warning( '/', "Report group not found: #{ params[:group] }" ) unless @group
151
+
152
+ @reports = @db[:reports].where( group_id: @group[:id] ).all
153
+ haml :group
154
+ end
155
+
156
+ get '/' do
157
+ haml :index
158
+ end
159
+
160
+
161
+ private
162
+
163
+ def assert_group(group_name)
164
+ group = @groups.detect { |g| group_name == g[:name] }
165
+ halt 404, "Report group not found: #{group_name}" unless group
166
+ group
167
+ end
168
+
169
+ def assert_report(group, report_name)
170
+ reports = @db[:reports].where( group_id: group[:id] ).all
171
+ report = reports.detect { |r| report_name == r[:name] }
172
+ halt 404, "Report not found: #{report_name}" unless report
173
+ report
174
+ end
175
+
176
+ def redirect_with_warning(path, message)
177
+ flash[:warning] = message
178
+ redirect to(path)
179
+ end
180
+
181
+ def return_csv( dataset, params = {} )
182
+ defaults = { max: -1 }
183
+ opts = defaults.merge params
184
+
185
+ data = [ %w( date label value ).to_csv ]
186
+ dataset.each do |row|
187
+ date = %i( year month day hour ).collect { |k| row[k] }.compact.join('-')
188
+ label = row[:label] || ''
189
+ value = row[:value].to_i
190
+ data << [ date, label, value ].to_csv
191
+ end
192
+
193
+ if opts[:max] > -1
194
+ headers = data.shift
195
+ data = [ headers, data.reverse.slice( 0 .. ( opts[:max] - 1 ) ).reverse ].flatten
196
+ end
197
+
198
+ content_type :text
199
+ return data.join
200
+ end
201
+
202
+ def return_json( dataset, params = {} )
203
+ defaults = { max: -1 }
204
+ opts = defaults.merge params
205
+
206
+ data = {}
207
+ dataset.each do |row|
208
+ date = opts[:date_formatter].call row[:year], row[:month], row[:day], row[:hour]
209
+ label = row[:label] || ''
210
+ value = row[:value].to_i
211
+
212
+ unless data.key?(label)
213
+ data[label] = { data: [] }
214
+ data[label][:label] = label unless label.empty?
215
+ end
216
+ data[label][:data].push [ date, value ]
217
+ end
218
+
219
+ if opts[:max] > -1
220
+ data.each_pair do |label, values|
221
+ data[label][:data] = data[label][:data].reverse.slice( 0 .. ( opts[:max] - 1 ) ).reverse
222
+ end
223
+ end
224
+
225
+ content_type :json
226
+ return { data: data.values }.to_json
227
+ end
228
+
229
+ end # class Graffable::App
230
+ end # module Graffable
231
+
@@ -0,0 +1,14 @@
1
+ require 'sequel'
2
+
3
+ module Graffable
4
+
5
+ class Database
6
+ def self.connect
7
+ key = 'GRAFFABLE_DATABASE_URL'
8
+ raise "ERROR: #{key} not defined" unless ENV.key?(key)
9
+ Sequel.connect ENV[key]
10
+ end
11
+
12
+ end # class Graffable::Database
13
+ end # module Graffable
14
+
@@ -0,0 +1,63 @@
1
+
2
+ require 'graffable'
3
+
4
+ module Graffable
5
+ class Importer
6
+
7
+ def initialize
8
+ @db = Graffable::Database.connect
9
+ @groups = {}
10
+ @reports = {}
11
+ yield self if block_given?
12
+ self
13
+ end
14
+
15
+ def transaction(&block)
16
+ @db.transaction block
17
+ end
18
+
19
+ def insert( group_name, report_name, value, params = {} )
20
+ defaults = {
21
+ day: nil, hour: nil, label: nil, month: nil, year: nil
22
+ }
23
+ opts = defaults.merge(params)
24
+ r = _find_report group_name, report_name
25
+
26
+ # TODO Silently skip existing rows? And why isn't the database enforcing this?
27
+ return if @db[:numbers][ report_id: r[:id], year: opts[:year], month: opts[:month], day: opts[:day], hour: opts[:hour], label: opts[:label] ]
28
+
29
+ @db.transaction do
30
+ rv = @db[:numbers].insert report_id: r[:id],
31
+ value: value,
32
+ year: opts[:year],
33
+ month: opts[:month],
34
+ day: opts[:day],
35
+ hour: opts[:hour],
36
+ label: opts[:label]
37
+ raise "insert failed - #{rv.inspect}" unless rv > 0
38
+ end
39
+ end
40
+
41
+
42
+ private
43
+
44
+ def _find_report(group, report)
45
+ unless @groups.key?(group)
46
+ @groups[group] = @db[:groups][ name: group ]
47
+ end
48
+ g = @groups[group]
49
+ raise "invalid group - #{group}" unless g
50
+
51
+ unless @reports.key?(group)
52
+ @reports[group] = {}
53
+ @db[:reports].where( group_id: g[:id] ).each { |row| @reports[group][ row[:name] ] = row }
54
+ end
55
+ r = @reports[group][report]
56
+ raise "invalid report - #{report}" unless r
57
+
58
+ r
59
+ end
60
+
61
+ end # class Graffable::Importer
62
+ end # module Graffable
63
+
@@ -0,0 +1,52 @@
1
+ require 'rake'
2
+ require 'rake/tasklib'
3
+ require 'graffable'
4
+
5
+ module Graffable
6
+ class MigrationTask < ::Rake::TaskLib
7
+
8
+ def initialize
9
+
10
+ Sequel.extension :migration
11
+ db = Graffable::Database.connect
12
+ namespace = 'graffable:migrate'
13
+ migrations = File.join( File.dirname(__FILE__), '../../db/migrations' )
14
+
15
+ desc 'Perform migration down (erase all data)'
16
+ task "#{namespace}:down" do
17
+ Sequel::Migrator.run db, migrations, target: 0
18
+ puts "<= #{namespace}:down executed"
19
+ end
20
+
21
+ desc 'Perform migration reset (full erase and migration up)'
22
+ task "#{namespace}:reset" do
23
+ Sequel::Migrator.run db, migrations, target: 0
24
+ Sequel::Migrator.run db, migrations
25
+ puts "<= #{namespace}:reset executed"
26
+ end
27
+
28
+ desc 'Perform migration down (erase all data)'
29
+ task "#{namespace}:down" do
30
+ Sequel::Migrator.run db, migrations, target: 0
31
+ puts "<= #{namespace}:down executed"
32
+ end
33
+
34
+ desc 'Perform migration up/down to VERSION'
35
+ task "#{namespace}:to" do
36
+ version = ENV['VERSION'].to_i
37
+ raise 'No VERSION was provided' if version.nil?
38
+ Sequel::Migrator.run db, migrations, target: version
39
+ puts "<= #{namespace}:to version=[#{version}] executed"
40
+ end
41
+
42
+ desc 'Perform migration to latest migration available'
43
+ task "#{namespace}:up" do
44
+ Sequel::Migrator.run db, migrations
45
+ puts "<= #{namespace}:up executed"
46
+ end
47
+
48
+ end
49
+
50
+ end # class Graffable::MigrationTask
51
+ end # module Graffable
52
+
Binary file
@@ -0,0 +1,12 @@
1
+ /*
2
+ * jquery.flot.tooltip
3
+ *
4
+ * description: easy-to-use tooltips for Flot charts
5
+ * version: 0.6.5
6
+ * author: Krzysztof Urbas @krzysu [myviews.pl]
7
+ * website: https://github.com/krzysu/flot.tooltip
8
+ *
9
+ * build on 2014-01-23
10
+ * released under MIT License, 2012
11
+ */
12
+ (function(t){var i={tooltip:!1,tooltipOpts:{content:"%s | X: %x | Y: %y",xDateFormat:null,yDateFormat:null,monthNames:null,dayNames:null,shifts:{x:10,y:20},defaultTheme:!0,onHover:function(){}}},o=function(t){this.tipPosition={x:0,y:0},this.init(t)};o.prototype.init=function(i){function o(t){var i={};i.x=t.pageX,i.y=t.pageY,s.updateTooltipPosition(i)}function e(t,i,o){var e=s.getDomElement();if(o){var n;n=s.stringFormat(s.tooltipOptions.content,o),e.html(n),s.updateTooltipPosition({x:i.pageX,y:i.pageY}),e.css({left:s.tipPosition.x+s.tooltipOptions.shifts.x,top:s.tipPosition.y+s.tooltipOptions.shifts.y}).show(),"function"==typeof s.tooltipOptions.onHover&&s.tooltipOptions.onHover(o,e)}else e.hide().html("")}var s=this;i.hooks.bindEvents.push(function(i,n){s.plotOptions=i.getOptions(),s.plotOptions.tooltip!==!1&&void 0!==s.plotOptions.tooltip&&(s.tooltipOptions=s.plotOptions.tooltipOpts,s.getDomElement(),t(i.getPlaceholder()).bind("plothover",e),t(n).bind("mousemove",o))}),i.hooks.shutdown.push(function(i,s){t(i.getPlaceholder()).unbind("plothover",e),t(s).unbind("mousemove",o)})},o.prototype.getDomElement=function(){var i;return t("#flotTip").length>0?i=t("#flotTip"):(i=t("<div />").attr("id","flotTip"),i.appendTo("body").hide().css({position:"absolute"}),this.tooltipOptions.defaultTheme&&i.css({background:"#fff","z-index":"100",padding:"0.4em 0.6em","border-radius":"0.5em","font-size":"0.8em",border:"1px solid #111",display:"none","white-space":"nowrap"})),i},o.prototype.updateTooltipPosition=function(i){var o=t("#flotTip").outerWidth()+this.tooltipOptions.shifts.x,e=t("#flotTip").outerHeight()+this.tooltipOptions.shifts.y;i.x-t(window).scrollLeft()>t(window).innerWidth()-o&&(i.x-=o),i.y-t(window).scrollTop()>t(window).innerHeight()-e&&(i.y-=e),this.tipPosition.x=i.x,this.tipPosition.y=i.y},o.prototype.stringFormat=function(t,i){var o,e,s=/%p\.{0,1}(\d{0,})/,n=/%s/,p=/%x\.{0,1}(\d{0,})/,a=/%y\.{0,1}(\d{0,})/,r="%x",l="%y";return i.series.threshold!==void 0?(o=i.datapoint[0],e=i.datapoint[1]):(o=i.series.data[i.dataIndex][0],e=i.series.data[i.dataIndex][1]),null===i.series.label&&i.series.originSeries&&(i.series.label=i.series.originSeries.label),"function"==typeof t&&(t=t(i.series.label,o,e,i)),i.series.percent!==void 0&&(t=this.adjustValPrecision(s,t,i.series.percent)),t=i.series.label!==void 0?t.replace(n,i.series.label):t.replace(n,""),this.isTimeMode("xaxis",i)&&this.isXDateFormat(i)&&(t=t.replace(p,this.timestampToDate(o,this.tooltipOptions.xDateFormat))),this.isTimeMode("yaxis",i)&&this.isYDateFormat(i)&&(t=t.replace(a,this.timestampToDate(e,this.tooltipOptions.yDateFormat))),"number"==typeof o&&(t=this.adjustValPrecision(p,t,o)),"number"==typeof e&&(t=this.adjustValPrecision(a,t,e)),i.series.xaxis.ticks!==void 0&&i.series.xaxis.ticks.length>i.dataIndex&&!this.isTimeMode("xaxis",i)&&(t=t.replace(p,i.series.xaxis.ticks[i.dataIndex].label)),i.series.xaxis.tickFormatter!==void 0&&(t=t.replace(r,i.series.xaxis.tickFormatter(o,i.series.xaxis).replace(/\$/g,"$$"))),i.series.yaxis.tickFormatter!==void 0&&(t=t.replace(l,i.series.yaxis.tickFormatter(e,i.series.yaxis).replace(/\$/g,"$$"))),t},o.prototype.isTimeMode=function(t,i){return i.series[t].options.mode!==void 0&&"time"===i.series[t].options.mode},o.prototype.isXDateFormat=function(){return this.tooltipOptions.xDateFormat!==void 0&&null!==this.tooltipOptions.xDateFormat},o.prototype.isYDateFormat=function(){return this.tooltipOptions.yDateFormat!==void 0&&null!==this.tooltipOptions.yDateFormat},o.prototype.timestampToDate=function(i,o){var e=new Date(1*i);return t.plot.formatDate(e,o,this.tooltipOptions.monthNames,this.tooltipOptions.dayNames)},o.prototype.adjustValPrecision=function(t,i,o){var e,s=i.match(t);return null!==s&&""!==RegExp.$1&&(e=RegExp.$1,o=o.toFixed(e),i=i.replace(t,o)),i};var e=function(t){new o(t)};t.plot.plugins.push({init:e,options:i,name:"tooltip",version:"0.6.1"})})(jQuery);
@@ -0,0 +1,8 @@
1
+ div.flash.notice {
2
+ color: green;
3
+ }
4
+
5
+ div.flash.warning {
6
+ color: red;
7
+ }
8
+
@@ -0,0 +1,26 @@
1
+ require 'rake'
2
+ require 'rake/tasklib'
3
+ require 'graffable'
4
+
5
+ module Graffable
6
+ class SeedTask < ::Rake::TaskLib
7
+
8
+ def initialize
9
+
10
+ db = Graffable::Database.connect
11
+ namespace = 'graffable'
12
+
13
+ desc 'Load Graffable seed data from GRAFFABLE_SEED_FILE'
14
+ task "#{namespace}:seed" do
15
+ key = 'GRAFFABLE_SEED_FILE'
16
+ seed = ENV[key]
17
+ raise "No #{key} was provided" if seed.nil?
18
+ load seed
19
+ puts "<= #{namespace}:seed executed"
20
+ end
21
+
22
+ end
23
+
24
+ end # class Graffable::SeedTask
25
+ end # module Graffable
26
+
@@ -0,0 +1,4 @@
1
+ module Graffable
2
+ VERSION = '0.0.1'
3
+ end
4
+
@@ -0,0 +1,6 @@
1
+ %h1 Available #{ @group[:description] } Reports
2
+
3
+ - @reports.each do |r|
4
+ %li
5
+ %a{ href: url("/#{ @group[:name] }/#{ r[:name] }") }= r[:description]
6
+
@@ -0,0 +1,6 @@
1
+ %h1 Available Report Groups
2
+
3
+ - @groups.each do |g|
4
+ %li
5
+ %a{ href: url("/#{ g[:name] }") }= g[:description]
6
+
@@ -0,0 +1,64 @@
1
+ %html
2
+ %head
3
+ %meta{ name: 'viewport', content: 'width=device-width, initial-scale=1.0' }
4
+ %link{ rel: 'stylesheet', type: 'text/css', href: '//cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/3.0.3/css/bootstrap.min.css' }
5
+ %link{ rel: 'stylesheet', type: 'text/css', href: url('/stylesheets/graffable.css') }
6
+ %title= 'Metrics'
7
+ %body
8
+ %script{ type: 'text/javascript', src: '//cdnjs.cloudflare.com/ajax/libs/jquery/2.0.3/jquery.min.js' }
9
+ %script{ type: 'text/javascript', src: '//cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/3.0.3/js/bootstrap.min.js' }
10
+ %script{ type: 'text/javascript', src: '//cdnjs.cloudflare.com/ajax/libs/flot/0.8.2/jquery.flot.min.js' }
11
+ %script{ type: 'text/javascript', src: '//cdnjs.cloudflare.com/ajax/libs/flot/0.8.2/jquery.flot.categories.min.js' }
12
+ %script{ type: 'text/javascript', src: '//cdnjs.cloudflare.com/ajax/libs/flot/0.8.2/jquery.flot.stack.min.js' }
13
+ %script{ type: 'text/javascript', src: url('/javascript/jquery.flot.tooltip.min-0.6.5.js') }
14
+
15
+ %nav.navbar.navbar-default{ role: 'navigation' }
16
+ .navbar-header
17
+ %button.navbar-toggle{ type: 'button', 'data-toggle' => 'collapse', 'data-target' => '#bs-example-navbar-collapse-1' }
18
+ %span.sr-only{} Toggle navigation
19
+ %span.icon-bar
20
+ %span.icon-bar
21
+ %span.icon-bar
22
+ %a.navbar-brand{ href: url('/') } Metrics
23
+
24
+ .collapse.navbar-collapse{ id: 'bs-example-navbar-collapse-1' }
25
+ %ul.nav.navbar-nav
26
+ %li.dropdown
27
+ %a.dropdown-toggle{ href: '#', 'data-toggle' => 'dropdown' }
28
+ = @group ? @group[:description] : 'Report Group'
29
+ %b.caret
30
+ %ul.dropdown-menu
31
+ - @groups.each do |g|
32
+ %li
33
+ %a{ href: url("/#{ g[:name] }") }= g[:description]
34
+ - if @group && @reports
35
+ %li.dropdown
36
+ %a.dropdown-toggle{ href: '#', 'data-toggle' => 'dropdown' }
37
+ = @report ? @report[:description] : 'Reports'
38
+ %b.caret
39
+ %ul.dropdown-menu
40
+ - @reports.each do |r|
41
+ %li
42
+ %a{ href: url("/#{ @group[:name] }/#{ r[:name] }") }= r[:description]
43
+ - if @group && @reports && @report
44
+ %li.dropdown
45
+ %a.dropdown-toggle{ href: '#', 'data-toggle' => 'dropdown' }
46
+ = @interval || 'Interval'
47
+ %b.caret
48
+ %ul.dropdown-menu
49
+ -# TODO Helper(s)!
50
+ - url = "/#{ @group[:name] }/#{ @report[:name] }"
51
+ - year = Time.now.year
52
+ - month = Time.now.strftime("%m")
53
+ %li
54
+ %a{ href: url(url) } Recent
55
+ %li
56
+ %a{ href: url("#{url}/#{year}") }= year
57
+ %li
58
+ %a{ href: url("#{url}/#{year}/#{month}") }= "#{year}-#{month}"
59
+
60
+ .container
61
+ .flash
62
+ =styled_flash
63
+ = yield
64
+
@@ -0,0 +1,63 @@
1
+ - if @report
2
+ %div{ style: 'text-align: center' }
3
+ - if @previous
4
+ %a{ href: url(@previous) }
5
+ %span.glyphicon.glyphicon-chevron-left
6
+ %h1{ style: 'display: inline; padding: 0em 1em;' }= @report[:description]
7
+ - if @next
8
+ %a{ href: url(@next) }
9
+ %span.glyphicon.glyphicon-chevron-right
10
+
11
+ :javascript
12
+ data_url = "#{ url(@data_url) }";
13
+
14
+ $(document).ready( function() {
15
+
16
+ var options = {
17
+ bars: {
18
+ show: true
19
+ },
20
+ grid: {
21
+ hoverable: true
22
+ },
23
+ points: {
24
+ show: true
25
+ },
26
+ series: {
27
+ stack: true
28
+ },
29
+ tooltip: true,
30
+ tooltipOpts: {
31
+ content: '%s %y'
32
+ },
33
+ xaxis: {
34
+ labelWidth: 3,
35
+ mode: 'categories',
36
+ tickDecimals: 0,
37
+ tickSize: 1
38
+ },
39
+ yaxis: {
40
+ tickDecimals: 0,
41
+ }
42
+ };
43
+
44
+ var data = [];
45
+
46
+ function onDataReceived(series) {
47
+ // TODO options['yaxis'] = { max: series['max'], min: series['min'], tickDecimals: 0 }
48
+ $.plot( "#placeholder", series.data, options );
49
+ }
50
+
51
+ $.ajax({
52
+ url: data_url,
53
+ type: 'GET',
54
+ dataType: 'json',
55
+ success: onDataReceived
56
+ });
57
+
58
+ });
59
+
60
+ -# FIXME
61
+ #content{ style: 'align: center; margin: auto; padding: 1em; width: 95%;' }
62
+ #placeholder{ style: 'align: center; height: 75%; margin: auto; padding: 1em; width: 95%;' }
63
+
metadata ADDED
@@ -0,0 +1,182 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: graffable
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ platform: ruby
6
+ authors:
7
+ - blair christensen.
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2014-01-29 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: haml
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: '0'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ">="
25
+ - !ruby/object:Gem::Version
26
+ version: '0'
27
+ - !ruby/object:Gem::Dependency
28
+ name: json
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: '0'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ">="
39
+ - !ruby/object:Gem::Version
40
+ version: '0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: sequel
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ">="
46
+ - !ruby/object:Gem::Version
47
+ version: '0'
48
+ type: :runtime
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ">="
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: sinatra
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - ">="
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ type: :runtime
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - ">="
67
+ - !ruby/object:Gem::Version
68
+ version: '0'
69
+ - !ruby/object:Gem::Dependency
70
+ name: sinatra-flash
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - ">="
74
+ - !ruby/object:Gem::Version
75
+ version: '0'
76
+ type: :runtime
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - ">="
81
+ - !ruby/object:Gem::Version
82
+ version: '0'
83
+ - !ruby/object:Gem::Dependency
84
+ name: bundler
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - "~>"
88
+ - !ruby/object:Gem::Version
89
+ version: '1.3'
90
+ type: :development
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - "~>"
95
+ - !ruby/object:Gem::Version
96
+ version: '1.3'
97
+ - !ruby/object:Gem::Dependency
98
+ name: rake
99
+ requirement: !ruby/object:Gem::Requirement
100
+ requirements:
101
+ - - ">="
102
+ - !ruby/object:Gem::Version
103
+ version: '0'
104
+ type: :development
105
+ prerelease: false
106
+ version_requirements: !ruby/object:Gem::Requirement
107
+ requirements:
108
+ - - ">="
109
+ - !ruby/object:Gem::Version
110
+ version: '0'
111
+ - !ruby/object:Gem::Dependency
112
+ name: sqlite3
113
+ requirement: !ruby/object:Gem::Requirement
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
+ requirements:
122
+ - - ">="
123
+ - !ruby/object:Gem::Version
124
+ version: '0'
125
+ description: Sinatra-based data charting application
126
+ email:
127
+ - blair.christensen@gmail.com
128
+ executables: []
129
+ extensions: []
130
+ extra_rdoc_files: []
131
+ files:
132
+ - ".gitignore"
133
+ - CHANGES.md
134
+ - Gemfile
135
+ - LICENSE.txt
136
+ - README.md
137
+ - Rakefile
138
+ - TODO.md
139
+ - config.ru
140
+ - db/migrations/001_create_groups.rb
141
+ - db/migrations/002_create_reports.rb
142
+ - db/migrations/003_create_numbers.rb
143
+ - graffable.gemspec
144
+ - lib/graffable.rb
145
+ - lib/graffable/app.rb
146
+ - lib/graffable/database.rb
147
+ - lib/graffable/importer.rb
148
+ - lib/graffable/migration_task.rb
149
+ - lib/graffable/public/favicon.ico
150
+ - lib/graffable/public/javascript/jquery.flot.tooltip.min-0.6.5.js
151
+ - lib/graffable/public/stylesheets/graffable.css
152
+ - lib/graffable/seed_task.rb
153
+ - lib/graffable/version.rb
154
+ - lib/graffable/views/group.haml
155
+ - lib/graffable/views/index.haml
156
+ - lib/graffable/views/layout.haml
157
+ - lib/graffable/views/report.haml
158
+ homepage: https://github.com/blairc/graffable
159
+ licenses:
160
+ - MIT
161
+ metadata: {}
162
+ post_install_message:
163
+ rdoc_options: []
164
+ require_paths:
165
+ - lib
166
+ required_ruby_version: !ruby/object:Gem::Requirement
167
+ requirements:
168
+ - - ">="
169
+ - !ruby/object:Gem::Version
170
+ version: '0'
171
+ required_rubygems_version: !ruby/object:Gem::Requirement
172
+ requirements:
173
+ - - ">="
174
+ - !ruby/object:Gem::Version
175
+ version: '0'
176
+ requirements: []
177
+ rubyforge_project:
178
+ rubygems_version: 2.2.0
179
+ signing_key:
180
+ specification_version: 4
181
+ summary: Sinatra-based data charting application
182
+ test_files: []