landable 1.12.3 → 1.13.1

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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 8ac7e3da514137a5a1c613fba88a310902a9588a
4
- data.tar.gz: cb26053550dec33f9165ef7f708f4d925df0c9df
3
+ metadata.gz: 79b91e68096271b06e44f3660fe1a577b2106829
4
+ data.tar.gz: d2fc437c485007597bcbbbbafc9e525e5eda8290
5
5
  SHA512:
6
- metadata.gz: e7a9ee77e262b28ee572e6b7e58302cb1766bafc2d9811040bdfe618e439b64ad22a786a3d7a753989aa434cd159f5ee214fefb31e381ccdfd97c882a4027e55
7
- data.tar.gz: c31d2968ff8c11ca4d8ddab2d9a249edf84a9043eb4b25e0052adee1c14d8964c06c6bb6dfa6a30e2e3aafd34cc1a4b0ef48889288965203dcc27dba4f4e8d7f
6
+ metadata.gz: e3c967e86fec973e57c27c7ba24702cb9ff910f6b948c19660ec9f6ca3ffe56d846ca8c1b6bb3071e7661424d17eb5856902aab186b019f49bf94c0ad6fb5c79
7
+ data.tar.gz: ffbae9ae3d1d86064e7820f80ca173c574b5bf867523fc1a95fe41a6c6c35c34067554387bdc4e5b18764c7298b552de0925fa6113d1bee3d4674aa4ff1c7164
@@ -2,7 +2,11 @@
2
2
 
3
3
  See README.md before updating this file.
4
4
 
