mixpanel 0.6.1 → 0.7.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/README.rdoc CHANGED
@@ -3,7 +3,7 @@
3
3
  Mixpanel is a real-time analytics service that helps companies understand how users interact with web applications.
4
4
  http://mixpanel.com
5
5
 
6
- == What does this Gem?
6
+ == What does this Gem do?
7
7
 
8
8
  * Track events with properties directly from your backend.
9
9
  * Track events with properties through javascript using a rack middleware.
@@ -22,9 +22,15 @@ In your environment config file add this.
22
22
 
23
23
  config.middleware.use "MixpanelMiddleware", "YOUR_MIXPANEL_API_TOKEN"
24
24
 
25
+ If you want to use the asynchronous version of Mixpanel's javascript API
26
+
27
+ Rails::Initializer.run do |config|
28
+
29
+ config.middleware.use "MixpanelMiddleware", "YOUR_MIXPANEL_API_TOKEN", :async => true
30
+
25
31
  In your application_controller class add a method to instance mixpanel.
26
32
 
27
- before_filer :initialize_mixpanel
33
+ before_filter :initialize_mixpanel
28
34
 
29
35
  def initialize_mixpanel
30
36
  @mixpanel = Mixpanel.new("YOUR_MIXPANEL_API_TOKEN", request.env)
@@ -40,9 +46,20 @@ To track events after response with javascript...
40
46
 
41
47
  @mixpanel.append_event("Sign in", {:some => "property"})
42
48
 
49
+ To execute any javascript API call
50
+
51
+ @mixpanel.append_api("register", {:some => "property"})
52
+ @mixpanel.append_api("identify", "Unique Identifier")
53
+
43
54
 
44
55
  == Notes
45
56
 
46
57
  It is strongly recommended to call Mixpanel#track_event using an async lib
47
58
  like delayed job or similar, otherwise you will delay your server responses
48
59
  with mixpanel responses.
