tricle 0.1.0 → 0.2.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.gitignore +1 -0
- data/.travis.yml +3 -0
- data/Appraisals +9 -0
- data/CONTRIBUTING.md +2 -3
- data/Gemfile +13 -1
- data/Guardfile +1 -1
- data/README.md +33 -12
- data/gemfiles/rails_3.gemfile +21 -0
- data/gemfiles/rails_4.gemfile +21 -0
- data/lib/tricle/aggregation.rb +46 -0
- data/lib/tricle/email_helper.rb +32 -7
- data/lib/tricle/metric.rb +10 -3
- data/lib/tricle/presenters/group.rb +3 -5
- data/lib/tricle/templates/email.css +2 -2
- data/lib/tricle/templates/email.html.erb +7 -5
- data/lib/tricle/version.rb +1 -1
- data/spec/app/no_total_test_mailer.rb +11 -0
- data/spec/app/test_metric.rb +2 -34
- data/spec/app/test_metric_with_no_total.rb +35 -0
- data/spec/config/timecop.rb +1 -1
- data/spec/presenters/group_spec.rb +3 -4
- data/spec/presenters/report_spec.rb +5 -5
- data/spec/spec_helper.rb +0 -1
- data/spec/unit/aggregation_spec.rb +41 -0
- data/spec/unit/email_helper_spec.rb +87 -7
- data/spec/unit/mail_preview_spec.rb +2 -2
- data/spec/unit/mailer_spec.rb +22 -15
- data/spec/unit/metric_spec.rb +2 -2
- data/spec/unit/range_data_spec.rb +2 -2
- data/spec/unit/test_metric_spec.rb +3 -4
- data/tricle.gemspec +3 -13
- metadata +36 -155
- data/lib/tricle/presenters/metric.rb +0 -51
- data/spec/presenters/metric_spec.rb +0 -42
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 1e1bf9319ba25a271eaae97c379e62f2f6a6b68e
|
4
|
+
data.tar.gz: 4a515d52060d25dcae9a4f6c00c41786bf930b10
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: bc6c048e313464575809c42d0dfd56857893be868834de9b8fc3c411c05dcfe813401cd60589ba4f9a65c0d7a7cf810486a5b542a2ece34857f102cec507d040
|
7
|
+
data.tar.gz: 204151436a1410c2b7138dd1480f929e733a8260b4c6b1a97dc893ad6c3f3dd46f8a62ebbd7e15fa3743b608bab95c56c34e9566be0a858802205684229bdc98
|
data/.gitignore
CHANGED
data/.travis.yml
CHANGED
data/Appraisals
ADDED
data/CONTRIBUTING.md
CHANGED
data/Gemfile
CHANGED
@@ -1,4 +1,16 @@
|
|
1
1
|
source 'https://rubygems.org'
|
2
2
|
|
3
|
-
# Specify your gem's dependencies in tricle.gemspec
|
4
3
|
gemspec
|
4
|
+
|
5
|
+
group :development, :test do
|
6
|
+
gem 'appraisal'
|
7
|
+
gem 'bundler'
|
8
|
+
gem 'byebug', platforms: :mri_20
|
9
|
+
gem 'guard', '~> 2.6'
|
10
|
+
gem 'guard-rspec'
|
11
|
+
gem 'rake'
|
12
|
+
gem 'rspec', '~> 3.0'
|
13
|
+
gem 'ruby_gntp'
|
14
|
+
gem 'shotgun'
|
15
|
+
gem 'timecop', '~> 0.7'
|
16
|
+
end
|
data/Guardfile
CHANGED
data/README.md
CHANGED
@@ -1,4 +1,4 @@
|
|
1
|
-
# Tricle [![Build Status](https://travis-ci.org/
|
1
|
+
# Tricle [![Build Status](https://travis-ci.org/afeld/tricle.png?branch=master)](https://travis-ci.org/afeld/tricle) [![Code Climate](https://codeclimate.com/github/afeld/tricle.png)](https://codeclimate.com/github/afeld/tricle)
|
2
2
|
|
3
3
|
Automated metrics reporting via email. It's datastore-agnostic, so you can query SQL, MongoDB, external APIs, etc. to generate the stats you need. See [here](https://github.com/afeld/tricle-afeld) for an example implementation ([live demo](http://tricle.afeld.me/weekly_metrics)).
|
4
4
|
|
@@ -12,7 +12,7 @@ This gem can be used within an existing project (e.g. a Rails app), or standalon
|
|
12
12
|
|
13
13
|
```ruby
|
14
14
|
# Gemfile
|
15
|
-
gem 'tricle', '~> 0.
|
15
|
+
gem 'tricle', '~> 0.2.0'
|
16
16
|
|
17
17
|
# Rakefile
|
18
18
|
require 'tricle/tasks'
|
@@ -51,14 +51,15 @@ class MyMetric < Tricle::Metric
|
|
51
51
|
# ...
|
52
52
|
end
|
53
53
|
|
54
|
-
# Retrieve the cumulative value for this metric.
|
54
|
+
# Optional: Retrieve the cumulative value for this metric. If not defined,
|
55
|
+
# the total won't be displayed in the mailer.
|
55
56
|
#
|
56
57
|
# @return [Fixnum] the grand total
|
57
58
|
def total
|
58
59
|
# ...
|
59
60
|
end
|
60
61
|
|
61
|
-
# Optional:
|
62
|
+
# Optional: Only necessary if using `list` for this Metric within your Mailer.
|
62
63
|
#
|
63
64
|
# @param start_at [Time]
|
64
65
|
# @param end_at [Time] non-inclusive
|
@@ -73,11 +74,11 @@ end
|
|
73
74
|
ActiveRecord example:
|
74
75
|
|
75
76
|
```ruby
|
76
|
-
# metrics/new_users.rb
|
77
|
+
# app/metrics/new_users.rb
|
77
78
|
class NewUsers < Tricle::Metric
|
78
79
|
|
79
80
|
def size_for_range(start_at, end_at)
|
80
|
-
self.
|
81
|
+
self.items_for_range(start_at, end_at).count
|
81
82
|
end
|
82
83
|
|
83
84
|
def total
|
@@ -85,16 +86,12 @@ class NewUsers < Tricle::Metric
|
|
85
86
|
end
|
86
87
|
|
87
88
|
def items_for_range(start_at, end_at)
|
88
|
-
self.
|
89
|
+
self.users.where('created_at >= ? AND created_at < ?', start_at, end_at)
|
89
90
|
end
|
90
91
|
|
91
92
|
|
92
93
|
private
|
93
94
|
|
94
|
-
def size_for_range(start_at, end_at)
|
95
|
-
self.users.where('created_at >= ? AND created_at < ?', start_at, end_at)
|
96
|
-
end
|
97
|
-
|
98
95
|
# You can add whatever helper methods in that class that you need.
|
99
96
|
def users
|
100
97
|
# non-deleted Users
|
@@ -104,6 +101,24 @@ class NewUsers < Tricle::Metric
|
|
104
101
|
end
|
105
102
|
```
|
106
103
|
|
104
|
+
If you would like finer-grain optimization, the methods included from the [`Aggregation`](lib/tricle/arregation.rb) mixin can be overridden.
|
105
|
+
|
106
|
+
#### "Lower is better" metrics
|
107
|
+
|
108
|
+
By default, Tricle highlights numbers that increased in green, and those that decreased in red. If you have a metric where a *lower* number is considered better, you'll want to override the `#better` method so Tricle highlights your cells properly:
|
109
|
+
|
110
|
+
```ruby
|
111
|
+
class LowerIsBetterMetric < Tricle::Metric
|
112
|
+
def better
|
113
|
+
:lower
|
114
|
+
end
|
115
|
+
|
116
|
+
...
|
117
|
+
end
|
118
|
+
```
|
119
|
+
|
120
|
+
You can also return `:none`, and none of your cells for that metric will be highlighted green or red.
|
121
|
+
|
107
122
|
### Mailers
|
108
123
|
|
109
124
|
Mailers specify how a particular set of Metrics should be sent. You can define one or multiple, to send different metrics to different groups of people.
|
@@ -143,7 +158,7 @@ end
|
|
143
158
|
e.g.
|
144
159
|
|
145
160
|
```ruby
|
146
|
-
# mailers/weekly_insights.rb
|
161
|
+
# app/mailers/weekly_insights.rb
|
147
162
|
class WeeklyInsights < Tricle::Mailer
|
148
163
|
|
149
164
|
default(
|
@@ -199,6 +214,12 @@ To send all Tricle emails, run
|
|
199
214
|
rake tricle:emails:send
|
200
215
|
```
|
201
216
|
|
217
|
+
To set a speficic time zone, use the `TZ` environment variable (see the list [here](http://en.wikipedia.org/wiki/List_of_tz_database_time_zones)).
|
218
|
+
|
219
|
+
```bash
|
220
|
+
TZ=UTC rake tricle:emails:send
|
221
|
+
```
|
222
|
+
|
202
223
|
### Cron Setup
|
203
224
|
|
204
225
|
TODO
|
@@ -0,0 +1,21 @@
|
|
1
|
+
# This file was generated by Appraisal
|
2
|
+
|
3
|
+
source "https://rubygems.org"
|
4
|
+
|
5
|
+
gem "actionmailer", "3.2.19"
|
6
|
+
gem "activesupport", "3.2.19"
|
7
|
+
|
8
|
+
group :development, :test do
|
9
|
+
gem "appraisal"
|
10
|
+
gem "bundler"
|
11
|
+
gem "byebug", :platforms => :mri_20
|
12
|
+
gem "guard", "~> 2.6"
|
13
|
+
gem "guard-rspec"
|
14
|
+
gem "rake"
|
15
|
+
gem "rspec", "~> 3.0"
|
16
|
+
gem "ruby_gntp"
|
17
|
+
gem "shotgun"
|
18
|
+
gem "timecop", "~> 0.7"
|
19
|
+
end
|
20
|
+
|
21
|
+
gemspec :path => "../"
|
@@ -0,0 +1,21 @@
|
|
1
|
+
# This file was generated by Appraisal
|
2
|
+
|
3
|
+
source "https://rubygems.org"
|
4
|
+
|
5
|
+
gem "actionmailer", "4.1.5"
|
6
|
+
gem "activesupport", "4.1.5"
|
7
|
+
|
8
|
+
group :development, :test do
|
9
|
+
gem "appraisal"
|
10
|
+
gem "bundler"
|
11
|
+
gem "byebug", :platforms => :mri_20
|
12
|
+
gem "guard", "~> 2.6"
|
13
|
+
gem "guard-rspec"
|
14
|
+
gem "rake"
|
15
|
+
gem "rspec", "~> 3.0"
|
16
|
+
gem "ruby_gntp"
|
17
|
+
gem "shotgun"
|
18
|
+
gem "timecop", "~> 0.7"
|
19
|
+
end
|
20
|
+
|
21
|
+
gemspec :path => "../"
|
@@ -0,0 +1,46 @@
|
|
1
|
+
require 'active_support/concern'
|
2
|
+
|
3
|
+
module Tricle
|
4
|
+
module Aggregation
|
5
|
+
extend ActiveSupport::Concern
|
6
|
+
|
7
|
+
included do
|
8
|
+
attr_reader :now
|
9
|
+
|
10
|
+
def initialize
|
11
|
+
# TODO allow Time to be passed in so it can be frozen
|
12
|
+
@now = Time.now
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
def days_ago(n)
|
17
|
+
start_at = self.now.beginning_of_day.ago(n.days)
|
18
|
+
end_at = start_at + 1.day
|
19
|
+
self.size_for_range(start_at, end_at)
|
20
|
+
end
|
21
|
+
|
22
|
+
def yesterday
|
23
|
+
self.days_ago(1)
|
24
|
+
end
|
25
|
+
|
26
|
+
def weeks_ago(n)
|
27
|
+
start_at = self.now.beginning_of_week.weeks_ago(n)
|
28
|
+
end_at = start_at + 7.days
|
29
|
+
self.size_for_range(start_at, end_at)
|
30
|
+
end
|
31
|
+
|
32
|
+
def last_week
|
33
|
+
self.weeks_ago(1)
|
34
|
+
end
|
35
|
+
|
36
|
+
def weeks_average(past_num_weeks)
|
37
|
+
weeks_range = 1..past_num_weeks
|
38
|
+
total = weeks_range.reduce(0){|sum, n| sum + self.weeks_ago(n) }
|
39
|
+
total.to_f / past_num_weeks
|
40
|
+
end
|
41
|
+
|
42
|
+
def week_average_this_quarter
|
43
|
+
self.weeks_average(13)
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
data/lib/tricle/email_helper.rb
CHANGED
@@ -4,31 +4,56 @@ require 'active_support/core_ext/numeric/time'
|
|
4
4
|
module Tricle
|
5
5
|
module EmailHelper
|
6
6
|
def weeks_ago(n)
|
7
|
-
Date.today.beginning_of_week.
|
7
|
+
Date.today.beginning_of_week.ago(n.weeks)
|
8
8
|
end
|
9
9
|
|
10
10
|
def format_date(date)
|
11
11
|
date.strftime('%-m/%-d/%y')
|
12
12
|
end
|
13
13
|
|
14
|
+
def format_number(number)
|
15
|
+
number_with_delimiter((number.abs >= 100 ? number.round : sig_figs(number)))
|
16
|
+
end
|
17
|
+
|
14
18
|
def number_with_delimiter(number)
|
15
19
|
# from http://stackoverflow.com/a/11466770/358804
|
16
|
-
number.to_s.
|
20
|
+
integer, decimal = number.to_s.split(".")
|
21
|
+
[integer.to_s.reverse.gsub(/(\d{3})(?=\d)/, '\\1,').reverse, decimal].compact.join('.')
|
22
|
+
end
|
23
|
+
|
24
|
+
def sig_figs(number, num_sig_figs = 3)
|
25
|
+
# http://six-impossible.blogspot.com/2011/05/significant-digits-in-ruby-float.html
|
26
|
+
f = sprintf("%.#{num_sig_figs - 1}e", number).to_f
|
27
|
+
i = f.to_i # avoid
|
28
|
+
i == f && i.to_s.size > num_sig_figs ? i : f
|
17
29
|
end
|
18
30
|
|
19
31
|
def percent_change(new_val, old_val)
|
20
|
-
if old_val ==
|
32
|
+
if old_val == new_val
|
33
|
+
'No change'
|
34
|
+
elsif old_val == 0
|
21
35
|
new_val >= 0 ? '+' : '-'
|
22
36
|
else
|
23
37
|
fraction = (new_val - old_val) / old_val.to_f
|
24
|
-
|
38
|
+
(fraction >= 0 ? '+' : '').concat("#{sig_figs(fraction * 100.0)}%")
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
def percent_change_class(new_val, old_val, better)
|
43
|
+
case better
|
44
|
+
when :higher
|
45
|
+
(new_val >= old_val) ? 'good' : 'bad'
|
46
|
+
when :lower
|
47
|
+
(new_val >= old_val) ? 'bad' : 'good'
|
48
|
+
else
|
49
|
+
''
|
25
50
|
end
|
26
51
|
end
|
27
52
|
|
28
|
-
def percent_change_cell(new_val, old_val)
|
29
|
-
cls = (new_val
|
53
|
+
def percent_change_cell(new_val, old_val, better)
|
54
|
+
cls = self.percent_change_class(new_val, old_val, better)
|
30
55
|
pct_str = percent_change(new_val, old_val)
|
31
|
-
old_val_str =
|
56
|
+
old_val_str = format_number(old_val)
|
32
57
|
%[<td class="#{cls}"><div>#{pct_str}</div><div>#{old_val_str}</div></td>].html_safe
|
33
58
|
end
|
34
59
|
|
data/lib/tricle/metric.rb
CHANGED
@@ -1,9 +1,16 @@
|
|
1
1
|
require 'active_support/core_ext/date/calculations'
|
2
2
|
require 'active_support/core_ext/numeric/time'
|
3
3
|
require_relative 'abstract_method_error'
|
4
|
+
require_relative 'aggregation'
|
4
5
|
|
5
6
|
module Tricle
|
6
7
|
class Metric
|
8
|
+
include Aggregation
|
9
|
+
|
10
|
+
def better
|
11
|
+
:higher
|
12
|
+
end
|
13
|
+
|
7
14
|
def title
|
8
15
|
self.class.name.titleize
|
9
16
|
end
|
@@ -12,12 +19,12 @@ module Tricle
|
|
12
19
|
self.items_for_range(start_at, end_at).size
|
13
20
|
end
|
14
21
|
|
15
|
-
def
|
22
|
+
def items_for_range(start_at, end_at)
|
16
23
|
raise Tricle::AbstractMethodError.new
|
17
24
|
end
|
18
25
|
|
19
|
-
def
|
20
|
-
|
26
|
+
def total?
|
27
|
+
self.respond_to?(:total)
|
21
28
|
end
|
22
29
|
end
|
23
30
|
end
|
@@ -1,19 +1,17 @@
|
|
1
|
-
require_relative 'metric'
|
2
1
|
require_relative 'section'
|
3
2
|
|
4
3
|
module Tricle
|
5
4
|
module Presenters
|
6
5
|
class Group < Section
|
7
|
-
attr_reader :
|
6
|
+
attr_reader :metrics, :title
|
8
7
|
|
9
8
|
def initialize(title=nil)
|
10
9
|
@title = title
|
11
|
-
@
|
10
|
+
@metrics = []
|
12
11
|
end
|
13
12
|
|
14
13
|
def add_metric(klass)
|
15
|
-
|
16
|
-
self.metric_presenters << presenter
|
14
|
+
self.metrics << klass.new
|
17
15
|
end
|
18
16
|
end
|
19
17
|
end
|
@@ -27,15 +27,17 @@
|
|
27
27
|
<%= quarter_dates_cell %>
|
28
28
|
</th>
|
29
29
|
</tr>
|
30
|
-
<% section.
|
30
|
+
<% section.metrics.each do |metric| %>
|
31
31
|
<tr>
|
32
32
|
<th class="metric-title"><%= metric.title %></th>
|
33
33
|
<td>
|
34
|
-
<div><%=
|
35
|
-
|
34
|
+
<div><%= format_number(metric.last_week) %></div>
|
35
|
+
<% if metric.total? %>
|
36
|
+
<div><%= format_number(metric.total) %> (total)</div>
|
37
|
+
<% end %>
|
36
38
|
</td>
|
37
|
-
<%= percent_change_cell(metric.last_week, metric.weeks_ago(2)) %>
|
38
|
-
<%= percent_change_cell(metric.last_week, metric.week_average_this_quarter) %>
|
39
|
+
<%= percent_change_cell(metric.last_week, metric.weeks_ago(2), metric.better) %>
|
40
|
+
<%= percent_change_cell(metric.last_week, metric.week_average_this_quarter, metric.better) %>
|
39
41
|
</tr>
|
40
42
|
<% end %>
|
41
43
|
<% end %>
|
data/lib/tricle/version.rb
CHANGED