tricle 0.2.3 → 0.2.4

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: c0460bda7f826c939acd97c2e0334932b9f0ea64
4
- data.tar.gz: 2d54ea7afaa2b7de1d75f281a4a875ebf3096501
3
+ metadata.gz: a0a773a86cbdcd4e1c093bd098e1a79d450e7e73
4
+ data.tar.gz: 47963dbca244c39651964fd6516c5847d1e79e57
5
5
  SHA512:
6
- metadata.gz: 0578353886fe5153797d77ef3082e2e90a741812a0381fa1e14844d865a23ec967f3ccfc169032317cff2e8b9d073b86fdccf5950aefea051be67c6bb14b6e88
7
- data.tar.gz: be52787ddabd878a279254dcee7e529b7f300cdcac285cbfb89a652ee85d87f21bcd0dfee2e6760cf454ebcefa1f8cfe1a7d2a5d04d830872a7e7a38663c0e7e
6
+ metadata.gz: 5948b2159e96493cb19eeb3f0de03be23f941749a8ceb13ccb9c84169ce999de99ae5fe06a53db30ac3205cf1a090e6db1b5992a743bafd05c75046de8ef97ba
7
+ data.tar.gz: 544c67eb18aa0beaad093f4f04dab5ee7a42f2bbef03824e8d8fec9090ec796c4579e1bb8f041d0283fd8264d73486e0c9693518f9c9848cb89d72574ebef85e
data/README.md CHANGED
@@ -1,6 +1,6 @@
1
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
- 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)).
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)). Uses [sparkle](https://github.com/afeld/sparkle) for generating sparklines.
4
4
 
5
5
  ![screenshot](screenshot.png)
6
6
 
@@ -77,38 +77,41 @@ class MyMetric < Tricle::Metric
77
77
  end
78
78
  ```
79
79
 
80
- ActiveRecord example:
80
+ If you would like finer-grain optimization, the methods included from the [`Aggregation`](lib/tricle/arregation.rb) mixin can be overridden.
81
+
82
+ ### ActiveRecord Metrics
83
+
84
+ You can inherit from [`Tricle::ActiveRecordMetric`](lib/tricle/active_record_metric.rb) for even easier setup. By default, Tricle looks for records with a `created_at` timestamp between the `start_at` and `end_at` times.
81
85
 
82
86
  ```ruby
83
87
  # app/metrics/new_users.rb
84
- class NewUsers < Tricle::Metric
85
-
86
- def size_for_range(start_at, end_at)
87
- self.items_for_range(start_at, end_at).count
88
- end
88
+ class NewUsers < Tricle::ActiveRecordMetric
89
89
 
90
- def total
91
- self.users.count
90
+ # Apply any default scopes you need
91
+ def items
92
+ User.where(deleted_at: nil)
92
93
  end
93
94
 
94
- def items_for_range(start_at, end_at)
95
- self.users.where('created_at >= ? AND created_at < ?', start_at, end_at)
96
- end
95
+ end
96
+ ```
97
97
 
98
+ You can also override the `time_column` method to split the time intervals based on that value. For example, to see records that have been updated:
98
99
 
99
- private
100
+ ```ruby
101
+ # app/metrics/new_users.rb
102
+ class NewUsers < Tricle::ActiveRecordMetric
100
103
 
101
- # You can add whatever helper methods in that class that you need.
102
- def users
103
- # non-deleted Users
104
+ def items
104
105
  User.where(deleted_at: nil)
105
106
  end
106
107
 
108
+ def column
109
+ 'updated_at'
110
+ end
111
+
107
112
  end
108
113
  ```
109
114
 
110
- If you would like finer-grain optimization, the methods included from the [`Aggregation`](lib/tricle/arregation.rb) mixin can be overridden.
111
-
112
115
  #### "Lower is better" metrics
113
116
 
114
117
  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:
@@ -229,6 +232,16 @@ bundle exec rake tricle:preview
229
232
  open http://localhost:8080
230
233
  ```
231
234
 
235
+ ### Global Configuration
236
+
237
+ To set global configuration options, in an initializer:
238
+
239
+ ```ruby
240
+ Tricle.configure do |c|
241
+ c.sparklines = false # default: true
242
+ end
243
+ ```
244
+
232
245
  ## Deploying
233
246
 
234
247
  To send all Tricle emails, run
@@ -1,4 +1,5 @@
1
1
  # only the files needed publicly
2
2
  require_relative 'tricle/version'
3
+ require_relative 'tricle/configuration'
3
4
  require_relative 'tricle/metric'
4
5
  require_relative 'tricle/mailer'
@@ -0,0 +1,29 @@
1
+ require_relative 'metric'
2
+
3
+ module Tricle
4
+ class ActiveRecordMetric < Metric
5
+ def items
6
+ options[:items] || raise(Tricle::AbstractMethodError.new)
7
+ end
8
+
9
+ def time_column
10
+ options[:time_column] || 'created_at'
11
+ end
12
+
13
+ def unit
14
+ options[:unit] || 'record'
15
+ end
16
+
17
+ def size_for_range(start_at, end_at)
18
+ items.where("#{time_column}" => start_at..end_at).count
19
+ end
20
+
21
+ def items_for_range(start_at, end_at)
22
+ items.where("#{time_column}" => start_at..end_at)
23
+ end
24
+
25
+ def total
26
+ items.count
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,17 @@
1
+ module Tricle
2
+ class Configuration
3
+ attr_accessor :sparklines
4
+
5
+ def initialize
6
+ @sparklines = true
7
+ end
8
+ end
9
+
10
+ def self.configure
11
+ yield self.configuration
12
+ end
13
+
14
+ def self.configuration
15
+ @configuration ||= Configuration.new
16
+ end
17
+ end
@@ -3,6 +3,8 @@ require 'active_support/core_ext/numeric/time'
3
3
 
4
4
  module Tricle
5
5
  module EmailHelper
6
+ include ActiveSupport::Inflector
7
+
6
8
  def weeks_ago(n)
7
9
  Date.today.beginning_of_week.ago(n.weeks)
8
10
  end
@@ -11,8 +13,9 @@ module Tricle
11
13
  date.strftime('%-m/%-d/%y')
12
14
  end
13
15
 
14
- def format_number(number)
15
- number_with_delimiter((number.abs >= 100 ? number.round : sig_figs(number)))
16
+ def format_number(number, unit = nil)
17
+ number_with_delimiter(if number.abs >= 100 then number.round else sig_figs(number) end) +
18
+ (if unit then ' ' + unit.pluralize(number.abs) else '' end)
16
19
  end
17
20
 
18
21
  def number_with_delimiter(number)
@@ -50,10 +53,10 @@ module Tricle
50
53
  end
51
54
  end
52
55
 
53
- def percent_change_cell(new_val, old_val, better)
56
+ def percent_change_cell(new_val, old_val, better, unit)
54
57
  cls = self.percent_change_class(new_val, old_val, better)
55
58
  pct_str = percent_change(new_val, old_val)
56
- old_val_str = format_number(old_val)
59
+ old_val_str = format_number(old_val, unit)
57
60
  %[<td class="#{cls}"><div>#{pct_str}</div><div>#{old_val_str}</div></td>].html_safe
58
61
  end
59
62
 
@@ -14,6 +14,18 @@ module Tricle
14
14
  @options = opts
15
15
  end
16
16
 
17
+ def sparkline?
18
+ if options.key?(:sparkline)
19
+ options[:sparkline]
20
+ else
21
+ Tricle.configuration.sparklines
22
+ end
23
+ end
24
+
25
+ def unit
26
+ options[:unit]
27
+ end
28
+
17
29
  def better
18
30
  options[:better] || :higher
19
31
  end
@@ -30,20 +30,22 @@
30
30
  <% section.metrics.each do |metric| %>
31
31
  <tr>
32
32
  <th class="metric-title" rowspan="2"><%= metric.title %></th>
33
- <%= percent_change_cell(metric.last_week, metric.week_average_this_quarter, metric.better) %>
34
- <%= percent_change_cell(metric.last_week, metric.weeks_ago(2), metric.better) %>
33
+ <%= percent_change_cell(metric.last_week, metric.week_average_this_quarter, metric.better, metric.unit) %>
34
+ <%= percent_change_cell(metric.last_week, metric.weeks_ago(2), metric.better, metric.unit) %>
35
35
  <td>
36
- <div><%= format_number(metric.last_week) %></div>
36
+ <div><%= format_number(metric.last_week, metric.unit) %></div>
37
37
  <% if metric.total? %>
38
38
  <div><%= format_number(metric.total) %> (total)</div>
39
39
  <% end %>
40
40
  </td>
41
41
  </tr>
42
- <tr>
43
- <td colspan="3">
44
- <%= sparkline(metric) %>
45
- </td>
46
- </tr>
42
+ <% if metric.sparkline? %>
43
+ <tr>
44
+ <td colspan="3">
45
+ <%= sparkline(metric) %>
46
+ </td>
47
+ </tr>
48
+ <% end %>
47
49
  <tr class="separator"></tr>
48
50
  <% end %>
49
51
  <% end %>
@@ -1,3 +1,3 @@
1
1
  module Tricle
2
- VERSION = "0.2.3"
2
+ VERSION = "0.2.4"
3
3
  end
@@ -0,0 +1,38 @@
1
+ #!/bin/sh
2
+ # Tag and push a release.
3
+
4
+ set -e
5
+
6
+ # Make sure we're in the project root.
7
+
8
+ cd $(dirname "$0")/..
9
+
10
+ # Build a new gem archive.
11
+
12
+ rm -rf tricle-*.gem
13
+ gem build -q tricle.gemspec
14
+
15
+ # Make sure we're on the master branch.
16
+
17
+ (git branch | grep -q '* master') || {
18
+ echo "Only release from the master branch."
19
+ exit 1
20
+ }
21
+
22
+ # Figure out what version we're releasing.
23
+
24
+ tag=v`ls tricle-*.gem | sed 's/^tricle-\(.*\)\.gem$/\1/'`
25
+
26
+ # Make sure we haven't released this version before.
27
+
28
+ git fetch -t origin
29
+
30
+ (git tag -l | grep -q "$tag") && {
31
+ echo "Whoops, there's already a '${tag}' tag."
32
+ exit 1
33
+ }
34
+
35
+ # Tag it and bag it.
36
+
37
+ gem push tricle-*.gem && git tag "$tag" &&
38
+ git push origin master && git push origin "$tag"
@@ -0,0 +1,12 @@
1
+ require_relative '../../lib/tricle/active_record_metric'
2
+
3
+ class TestActiveRecordMetric < Tricle::ActiveRecordMetric
4
+ def initialize(opts = {})
5
+ @klass = opts.delete(:klass)
6
+ super(opts)
7
+ end
8
+
9
+ def items
10
+ @klass.where(foo: 'bar')
11
+ end
12
+ end
@@ -15,6 +15,8 @@ require_relative 'config/timecop'
15
15
  # TODO find a less heavy-handed way of handling this
16
16
  reset_time
17
17
 
18
+ require 'tricle'
19
+
18
20
  # See http://rubydoc.info/gems/rspec-core/RSpec/Core/Configuration
19
21
  RSpec.configure do |config|
20
22
  config.run_all_when_everything_filtered = true
@@ -67,6 +67,13 @@ describe Tricle::EmailHelper do
67
67
  expect(helper.format_number(0.00123456789)).to eq('0.00123')
68
68
  expect(helper.format_number(-0.00123456789)).to eq('-0.00123')
69
69
  end
70
+
71
+ it "can include units" do
72
+ expect(helper.format_number(0.00123456789, 'juice')).to eq('0.00123 juices')
73
+ expect(helper.format_number(1.0, 'juice')).to eq('1.0 juice')
74
+ expect(helper.format_number(-1, 'juice')).to eq('-1.0 juice')
75
+ expect(helper.format_number(-1, 'juice')).to eq('-1.0 juice')
76
+ end
70
77
  end
71
78
 
72
79
  describe "#percent_change" do
@@ -99,16 +106,16 @@ describe Tricle::EmailHelper do
99
106
 
100
107
  describe '#percent_change_cell' do
101
108
  it "should be positive with positive change and better = :higher" do
102
- expect(helper.percent_change_cell(4, 2, :higher)).to match('good')
109
+ expect(helper.percent_change_cell(4, 2, :higher, nil)).to match('good')
103
110
  end
104
111
 
105
112
  it "should be negative with positive change and better = :lower" do
106
- expect(helper.percent_change_cell(4, 2, :lower)).to match('bad')
113
+ expect(helper.percent_change_cell(4, 2, :lower, nil)).to match('bad')
107
114
  end
108
115
 
109
116
  it "should not be positive or negative with positive change and better = :none" do
110
- expect(helper.percent_change_cell(4, 2, :none)).to_not match('good')
111
- expect(helper.percent_change_cell(4, 2, :none)).to_not match('bad')
117
+ expect(helper.percent_change_cell(4, 2, :none, nil)).to_not match('good')
118
+ expect(helper.percent_change_cell(4, 2, :none, nil)).to_not match('bad')
112
119
  end
113
120
  end
114
121
 
@@ -39,6 +39,10 @@ describe Tricle::Mailer do
39
39
  source = message.text_part.body.to_s
40
40
  expect(source).to include('github.com/artsy/tricle')
41
41
  end
42
+
43
+ it "includes sparklines by default" do
44
+ expect(markup).to include('sparklines.herokuapp.com')
45
+ end
42
46
  end
43
47
 
44
48
  it "should exclude the total if not defined" do
@@ -15,4 +15,21 @@ describe Tricle::Metric do
15
15
  expect(metric.size_for_range(Time.now.yesterday, Time.now)).to eq(3)
16
16
  end
17
17
  end
18
+
19
+ describe '#sparkline?' do
20
+ it 'defaults to Tricle.configuration' do
21
+ expect(Tricle).to receive_message_chain(:configuration, :sparklines).
22
+ and_return(false)
23
+
24
+ expect(metric.sparkline?).to eq false
25
+ end
26
+
27
+ context 'with options override' do
28
+ let(:sparkline_metric) { Tricle::Metric.new(sparkline: true) }
29
+
30
+ it 'ignores Tricle.configuration' do
31
+ expect(sparkline_metric.sparkline?).to eq true
32
+ end
33
+ end
34
+ end
18
35
  end
@@ -0,0 +1,53 @@
1
+ require 'spec_helper'
2
+ require_relative '../app/test_active_record_metric'
3
+
4
+ describe TestActiveRecordMetric do
5
+ let(:klass) { class_double('User') }
6
+ let(:metric) { TestActiveRecordMetric.new(klass: klass) }
7
+ let!(:start_time) { Time.now }
8
+ let!(:end_time) { start_time - 1.day }
9
+
10
+ describe '#total' do
11
+ it 'executes a #count query' do
12
+ expect(klass).to receive_message_chain(:where, :count).and_return(1)
13
+ expect(metric.total).to eq 1
14
+ end
15
+ end
16
+
17
+ describe '#size_for_range' do
18
+ it 'executes a #count query with the correct filters' do
19
+ # These stubs are ugly, but we want to test the arguments received
20
+ # to the method called in the middle of the message chain...
21
+ expect(klass).to receive(:where).and_return(klass)
22
+ expect(klass).to receive(:where).
23
+ with('created_at' => start_time..end_time).
24
+ and_return(klass)
25
+ expect(klass).to receive(:count).and_return(1)
26
+ expect(metric.size_for_range(start_time, end_time)).to eq 1
27
+ end
28
+ end
29
+
30
+ describe '#items_for_range' do
31
+ it 'executes a query with the correct filters' do
32
+ expect(klass).to receive(:where).and_return(klass)
33
+ expect(klass).to receive(:where).
34
+ with('created_at' => start_time..end_time).
35
+ and_return(['foobar'])
36
+ expect(metric.items_for_range(start_time, end_time)).to eq ['foobar']
37
+ end
38
+ end
39
+
40
+ describe 'overriding the time_column' do
41
+ before do
42
+ allow(metric).to receive(:time_column).and_return('updated_at')
43
+ end
44
+
45
+ it 'executes a query with the correct filters' do
46
+ expect(klass).to receive(:where).and_return(klass)
47
+ expect(klass).to receive(:where).
48
+ with('updated_at' => start_time..end_time).
49
+ and_return(['fuzz'])
50
+ expect(metric.items_for_range(start_time, end_time)).to eq ['fuzz']
51
+ end
52
+ end
53
+ end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: tricle
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.3
4
+ version: 0.2.4
5
5
  platform: ruby
6
6
  authors:
7
7
  - Aidan Feldman
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2014-09-09 00:00:00.000000000 Z
11
+ date: 2014-10-07 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: actionmailer
@@ -115,7 +115,9 @@ files:
115
115
  - gemfiles/rails_4.gemfile
116
116
  - lib/tricle.rb
117
117
  - lib/tricle/abstract_method_error.rb
118
+ - lib/tricle/active_record_metric.rb
118
119
  - lib/tricle/aggregation.rb
120
+ - lib/tricle/configuration.rb
119
121
  - lib/tricle/email_helper.rb
120
122
  - lib/tricle/mail_preview.rb
121
123
  - lib/tricle/mailer.rb
@@ -132,10 +134,12 @@ files:
132
134
  - lib/tricle/time.rb
133
135
  - lib/tricle/version.rb
134
136
  - screenshot.png
137
+ - script/release
135
138
  - spec/app/group_test_mailer.rb
136
139
  - spec/app/list_test_mailer.rb
137
140
  - spec/app/list_test_mailer_with_options.rb
138
141
  - spec/app/no_total_test_mailer.rb
142
+ - spec/app/test_active_record_metric.rb
139
143
  - spec/app/test_mailer.rb
140
144
  - spec/app/test_metric.rb
141
145
  - spec/app/test_metric_with_long_name.rb
@@ -154,6 +158,7 @@ files:
154
158
  - spec/unit/mailer_spec.rb
155
159
  - spec/unit/metric_spec.rb
156
160
  - spec/unit/range_data_spec.rb
161
+ - spec/unit/test_active_record_metric_spec.rb
157
162
  - spec/unit/test_metric_spec.rb
158
163
  - tricle.gemspec
159
164
  homepage: https://github.com/artsy/tricle
@@ -176,7 +181,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
176
181
  version: '0'
177
182
  requirements: []
178
183
  rubyforge_project:
179
- rubygems_version: 2.4.1
184
+ rubygems_version: 2.2.2
180
185
  signing_key:
181
186
  specification_version: 4
182
187
  summary: A datastore-agnostic mailer where you can define custom metrics, where you
@@ -187,6 +192,7 @@ test_files:
187
192
  - spec/app/list_test_mailer.rb
188
193
  - spec/app/list_test_mailer_with_options.rb
189
194
  - spec/app/no_total_test_mailer.rb
195
+ - spec/app/test_active_record_metric.rb
190
196
  - spec/app/test_mailer.rb
191
197
  - spec/app/test_metric.rb
192
198
  - spec/app/test_metric_with_long_name.rb
@@ -205,4 +211,6 @@ test_files:
205
211
  - spec/unit/mailer_spec.rb
206
212
  - spec/unit/metric_spec.rb
207
213
  - spec/unit/range_data_spec.rb
214
+ - spec/unit/test_active_record_metric_spec.rb
208
215
  - spec/unit/test_metric_spec.rb
216
+ has_rdoc: