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 +4 -4
- data/README.md +31 -18
- data/lib/tricle.rb +1 -0
- data/lib/tricle/active_record_metric.rb +29 -0
- data/lib/tricle/configuration.rb +17 -0
- data/lib/tricle/email_helper.rb +7 -4
- data/lib/tricle/metric.rb +12 -0
- data/lib/tricle/templates/email.html.erb +10 -8
- data/lib/tricle/version.rb +1 -1
- data/script/release +38 -0
- data/spec/app/test_active_record_metric.rb +12 -0
- data/spec/spec_helper.rb +2 -0
- data/spec/unit/email_helper_spec.rb +11 -4
- data/spec/unit/mailer_spec.rb +4 -0
- data/spec/unit/metric_spec.rb +17 -0
- data/spec/unit/test_active_record_metric_spec.rb +53 -0
- metadata +11 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: a0a773a86cbdcd4e1c093bd098e1a79d450e7e73
|
4
|
+
data.tar.gz: 47963dbca244c39651964fd6516c5847d1e79e57
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 5948b2159e96493cb19eeb3f0de03be23f941749a8ceb13ccb9c84169ce999de99ae5fe06a53db30ac3205cf1a090e6db1b5992a743bafd05c75046de8ef97ba
|
7
|
+
data.tar.gz: 544c67eb18aa0beaad093f4f04dab5ee7a42f2bbef03824e8d8fec9090ec796c4579e1bb8f041d0283fd8264d73486e0c9693518f9c9848cb89d72574ebef85e
|
data/README.md
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
# Tricle [](https://travis-ci.org/afeld/tricle) [](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
|

|
6
6
|
|
@@ -77,38 +77,41 @@ class MyMetric < Tricle::Metric
|
|
77
77
|
end
|
78
78
|
```
|
79
79
|
|
80
|
-
|
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::
|
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
|
-
|
91
|
-
|
90
|
+
# Apply any default scopes you need
|
91
|
+
def items
|
92
|
+
User.where(deleted_at: nil)
|
92
93
|
end
|
93
94
|
|
94
|
-
|
95
|
-
|
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
|
-
|
100
|
+
```ruby
|
101
|
+
# app/metrics/new_users.rb
|
102
|
+
class NewUsers < Tricle::ActiveRecordMetric
|
100
103
|
|
101
|
-
|
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
|
data/lib/tricle.rb
CHANGED
@@ -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
|
data/lib/tricle/email_helper.rb
CHANGED
@@ -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(
|
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
|
|
data/lib/tricle/metric.rb
CHANGED
@@ -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
|
-
|
43
|
-
<
|
44
|
-
|
45
|
-
|
46
|
-
|
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 %>
|
data/lib/tricle/version.rb
CHANGED
data/script/release
ADDED
@@ -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"
|
data/spec/spec_helper.rb
CHANGED
@@ -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
|
|
data/spec/unit/mailer_spec.rb
CHANGED
@@ -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
|
data/spec/unit/metric_spec.rb
CHANGED
@@ -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.
|
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-
|
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.
|
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:
|