60
+
61
+ == Collaborators and Maintainers
62
+
63
+ * {Alvaro Gil}[https://github.com/zevarito] (Author)
64
+ * {Nathan Baxter}[https://github.com/LogicWolfe]
65
+ * {Jake Mallory}[https://github.com/tinomen]
@@ -10,9 +10,13 @@ class Mixpanel
10
10
  end
11
11
 
12
12
  def append_event(event, properties = {})
13
- queue << build_event(event, properties)
13
+ append_api('track', event, properties)
14
14
  end
15
-
15
+
16
+ def append_api(type, *args)
17
+ queue << [type, args.map {|arg| arg.to_json}]
18
+ end
19
+
16
20
  def track_event(event, properties = {})
17
21
  params = build_event(event, properties.merge(:token => @token, :time => Time.now.utc.to_i, :ip => ip))
18
22
  parse_response request(params)
@@ -1,20 +1,23 @@
1
1
  require 'rack'
2
2
 
3
3
  class MixpanelMiddleware
4
- def initialize(app, mixpanel_token)
4
+ def initialize(app, mixpanel_token, options={})
5
5
  @app = app
6
6
  @token = mixpanel_token
7
+ @options = {
8
+ :async => false
9
+ }.merge(options)
7
10
  end
8
11
 
9
12
  def call(env)
10
13
  @env = env
11
14
 
12
15
  @status, @headers, @response = @app.call(env)
13
-
16
+
14
17
  update_response!
15
18
  update_content_length!
16
19
  delete_event_queue!
17
-
20
+
18
21
  [@status, @headers, @response]
19
22
  end
20
23
 
@@ -23,12 +26,15 @@ class MixpanelMiddleware
23
26
  def update_response!
24
27
  @response.each do |part|
25
28
  if is_regular_request? && is_html_response?
26
- part.gsub!("</head>", "#{render_mixpanel_scripts}</head>")
27
- part.gsub!("</head>", "#{render_event_tracking_scripts}</head>")
29
+ insert_at = part.index('</head')
30
+ unless insert_at.nil?
31
+ part.insert(insert_at, render_event_tracking_scripts) unless queue.empty?
32
+ part.insert(insert_at, render_mixpanel_scripts) #This will insert the mixpanel initialization code before the queue of tracking events.
33
+ end
28
34
  elsif is_ajax_request? && is_html_response?
29
- part.gsub!(part, render_event_tracking_scripts + part)
35
+ part.insert(0, render_event_tracking_scripts) unless queue.empty?
30
36
  elsif is_ajax_request? && is_javascript_response?
31
- part.gsub!(part, render_event_tracking_scripts(false) + part)
37
+ part.insert(0, render_event_tracking_scripts(false)) unless queue.empty?
32
38
  end
33
39
  end
34
40
  end
@@ -56,22 +62,36 @@ class MixpanelMiddleware
56
62
  end
57
63
 
58
64
  def render_mixpanel_scripts
59
- <<-EOT
60
- <script type='text/javascript'>
61
- var mp_protocol = (('https:' == document.location.protocol) ? 'https://' : 'http://');
62
- document.write(unescape('%3Cscript src="' + mp_protocol + 'api.mixpanel.com/site_media/js/api/mixpanel.js" type="text/javascript"%3E%3C/script%3E'));
63
- </script>
64
- <script type='text/javascript'>
65
- try {
66
- var mpmetrics = new MixpanelLib('#{@token}');
67
- } catch(err) {
68
- null_fn = function () {};
69
- var mpmetrics = {
70
- track: null_fn, track_funnel: null_fn, register: null_fn, register_once: null_fn, register_funnel: null_fn
71
- };
72
- }
73
- </script>
74
- EOT
65
+ if @options[:async]
66
+ <<-EOT
67
+ <script type='text/javascript'>
68
+ var mpq = [];
69
+ mpq.push(["init", "#{@token}"]);
70
+ (function() {
71
+ var mp = document.createElement("script"); mp.type = "text/javascript"; mp.async = true;
72
+ mp.src = (document.location.protocol == 'https:' ? 'https:' : 'http:') + "//api.mixpanel.com/site_media/js/api/mixpanel.js";
73
+ var s = document.getElementsByTagName("script")[0]; s.parentNode.insertBefore(mp, s);
74
+ })();
75
+ </script>
76
+ EOT
77
+ else
78
+ <<-EOT
79
+ <script type='text/javascript'>
80
+ var mp_protocol = (('https:' == document.location.protocol) ? 'https://' : 'http://');
81
+ document.write(unescape('%3Cscript src="' + mp_protocol + 'api.mixpanel.com/site_media/js/api/mixpanel.js" type="text/javascript"%3E%3C/script%3E'));
82
+ </script>
83
+ <script type='text/javascript'>
84
+ try {
85
+ var mpmetrics = new MixpanelLib('#{@token}');
86
+ } catch(err) {
87
+ null_fn = function () {};
88
+ var mpmetrics = {
89
+ track: null_fn, track_funnel: null_fn, register: null_fn, register_once: null_fn, register_funnel: null_fn
90
+ };
91
+ }
92
+ </script>
93
+ EOT
94
+ end
75
95
  end
76
96
 
77
97
  def delete_event_queue!
@@ -86,7 +106,11 @@ class MixpanelMiddleware
86
106
  def render_event_tracking_scripts(include_script_tag=true)
87
107
  return "" if queue.empty?
88
108
 
89
- output = queue.map {|event| %(mpmetrics.track("#{event[:event]}", #{event[:properties].to_json});) }.join("\n")
109
+ if @options[:async]
110
+ output = queue.map {|type, arguments| %(mpq.push(["#{type}", #{arguments.join(', ')}]);) }.join("\n")
111
+ else
112
+ output = queue.map {|type, arguments| %(mpmetrics.#{type}(#{arguments.join(', ')});) }.join("\n")
113
+ end
90
114
 
91
115
  output = "try {#{output}} catch(err) {}"
92
116
 
data/mixpanel.gemspec CHANGED
@@ -2,7 +2,7 @@ files = ['README.rdoc', 'LICENSE', 'Rakefile', 'mixpanel.gemspec', '{spec,lib}/*
2
2
 
3
3
  spec = Gem::Specification.new do |s|
4
4
  s.name = "mixpanel"
5
- s.version = "0.6.1"
5
+ s.version = "0.7.0"
6
6
  s.rubyforge_project = "mixpanel"
7
7
  s.description = "Simple lib to track events in Mixpanel service. It can be used in any rack based framework."
8
8
  s.author = "Alvaro Gil"
@@ -14,6 +14,66 @@ describe MixpanelMiddleware do
14
14
  end
15
15
  end
16
16
 
17
+ describe "Appending async mixpanel scripts" do
18
+ describe "With ajax requests" do
19
+ before do
20
+ setup_rack_application(DummyApp, {:body => html_document, :headers => {"Content-Type" => "text/html"}}, {:async => true})
21
+ get "/", {}, {"HTTP_X_REQUESTED_WITH" => "XMLHttpRequest"}
22
+ end
23
+
24
+ it "should not append mixpanel scripts to head element" do
25
+ Nokogiri::HTML(last_response.body).search('script').should be_empty
26
+ end
27
+
28
+ it "should not update Content-Length in headers" do
29
+ last_response.headers["Content-Length"].should == html_document.length.to_s
30
+ end
31
+ end
32
+
33
+ describe "With large ajax response" do
34
+ before do
35
+ setup_rack_application(DummyApp, {:body => large_script, :headers => {"Content-Type" => "text/html"}}, {:async => true})
36
+ get "/", {}, {"HTTP_X_REQUESTED_WITH" => "XMLHttpRequest"}
37
+ end
38
+
39
+ it "should not append mixpanel scripts to head element" do
40
+ last_response.body.index('var mp_protocol').should be_nil
41
+ end
42
+
43
+ it "should pass through if the document is not text/html content type" do
44
+ last_response.body.should == large_script
45
+ end
46
+ end
47
+
48
+ describe "With regular requests" do
49
+ before do
50
+ setup_rack_application(DummyApp, {:body => html_document, :headers => {"Content-Type" => "text/html"}}, {:async => true})
51
+ get "/"
52
+ end
53
+
54
+ it "should append mixpanel scripts to head element" do
55
+ Nokogiri::HTML(last_response.body).search('head script').should_not be_empty
56
+ Nokogiri::HTML(last_response.body).search('body script').should be_empty
57
+ end
58
+
59
+ it "should have 1 included script" do
60
+ Nokogiri::HTML(last_response.body).search('script').size.should == 1
61
+ end
62
+
63
+ it "should use the specified token instantiating mixpanel lib" do
64
+ last_response.should =~ /mpq.push\(\["init", "#{MIX_PANEL_TOKEN}"\]\)/
65
+ end
66
+
67
+ it "should define Content-Length if not exist" do
68
+ last_response.headers.has_key?("Content-Length").should == true
69
+ end
70
+
71
+ it "should update Content-Length in headers" do
72
+ last_response.headers["Content-Length"].should_not == html_document.length.to_s
73
+ end
74
+ end
75
+ end
76
+
17
77
  describe "Appending mixpanel scripts" do
18
78
  describe "With ajax requests" do
19
79
  before do
@@ -29,7 +89,22 @@ describe MixpanelMiddleware do
29
89
  last_response.headers["Content-Length"].should == html_document.length.to_s
30
90
  end
31
91
  end
92
+
93
+ describe "With large ajax response" do
94
+ before do
95
+ setup_rack_application(DummyApp, :body => large_script, :headers => {"Content-Type" => "text/html"})
96
+ get "/", {}, {"HTTP_X_REQUESTED_WITH" => "XMLHttpRequest"}
97
+ end
98
+
99
+ it "should not append mixpanel scripts to head element" do
100
+ last_response.body.index('var mp_protocol').should be_nil
101
+ end
32
102
 
103
+ it "should pass through if the document is not text/html content type" do
104
+ last_response.body.should == large_script
105
+ end
106
+ end
107
+
33
108
  describe "With regular requests" do
34
109
  before do
35
110
  setup_rack_application(DummyApp, :body => html_document, :headers => {"Content-Type" => "text/html"})
@@ -59,6 +134,79 @@ describe MixpanelMiddleware do
59
134
  end
60
135
  end
61
136
 
137
+ describe "Tracking async appended events" do
138
+ before do
139
+ @mixpanel = Mixpanel.new(MIX_PANEL_TOKEN, {})
140
+ @mixpanel.append_event("Visit", {:article => 1})
141
+ @mixpanel.append_event("Sign in")
142
+ end
143
+
144
+ describe "With ajax requests and text/html response" do
145
+ before do
146
+ setup_rack_application(DummyApp, {:body => "<p>response</p>", :headers => {"Content-Type" => "text/html"}}, {:async => true})
147
+
148
+ get "/", {}, {"mixpanel_events" => @mixpanel.queue, "HTTP_X_REQUESTED_WITH" => "XMLHttpRequest"}
149
+ end
150
+
151
+ it "should render only one script tag" do
152
+ Nokogiri::HTML(last_response.body).search('script').size.should == 1
153
+ end
154
+
155
+ it "should be tracking the correct events inside a script tag" do
156
+ script = Nokogiri::HTML(last_response.body).search('script')
157
+ script.inner_html.should =~ /try\s?\{(.*)\}\s?catch/m
158
+ script.inner_html.should =~ /mpq\.push\(\["track",\s?"Visit",\s?\{"article":1\}\]\)/
159
+ script.inner_html.should =~ /mpq\.push\(\["track",\s?"Sign in",\s?\{\}\]\)/
160
+ end
161
+
162
+ it "should delete events queue after use it" do
163
+ last_request.env.has_key?("mixpanel_events").should == false
164
+ end
165
+ end
166
+
167
+ describe "With ajax requests and text/javascript response" do
168
+ before do
169
+ setup_rack_application(DummyApp, {:body => "alert('response')", :headers => {"Content-Type" => "text/javascript"}}, {:async => true})
170
+ get "/", {}, {"mixpanel_events" => @mixpanel.queue, "HTTP_X_REQUESTED_WITH" => "XMLHttpRequest"}
171
+ end
172
+
173
+ it "should not render a script tag" do
174
+ Nokogiri::HTML(last_response.body).search('script').size.should == 0
175
+ end
176
+
177
+ it "should be tracking the correct events inside a try/catch" do
178
+ script = last_response.body.match(/try\s?\{(.*)\}\s?catch/m)[1]
179
+ script.should =~ /mpq\.push\(\["track",\s?"Visit",\s?\{"article":1\}\]\)/
180
+ script.should =~ /mpq\.push\(\["track",\s?"Sign in",\s?\{\}\]\)/
181
+ end
182
+
183
+ it "should delete events queue after use it" do
184
+ last_request.env.has_key?("mixpanel_events").should == false
185
+ end
186
+ end
187
+
188
+ describe "With regular requests" do
189
+ before do
190
+ setup_rack_application(DummyApp, {:body => html_document, :headers => {"Content-Type" => "text/html"}}, {:async => true})
191
+
192
+ get "/", {}, {"mixpanel_events" => @mixpanel.queue}
193
+ end
194
+
195
+ it "should render 2 script tags" do
196
+ Nokogiri::HTML(last_response.body).search('script').size.should == 2
197
+ end
198
+
199
+ it "should be tracking the correct events" do
200
+ last_response.body.should =~ /mpq\.push\(\["track",\s?"Visit",\s?\{"article":1\}\]\)/
201
+ last_response.body.should =~ /mpq\.push\(\["track",\s?"Sign in",\s?\{\}\]\)/
202
+ end
203
+
204
+ it "should delete events queue after use it" do
205
+ last_request.env.has_key?("mixpanel_events").should == false
206
+ end
207
+ end
208
+ end
209
+
62
210
  describe "Tracking appended events" do
63
211
  before do
64
212
  @mixpanel = Mixpanel.new(MIX_PANEL_TOKEN, {})
@@ -48,18 +48,38 @@ describe Mixpanel do
48
48
 
49
49
  it "should append simple events" do
50
50
  @mixpanel.append_event("Sign up")
51
- mixpanel_queue_should_include(@mixpanel, "Sign up", {})
51
+ mixpanel_queue_should_include(@mixpanel, "track", "Sign up", {})
52
52
  end
53
53
 
54
54
  it "should append events with properties" do
55
55
  @mixpanel.append_event("Sign up", {:referer => 'http://example.com'})
56
- mixpanel_queue_should_include(@mixpanel, "Sign up", {:referer => 'http://example.com'})
56
+ mixpanel_queue_should_include(@mixpanel, "track", "Sign up", {:referer => 'http://example.com'})
57
57
  end
58
58
 
59
59
  it "should give direct access to queue" do
60
60
  @mixpanel.append_event("Sign up", {:referer => 'http://example.com'})
61
61
  @mixpanel.queue.size.should == 1
62
62
  end
63
+
64
+ it "should provide direct access to the JS api" do
65
+ @mixpanel.append_api('track', "Sign up", {:referer => 'http://example.com'})
66
+ mixpanel_queue_should_include(@mixpanel, "track", "Sign up", {:referer => 'http://example.com'})
67
+ end
68
+
69
+ it "should allow identify to be called through the JS api" do
70
+ @mixpanel.append_api('identify', "some@one.com")
71
+ mixpanel_queue_should_include(@mixpanel, "identify", "some@one.com")
72
+ end
73
+
74
+ it "should allow identify to be called through the JS api" do
75
+ @mixpanel.append_api('identify', "some@one.com")
76
+ mixpanel_queue_should_include(@mixpanel, "identify", "some@one.com")
77
+ end
78
+
79
+ it "should allow the tracking of super properties in JS" do
80
+ @mixpanel.append_api('register', {:user_id => 12345, :email => "some@one.com"})
81
+ mixpanel_queue_should_include(@mixpanel, 'register', {:user_id => 12345, :email => "some@one.com"})
82
+ end
63
83
  end
64
84
  end
65
85
  end
data/spec/spec_helper.rb CHANGED
@@ -7,10 +7,11 @@ Dir[File.expand_path(File.join(File.dirname(__FILE__),'support','**','*.rb'))].e
7
7
 
8
8
  MIX_PANEL_TOKEN = "e2d8b0bea559147844ffab3d607d26a6"
9
9
 
10
- def mixpanel_queue_should_include(mixpanel, event, properties)
11
- mixpanel.queue.each do |event_hash|
12
- event_hash[:event].should == event
13
- event_hash[:properties].should == properties if properties
10
+
11
+ def mixpanel_queue_should_include(mixpanel, type, *arguments)
12
+ mixpanel.queue.each do |event_type, event_arguments|
13
+ event_arguments.should == arguments.map{|arg| arg.to_json}
14
+ event_type.should == type
14
15
  end
15
16
  end
16
17
 
@@ -1,5 +1,5 @@
1
- def setup_rack_application(application, options = {})
2
- stub!(:app).and_return(MixpanelMiddleware.new(application.new(options), MIX_PANEL_TOKEN))
1
+ def setup_rack_application(application, options = {}, mixpanel_options = {})
2
+ stub!(:app).and_return(MixpanelMiddleware.new(application.new(options), MIX_PANEL_TOKEN, mixpanel_options))
3
3
  end
4
4
 
5
5
  def html_document
@@ -25,3 +25,179 @@ class DummyApp
25
25
  [@response_with[:status], @response_with[:headers], [@response_with[:body]]]
26
26
  end
27
27
  end
28
+
29
+ def large_script
30
+ <<-EOT
31
+ <script type='text/javascript'>
32
+ //<![CDATA[
33
+ function update_milestone_divs(obj){
34
+ $('#milestones').show();
35
+ $('#milestones').children('div').hide();
36
+
37
+ divid = obj.options[obj.selectedIndex].value;
38
+
39
+ divid = '#milestone_' + divid
40
+ $(divid).show()
41
+ }
42
+
43
+ $(document).ready(function() {
44
+ /*
45
+ * First step is to create title attributes for the rows in the table
46
+ * This isn't needed if the required 'title' attribute is already set in the HTML in the
47
+ * DOM
48
+ */
49
+ $('#milestone_table tbody tr').each( function() {
50
+ var sTitle;
51
+ var nTds = $('td', this);
52
+ var sPic = $(nTds[3]).text();
53
+ var sName = $(nTds[1]).text();
54
+
55
+ sTitle = '<img src='+sPic+' height=60 width=60/><br>'+sName;
56
+ this.setAttribute( 'title', sTitle );
57
+
58
+ } );
59
+
60
+ /* Apply the tooltips */
61
+ $('#milestone_table tbody tr[title]').tooltip( {
62
+ "delay": 0,
63
+ "track": true,
64
+ "fade": 250
65
+ } );
66
+
67
+ /* Init DataTables */
68
+ $('#milestone_table').dataTable({
69
+ "iDisplayLength": 10,
70
+ "aaSorting": [[ 2, "desc" ]],
71
+ });
72
+ });
73
+ //]]>
74
+ </script>
75
+ <style type='text/css'>
76
+ /*<![CDATA[*/
77
+ #milestone_table {
78
+ width:550px;
79
+
80
+ }
81
+
82
+ td { vertical-align:middle;}
83
+ td.select { width: 50px; padding-left:10px;}
84
+ td.title { text-align:left; font-size:120%; }
85
+ td.picture { width: 60px; display:none;}
86
+ td.when { text-align: center; width: 100px; }
87
+ /*]]>*/
88
+ </style>
89
+ <div class='item_box' id='milestone_attachment'>
90
+ <form action="http://big.application.com/inbox_items/28" enctype="multipart/form-data" method="post"><div style="margin:0;padding:0;display:inline"><input name="_method" type="hidden" value="put" /><input name="authenticity_token" type="hidden" value="QF1y2YhiuVJv7qS3u5jShr6mvqjx0NWAD1FPPJTwY/w=" /></div>
91
+ <input id="transform_to" name="transform_to" type="hidden" value="milestone_attachment" />
92
+ <div>
93
+ <div class='addBox roundo' style='float:left; display:block; margin:5px 0; padding:0 5px 5px 5px;'>
94
+ <p>
95
+ Attach to memory for
96
+ <select name='person_id' onchange='update_milestone_divs(this)'>
97
+ <option></option>
98
+ <option value='40'> Bill</option>
99
+ <option disabled='disabled'> Sally (no memories)</option>
100
+ <option value='169'> Tim</option>
101
+ <option disabled='disabled'> Betty (no memories)</option>
102
+ <option value='173'> Ted</option>
103
+ </select>
104
+ </p>
105
+ </div>
106
+ </div>
107
+ <div id='milestones'>
108
+ <div id='milestone_40' style='display:none;'>
109
+ <h4>Memories for Bill</h4>
110
+ <table id='milestone_table'>
111
+ <thead>
112
+ <tr>
113
+ <th>Select</th>
114
+ <th>Title</th>
115
+ <th>When?</th>
116
+ <th style='display:none;'>Picture</th>
117
+ </tr>
118
+ </thead>
119
+ <tr>
120
+ <td class='select'><input id="milestone_40_179" name="milestone_40" type="radio" value="179" /></td>
121
+ <td class='title'>Ran a race</td>
122
+ <td class='when'>10/07/2010</td>
123
+ <td class='picture'>
124
+ /system/photos/first_and_milestones/179/thumb/ed0e4428.jpeg
125
+ </td>
126
+ </tr>
127
+ </table>
128
+ </div>
129
+ <div id='milestone_169' style='display:none;'>
130
+ <h4>Memories for Tim</h4>
131
+ <table id='milestone_table'>
132
+ <thead>
133
+ <tr>
134
+ <th>Select</th>
135
+ <th>Title</th>
136
+ <th>When?</th>
137
+ <th style='display:none;'>Picture</th>
138
+ </tr>
139
+ </thead>
140
+ <tr>
141
+ <td class='select'><input id="milestone_169_204" name="milestone_169" type="radio" value="204" /></td>
142
+ <td class='title'>Kicked ball first time</td>
143
+ <td class='when'>03/12/1978</td>
144
+ <td class='picture'>
145
+ </td>
146
+ </tr>
147
+ </table>
148
+ </div>
149
+ <div id='milestone_173' style='display:none;'>
150
+ <h4>Memories for Ted</h4>
151
+ <table id='milestone_table'>
152
+ <thead>
153
+ <tr>
154
+ <th>Select</th>
155
+ <th>Title</th>
156
+ <th>When?</th>
157
+ <th style='display:none;'>Picture</th>
158
+ </tr>
159
+ </thead>
160
+ <tr>
161
+ <td class='select'><input id="milestone_173_195" name="milestone_173" type="radio" value="195" /></td>
162
+ <td class='title'>Testing the log</td>
163
+ <td class='when'>11/03/2010</td>
164
+ <td class='picture'>
165
+ </td>
166
+ </tr>
167
+ <tr>
168
+ <td class='select'><input id="milestone_173_196" name="milestone_173" type="radio" value="196" /></td>
169
+ <td class='title'>another test</td>
170
+ <td class='when'>11/03/2010</td>
171
+ <td class='picture'>
172
+ </td>
173
+ </tr>
174
+ <tr>
175
+ <td class='select'><input id="milestone_173_197" name="milestone_173" type="radio" value="197" /></td>
176
+ <td class='title'>one more</td>
177
+ <td class='when'>11/01/2010</td>
178
+ <td class='picture'>
179
+ </td>
180
+ </tr>
181
+ <tr>
182
+ <td class='select'><input id="milestone_173_198" name="milestone_173" type="radio" value="198" /></td>
183
+ <td class='title'>great time</td>
184
+ <td class='when'>11/03/2010</td>
185
+ <td class='picture'>
186
+ </td>
187
+ </tr>
188
+ <tr>
189
+ <td class='select'><input id="milestone_173_199" name="milestone_173" type="radio" value="199" /></td>
190
+ <td class='title'>please</td>
191
+ <td class='when'>11/03/2010</td>
192
+ <td class='picture'>
193
+ </td>
194
+ </tr>
195
+ </table>
196
+ </div>
197
+ <div class='clear'></div>
198
+ </div>
199
+ <div class="clear"></div><div id="buttonArea" style="width: 550px; margin-left: -20px"><input class="btn topper botter lbump20 blue " disabled="disabled" name="commit" style="display:none;" type="submit" value=".. saving .." /><input class="btn topper botter lbump20 blue " id="submit_btn" name="commit" onclick="$(this).hide(); $(this).prev().show();" type="submit" value="Save" /><a href="/inbox_items" class="btnSm topper botter lbump20">Cancel</a><a href="/inbox_items/28" class="btnSm red topper botter lbump20" id="delete_btn" onclick="if (confirm('Are you sure you want to delete this?')) { var f = document.createElement('form'); f.style.display = 'none'; this.parentNode.appendChild(f); f.method = 'POST'; f.action = this.href;var m = document.createElement('input'); m.setAttribute('type', 'hidden'); m.setAttribute('name', '_method'); m.setAttribute('value', 'delete'); f.appendChild(m);var s = document.createElement('input'); s.setAttribute('type', 'hidden'); s.setAttribute('name', 'authenticity_token'); s.setAttribute('value', 'QF1y2YhiuVJv7qS3u5jShr6mvqjx0NWAD1FPPJTwY/w='); f.appendChild(s);f.submit(); };return false;">Delete</a></div><div class="clear"></div>
200
+ </form>
201
+ </div>
202
+ EOT
203
+ end
metadata CHANGED
@@ -1,13 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: mixpanel
3
3
  version: !ruby/object:Gem::Version
4
- hash: 5
4
+ hash: 3
5
5
  prerelease: false
6
6
  segments:
7
7
  - 0
8
- - 6
9
- - 1
10
- version: 0.6.1
8
+ - 7
9
+ - 0
10
+ version: 0.7.0
11
11
  platform: ruby
12
12
  authors:
13
13
  - Alvaro Gil
@@ -15,7 +15,7 @@ autorequire:
15
15
  bindir: bin
16
16
  cert_chain: []
17
17
 
18
- date: 2010-07-16 00:00:00 -03:00
18
+ date: 2010-11-26 00:00:00 -02:00
19
19
  default_executable:
20
20
  dependencies:
21
21
  - !ruby/object:Gem::Dependency