reparty 0.3.2 → 0.5.0

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