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