5
- ## Unreleased [#](https://github.com/enova/landable/compare/v1.12.1...master)
5
+ ## Unreleased [#](https://github.com/enova/landable/compare/v1.13.0...v1.13.1)
6
+
7
+ ## 1.13.1 [#](https://github.com/enova/landable/compare/v1.12.3...v1.13.1)
8
+ * BugFix: Default referer.path to '' [#60]
9
+ * Feature: Advanced Message Queuing Protocol to Publish Events [#58]
6
10
 
7
11
  ## 1.12.3 [#](https://github.com/enova/landable/compare/v1.12.2...v1.12.3)
8
12
  * Feature: Add Traffic Object Helper [#50]
@@ -51,6 +51,28 @@ Landable.configure do |config|
51
51
 
52
52
  # If you want to save a different UserAgent if the request.user_agent is blank, set it here
53
53
  # config.blank_user_agent_string = 'blank'
54
+
55
+ # Uncomment to enable publishing of events to an amqp messaging service.
56
+ #
57
+ # This feature requires the mounting application to configure a messenger
58
+ # class which includes a publish method. The publish method should
59
+ # take the message as an input, cast it appropriately
60
+ # then deliver the message to whatever amqp service you are running.
61
+ # the EventPublisher class will only attempt to send a message if
62
+ # enabled = true and messaging_service is set.
63
+ # The configuration params are a collection, like so:
64
+ # config.amqp_configuration = {
65
+ # site_segment: 'mybrand:myproduct:myapp',
66
+ # messaging_service: AmqpMessagingService,
67
+ # enabled: 'true',
68
+ # event_mapping: {
69
+ # '/' => 'Home page',
70
+ # '/my_path' => { 'GET' => 'Customer Landed',
71
+ # 'POST' => 'Customer Submitted',
72
+ # 'DELETE' => 'Customer Left'
73
+ # }
74
+ # }.freeze
75
+ # }
54
76
  end
55
77
 
56
78
  # Configure asset uploads. Assets will be uploaded to public/uploads by default.
@@ -1,6 +1,6 @@
1
1
  module Landable
2
2
  class Configuration
3
- attr_accessor :api_url, :public_url
3
+ attr_accessor :api_url, :public_url, :amqp_configuration
4
4
  attr_writer :api_namespace, :public_namespace
5
5
  attr_writer :api_host, :public_host
6
6
  attr_writer :categories
@@ -10,7 +10,12 @@ module Landable
10
10
  attr_writer :reserved_paths, :partials_to_templates, :database_schema_prefix
11
11
  attr_writer :publicist_url, :audit_flags
12
12
  attr_writer :blank_user_agent_string, :untracked_paths
13
- attr_writer :dnt_enabled
13
+ attr_writer :dnt_enabled, :amqp_event_mapping, :amqp_site_segment
14
+ attr_writer :amqp_service_enabled, :amqp_messaging_service
15
+
16
+ def amqp_configuration
17
+ @amqp_configuration ||= {}
18
+ end
14
19
 
15
20
  def authenticators
16
21
  @authenticators || raise("No Landable authenticator configured.")
@@ -152,10 +157,21 @@ module Landable
152
157
 
153
158
  def dnt_enabled
154
159
  return true if @dnt_enabled.nil?
155
-
156
160
  @dnt_enabled
157
161
  end
158
162
 
163
+ def amqp_site_segment
164
+ @amqp_site_segment ||= Rails.application.class.parent_name
165
+ end
166
+
167
+ def amqp_event_mapping
168
+ @amqp_event_mapping ||= {}
169
+ end
170
+
171
+ def amqp_service_enabled
172
+ amqp_configuration[:enabled] && amqp_configuration[:messaging_service]
173
+ end
174
+
159
175
 
160
176
  class Screenshots
161
177
  attr_accessor :autorun
@@ -5,6 +5,7 @@ require 'landable/traffic/scan_tracker'
5
5
  require 'landable/traffic/scrape_tracker'
6
6
  require 'landable/traffic/user_tracker'
7
7
  require 'landable/traffic/noop_tracker'
8
+ require 'landable/traffic/event_publisher'
8
9
 
9
10
  module Landable
10
11
  module Traffic
@@ -0,0 +1,115 @@
1
+ module Landable
2
+ module EventPublisher
3
+ mattr_reader :event_type, :page_view, :visit
4
+
5
+ class << self
6
+ def publish(page_view)
7
+ @configuration = Landable.configuration.amqp_configuration
8
+ return unless enabled?
9
+
10
+ @event_type = event_type(page_view)
11
+ return unless @event_type
12
+
13
+ @page_view = page_view
14
+ @visit = page_view.visit
15
+ messaging_service.publish(message)
16
+ end
17
+
18
+ private
19
+
20
+ def enabled?
21
+ @enabled ||= @configuration[:enabled] && messaging_service.present?
22
+ end
23
+
24
+ def messaging_service
25
+ @messaging_service ||= @configuration[:messaging_service]
26
+ end
27
+
28
+ def event_mapping
29
+ @event_mapping ||= @configuration[:event_mapping]
30
+ end
31
+
32
+ def event_type(page_view)
33
+ event_type = event_mapping[page_view.path]
34
+
35
+ if event_type.is_a?(Hash)
36
+ request_type = page_view.http_method
37
+ event_type = event_type[request_type]
38
+ end
39
+ event_type
40
+ end
41
+
42
+ def site_segment
43
+ @site_segment ||= @configuration[:site_segment]
44
+ end
45
+
46
+ def message
47
+ visit = @visit
48
+ attribution = visit.try(:attribution)
49
+ referer = visit.try(:referer)
50
+ visitor = visit.visitor
51
+ page_view = @page_view
52
+ user_agent = visitor.try(:raw_user_agent)
53
+ user_agent_type = user_agent.try(:raw_user_agent_type)
54
+ event_type = @event_type
55
+ {
56
+ site_segment: site_segment,
57
+ visit_id: visit.id,
58
+ event: event_type.to_s,
59
+ page_view_id: page_view.page_view_id,
60
+ request_type: page_view.http_method,
61
+ created_at: page_view.created_at,
62
+ cookie_id: visit.cookie_id,
63
+ owner_id: visit.try(:owner_id),
64
+ owner: visit.try(:owner).try(:owner),
65
+ referer_id: referer.try(:id),
66
+ domain_id: referer.try(:domain_id),
67
+ domain: referer.try(:domain),
68
+ ip_address_id: visitor.try(:ip_address_id),
69
+ ip_address: visitor.try(:ip_address).try(:to_s),
70
+ user_agent_id: user_agent.try(:id),
71
+ user_agent: user_agent.try(:user_agent),
72
+ user_agent_type_id: user_agent_type.try(:id),
73
+ user_agent_type: user_agent_type.try(:user_agent_type),
74
+ attribution_id: attribution.try(:id),
75
+ ad_group_id: attribution.try(:ad_group_id),
76
+ ad_group: attribution.try(:ad_group),
77
+ ad_type_id: attribution.try(:ad_type_id),
78
+ ad_type: attribution.try(:ad_type),
79
+ bid_match_type_id: attribution.try(:bid_match_type_id),
80
+ bid_match_type: attribution.try(:bid_match_type),
81
+ campaign_id: attribution.try(:campaign_id),
82
+ campaign: attribution.try(:campaign),
83
+ content_id: attribution.try(:content_id),
84
+ content: attribution.try(:content),
85
+ creative_id: attribution.try(:creative_id),
86
+ creative: attribution.try(:creative),
87
+ device_type_id: attribution.try(:device_type_id),
88
+ device_type: attribution.try(:device_type),
89
+ experiment_id: attribution.try(:experiment_id),
90
+ experiment: attribution.try(:experiment),
91
+ keyword_id: attribution.try(:keyword_id),
92
+ keyword: attribution.try(:keyword),
93
+ match_type_id: attribution.try(:match_type_id),
94
+ match_type: attribution.try(:match_type),
95
+ medium_id: attribution.try(:medium_id),
96
+ medium: attribution.try(:medium),
97
+ network_id: attribution.try(:network_id),
98
+ network: attribution.try(:network),
99
+ placement_id: attribution.try(:placement_id),
100
+ placement: attribution.try(:placement),
101
+ position_id: attribution.try(:position_id),
102
+ position: attribution.try(:position),
103
+ search_term_id: attribution.try(:search_term_id),
104
+ search_term: attribution.try(:search_term),
105
+ source_id: attribution.try(:source_id),
106
+ source: attribution.try(:source),
107
+ target_id: attribution.try(:target_id),
108
+ target: attribution.try(:target),
109
+ path_id: page_view.path_id,
110
+ path: page_view.path
111
+ }
112
+ end
113
+ end
114
+ end
115
+ end
@@ -76,8 +76,8 @@ module Landable
76
76
  def for(controller)
77
77
  type = controller.request.user_agent.presence && Landable::Traffic::UserAgent[controller.request.user_agent].user_agent_type
78
78
  type = 'noop' if Landable.configuration.traffic_enabled == :html and not controller.request.format.html?
79
- type = 'user'if type.nil?
80
- type = 'user'if controller.request.query_parameters.slice(*TRACKING_KEYS).any?
79
+ type = 'user' if type.nil?
80
+ type = 'user' if controller.request.query_parameters.with_indifferent_access.slice(*TRACKING_KEYS).any?
81
81
 
82
82
  "Landable::Traffic::#{type.classify}Tracker".constantize.new(controller)
83
83
  end
@@ -173,7 +173,7 @@ module Landable
173
173
  query = params.except(*ATTRIBUTION_KEYS)
174
174
 
175
175
  @referer = Referer.where(domain_id: Domain[referer_uri.host],
176
- path_id: Path[referer_uri.path],
176
+ path_id: Path[referer_uri_path],
177
177
  query_string_id: QueryString[query.to_query],
178
178
  attribution_id: attribution.id).first_or_create
179
179
  end
@@ -236,6 +236,10 @@ module Landable
236
236
  @referer_uri ||= URI(URI.encode(request.referer)) if request.referer
237
237
  end
238
238
 
239
+ def referer_uri_path
240
+ referer_uri.try(:path) || ''
241
+ end
242
+
239
243
  def external_referer?
240
244
  referer_uri && referer_uri.host != request.host
241
245
  end
@@ -271,6 +275,10 @@ module Landable
271
275
  hash
272
276
  end
273
277
 
278
+ def query_parameters
279
+ @query_parameters ||= request.query_parameters.with_indifferent_access
280
+ end
281
+
274
282
  def tracking_parameters
275
283
  @tracking_parameters ||= extract_tracking(query_parameters)
276
284
  end
@@ -280,7 +288,7 @@ module Landable
280
288
  end
281
289
 
282
290
  def attribution_parameters
283
- @attribution_parameters ||= tracking_parameters.slice(*ATTRIBUTION_KEYS)
291
+ @attribution_parameters ||= tracking_parameters.with_indifferent_access.slice(*ATTRIBUTION_KEYS)
284
292
  end
285
293
 
286
294
  def attribution
@@ -33,13 +33,18 @@ module Landable
33
33
  p.http_status = response.status
34
34
 
35
35
  p.visit_id = @visit_id
36
+ # this is strange, yes, but is it better than a db call?
37
+ p.created_at = Time.current
36
38
 
37
39
  p.response_time = ( Time.now - @start_time ) * 1000
38
40
  end
39
41
  end
40
42
 
41
43
  def save
42
- record_page_view
44
+ p = record_page_view
45
+ if Landable.configuration.amqp_service_enabled
46
+ EventPublisher.publish(p)
47
+ end
43
48
 
44
49
  session[:landable] = {
45
50
  KEYS[:visit_id] => @visit_id,
@@ -1,8 +1,8 @@
1
1
  module Landable
2
2
  module VERSION
3
3
  MAJOR = 1
4
- MINOR = 12
5
- PATCH = 3
4
+ MINOR = 13
5
+ PATCH = 1
6
6
 
7
7
  STRING = [MAJOR, MINOR, PATCH].compact.join('.')
8
8
  end
@@ -26,4 +26,5 @@ Dummy::Application.configure do
26
26
  # This option may cause significant delays in view rendering with a large
27
27
  # number of complex assets.
28
28
  config.assets.debug = true
29
+ config.serve_static_files = true
29
30
  end
@@ -13,7 +13,7 @@ Dummy::Application.configure do
13
13
  config.eager_load = false
14
14
 
15
15
  # Configure static asset server for tests with Cache-Control for performance.
16
- config.serve_static_assets = true
16
+ config.serve_static_files = true
17
17
  config.static_cache_control = "public, max-age=3600"
18
18
 
19
19
  # Show full error reports and disable caching.
@@ -1,4 +1,5 @@
1
1
  require 'landable'
2
+ require Rails.root.join('lib', 'bunny_messaging_service.rb')
2
3
 
3
4
  Landable.configure do |config|
4
5
  config.api_namespace = '/api'
@@ -11,10 +12,20 @@ Landable.configure do |config|
11
12
  config.partials_to_templates = %w(partials/foobazz)
12
13
 
13
14
  config.reserved_paths = %w(/reserved_path_set_in_initializer /reject/.* /admin.*)
14
-
15
15
  config.database_schema_prefix = 'dummy'
16
-
17
16
  config.audit_flags = %w(loans apr)
17
+
18
+ config.amqp_configuration = {
19
+ site_segment: 'mybrand:myproduct:myapp',
20
+ messaging_service: BunnyMessagingService,
21
+ enabled: 'true',
22
+ event_mapping: {
23
+ '/my_path' => { 'GET' => 'Customer Landed',
24
+ 'POST' => 'Customer Submitted',
25
+ 'DELETE' => 'Customer Left'
26
+ }
27
+ }.freeze
28
+ }
18
29
  end
19
30
 
20
31
  # Configure asset uploads. Assets will be uploaded to public/uploads by default.
@@ -0,0 +1,7 @@
1
+ class BunnyMessagingService
2
+ class << self
3
+ def publish(message)
4
+ message
5
+ end
6
+ end
7
+ end
@@ -0,0 +1,94 @@
1
+ require 'spec_helper'
2
+
3
+ module Landable
4
+ module Traffic
5
+ describe 'EventPublisher', type: :controller do
6
+
7
+ controller(ApplicationController) do
8
+ include Landable::Traffic
9
+ prepend_around_action :track_with_landable!
10
+
11
+ def my_path
12
+ render nothing: true
13
+ end
14
+ end
15
+
16
+ before do
17
+ routes.draw do
18
+ get "/my_path" => 'anonymous#my_path'
19
+ post '/my_path' => 'anonymous#my_path'
20
+ delete '/my_path' => 'anonymous#my_path'
21
+ end
22
+ end
23
+
24
+ let(:message_keys) do
25
+ [:ad_group, :ad_type, :bid_match_type, :campaign, :content, :creative, :device_type,
26
+ :experiment, :keyword, :match_type, :medium, :network, :placement, :position,
27
+ :search_term, :source, :target]
28
+ end
29
+
30
+ let(:attribution) do
31
+ message_keys.each_with_object({}) do |attribute, hash|
32
+ hash[attribute] = "test_#{attribute}"
33
+ end
34
+ end
35
+
36
+ let(:tracker) { controller.instance_variable_get(:@tracker) }
37
+ let(:page_view) { PageView.last }
38
+ let(:published_message) { EventPublisher.publish(page_view) }
39
+
40
+ it 'should properly properly set the attribution data and send it within a message' do
41
+ get :my_path, attribution
42
+ # binding.pry
43
+ message_keys.each do |attribute|
44
+ expect(published_message[attribute]).to eq("test_#{attribute}")
45
+ end
46
+ end
47
+
48
+ it 'should properly set the event types for GET requests when multiple request types use the same route' do
49
+ get :my_path, attribution
50
+ expect(published_message[:path]).to eq('/my_path')
51
+ expect(published_message[:request_type]).to eq('GET')
52
+ expect(published_message[:event]).to eq('Customer Landed')
53
+ end
54
+
55
+ it 'should properly set the event types for POST requests when multiple request types use the same route' do
56
+ post :my_path, attribution
57
+ expect(published_message[:path]).to eq('/my_path')
58
+ expect(published_message[:request_type]).to eq('POST')
59
+ expect(published_message[:event]).to eq('Customer Submitted')
60
+ end
61
+
62
+ it 'should properly set the event types for DELETE requests when multiple request types use the same route' do
63
+ delete :my_path, attribution
64
+ expect(published_message[:path]).to eq('/my_path')
65
+ expect(published_message[:request_type]).to eq('DELETE')
66
+ expect(published_message[:event]).to eq('Customer Left')
67
+ end
68
+
69
+ it 'should correctly set the user agent information in the message' do
70
+ get :my_path, attribution
71
+ user_agent = UserAgent[controller.request.user_agent]
72
+ expect(published_message[:user_agent_id]).to eq user_agent.id
73
+ expect(published_message[:user_agent]).to eq user_agent.user_agent
74
+ end
75
+
76
+ it 'should correctly set the page view information in the message' do
77
+ get :my_path, attribution
78
+ expect(published_message[:page_view_id]).to eq page_view.id
79
+ expect(published_message[:created_at]).to eq page_view.created_at
80
+ expect(published_message[:created_at]).to_not be_nil
81
+ end
82
+
83
+ it 'should correctly set the visit information in the message' do
84
+ get :my_path, attribution
85
+ visit = page_view.visit
86
+ visitor = page_view.visit.visitor
87
+ expect(published_message[:visit_id]).to eq visit.id
88
+ expect(published_message[:cookie_id]).to eq visit.cookie_id
89
+ expect(published_message[:ip_address_id]).to eq visitor.ip_address_id
90
+ expect(published_message[:ip_address]).to eq visitor.ip_address.to_s
91
+ end
92
+ end
93
+ end
94
+ end
@@ -114,8 +114,18 @@ module Landable
114
114
  end
115
115
 
116
116
  context 'no referer' do
117
+ let(:referer) { double('referer', { path: nil}) }
117
118
  let(:visit) { double('visit', { referer: nil }) }
118
119
 
120
+ describe '#referer_uri_path' do
121
+ it 'should return empty string' do
122
+ tracker = Landable::Traffic::UserTracker.new controller
123
+ tracker.stub(:referer_uri) { referer }
124
+
125
+ tracker.send(:referer_uri_path).should == ''
126
+ end
127
+ end
128
+
119
129
  describe '#visit_referer_domain' do
120
130
  it 'should return nil' do
121
131
  tracker = Landable::Traffic::UserTracker.new controller
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: landable
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.12.3
4
+ version: 1.13.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Team Trogdor
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2015-03-12 00:00:00.000000000 Z
11
+ date: 2015-04-30 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rails
@@ -539,6 +539,7 @@ files:
539
539
  - lib/landable/seeds.rb
540
540
  - lib/landable/traffic.rb
541
541
  - lib/landable/traffic/crawl_tracker.rb
542
+ - lib/landable/traffic/event_publisher.rb
542
543
  - lib/landable/traffic/noop_tracker.rb
543
544
  - lib/landable/traffic/ping_tracker.rb
544
545
  - lib/landable/traffic/scan_tracker.rb
@@ -620,6 +621,7 @@ files:
620
621
  - spec/dummy/db/.keep
621
622
  - spec/dummy/db/structure.sql
622
623
  - spec/dummy/lib/assets/.keep
624
+ - spec/dummy/lib/bunny_messaging_service.rb
623
625
  - spec/dummy/log/.keep
624
626
  - spec/dummy/public/404.html
625
627
  - spec/dummy/public/422.html
@@ -640,6 +642,7 @@ files:
640
642
  - spec/fixtures/assets/small.pdf
641
643
  - spec/helpers/pages_helper_spec.rb
642
644
  - spec/lib/landable/configuration_spec.rb
645
+ - spec/lib/landable/event_publisher_spec.rb
643
646
  - spec/lib/landable/layout_spec.rb
644
647
  - spec/lib/landable/liquid_spec.rb
645
648
  - spec/lib/landable/migration_spec.rb
@@ -779,6 +782,7 @@ test_files:
779
782
  - spec/dummy/db/.keep
780
783
  - spec/dummy/db/structure.sql
781
784
  - spec/dummy/lib/assets/.keep
785
+ - spec/dummy/lib/bunny_messaging_service.rb
782
786
  - spec/dummy/log/.keep
783
787
  - spec/dummy/public/404.html
784
788
  - spec/dummy/public/422.html
@@ -799,6 +803,7 @@ test_files:
799
803
  - spec/fixtures/assets/small.pdf
800
804
  - spec/helpers/pages_helper_spec.rb
801
805
  - spec/lib/landable/configuration_spec.rb
806
+ - spec/lib/landable/event_publisher_spec.rb
802
807
  - spec/lib/landable/layout_spec.rb
803
808
  - spec/lib/landable/liquid_spec.rb
804
809
  - spec/lib/landable/migration_spec.rb