tricle 0.2.3 → 0.2.4

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