reparty 0.3.2 → 0.5.0

Sign up to get free protection for your applications and to get access to all the features.
data/History.md CHANGED
@@ -1,3 +1,9 @@
1
+ ## 0.5.0
2
+
3
+ - Weekly emails (hack-ish for now...)
4
+ - Allow reports to have an interval (1 for daily, 7 for weekly)
5
+ - Add Mixpanel funnel report
6
+
1
7
  ## 0.3.2
2
8
 
3
9
  - Separate out graph types for counter and gauge graphs
data/README.md CHANGED
@@ -3,18 +3,22 @@
3
3
  __A business analytics reporting party!__
4
4
 
5
5
  Although initially concentrating on a daily email report, Reparty intends to be an easy
6
- tool for building dashboards and reports focused on business analytics about your app.
6
+ tool for generating reports focused on business analytics about your app.
7
7
 
8
- Planned reporting modules include:
8
+ A modular design is used, so reporting on a variety of different data sources is possible.
9
+ Custom modules can be created within your app. The currently bundled modules are:
9
10
 
10
11
  * ActiveRecord, including arbitrary sum/count columns
11
- * Google Analytics/MixPanel/Gaug.es/etc etc
12
- * Stripe
13
- * Twitter
12
+ * SendGrid
13
+ * Mixpanel (just funnels for now)
14
+
15
+ Other modules will be added over time. This is in use in production at my company, SalesLoft,
16
+ so focus will be on the modules that best suit our needs. Feel free to submit a pull request
17
+ with any modules you have created.
14
18
 
15
19
  ## Configuration
16
20
 
17
- Whilst not completely done, this is how you'll configure it for emailing a daily user count in the near future.
21
+ You can configure Reparty in an initalizer using a config block:
18
22
 
19
23
  Reparty.config do |config|
20
24
  config.from = "test@test.com"
@@ -23,10 +27,18 @@ Whilst not completely done, this is how you'll configure it for emailing a daily
23
27
  config.add_report Reparty::Report::ActiveRecord, "New User Signups", :user
24
28
  end
25
29
 
30
+ Better documentation of Reparty and each individual module's configuration is coming. For now,
31
+ you'll just have to read the code (sorry!). Check the test suite for examples.
32
+
26
33
  ## Usage
27
34
 
28
35
  Generating an email is as simple as a rake task:
29
36
 
30
37
  rake reparty:email[someone@somewhere.com]
31
38
 
32
- You can run this in a cron job or other scheduling system.
39
+ You can run this in a cron job or other scheduling system. A weekly email is currently hacked
40
+ in to the code, which you can run using it's separate rake task:
41
+
42
+ rake reparty:weekly_email[someone@somewhere.com]
43
+
44
+ This will likely change in the future, so watch out when upgrading!
@@ -2,7 +2,6 @@ ActionMailer::Base.view_paths = File.expand_path('../../views/', __FILE__)
2
2
 
3
3
  class ReportMailer < ActionMailer::Base
4
4
  def daily(to)
5
-
6
5
  @title = Reparty::Email.title
7
6
 
8
7
  @reports = Reparty::Email.reports
@@ -15,4 +14,18 @@ class ReportMailer < ActionMailer::Base
15
14
  subject: Reparty::Email.subject
16
15
  )
17
16
  end
17
+
18
+ def weekly(to)
19
+ @title = Reparty::Email.weekly_title
20
+
21
+ @reports = Reparty::Email.weekly_reports
22
+ @reports.each {|r| r.attach(attachments)}
23
+ attachments.inline['spacer.gif'] = File.read(Reparty.root + 'app/assets/images/spacer.gif')
24
+
25
+ mail(
26
+ from: Reparty::Email.from,
27
+ to: to,
28
+ subject: Reparty::Email.subject
29
+ )
30
+ end
18
31
  end
