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 +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 [![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
|
-
|
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:
|