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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 24b5d4ea790824df18783742e69969e024b31cf44b4164a191d3ff0215804873
4
- data.tar.gz: 72143e22bb37bd47da23c70d527011e86f35c2d840dd9593d1a845e0619e40a1
3
+ metadata.gz: 658a25239c266d3ecdb01b9a7e189fe872e527b93f2eebf4d18a1ced0c6f44b7
4
+ data.tar.gz: a568dcedbed1b33cd18f5a8fac81acb997557962475f7930399c0d805a0b6ab3
5
5
  SHA512:
6
- metadata.gz: 753fe8c0f2d2e47b36042426d00a4dfe0352745b4bccc7142de4f4e8f621b7cefb3a19276e56894508f19e53911f836a71300b289dd83a337d9ffe0b0fa0ffea
7
- data.tar.gz: cf3896f56e6475af16c5f1d6984d1a177ced083da92f416942ca0eeeeaffe3b70ab1b1caae51c57d8f3cadbcdf77841ff3dd29091dd7cafe86435814aa4afbbe
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 User --scope User --type :count --period :day`
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 :users
19
+ render_metric :total_users
17
20
  ```
18
21
 
19
- In `app/metrics/users_metric.rb`
22
+ ## Customize it
23
+
24
+ In `app/metrics/total_users_metric.rb`
20
25
 
21
26
  ```ruby
22
- class UsersMetric < Metricky::Base
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 date
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
- def self.ranges
116
- {
117
- 'all' => 'All',
118
- '30' => '30 Days',
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
- And then their corresponding values (instance-level):
134
+ #### Removing a range
126
135
 
127
- ```ruby
128
- def range_value
129
- case range
130
- when 'all'
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
- This is used with a `where` query against the `range_column`.
143
+ #### Setting the default range
143
144
 
144
- ```ruby
145
- def range_column
146
- :created_at
145
+ ```ruby
146
+ class TotalUsersMetric < ApplicationMetric
147
+ default_range '24h'
147
148
  end
148
149
  ```
149
150
 
@@ -1,3 +1,2 @@
1
1
  class ApplicationMetric < Metricky::Base
2
-
3
2
  end
@@ -6,7 +6,7 @@
6
6
  </div>
7
7
  <% if metric.class.ranges.any? %>
8
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()" } %>
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
- '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
- }
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
- if range_value != nil
161
- @query = scope.where("#{range_column} > ?", range_value)
162
- else
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(trend, trend_column)
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 valid_period?
173
- return true if Groupdate::PERIODS.include?(period.to_sym)
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,8 @@
1
+ class Object
2
+ def self.deprecate_and_alias_method(new_method, old_method)
3
+ define_method(old_method) do
4
+ ActiveSupport::Deprecation.warn("#{old_method} is deprecated. Use #{new_method} instead.")
5
+ send(new_method)
6
+ end
7
+ end
8
+ 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,10 @@
1
+ module Metricky
2
+ class RangeThing
3
+ attr_reader :label, :priority, :value
4
+ def initialize(label, priority, value)
5
+ @label = label
6
+ @priority = priority
7
+ @value = value
8
+ end
9
+ end
10
+ 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
@@ -1,3 +1,3 @@
1
1
  module Metricky
2
- VERSION = '0.5.0'
2
+ VERSION = '0.6.0'
3
3
  end
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.5.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-09 00:00:00.000000000 Z
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: