tricle 0.1.0 → 0.2.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
  SHA1:
3
- metadata.gz: 8ae60bb0e6e671d65f0c1901458bdb6a2a8ea730
4
- data.tar.gz: 4714ec2150c42c605587d3cea5fd05382a60f4ab
3
+ metadata.gz: 1e1bf9319ba25a271eaae97c379e62f2f6a6b68e
4
+ data.tar.gz: 4a515d52060d25dcae9a4f6c00c41786bf930b10
5
5
  SHA512:
6
- metadata.gz: b0b2c91ada726e91f976c1cb5ed6c421c9a8a8e4a8d6f633f599bd644e71aa80af59157835cbad37ad844952abe3d60a59e5fe272d1a92c672133d44d871e9c1
7
- data.tar.gz: 365357668547fc809ace7212a7d2a9931850b8d69d97f0d85b7e0713b23c205cddcbee6ecb96596c92ae1caba151ba5786441594dc8492e26c279e41df122f5a
6
+ metadata.gz: bc6c048e313464575809c42d0dfd56857893be868834de9b8fc3c411c05dcfe813401cd60589ba4f9a65c0d7a7cf810486a5b542a2ece34857f102cec507d040
7
+ data.tar.gz: 204151436a1410c2b7138dd1480f929e733a8260b4c6b1a97dc893ad6c3f3dd46f8a62ebbd7e15fa3743b608bab95c56c34e9566be0a858802205684229bdc98
data/.gitignore CHANGED
@@ -15,3 +15,4 @@ spec/reports
15
15
  test/tmp
16
16
  test/version_tmp
17
17
  tmp
18
+ gemfiles/*.lock
data/.travis.yml CHANGED
@@ -2,3 +2,6 @@ language: ruby
2
2
  rvm:
3
3
  - 1.9.3
4
4
  - 2.0.0
5
+ gemfile:
6
+ - gemfiles/rails_3.gemfile
7
+ - gemfiles/rails_4.gemfile
data/Appraisals ADDED
@@ -0,0 +1,9 @@
1
+ appraise "rails-3" do
2
+ gem 'actionmailer', '3.2.19'
3
+ gem 'activesupport', '3.2.19'
4
+ end
5
+
6
+ appraise "rails-4" do
7
+ gem 'actionmailer', '4.1.5'
8
+ gem 'activesupport', '4.1.5'
9
+ end
data/CONTRIBUTING.md CHANGED
@@ -17,7 +17,6 @@ bundle exec guard
17
17
  To see a live preview with dummy data:
18
18
 
19
19
  ```bash
20
- gem install shotgun
21
- shotgun
22
- open http://localhost:9393/test_mailer.html
20
+ bundle exec rake tricle:preview
21
+ open http://localhost:8080
23
22
  ```
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
@@ -1,6 +1,6 @@
1
1
  notification :gntp
2
2
 
3
- guard :rspec, all_on_start: true do
3
+ guard :rspec, cmd: 'bundle exec rspec', all_on_start: true do
4
4
  watch(%r{^spec/.+_spec\.rb$})
5
5
  watch(%r{^lib/}) { 'spec' }
6
6
  watch('spec/spec_helper.rb') { 'spec' }
data/README.md CHANGED
@@ -1,4 +1,4 @@
1
- # Tricle [![Build Status](https://travis-ci.org/artsy/tricle.png?branch=master)](https://travis-ci.org/artsy/tricle) [![Code Climate](https://codeclimate.com/github/artsy/tricle.png)](https://codeclimate.com/github/artsy/tricle)
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.1.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: only necessary if using `list` for this Metric within your Mailer.
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.size_for_range(start_at, end_at).size
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.size_for_range(start_at, end_at)
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
@@ -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.weeks_ago(n)
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.reverse.gsub(/(\d{3})(?=\d)/, '\\1,').reverse
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 == 0
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
- sprintf('%+.1f%', fraction * 100.0)
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 >= old_val) ? 'positive' : 'negative'
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 = number_with_delimiter(old_val.round)
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 total
22
+ def items_for_range(start_at, end_at)
16
23
  raise Tricle::AbstractMethodError.new
17
24
  end
18
25
 
19
- def items_for_range(start_at, end_at)
20
- raise Tricle::AbstractMethodError.new
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 :metric_presenters, :title
6
+ attr_reader :metrics, :title
8
7
 
9
8
  def initialize(title=nil)
10
9
  @title = title
11
- @metric_presenters = []
10
+ @metrics = []
12
11
  end
13
12
 
14
13
  def add_metric(klass)
15
- presenter = Tricle::Presenters::Metric.new(klass)
16
- self.metric_presenters << presenter
14
+ self.metrics << klass.new
17
15
  end
18
16
  end
19
17
  end
@@ -36,12 +36,12 @@ td div:first-child {
36
36
  font-size: 20px;
37
37
  }
38
38
 
39
- .positive {
39
+ .good {
40
40
  background-color: #e4ffea;
41
41
  color: green;
42
42
  }
43
43
 
44
- .negative {
44
+ .bad {
45
45
  background-color: #ffe3e4;
46
46
  color: #c21717;
47
47
  }
@@ -27,15 +27,17 @@
27
27
  <%= quarter_dates_cell %>
28
28
  </th>
29
29
  </tr>
30
- <% section.metric_presenters.each do |metric| %>
30
+ <% section.metrics.each do |metric| %>
31
31
  <tr>
32
32
  <th class="metric-title"><%= metric.title %></th>
33
33
  <td>
34
- <div><%= number_with_delimiter(metric.last_week) %></div>
35
- <div><%= number_with_delimiter(metric.total) %> (total)</div>
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 %>
@@ -1,3 +1,3 @@
1
1
  module Tricle
2
- VERSION = "0.1.0"
2
+ VERSION = "0.2.0"
3
3
  end