@@ -0,0 +1,33 @@
1
+ <table width="100%" cellpadding="0" cellspacing="25" border="0">
2
+ <tr>
3
+ <td align="left">
4
+ <h3 style="margin:0"><%= report.title %></h3>
5
+ </td>
6
+ </tr>
7
+ <tr>
8
+ <td align="center">
9
+ <table class="data" cellpadding="5" cellspacing="0" border="0">
10
+ <% (report.funnel_data.first[1]["steps"].size+1).times.each do |i| %>
11
+ <% if i == 0 %>
12
+ <thead><tr>
13
+ <th width="100"></th>
14
+ <% report.funnel_data.each do |data| %>
15
+ <th><%= Date.parse(data[0]).strftime("%b %-d") %></th>
16
+ <% end %>
17
+ </tr></thead>
18
+ <% else %>
19
+ <tr>
20
+ <% report.funnel_data.each_with_index do |data, j| %>
21
+ <% if j == 0 %><td class="first"><%= data[1]["steps"][i-1]["goal"] %></td><% end %>
22
+ <td>
23
+ <%= number_to_percentage data[1]["steps"][i-1]["step_conv_ratio"]*100, precision: 2 %>
24
+ (<%= data[1]["steps"][i-1]["count"] %>)
25
+ </td>
26
+ <% end %>
27
+ </tr>
28
+ <% end %>
29
+ <% end %>
30
+ </table>
31
+ </td>
32
+ </tr>
33
+ </table>
@@ -10,6 +10,11 @@
10
10
  .report h3 { margin: 0; color: #444444; font-size: 16px; line-height: 24px; }
11
11
  .report ul { margin-top: 5px; margin-bottom: 0; list-style-type: square; }
12
12
  .report ul li { color: #888888; font-size: 14px; line-height: 20px; }
13
+
14
+ .data thead tr { background-color: #eef5fc; }
15
+ .data th { font-size: 14px; text-align: center; }
16
+ .data td { font-size: 13px; text-align: center; }
17
+ .data td.first { background-color: #ecedee; color: #666666; font-size: 11px; text-align: left; }
13
18
  </style>
14
19
  </head>
15
20
  <body bgcolor="#eeeeee" topmargin="0" leftmargin="0" marginheight="0" marginwidth="0">
@@ -0,0 +1,62 @@
1
+ <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN" "http://www.w3.org/TR/REC-html40/loose.dtd">
2
+ <html lang="en">
3
+ <head>
4
+ <meta content="text/html; charset=utf-8" http-equiv="Content-Type">
5
+ <style type="text/css">
6
+ body { margin: 0; padding: 0; background: #eeeeee; font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; }
7
+
8
+ .title h1 { margin: 0; color: #999999; font-weight: normal; font-size: 25px; text-shadow: 0 1px 0 #ffffff; }
9
+
10
+ .report h3 { margin: 0; color: #444444; font-size: 16px; line-height: 24px; }
11
+ .report ul { margin-top: 5px; margin-bottom: 0; list-style-type: square; }
12
+ .report ul li { color: #888888; font-size: 14px; line-height: 20px; }
13
+
14
+ .data thead tr { background-color: #eef5fc; }
15
+ .data th { font-size: 14px; text-align: center; }
16
+ .data td { font-size: 13px; text-align: center; }
17
+ .data td.first { background-color: #ecedee; color: #666666; font-size: 11px; text-align: left; }
18
+ </style>
19
+ </head>
20
+ <body bgcolor="#eeeeee" topmargin="0" leftmargin="0" marginheight="0" marginwidth="0">
21
+
22
+ <table width="100%" cellpadding="0" cellspacing="0" border="0" bgcolor="#eeeeee">
23
+ <tr>
24
+ <td bgcolor="#eeeeee" width="100%">
25
+
26
+ <table width="600" cellpadding="0" cellspacing="0" border="0" align="center" class="main">
27
+ <tr><td><img border="0" src="<%= attachments["spacer.gif"].url %>" width="1" height="25"><br></td></tr>
28
+
29
+ <tr>
30
+ <td width="600" class="title">
31
+ <h1><%= @title %></h1>
32
+ </td>
33
+ </tr>
34
+
35
+ <tr><td><img border="0" src="<%= attachments["spacer.gif"].url %>" width="1" height="25"><br></td></tr>
36
+
37
+ <% @reports.each do |report| -%>
38
+ <tr>
39
+ <td bgcolor="#ffffff">
40
+
41
+ <table width="600" cellpadding="0" cellspacing="0" border="0" align="center">
42
+ <tr>
43
+ <td bgcolor="<%= report.color %>" nowrap><img border="0" src="<%= attachments["spacer.gif"].url %>" width="5" height="1"></td>
44
+ <td class="report">
45
+ <%= render partial: report.class.name.demodulize.underscore, locals:{report:report} %>
46
+ </td>
47
+ </tr>
48
+ </table>
49
+
50
+ </td>
51
+ </tr>
52
+
53
+ <tr><td><img border="0" src="<%= attachments["spacer.gif"].url %>" width="1" height="25"><br></td></tr>
54
+ <% end -%>
55
+ </table>
56
+
57
+ </td>
58
+ </tr>
59
+ </table>
60
+
61
+ </body>
62
+ </html>
@@ -2,15 +2,20 @@ require "ostruct"
2
2
 
3
3
  module Reparty
4
4
  class Config < ::OpenStruct
5
- attr_reader :reports
5
+ attr_reader :reports, :weekly_reports
6
6
 
7
7
  def initialize
8
8
  @reports = []
9
+ @weekly_reports = []
9
10
  super
10
11
  end
11
12
 
12
13
  def add_report(report, *args, &block)
13
- @reports << report.new(*args, &block)
14
+ @reports << report.new(1, *args, &block)
15
+ end
16
+
17
+ def add_weekly_report(report, *args, &block)
18
+ @weekly_reports << report.new(7, *args, &block)
14
19
  end
15
20
  end
16
21
  end
data/lib/reparty/email.rb CHANGED
@@ -5,6 +5,10 @@ module Reparty
5
5
  Reparty.reports
6
6
  end
7
7
 
8
+ def weekly_reports
9
+ Reparty.weekly_reports
10
+ end
11
+
8
12
  def from
9
13
  Reparty.configuration.from || 'nobody@nowhere.com'
10
14
  end
@@ -16,6 +20,10 @@ module Reparty
16
20
  def title
17
21
  Reparty.configuration.title || 'Your Daily Report:'
18
22
  end
23
+
24
+ def weekly_title
25
+ Reparty.configuration.weekly_title || 'Your Weekly Report:'
26
+ end
19
27
  end
20
28
  end
21
29
  end
@@ -7,7 +7,7 @@ module Reparty
7
7
  attr_reader :model, :operation, :field
8
8
 
9
9
  def initialize(*args, &block)
10
- super(args.shift)
10
+ super(args.shift, args.shift)
11
11
 
12
12
  if args.first.is_a?(Symbol)
13
13
  @model = Kernel.const_get(args.first.to_s.capitalize)
@@ -0,0 +1,38 @@
1
+ require 'mixpanel_client'
2
+
3
+ module Reparty
4
+ class Report
5
+ class MixpanelFunnel < Report
6
+ attr_reader :funnel_id, :api_key, :api_secret
7
+
8
+ def initialize(*args, &block)
9
+ super(args.shift, args.shift)
10
+
11
+ @funnel_id = args.shift
12
+ @api_key = args.shift
13
+ @api_secret = args.shift
14
+
15
+ @color = "#7548a2"
16
+ end
17
+
18
+ def client
19
+ @client ||= Mixpanel::Client.new({api_key: api_key, api_secret: api_secret})
20
+ end
21
+
22
+ def funnel_data
23
+ from_date = (DateTime.now-(interval*7)).strftime("%Y-%m-%d")
24
+ to_date = (DateTime.now-1).strftime("%Y-%m-%d")
25
+
26
+ @funnel_data ||= Hash[client.request(
27
+ "funnels",
28
+ {
29
+ funnel_id: funnel_id,
30
+ interval: interval,
31
+ from_date: from_date,
32
+ to_date: to_date
33
+ }
34
+ )["data"].sort.reverse]
35
+ end
36
+ end
37
+ end
38
+ end
@@ -10,7 +10,7 @@ module Reparty
10
10
  attr_reader :api_user, :api_key
11
11
 
12
12
  def initialize(*args, &block)
13
- super(args.shift)
13
+ super(args.shift, args.shift)
14
14
 
15
15
  @api_user = args.shift
16
16
  @api_key = args.shift
@@ -2,11 +2,14 @@ require 'gruff'
2
2
 
3
3
  module Reparty
4
4
  class Report
5
- attr_reader :title, :color
5
+ attr_reader :interval, :title, :color
6
+
7
+ def initialize(interval, title)
8
+ @interval = interval
6
9
 
7
- def initialize(title)
8
10
  raise "Report: title must be defined" if title.blank?
9
11
  @title = title
12
+
10
13
  @color = "#832701"
11
14
  end
12
15
 
@@ -1,3 +1,3 @@
1
1
  module Reparty
2
- VERSION = "0.3.2"
2
+ VERSION = "0.5.0"
3
3
  end
data/lib/reparty.rb CHANGED
@@ -6,6 +6,7 @@ require "reparty/email"
6
6
  require "reparty/report"
7
7
  require "reparty/report/active_record"
8
8
  require "reparty/report/sendgrid"
9
+ require "reparty/report/mixpanel_funnel"
9
10
 
10
11
  require "reparty/engine"
11
12
 
@@ -16,7 +17,7 @@ module Reparty
16
17
  end
17
18
 
18
19
  attr_accessor :configuration
19
- attr_reader :reports
20
+ attr_reader :reports, :weekly_reports
20
21
 
21
22
  def config
22
23
  self.configuration = Config.new
@@ -24,6 +25,7 @@ module Reparty
24
25
  yield(configuration)
25
26
 
26
27
  @reports = self.configuration.reports
28
+ @weekly_reports = self.configuration.weekly_reports
27
29
  end
28
30
  end
29
31
  end
@@ -8,4 +8,14 @@ namespace :reparty do
8
8
 
9
9
  ReportMailer.daily(args.address).deliver
10
10
  end
11
+
12
+ desc 'Generate a weekly email to be sent'
13
+ task :weekly_email, [:address] => :environment do |t,args|
14
+ unless args.address
15
+ puts "Error: An address is required."
16
+ next
17
+ end
18
+
19
+ ReportMailer.weekly(args.address).deliver
20
+ end
11
21
  end
data/reparty.gemspec CHANGED
@@ -25,6 +25,7 @@ Gem::Specification.new do |spec|
25
25
  spec.add_dependency "gruff"
26
26
  spec.add_dependency "roadie"
27
27
  spec.add_dependency "httparty"
28
+ spec.add_dependency "mixpanel_client"
28
29
 
29
30
  spec.add_development_dependency "bundler", "~> 1.3"
30
31
  spec.add_development_dependency "rake"
@@ -0,0 +1,145 @@
1
+ ---
2
+ http_interactions:
3
+ - request:
4
+ method: get
5
+ uri: https://mixpanel.com/api/2.0/funnels?api_key=<MIXPANEL_KEY>&expire=1371655510&format=json&from_date=2013-06-12&funnel_id=<MIXPANEL_FUNNEL>&interval=1&sig=822d5273993b8b06e71358246bfa2d6f&to_date=2013-06-18
6
+ body:
7
+ encoding: US-ASCII
8
+ string: ''
9
+ headers:
10
+ Accept:
11
+ - ! '*/*'
12
+ User-Agent:
13
+ - Ruby
14
+ response:
15
+ status:
16
+ code: 200
17
+ message: OK
18
+ headers:
19
+ Server:
20
+ - nginx/1.1.19
21
+ Date:
22
+ - Wed, 19 Jun 2013 15:15:10 GMT
23
+ Content-Type:
24
+ - application/json
25
+ Content-Length:
26
+ - '3161'
27
+ Connection:
28
+ - keep-alive
29
+ Vary:
30
+ - Accept-Encoding
31
+ Cache-Control:
32
+ - no-cache, no-store
33
+ body:
34
+ encoding: US-ASCII
35
+ string: ! '{"meta": {"dates": ["2013-06-12", "2013-06-13", "2013-06-14", "2013-06-15",
36
+ "2013-06-16", "2013-06-17", "2013-06-18"]}, "data": {"2013-06-17": {"steps":
37
+ [{"count": 848, "step_conv_ratio": 1, "goal": "Opened LinkedIn Email", "overall_conv_ratio":
38
+ 1, "avg_time": null, "event": "Opened LinkedIn Email"}, {"count": 29, "step_conv_ratio":
39
+ 0.03419811320754717, "goal": "Viewed JCA Stream", "overall_conv_ratio": 0.03419811320754717,
40
+ "avg_time": 25755, "event": "Viewed JCA Stream"}], "analysis": {"completion":
41
+ 29, "starting_amount": 848, "steps": 2, "worst": 1}}, "2013-06-16": {"steps":
42
+ [{"count": 571, "step_conv_ratio": 1, "goal": "Opened LinkedIn Email", "overall_conv_ratio":
43
+ 1, "avg_time": null, "event": "Opened LinkedIn Email"}, {"count": 14, "step_conv_ratio":
44
+ 0.024518388791593695, "goal": "Viewed JCA Stream", "overall_conv_ratio": 0.024518388791593695,
45
+ "avg_time": 109055, "event": "Viewed JCA Stream"}], "analysis": {"completion":
46
+ 14, "starting_amount": 571, "steps": 2, "worst": 1}}, "2013-06-15": {"steps":
47
+ [{"count": 776, "step_conv_ratio": 1, "goal": "Opened LinkedIn Email", "overall_conv_ratio":
48
+ 1, "avg_time": null, "event": "Opened LinkedIn Email"}, {"count": 20, "step_conv_ratio":
49
+ 0.02577319587628866, "goal": "Viewed JCA Stream", "overall_conv_ratio": 0.02577319587628866,
50
+ "avg_time": 195452, "event": "Viewed JCA Stream"}], "analysis": {"completion":
51
+ 20, "starting_amount": 776, "steps": 2, "worst": 1}}, "2013-06-14": {"steps":
52
+ [{"count": 901, "step_conv_ratio": 1, "goal": "Opened LinkedIn Email", "overall_conv_ratio":
53
+ 1, "avg_time": null, "event": "Opened LinkedIn Email"}, {"count": 35, "step_conv_ratio":
54
+ 0.038845726970033294, "goal": "Viewed JCA Stream", "overall_conv_ratio": 0.038845726970033294,
55
+ "avg_time": 148191, "event": "Viewed JCA Stream"}], "analysis": {"completion":
56
+ 35, "starting_amount": 901, "steps": 2, "worst": 1}}, "2013-06-13": {"steps":
57
+ [{"count": 959, "step_conv_ratio": 1, "goal": "Opened LinkedIn Email", "overall_conv_ratio":
58
+ 1, "avg_time": null, "event": "Opened LinkedIn Email"}, {"count": 44, "step_conv_ratio":
59
+ 0.045881126173096975, "goal": "Viewed JCA Stream", "overall_conv_ratio": 0.045881126173096975,
60
+ "avg_time": 197465, "event": "Viewed JCA Stream"}], "analysis": {"completion":
61
+ 44, "starting_amount": 959, "steps": 2, "worst": 1}}, "2013-06-12": {"steps":
62
+ [{"count": 980, "step_conv_ratio": 1, "goal": "Opened LinkedIn Email", "overall_conv_ratio":
63
+ 1, "avg_time": null, "event": "Opened LinkedIn Email"}, {"count": 41, "step_conv_ratio":
64
+ 0.04183673469387755, "goal": "Viewed JCA Stream", "overall_conv_ratio": 0.04183673469387755,
65
+ "avg_time": 206732, "event": "Viewed JCA Stream"}], "analysis": {"completion":
66
+ 41, "starting_amount": 980, "steps": 2, "worst": 1}}, "2013-06-18": {"steps":
67
+ [{"count": 1071, "step_conv_ratio": 1, "goal": "Opened LinkedIn Email", "overall_conv_ratio":
68
+ 1, "avg_time": null, "event": "Opened LinkedIn Email"}, {"count": 23, "step_conv_ratio":
69
+ 0.021475256769374416, "goal": "Viewed JCA Stream", "overall_conv_ratio": 0.021475256769374416,
70
+ "avg_time": 15241, "event": "Viewed JCA Stream"}], "analysis": {"completion":
71
+ 23, "starting_amount": 1071, "steps": 2, "worst": 1}}}}'
72
+ http_version:
73
+ recorded_at: Wed, 19 Jun 2013 15:15:10 GMT
74
+ - request:
75
+ method: get
76
+ uri: https://mixpanel.com/api/2.0/funnels?api_key=<MIXPANEL_KEY>&expire=1371655958&format=json&from_date=2013-05-01&funnel_id=<MIXPANEL_FUNNEL>&interval=7&sig=1d9e9bb9e518724bd091831b21c80f61&to_date=2013-06-18
77
+ body:
78
+ encoding: US-ASCII
79
+ string: ''
80
+ headers:
81
+ Accept:
82
+ - ! '*/*'
83
+ User-Agent:
84
+ - Ruby
85
+ response:
86
+ status:
87
+ code: 200
88
+ message: OK
89
+ headers:
90
+ Server:
91
+ - nginx/1.1.19
92
+ Date:
93
+ - Wed, 19 Jun 2013 15:22:39 GMT
94
+ Content-Type:
95
+ - application/json
96
+ Content-Length:
97
+ - '2939'
98
+ Connection:
99
+ - keep-alive
100
+ Vary:
101
+ - Accept-Encoding
102
+ Cache-Control:
103
+ - no-cache, no-store
104
+ body:
105
+ encoding: US-ASCII
106
+ string: ! '{"meta": {"dates": ["2013-05-01", "2013-05-08", "2013-05-15", "2013-05-22",
107
+ "2013-05-29", "2013-06-05", "2013-06-12"]}, "data": {"2013-05-15": {"steps":
108
+ [{"count": 0, "step_conv_ratio": 1, "goal": "Opened LinkedIn Email", "overall_conv_ratio":
109
+ 1, "avg_time": null, "event": "Opened LinkedIn Email"}, {"count": 0, "step_conv_ratio":
110
+ 0, "goal": "Viewed JCA Stream", "overall_conv_ratio": 0, "avg_time": null,
111
+ "event": "Viewed JCA Stream"}], "analysis": {"completion": 0, "starting_amount":
112
+ 0, "steps": 2, "worst": 1}}, "2013-05-01": {"steps": [{"count": 0, "step_conv_ratio":
113
+ 1, "goal": "Opened LinkedIn Email", "overall_conv_ratio": 1, "avg_time": null,
114
+ "event": "Opened LinkedIn Email"}, {"count": 0, "step_conv_ratio": 0, "goal":
115
+ "Viewed JCA Stream", "overall_conv_ratio": 0, "avg_time": null, "event": "Viewed
116
+ JCA Stream"}], "analysis": {"completion": 0, "starting_amount": 0, "steps":
117
+ 2, "worst": 1}}, "2013-06-12": {"steps": [{"count": 1842, "step_conv_ratio":
118
+ 1, "goal": "Opened LinkedIn Email", "overall_conv_ratio": 1, "avg_time": null,
119
+ "event": "Opened LinkedIn Email"}, {"count": 77, "step_conv_ratio": 0.041802388707926165,
120
+ "goal": "Viewed JCA Stream", "overall_conv_ratio": 0.041802388707926165, "avg_time":
121
+ 131583, "event": "Viewed JCA Stream"}], "analysis": {"completion": 77, "starting_amount":
122
+ 1842, "steps": 2, "worst": 1}}, "2013-05-08": {"steps": [{"count": 0, "step_conv_ratio":
123
+ 1, "goal": "Opened LinkedIn Email", "overall_conv_ratio": 1, "avg_time": null,
124
+ "event": "Opened LinkedIn Email"}, {"count": 0, "step_conv_ratio": 0, "goal":
125
+ "Viewed JCA Stream", "overall_conv_ratio": 0, "avg_time": null, "event": "Viewed
126
+ JCA Stream"}], "analysis": {"completion": 0, "starting_amount": 0, "steps":
127
+ 2, "worst": 1}}, "2013-05-29": {"steps": [{"count": 0, "step_conv_ratio":
128
+ 1, "goal": "Opened LinkedIn Email", "overall_conv_ratio": 1, "avg_time": null,
129
+ "event": "Opened LinkedIn Email"}, {"count": 0, "step_conv_ratio": 0, "goal":
130
+ "Viewed JCA Stream", "overall_conv_ratio": 0, "avg_time": null, "event": "Viewed
131
+ JCA Stream"}], "analysis": {"completion": 0, "starting_amount": 0, "steps":
132
+ 2, "worst": 1}}, "2013-05-22": {"steps": [{"count": 0, "step_conv_ratio":
133
+ 1, "goal": "Opened LinkedIn Email", "overall_conv_ratio": 1, "avg_time": null,
134
+ "event": "Opened LinkedIn Email"}, {"count": 0, "step_conv_ratio": 0, "goal":
135
+ "Viewed JCA Stream", "overall_conv_ratio": 0, "avg_time": null, "event": "Viewed
136
+ JCA Stream"}], "analysis": {"completion": 0, "starting_amount": 0, "steps":
137
+ 2, "worst": 1}}, "2013-06-05": {"steps": [{"count": 1588, "step_conv_ratio":
138
+ 1, "goal": "Opened LinkedIn Email", "overall_conv_ratio": 1, "avg_time": null,
139
+ "event": "Opened LinkedIn Email"}, {"count": 82, "step_conv_ratio": 0.05163727959697733,
140
+ "goal": "Viewed JCA Stream", "overall_conv_ratio": 0.05163727959697733, "avg_time":
141
+ 415671, "event": "Viewed JCA Stream"}], "analysis": {"completion": 82, "starting_amount":
142
+ 1588, "steps": 2, "worst": 1}}}}'
143
+ http_version:
144
+ recorded_at: Wed, 19 Jun 2013 15:22:39 GMT
145
+ recorded_with: VCR 2.4.0
data/spec/reparty_spec.rb CHANGED
@@ -12,11 +12,21 @@ describe Reparty do
12
12
  Reparty.configuration.sender.should == "test@test.com"
13
13
  end
14
14
 
15
- it "adds reports" do
16
- Reparty.config do |config|
17
- config.add_report Reparty::Report, "Some Report"
15
+ describe "adds reports" do
16
+ subject do
17
+ Reparty.config do |config|
18
+ config.add_report Reparty::Report, "Some Report"
19
+ config.add_weekly_report Reparty::Report, "Some Weekly Report"
20
+ end
21
+ Reparty
18
22
  end
19
- Reparty.reports.first.should be_kind_of(Reparty::Report)
20
- Reparty.reports.first.title.should == "Some Report"
23
+
24
+ its("reports.first") { should be_kind_of(Reparty::Report) }
25
+ its("reports.first.title") { should == "Some Report" }
26
+ its("reports.size") { should == 1 }
27
+
28
+ its("weekly_reports.first") { should be_kind_of(Reparty::Report) }
29
+ its("weekly_reports.first.title") { should == "Some Weekly Report" }
30
+ its("weekly_reports.size") { should == 1 }
21
31
  end
22
32
  end
@@ -0,0 +1,38 @@
1
+ require_relative '../spec_helper'
2
+
3
+ interval_matcher = lambda do |request_made, request_stored|
4
+ CGI.parse(URI(request_made.uri).query).fetch("interval",nil) == CGI.parse(URI(request_stored.uri).query).fetch("interval",nil)
5
+ end
6
+
7
+ describe Reparty::Report::MixpanelFunnel, vcr: { cassette_name: "mixpanel", match_requests_on: [:method, :host, :path, interval_matcher] } do
8
+ let(:funnel_id) { ENV.fetch("MIXPANEL_FUNNEL", 123456) }
9
+ let(:mixpanel_key) { ENV.fetch("MIXPANEL_KEY", "abc123") }
10
+ let(:mixpanel_secret) { ENV.fetch("MIXPANEL_SECRET", "cba321") }
11
+
12
+ subject do
13
+ Reparty.config do |config|
14
+ config.add_report Reparty::Report::MixpanelFunnel, "Mixpanel Funnel", funnel_id, mixpanel_key, mixpanel_secret
15
+ end
16
+ Reparty.reports.last
17
+ end
18
+
19
+ it { should be_kind_of(Reparty::Report::MixpanelFunnel) }
20
+ its(:funnel_id) { should == funnel_id }
21
+ its(:api_key) { should == mixpanel_key }
22
+ its(:api_secret) { should == mixpanel_secret }
23
+ its(:color) { should == "#7548a2" }
24
+ its(:client) { should be_kind_of(Mixpanel::Client)}
25
+
26
+ its("funnel_data.size") { should == 7 }
27
+
28
+ describe "weekly interval" do
29
+ subject do
30
+ Reparty.config do |config|
31
+ config.add_weekly_report Reparty::Report::MixpanelFunnel, "Mixpanel Funnel", funnel_id, mixpanel_key, mixpanel_secret
32
+ end
33
+ Reparty.weekly_reports.last
34
+ end
35
+
36
+ its("funnel_data.size") { should == 7 }
37
+ end
38
+ end
data/spec/report_spec.rb CHANGED
@@ -1,27 +1,24 @@
1
1
  require 'spec_helper'
2
2
 
3
3
  describe Reparty::Report do
4
- subject { Reparty::Report.new("title") }
4
+ let(:interval) { 1 }
5
+ let(:title) { "title" }
5
6
 
6
- its(:color) { should == "#832701"}
7
+ subject { Reparty::Report.new(interval, title) }
7
8
 
8
- it "requires a title" do
9
- expect {
10
- Reparty::Report.new("title!")
11
- }.to_not raise_exception
12
-
13
- expect {
14
- Reparty::Report.new(nil)
15
- }.to raise_exception
9
+ its(:color) { should == "#832701"}
10
+ its(:interval) { should == interval}
11
+ its(:title) { should == title}
16
12
 
13
+ it "requires a title" do
17
14
  expect {
18
- Reparty::Report.new("")
15
+ Reparty::Report.new(interval, "")
19
16
  }.to raise_exception
20
17
  end
21
18
 
22
19
  it "has an attach method" do
23
20
  expect {
24
- Reparty::Report.new("title!").attach(nil)
21
+ subject.attach(nil)
25
22
  }.to_not raise_exception
26
23
  end
27
24
  end
@@ -42,10 +42,48 @@ describe "reparty:email" do
42
42
  ].each{|u| User.create!(u) }
43
43
 
44
44
  Reparty.config do |config|
45
+ config.add_report Reparty::Report::MixpanelFunnel, "Mixpanel Funnel", ENV["MIXPANEL_FUNNEL"], ENV["MIXPANEL_KEY"], ENV["MIXPANEL_SECRET"]
45
46
  config.add_report Reparty::Report::ActiveRecord, "New User Signups", :user
46
47
  config.add_report Reparty::Report::Sendgrid, "Sendgrid Emails", ENV["SENDGRID_USER"], ENV["SENDGRID_PASSWORD"]
47
48
  end
48
49
 
50
+ subject.invoke("test@test.com")
51
+ end
52
+ end
53
+
54
+ describe "reparty:weekly_email" do
55
+ include_context "rake"
56
+
57
+ before(:each) do
58
+ User.delete_all
59
+
60
+ Reparty.config do |config|
61
+ config.add_weekly_report Reparty::Report::ActiveRecord, "New User Signups", :user
62
+ end
63
+ end
64
+
65
+ its(:prerequisites) { should include("environment") }
66
+
67
+ it "requires an address" do
68
+ STDOUT.should_receive(:puts)
69
+ subject.invoke
70
+ end
71
+
72
+ it "should generate an email" do
73
+ expect{
74
+ subject.invoke("test@test.com")
75
+ }.to change(ActionMailer::Base, :deliveries)
76
+ end
77
+
78
+ it "should send out via letter_opener", letteropener: true do
79
+ require 'letter_opener'
80
+ ActionMailer::Base.add_delivery_method :letter_opener, LetterOpener::DeliveryMethod, :location => File.expand_path('../../../tmp/letter_opener', __FILE__)
81
+ ActionMailer::Base.delivery_method = :letter_opener
82
+
83
+ Reparty.config do |config|
84
+ config.add_weekly_report Reparty::Report::MixpanelFunnel, "Mixpanel Funnel", ENV["MIXPANEL_FUNNEL"], ENV["MIXPANEL_KEY"], ENV["MIXPANEL_SECRET"]
85
+ end
86
+
49
87
  subject.invoke("test@test.com")
50
88
  end
51
89
  end
data/spec/vcr_helper.rb CHANGED
@@ -10,4 +10,8 @@ VCR.configure do |c|
10
10
 
11
11
  c.filter_sensitive_data("<SENDGRID_USER>") { ENV.fetch("SENDGRID_USER","admin@someplace.com") }
12
12
  c.filter_sensitive_data("<SENDGRID_PASSWORD>") { ENV.fetch("SENDGRID_PASSWORD","asdfasdf") }
13
+
14
+ c.filter_sensitive_data("<MIXPANEL_FUNNEL>") { ENV.fetch("MIXPANEL_FUNNEL",123456) }
15
+ c.filter_sensitive_data("<MIXPANEL_KEY>") { ENV.fetch("MIXPANEL_KEY","abc123") }
16
+ c.filter_sensitive_data("<MIXPANEL_SECRET>") { ENV.fetch("MIXPANEL_SECRET","cba321") }
13
17
  end
metadata CHANGED
@@ -2,14 +2,14 @@
2
2
  name: reparty
3
3
  version: !ruby/object:Gem::Version
4
4
  prerelease:
5
- version: 0.3.2
5
+ version: 0.5.0
6
6
  platform: ruby
7
7
  authors:
8
8
  - Tim Dorr
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2013-05-24 00:00:00.000000000 Z
12
+ date: 2013-06-19 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: railties
@@ -123,6 +123,22 @@ dependencies:
123
123
  version: '0'
124
124
  none: false
125
125
  prerelease: false
126
+ - !ruby/object:Gem::Dependency
127
+ name: mixpanel_client
128
+ type: :runtime
129
+ requirement: !ruby/object:Gem::Requirement
130
+ requirements:
131
+ - - ! '>='
132
+ - !ruby/object:Gem::Version
133
+ version: '0'
134
+ none: false
135
+ version_requirements: !ruby/object:Gem::Requirement
136
+ requirements:
137
+ - - ! '>='
138
+ - !ruby/object:Gem::Version
139
+ version: '0'
140
+ none: false
141
+ prerelease: false
126
142
  - !ruby/object:Gem::Dependency
127
143
  name: bundler
128
144
  type: :development
@@ -254,22 +270,27 @@ files:
254
270
  - app/assets/images/spacer.gif
255
271
  - app/mailers/report_mailer.rb
256
272
  - app/views/report_mailer/_active_record.html.erb
273
+ - app/views/report_mailer/_mixpanel_funnel.html.erb
257
274
  - app/views/report_mailer/_sendgrid.html.erb
258
275
  - app/views/report_mailer/daily.html.erb
276
+ - app/views/report_mailer/weekly.html.erb
259
277
  - lib/reparty.rb
260
278
  - lib/reparty/config.rb
261
279
  - lib/reparty/email.rb
262
280
  - lib/reparty/engine.rb
263
281
  - lib/reparty/report.rb
264
282
  - lib/reparty/report/active_record.rb
283
+ - lib/reparty/report/mixpanel_funnel.rb
265
284
  - lib/reparty/report/sendgrid.rb
266
285
  - lib/reparty/version.rb
267
286
  - lib/tasks/reparty.rake
268
287
  - reparty.gemspec
288
+ - spec/cassettes/mixpanel.yml
269
289
  - spec/cassettes/sendgrid.yml
270
290
  - spec/email_spec.rb
271
291
  - spec/reparty_spec.rb
272
292
  - spec/report/active_record_spec.rb
293
+ - spec/report/mixpanel_funnel_spec.rb
273
294
  - spec/report/sendgrid_spec.rb
274
295
  - spec/report_spec.rb
275
296
  - spec/spec_helper.rb
@@ -289,7 +310,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
289
310
  - !ruby/object:Gem::Version
290
311
  segments:
291
312
  - 0
292
- hash: -1981622501360385862
313
+ hash: 1358861647946779491
293
314
  version: '0'
294
315
  none: false
295
316
  required_rubygems_version: !ruby/object:Gem::Requirement
@@ -298,7 +319,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
298
319
  - !ruby/object:Gem::Version
299
320
  segments:
300
321
  - 0
301
- hash: -1981622501360385862
322
+ hash: 1358861647946779491
302
323
  version: '0'
303
324
  none: false
304
325
  requirements: []
@@ -308,10 +329,12 @@ signing_key:
308
329
  specification_version: 3
309
330
  summary: Stupid easy business analytics
310
331
  test_files:
332
+ - spec/cassettes/mixpanel.yml
311
333
  - spec/cassettes/sendgrid.yml
312
334
  - spec/email_spec.rb
313
335
  - spec/reparty_spec.rb
314
336
  - spec/report/active_record_spec.rb
337
+ - spec/report/mixpanel_funnel_spec.rb
315
338
  - spec/report/sendgrid_spec.rb
316
339
  - spec/report_spec.rb
317
340
  - spec/spec_helper.rb