mixpanel 0.6.1 → 0.7.0
Sign up to get free protection for your applications and to get access to all the features.
- data/README.rdoc +19 -2
- data/lib/mixpanel/mixpanel.rb +6 -2
- data/lib/mixpanel/mixpanel_middleware.rb +48 -24
- data/mixpanel.gemspec +1 -1
- data/spec/mixpanel/mixpanel_middleware_spec.rb +148 -0
- data/spec/mixpanel/mixpanel_spec.rb +22 -2
- data/spec/spec_helper.rb +5 -4
- data/spec/support/rack_apps.rb +178 -2
- metadata +5 -5
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
|
-
|
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]
|
data/lib/mixpanel/mixpanel.rb
CHANGED
@@ -10,9 +10,13 @@ class Mixpanel
|
|
10
10
|
end
|
11
11
|
|
12
12
|
def append_event(event, properties = {})
|
13
|
-
|
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.
|
27
|
-
|
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.
|
35
|
+
part.insert(0, render_event_tracking_scripts) unless queue.empty?
|
30
36
|
elsif is_ajax_request? && is_javascript_response?
|
31
|
-
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
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
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
|
-
|
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.
|
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
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
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
|
|
data/spec/support/rack_apps.rb
CHANGED
@@ -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:
|
4
|
+
hash: 3
|
5
5
|
prerelease: false
|
6
6
|
segments:
|
7
7
|
- 0
|
8
|
-
-
|
9
|
-
-
|
10
|
-
version: 0.
|
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-
|
18
|
+
date: 2010-11-26 00:00:00 -02:00
|
19
19
|
default_executable:
|
20
20
|
dependencies:
|
21
21
|
- !ruby/object:Gem::Dependency
|