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 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