metricky 0.5.0 → 0.6.0
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.
- 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:
|