metricky 0.1.0

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: 4e086c84c42383acedfc1764f23062f6641aeb60
4
+ data.tar.gz: f0593fe336c19a310c77e5b369752ae86945fa3f
5
+ SHA512:
6
+ metadata.gz: 2a3899d310416542e06a3b670686c3124508e1324efd4447f653cce260c7160b7b55b116fc3695ea4148776a3d360615ed04ade87dea38ffb1c623289f57e2a2
7
+ data.tar.gz: 726d8a8d650ec84b9b714720717b9d9c94c18438c7e10041811de51444314ce628cdd56b839f91220b26bf80011df67b76bd0e9046eabb143a12a802ed75a123
data/MIT-LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright 2019 Josh Brody
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,163 @@
1
+ # Metricky
2
+ Display metrics about your database in your application. Depends on the awesome [Chartkick](https://github.com/ankane/chartkick) and [Groupdate](https://github.com/ankane/groupdate).
3
+
4
+ ## Usage
5
+
6
+ Make this in Ruby:
7
+
8
+ <img src="https://i.imgur.com/PQhFyAE.png" alt="Metricky example">
9
+
10
+ In your view where you want to display the metric:
11
+
12
+ ```erbruby
13
+ render_metric :users
14
+ ```
15
+
16
+ In `app/metrics/users_metric.rb`
17
+
18
+ ```ruby
19
+ class UsersMetric < Metricky::Base
20
+ def scope
21
+ User
22
+ end
23
+
24
+ def type
25
+ :count
26
+ end
27
+
28
+ def columns
29
+ :id
30
+ end
31
+
32
+ def trend
33
+ :day
34
+ end
35
+ end
36
+ ```
37
+
38
+ ## Installation
39
+ Add this line to your application's Gemfile:
40
+
41
+ ```ruby
42
+ gem 'metricky'
43
+ ```
44
+
45
+ And then execute:
46
+ ```bash
47
+ $ bundle
48
+ ```
49
+
50
+ Or install it yourself as:
51
+ ```bash
52
+ $ gem install metricky
53
+ ```
54
+
55
+ Then drop in Chartkick into your `application.js` (or similar):
56
+
57
+ ```javascript
58
+ //= require chartkick
59
+ ```
60
+
61
+ ## Customizing
62
+
63
+ ~~Blatantly ripped from~~ Super-inspired by Laravel Nova's metric system.
64
+
65
+ ### Value to calculate
66
+
67
+ In your metric, define columns:
68
+
69
+ ```ruby
70
+ def columns
71
+ :id
72
+ end
73
+ ```
74
+
75
+ ### Grouping
76
+
77
+ In your metric, define what trend:
78
+
79
+ ```ruby
80
+ def trend
81
+ :day
82
+ end
83
+ ```
84
+
85
+ This can be any one of `Groupdate::PERIODS`
86
+
87
+ Define what column should be used:
88
+
89
+ ```ruby
90
+ def trend_column
91
+ :created_at
92
+ end
93
+ ```
94
+
95
+ ### Value type
96
+
97
+ In your metric, define what type:
98
+
99
+ ```ruby
100
+ def type
101
+ :count
102
+ end
103
+ ```
104
+
105
+ This can be any one of `:min, :max, :sum, :count`
106
+
107
+ ### Ranges
108
+
109
+ In your metric, define what ranges as a class method:
110
+
111
+ ```ruby
112
+ def self.ranges
113
+ {
114
+ 'all' => 'All',
115
+ '30' => '30 Days',
116
+ '60' => '60 Days',
117
+ '365' => '365 Days',
118
+ }
119
+ end
120
+ ```
121
+
122
+ And then their corresponding values (instance-level):
123
+
124
+ ```ruby
125
+ def range_value
126
+ case range
127
+ when 'all'
128
+ nil
129
+ when '30'
130
+ 30.days.ago
131
+ when '60'
132
+ 60.days.ago
133
+ when '365'
134
+ 365.days.ago
135
+ end
136
+ end
137
+ ```
138
+
139
+ This is used with a `where` query against the `range_column`.
140
+
141
+ ```ruby
142
+ def range_column
143
+ :created_at
144
+ end
145
+ ```
146
+
147
+ ### Partial
148
+
149
+ In your metric, define the partial path:
150
+
151
+ ```ruby
152
+ def to_partial_path
153
+ "shared/metric"
154
+ end
155
+ ```
156
+
157
+ Take a look at `app/views/metricky/_metric.html.erb` for implementation.
158
+
159
+ ## Contributing
160
+ Add stuff.
161
+
162
+ ## License
163
+ The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
data/Rakefile ADDED
@@ -0,0 +1,27 @@
1
+ begin
2
+ require 'bundler/setup'
3
+ rescue LoadError
4
+ puts 'You must `gem install bundler` and `bundle install` to run rake tasks'
5
+ end
6
+
7
+ require 'rdoc/task'
8
+
9
+ RDoc::Task.new(:rdoc) do |rdoc|
10
+ rdoc.rdoc_dir = 'rdoc'
11
+ rdoc.title = 'Metricky'
12
+ rdoc.options << '--line-numbers'
13
+ rdoc.rdoc_files.include('README.md')
14
+ rdoc.rdoc_files.include('lib/**/*.rb')
15
+ end
16
+
17
+ require 'bundler/gem_tasks'
18
+
19
+ require 'rake/testtask'
20
+
21
+ Rake::TestTask.new(:test) do |t|
22
+ t.libs << 'test'
23
+ t.pattern = 'test/**/*_test.rb'
24
+ t.verbose = false
25
+ end
26
+
27
+ task default: :test
@@ -0,0 +1,24 @@
1
+ <div class="card card-body">
2
+ <%= metric_form_for metric do |f| %>
3
+ <div class="header-elements-inline">
4
+ <div>
5
+ <h5><%= metric.title %></h5>
6
+ </div>
7
+ <% if metric.class.ranges.any? %>
8
+ <div>
9
+ <%= f.select :range, metric.class.ranges.invert, { selected: metric.range }, { class: "form-control-sm", name: "#{metric.form_name}[range]", onchange: "this.form.submit()" } %>
10
+ </div>
11
+ <% end %>
12
+ </div>
13
+ <div class="media">
14
+ <div class="media-body">
15
+ <% if metric.results.is_a?(Hash) %>
16
+ <%= metricky_chart(metric) %>
17
+ <% else %>
18
+ <h3 class="font-weight-semibold mb-0"><%= metric.results %></h3>
19
+ <% end %>
20
+ <span class="text-uppercase font-size-sm text-muted"><%= metric.subtitle %></span>
21
+ </div>
22
+ </div>
23
+ <% end %>
24
+ </div>
@@ -0,0 +1,173 @@
1
+ module Metricky
2
+ class Base
3
+ VALID_TYPES = [:sum, :max, :min, :average, :count].freeze
4
+ attr_reader :params
5
+ def initialize(params)
6
+ @params = params
7
+ @query = nil
8
+ end
9
+
10
+ # Must be one of Chartkick charts
11
+ # line_chart, pie_chart, columnchart, bar_chart, areachart, geo_chart, timeline
12
+ def chart
13
+ :column_chart
14
+ end
15
+
16
+ # What partial is rendered.
17
+ def to_partial_path
18
+ '/metricky/metric'
19
+ end
20
+
21
+ def noun
22
+ case type.to_sym
23
+ when :count
24
+ "total number of"
25
+ when :average
26
+ "average #{columns}"
27
+ when :sum
28
+ "total"
29
+ when :min
30
+ "minimum"
31
+ when :max
32
+ "maximum"
33
+ end
34
+ end
35
+
36
+ # List of ranges and their values. Used (inverted) on the form.
37
+ def self.ranges
38
+ {
39
+ 'all' => 'All',
40
+ 'Today' => 'Today',
41
+ '30' => '30 Days',
42
+ '60' => '60 Days',
43
+ '365' => '365 Days',
44
+ 'WTD' => 'WTD',
45
+ 'MTD' => 'MTD',
46
+ 'YTD' => 'YTD',
47
+ }
48
+ end
49
+
50
+ # Actual result of the metric
51
+ def results
52
+ assets
53
+ end
54
+
55
+ def self.metric_name
56
+ name.demodulize.sub(/Metric$/, "")
57
+ end
58
+
59
+ def title
60
+ self.class.metric_name
61
+ end
62
+
63
+ def subtitle
64
+ "#{noun} #{scope.model_name.human.pluralize}"
65
+ end
66
+
67
+ # Form key
68
+ def form_name
69
+ "#{uri_key}_metric"
70
+ end
71
+
72
+ # Param key
73
+ def uri_key
74
+ self.class.metric_name.tableize
75
+ end
76
+
77
+ # Converts range string to a Ruby object
78
+ def range_value
79
+ case range
80
+ when nil
81
+ nil
82
+ when 'all'
83
+ nil
84
+ when '30'
85
+ 30.days.ago
86
+ when '60'
87
+ 60.days.ago
88
+ when '365'
89
+ 365.days.ago
90
+ when 'WTD'
91
+ DateTime.now.beginning_of_week
92
+ when 'MTD'
93
+ DateTime.now.beginning_of_month
94
+ when 'YTD'
95
+ DateTime.now.beginning_of_year
96
+ when 'Today'
97
+ DateTime.now.beginning_of_day
98
+ else
99
+ raise TypeError, "unknown range_value for range #{range}. Please define it on range_value"
100
+ end
101
+ end
102
+
103
+ # What ActiveRecord class (or scoped class) is being used for the metric
104
+ def scope
105
+ raise NotImplementedError, "please add a scope to your metric."
106
+ end
107
+
108
+ # What kind of metric we're pulling.
109
+ #
110
+ # Must be one of :sum, :max, :min, :average, :count
111
+ def type
112
+ :count
113
+ end
114
+
115
+ # Column(s) to perform the calculation on.
116
+ # [:total_in_cents, :department]
117
+ def columns
118
+ 'id'
119
+ end
120
+
121
+ # How it's grouped. Leave nil if no grouping
122
+ #
123
+ # [:second, :minute, :hour, :day, :week, :month, :quarter, :year, :day_of_week,
124
+ # :hour_of_day, :minute_of_hour, :day_of_month, :month_of_year]
125
+ def trend
126
+ nil
127
+ end
128
+
129
+ # What column to specify for the range calculation. Normally `created_at`
130
+ def range_column
131
+ 'created_at'
132
+ end
133
+
134
+ # What column to specify for the trend calculation. Normally `created_at`
135
+ def trend_column
136
+ 'created_at'
137
+ end
138
+
139
+ def range
140
+ params.dig(form_name, :range)
141
+ end
142
+
143
+ private
144
+
145
+ def trend?
146
+ trend.present?
147
+ end
148
+
149
+ def assets
150
+ if range_value != nil
151
+ @query = scope.where("#{range_column} > ?", range_value)
152
+ else
153
+ @query = scope
154
+ end
155
+ if trend? && valid_trend?
156
+ @query = @query.group_by_period(trend, trend_column)
157
+ end
158
+ @query = @query.send(type, *columns)
159
+ @query
160
+ end
161
+
162
+ def valid_trend?
163
+ return true if Groupdate::PERIODS.include?(trend.to_sym)
164
+ raise NameError, "trend must be one of #{Groupdate::PERIODS}. It is #{trend}."
165
+ end
166
+
167
+ def check_type
168
+ unless VALID_TYPES.include?(type.to_sym)
169
+ raise NameError, "#{type} must be one of :sum, :max, :min, :average, :count"
170
+ end
171
+ end
172
+ end
173
+ end
@@ -0,0 +1,7 @@
1
+ require 'rails'
2
+
3
+ module Metricky
4
+ class Engine < Rails::Engine
5
+ isolate_namespace Metricky
6
+ end
7
+ end
@@ -0,0 +1,30 @@
1
+ module Metricky
2
+ module Helper
3
+ def metric_form_for(metric)
4
+ form_for("#{request.path}", as: metric.form_name.to_sym, method: :get) do |f|
5
+ yield(f)
6
+ end
7
+ end
8
+
9
+ def render_metric(metric_name)
10
+ if metric_name.is_a?(String) || metric_name.is_a?(Symbol)
11
+ metric_name = metric_name.to_s
12
+ if metric_name.end_with?("Metric")
13
+ metric = metric_name.safe_constantize
14
+ else
15
+ klass_name = "#{metric_name.to_s}Metric".camelize
16
+ metric = "Metrics::#{klass_name}".safe_constantize
17
+ end
18
+ raise NameError, "#{metric_name} does not exist" unless metric
19
+ else
20
+ metric = metric_name
21
+ end
22
+ metric = metric.new(params)
23
+ render metric, metric: metric
24
+ end
25
+
26
+ def metricky_chart(metric)
27
+ send(metric.chart, metric.results)
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,3 @@
1
+ module Metricky
2
+ VERSION = '0.1.0'
3
+ end
data/lib/metricky.rb ADDED
@@ -0,0 +1,13 @@
1
+ require "groupdate"
2
+ require "chartkick"
3
+ require "metricky/base"
4
+ require "metricky/helper"
5
+
6
+ require "metricky/engine"
7
+
8
+ module Metricky
9
+ end
10
+
11
+ ActiveSupport.on_load(:action_view) do
12
+ include Metricky::Helper
13
+ end
@@ -0,0 +1,4 @@
1
+ # desc "Explaining what the task does"
2
+ # task :metricky do
3
+ # # Task goes here
4
+ # end
metadata ADDED
@@ -0,0 +1,96 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: metricky
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Josh Brody
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2019-09-06 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: rails
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">"
18
+ - !ruby/object:Gem::Version
19
+ version: '5.1'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ">"
25
+ - !ruby/object:Gem::Version
26
+ version: '5.1'
27
+ - !ruby/object:Gem::Dependency
28
+ name: groupdate
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">"
32
+ - !ruby/object:Gem::Version
33
+ version: '4.0'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ">"
39
+ - !ruby/object:Gem::Version
40
+ version: '4.0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: chartkick
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ">"
46
+ - !ruby/object:Gem::Version
47
+ version: '3.0'
48
+ type: :runtime
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ">"
53
+ - !ruby/object:Gem::Version
54
+ version: '3.0'
55
+ description: Metricky
56
+ email:
57
+ - josh@josh.mn
58
+ executables: []
59
+ extensions: []
60
+ extra_rdoc_files: []
61
+ files:
62
+ - MIT-LICENSE
63
+ - README.md
64
+ - Rakefile
65
+ - app/views/metricky/_metric.html.erb
66
+ - lib/metricky.rb
67
+ - lib/metricky/base.rb
68
+ - lib/metricky/engine.rb
69
+ - lib/metricky/helper.rb
70
+ - lib/metricky/version.rb
71
+ - lib/tasks/metricky_tasks.rake
72
+ homepage: https://github.com/joshmn/metricky
73
+ licenses:
74
+ - MIT
75
+ metadata: {}
76
+ post_install_message:
77
+ rdoc_options: []
78
+ require_paths:
79
+ - lib
80
+ required_ruby_version: !ruby/object:Gem::Requirement
81
+ requirements:
82
+ - - ">="
83
+ - !ruby/object:Gem::Version
84
+ version: '0'
85
+ required_rubygems_version: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - ">="
88
+ - !ruby/object:Gem::Version
89
+ version: '0'
90
+ requirements: []
91
+ rubyforge_project:
92
+ rubygems_version: 2.6.13
93
+ signing_key:
94
+ specification_version: 4
95
+ summary: A simple stats.
96
+ test_files: []