metricky 0.5.0 → 0.6.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +35 -34
- data/app/metrics/application_metric.rb +0 -1
- data/app/views/metricky/_metric.html.erb +1 -1
- data/lib/generators/metricky/templates/metric.rb.tt +24 -1
- data/lib/metricky/base.rb +20 -102
- data/lib/metricky/core_ext.rb +8 -0
- data/lib/metricky/period.rb +32 -0
- data/lib/metricky/range_thing.rb +10 -0
- data/lib/metricky/ranges.rb +104 -0
- data/lib/metricky/version.rb +1 -1
- data/lib/metricky.rb +6 -2
- metadata +6 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 658a25239c266d3ecdb01b9a7e189fe872e527b93f2eebf4d18a1ced0c6f44b7
|
4
|
+
data.tar.gz: a568dcedbed1b33cd18f5a8fac81acb997557962475f7930399c0d805a0b6ab3
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: a07937c18a85e031d0dbe2abcda2d9c3234327cccac545841f159f147e9ed542cbec0c6ad31bc7a68d5e5f6e5fb287a0b3187ce5368f268e85ffb3c2010fde17
|
7
|
+
data.tar.gz: de9ed1c8abd005edb7dba4a86596feaa833a28093b42f9c108803bbc9700cd218f62f6b5315b245669ab99f23d18fb3b901828bbfdfa1430993de936abb4330f
|
data/README.md
CHANGED
@@ -9,17 +9,22 @@ Make this in Ruby:
|
|
9
9
|
|
10
10
|
## Generate it
|
11
11
|
|
12
|
-
`rails g metricky:metric
|
12
|
+
`rails g metricky:metric TotalUsersMetric --scope User --type :count --period :day`
|
13
|
+
|
14
|
+
## Display it
|
15
|
+
|
13
16
|
In your view where you want to display the metric:
|
14
17
|
|
15
18
|
```erbruby
|
16
|
-
render_metric :
|
19
|
+
render_metric :total_users
|
17
20
|
```
|
18
21
|
|
19
|
-
|
22
|
+
## Customize it
|
23
|
+
|
24
|
+
In `app/metrics/total_users_metric.rb`
|
20
25
|
|
21
26
|
```ruby
|
22
|
-
class
|
27
|
+
class TotalUsersMetric < ApplicationMetric
|
23
28
|
def scope
|
24
29
|
User
|
25
30
|
end
|
@@ -28,10 +33,6 @@ class UsersMetric < Metricky::Base
|
|
28
33
|
:count
|
29
34
|
end
|
30
35
|
|
31
|
-
def columns
|
32
|
-
:id
|
33
|
-
end
|
34
|
-
|
35
36
|
def period
|
36
37
|
:day
|
37
38
|
end
|
@@ -75,7 +76,7 @@ def columns
|
|
75
76
|
end
|
76
77
|
```
|
77
78
|
|
78
|
-
### Grouping by
|
79
|
+
### Grouping by period (day, month, year, quarter, etc.)
|
79
80
|
|
80
81
|
In your metric, define what period:
|
81
82
|
|
@@ -105,45 +106,45 @@ def type
|
|
105
106
|
end
|
106
107
|
```
|
107
108
|
|
108
|
-
This can be any one of `:min, :max, :sum, :count`
|
109
|
+
This can be any one of `:min, :max, :sum, :count, :average`
|
109
110
|
|
110
111
|
### Ranges
|
111
112
|
|
113
|
+
Ranges are what values are available on the form (used to query the metric, if applicable) via the `range_column`
|
114
|
+
|
115
|
+
```ruby
|
116
|
+
def range_column
|
117
|
+
:created_at
|
118
|
+
end
|
119
|
+
```
|
120
|
+
|
121
|
+
Defaults are `all`, `today`, `24 hours`, `3 days`, `7 days`, `10 days`, `14 days`, `30 days`, `3 months`, `6 months`, `12 months`
|
122
|
+
|
123
|
+
#### Creating a new range
|
112
124
|
In your metric, define what ranges as a class method:
|
113
125
|
|
114
126
|
```ruby
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
'60' => '60 Days',
|
120
|
-
'365' => '365 Days',
|
121
|
-
}
|
127
|
+
class TotalUsersMetric < ApplicationMetric
|
128
|
+
register_range '15h', label: "15 hours" do
|
129
|
+
15.hours.ago
|
130
|
+
end
|
122
131
|
end
|
123
132
|
```
|
124
133
|
|
125
|
-
|
134
|
+
#### Removing a range
|
126
135
|
|
127
|
-
```ruby
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
nil
|
132
|
-
when '30'
|
133
|
-
30.days.ago
|
134
|
-
when '60'
|
135
|
-
60.days.ago
|
136
|
-
when '365'
|
137
|
-
365.days.ago
|
138
|
-
end
|
136
|
+
```ruby
|
137
|
+
class TotalUsersMetric < ApplicationMetric
|
138
|
+
remove_ranges '24h', '7d' # an array
|
139
|
+
remove_range '3d' # individual
|
139
140
|
end
|
140
141
|
```
|
141
142
|
|
142
|
-
|
143
|
+
#### Setting the default range
|
143
144
|
|
144
|
-
```ruby
|
145
|
-
|
146
|
-
|
145
|
+
```ruby
|
146
|
+
class TotalUsersMetric < ApplicationMetric
|
147
|
+
default_range '24h'
|
147
148
|
end
|
148
149
|
```
|
149
150
|
|
@@ -6,7 +6,7 @@
|
|
6
6
|
</div>
|
7
7
|
<% if metric.class.ranges.any? %>
|
8
8
|
<div>
|
9
|
-
<%= f.select :range, metric.
|
9
|
+
<%= f.select :range, metric.range_collection, { selected: metric.range }, { class: "form-control-sm", name: "#{metric.form_name}[range]", onchange: "this.form.submit()" } %>
|
10
10
|
</div>
|
11
11
|
<% end %>
|
12
12
|
</div>
|
@@ -1,5 +1,8 @@
|
|
1
1
|
<% module_namespacing do -%>
|
2
2
|
class <%= class_name %>Metric < ApplicationMetric
|
3
|
+
# ==> Change the default range (default is all)
|
4
|
+
# default_range '24h'
|
5
|
+
|
3
6
|
def scope
|
4
7
|
<%= options[:scope] %>
|
5
8
|
end
|
@@ -7,11 +10,31 @@ class <%= class_name %>Metric < ApplicationMetric
|
|
7
10
|
def type
|
8
11
|
<%= options[:type][0] == ':' ? options[:type] : ":#{options[:type]}" %>
|
9
12
|
end
|
10
|
-
|
11
13
|
<% if options[:period] %>
|
12
14
|
def period
|
13
15
|
<%= options[:period][0] == ':' ? options[:period] : ":#{options[:period]}" %>
|
14
16
|
end
|
17
|
+
<% else %>
|
18
|
+
# ==> Change the period grouping
|
19
|
+
# Must be one of Groupdate::PERIODS
|
20
|
+
# def period
|
21
|
+
# :week
|
22
|
+
# end
|
15
23
|
<% end %>
|
24
|
+
|
25
|
+
# ==> Change the chart
|
26
|
+
# def chart
|
27
|
+
# :column_chart
|
28
|
+
# end
|
29
|
+
|
30
|
+
# ==> Register a new range
|
31
|
+
# Add a range to the select
|
32
|
+
# register_range '15w', label: "15 weeks" do
|
33
|
+
# 15.weeks.ago
|
34
|
+
# end
|
35
|
+
|
36
|
+
# ==> Remove ranges
|
37
|
+
# Remove ranges from the select
|
38
|
+
# exclude_ranges '24h', '30d'
|
16
39
|
end
|
17
40
|
<% end -%>
|
data/lib/metricky/base.rb
CHANGED
@@ -1,3 +1,6 @@
|
|
1
|
+
require 'metricky/period'
|
2
|
+
require 'metricky/ranges'
|
3
|
+
|
1
4
|
module Metricky
|
2
5
|
class Base
|
3
6
|
VALID_TYPES = [:sum, :max, :min, :average, :count].freeze
|
@@ -19,32 +22,13 @@ module Metricky
|
|
19
22
|
end
|
20
23
|
|
21
24
|
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
25
|
{
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
'MTD' => 'MTD',
|
46
|
-
'YTD' => 'YTD',
|
47
|
-
}
|
26
|
+
count: "total number of",
|
27
|
+
average: "average #{columns}",
|
28
|
+
sum: "total",
|
29
|
+
min: "minimum",
|
30
|
+
max: "maximum"
|
31
|
+
}[type.to_sym]
|
48
32
|
end
|
49
33
|
|
50
34
|
# Actual result of the metric
|
@@ -74,32 +58,6 @@ module Metricky
|
|
74
58
|
self.class.metric_name.tableize
|
75
59
|
end
|
76
60
|
|
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
61
|
# What ActiveRecord class (or scoped class) is being used for the metric
|
104
62
|
def scope
|
105
63
|
raise NotImplementedError, "please add a scope to your metric."
|
@@ -118,66 +76,26 @@ module Metricky
|
|
118
76
|
'id'
|
119
77
|
end
|
120
78
|
|
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
|
-
ActiveSupport::Deprecation.warn('Use period instead')
|
127
|
-
period
|
128
|
-
end
|
129
|
-
|
130
|
-
def period
|
131
|
-
nil
|
132
|
-
end
|
133
|
-
|
134
|
-
# What column to specify for the range calculation. Normally `created_at`
|
135
|
-
def range_column
|
136
|
-
'created_at'
|
137
|
-
end
|
138
|
-
|
139
|
-
# What column to specify for the trend calculation. Normally `created_at`
|
140
|
-
def period_column
|
141
|
-
'created_at'
|
142
|
-
end
|
143
|
-
|
144
|
-
def trend_column
|
145
|
-
ActiveSupport::Deprecation.warn('Use period_column instead')
|
146
|
-
period_column
|
147
|
-
end
|
148
|
-
|
149
|
-
def range
|
150
|
-
params.dig(form_name, :range)
|
151
|
-
end
|
152
|
-
|
153
79
|
private
|
154
80
|
|
155
|
-
def period?
|
156
|
-
period.present?
|
157
|
-
end
|
158
|
-
|
159
81
|
def assets
|
160
|
-
|
161
|
-
|
162
|
-
|
163
|
-
@query = scope
|
82
|
+
@query = scope
|
83
|
+
unless range_to_value.nil?
|
84
|
+
@query = scope.where("#{range_column} > ?", range_to_value)
|
164
85
|
end
|
165
86
|
if period? && valid_period?
|
166
|
-
@query = @query.group_by_period(
|
87
|
+
@query = @query.group_by_period(period, period_column)
|
88
|
+
end
|
89
|
+
if valid_type?
|
90
|
+
@query = @query.send(type, *columns)
|
91
|
+
else
|
92
|
+
raise TypeError, "#{type} is not a valid type."
|
167
93
|
end
|
168
|
-
@query = @query.send(type, *columns)
|
169
94
|
@query
|
170
95
|
end
|
171
96
|
|
172
|
-
def
|
173
|
-
|
174
|
-
raise NameError, "period must be one of #{Groupdate::PERIODS}. It is #{period}."
|
175
|
-
end
|
176
|
-
|
177
|
-
def check_type
|
178
|
-
unless VALID_TYPES.include?(type.to_sym)
|
179
|
-
raise NameError, "#{type} must be one of :sum, :max, :min, :average, :count"
|
180
|
-
end
|
97
|
+
def valid_type?
|
98
|
+
VALID_TYPES.include?(type.to_sym)
|
181
99
|
end
|
182
100
|
end
|
183
101
|
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
module Metricky
|
2
|
+
class Base
|
3
|
+
# How it's grouped. Leave nil if no grouping
|
4
|
+
#
|
5
|
+
# [:second, :minute, :hour, :day, :week, :month, :quarter, :year, :day_of_week,
|
6
|
+
# :hour_of_day, :minute_of_hour, :day_of_month, :month_of_year]
|
7
|
+
def period
|
8
|
+
nil
|
9
|
+
end
|
10
|
+
deprecate_and_alias_method :period, :trend
|
11
|
+
|
12
|
+
# What column to specify for the period calculation. Normally `created_at`
|
13
|
+
def period_column
|
14
|
+
'created_at'
|
15
|
+
end
|
16
|
+
deprecate_and_alias_method :period_column, :trend_column
|
17
|
+
|
18
|
+
private
|
19
|
+
|
20
|
+
def period?
|
21
|
+
period.present?
|
22
|
+
end
|
23
|
+
deprecate_and_alias_method :period?, :trend?
|
24
|
+
|
25
|
+
def valid_period?
|
26
|
+
return true if Groupdate::PERIODS.include?(period.to_sym)
|
27
|
+
raise NameError, "period must be one of #{Groupdate::PERIODS}. It is #{period}."
|
28
|
+
end
|
29
|
+
deprecate_and_alias_method :valid_period?, :valid_trend?
|
30
|
+
|
31
|
+
end
|
32
|
+
end
|
@@ -0,0 +1,104 @@
|
|
1
|
+
module Metricky
|
2
|
+
class Base
|
3
|
+
class_attribute :ranges, default: {}
|
4
|
+
class_attribute :excluded_ranges, default: []
|
5
|
+
class_attribute :default_range_key, default: 'all'
|
6
|
+
|
7
|
+
def self.default_range(val = nil)
|
8
|
+
self.default_range_key = val
|
9
|
+
end
|
10
|
+
|
11
|
+
# Rewrite the priority of a range thing.
|
12
|
+
#
|
13
|
+
# class UserMetric
|
14
|
+
# range_priority '24h', 99
|
15
|
+
# end
|
16
|
+
def self.range_priority(key, priority)
|
17
|
+
self.ranges[key.to_s].priority = priority
|
18
|
+
end
|
19
|
+
|
20
|
+
# Register a range. Priority is used for the select order.
|
21
|
+
#
|
22
|
+
# @param [String] key The value for the option in the select for the form
|
23
|
+
# @param [String] label The text shown on the option in the select for the form
|
24
|
+
# @param [Integer] priority What order the resulting collection
|
25
|
+
# @param [Block/Proc] block The Ruby-converted value. Usually a DateTime/Date/Time object.
|
26
|
+
#
|
27
|
+
# class UserMetric
|
28
|
+
# register_range '15w', label: '15 weeks', priority: 99 do
|
29
|
+
# 15.weeks.ago
|
30
|
+
# end
|
31
|
+
# end
|
32
|
+
#
|
33
|
+
# @return RangeThing
|
34
|
+
def self.register_range(key, label: nil, priority: nil, &block)
|
35
|
+
label ||= key
|
36
|
+
priority ||= self.ranges.size
|
37
|
+
self.ranges[key.to_s] = RangeThing.new(label, priority, block)
|
38
|
+
end
|
39
|
+
|
40
|
+
# Removes the listed ranges from the select
|
41
|
+
def self.remove_ranges(*keys)
|
42
|
+
keys.each { self.remove_range(key) }
|
43
|
+
end
|
44
|
+
|
45
|
+
def self.remove_range(key)
|
46
|
+
self.excluded_ranges << key.to_s
|
47
|
+
end
|
48
|
+
|
49
|
+
register_range 'all', label: 'All' do
|
50
|
+
nil
|
51
|
+
end
|
52
|
+
|
53
|
+
register_range 'today', label: 'Today' do
|
54
|
+
DateTime.now.beginning_of_day
|
55
|
+
end
|
56
|
+
|
57
|
+
register_range '24h', label: '24 hours' do
|
58
|
+
24.hours.ago
|
59
|
+
end
|
60
|
+
|
61
|
+
[3, 7, 10, 14, 21, 30].each do |day|
|
62
|
+
register_range "#{day}d", label: "#{day} Days" do
|
63
|
+
day.days.ago
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
register_range "MTD", label: "MTD" do
|
68
|
+
DateTime.now.beginning_of_month
|
69
|
+
end
|
70
|
+
|
71
|
+
register_range "QTD", label: "QTD" do
|
72
|
+
DateTime.now.beginning_of_quarter
|
73
|
+
end
|
74
|
+
|
75
|
+
register_range "YTD", label: "YTD" do
|
76
|
+
DateTime.now.beginning_of_year
|
77
|
+
end
|
78
|
+
|
79
|
+
# Lookup the passed range and convert it to a value for our ActiveRecord query
|
80
|
+
def range_to_value
|
81
|
+
return nil if range.nil?
|
82
|
+
if val = self.ranges[range]
|
83
|
+
val.value.call
|
84
|
+
else
|
85
|
+
raise TypeError, "invalid range #{range}. Please define it."
|
86
|
+
end
|
87
|
+
end
|
88
|
+
deprecate_and_alias_method :range_to_value, :range_value
|
89
|
+
|
90
|
+
# What column to specify for the range calculation. Normally `created_at`
|
91
|
+
def range_column
|
92
|
+
'created_at'
|
93
|
+
end
|
94
|
+
|
95
|
+
def range
|
96
|
+
params.dig(form_name, :range) || default_range_key
|
97
|
+
end
|
98
|
+
|
99
|
+
# Used in the HTML form for the metric as the select options
|
100
|
+
def range_collection
|
101
|
+
ranges.except(*excluded_ranges).sort_by { |_, range| range.priority }.collect { |key, range_thing| [range_thing.label, key] }
|
102
|
+
end
|
103
|
+
end
|
104
|
+
end
|
data/lib/metricky/version.rb
CHANGED
data/lib/metricky.rb
CHANGED
@@ -1,11 +1,15 @@
|
|
1
1
|
require "groupdate"
|
2
2
|
require "chartkick"
|
3
|
+
require 'metricky/core_ext'
|
4
|
+
require 'active_support/core_ext'
|
5
|
+
require 'active_support/concern'
|
6
|
+
require "metricky/range_thing"
|
7
|
+
require "metricky/ranges"
|
3
8
|
require "metricky/base"
|
4
9
|
require "metricky/helper"
|
5
10
|
require "metricky/engine"
|
6
11
|
|
7
|
-
module Metricky
|
8
|
-
end
|
12
|
+
module Metricky; end
|
9
13
|
|
10
14
|
ActiveSupport.on_load(:action_view) do
|
11
15
|
include Metricky::Helper
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: metricky
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.6.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Josh Brody
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2019-09-
|
11
|
+
date: 2019-09-10 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: rails
|
@@ -126,8 +126,12 @@ files:
|
|
126
126
|
- lib/generators/metricky/templates/metric.rb.tt
|
127
127
|
- lib/metricky.rb
|
128
128
|
- lib/metricky/base.rb
|
129
|
+
- lib/metricky/core_ext.rb
|
129
130
|
- lib/metricky/engine.rb
|
130
131
|
- lib/metricky/helper.rb
|
132
|
+
- lib/metricky/period.rb
|
133
|
+
- lib/metricky/range_thing.rb
|
134
|
+
- lib/metricky/ranges.rb
|
131
135
|
- lib/metricky/version.rb
|
132
136
|
homepage: https://github.com/joshmn/metricky
|
133
137
|
licenses:
|