metricky 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/MIT-LICENSE +20 -0
- data/README.md +163 -0
- data/Rakefile +27 -0
- data/app/views/metricky/_metric.html.erb +24 -0
- data/lib/metricky/base.rb +173 -0
- data/lib/metricky/engine.rb +7 -0
- data/lib/metricky/helper.rb +30 -0
- data/lib/metricky/version.rb +3 -0
- data/lib/metricky.rb +13 -0
- data/lib/tasks/metricky_tasks.rake +4 -0
- metadata +96 -0
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,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
|
data/lib/metricky.rb
ADDED
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: []
|