workarea-core 3.5.0.beta.1 → 3.5.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (66) hide show
  1. checksums.yaml +4 -4
  2. data/app/controllers/workarea/current_tracking.rb +4 -0
  3. data/app/models/workarea/checkout/collect_payment.rb +5 -5
  4. data/app/models/workarea/data_file/operation.rb +4 -0
  5. data/app/models/workarea/fulfillment/policies/base.rb +0 -4
  6. data/app/models/workarea/fulfillment/policies/{ignore.rb → shipping.rb} +2 -2
  7. data/app/models/workarea/fulfillment/sku.rb +0 -2
  8. data/app/models/workarea/insights/trending_searches.rb +8 -1
  9. data/app/models/workarea/metrics/search_by_day.rb +2 -0
  10. data/app/models/workarea/order.rb +12 -2
  11. data/app/models/workarea/order/item.rb +28 -2
  12. data/app/models/workarea/pricing/calculators/tax_calculator.rb +2 -2
  13. data/app/models/workarea/pricing/discount.rb +3 -2
  14. data/app/models/workarea/releasable.rb +3 -5
  15. data/app/models/workarea/reports/custom_event.rb +22 -0
  16. data/app/models/workarea/search/admin/releasable.rb +18 -6
  17. data/app/models/workarea/segment/life_cycle.rb +5 -5
  18. data/app/models/workarea/segment/rules/last_order.rb +9 -2
  19. data/app/models/workarea/segment/rules/traffic_referrer.rb +16 -5
  20. data/app/models/workarea/segmentable.rb +13 -0
  21. data/app/models/workarea/traffic_referrer.rb +3 -0
  22. data/app/queries/workarea/alerts.rb +21 -0
  23. data/app/queries/workarea/order_item_details.rb +16 -16
  24. data/app/queries/workarea/search/admin_index_search.rb +2 -1
  25. data/app/queries/workarea/search/product_display_rules.rb +0 -2
  26. data/app/services/workarea/direct_upload.rb +5 -0
  27. data/app/services/workarea/packaging.rb +1 -1
  28. data/app/workers/workarea/deactivate_stale_discounts.rb +1 -1
  29. data/app/workers/workarea/synchronize_user_metrics.rb +9 -0
  30. data/config/locales/en.yml +22 -0
  31. data/lib/tasks/migrate.rake +9 -12
  32. data/lib/workarea/configuration.rb +6 -6
  33. data/lib/workarea/configuration/redis.rb +21 -3
  34. data/lib/workarea/core.rb +3 -0
  35. data/lib/workarea/ext/freedom_patches/referer_parser.rb +7 -0
  36. data/lib/workarea/ext/mongoid/embedded_children.rb +20 -0
  37. data/lib/workarea/latest_version.rb +24 -0
  38. data/lib/workarea/ping_home_base.rb +0 -1
  39. data/lib/workarea/version.rb +1 -1
  40. data/lib/workarea/visit.rb +5 -2
  41. data/lib/workarea/warnings.rb +6 -6
  42. data/test/lib/workarea/ext/mongoid/embedded_children_test.rb +32 -0
  43. data/test/lib/workarea/latest_version_test.rb +11 -0
  44. data/test/models/workarea/checkout/collect_payment_test.rb +6 -6
  45. data/test/models/workarea/data_file/csv_test.rb +15 -0
  46. data/test/models/workarea/fulfillment/sku_test.rb +5 -5
  47. data/test/models/workarea/insights/cold_searches_test.rb +13 -11
  48. data/test/models/workarea/insights/hot_searches_test.rb +13 -11
  49. data/test/models/workarea/insights/searches_to_improve_test.rb +9 -6
  50. data/test/models/workarea/insights/star_searches_test.rb +5 -4
  51. data/test/models/workarea/insights/trending_searches_test.rb +12 -9
  52. data/test/models/workarea/pricing/calculators/tax_calculator_test.rb +1 -1
  53. data/test/models/workarea/search/admin/releasable_test.rb +5 -7
  54. data/test/models/workarea/segment/life_cycle_test.rb +5 -0
  55. data/test/models/workarea/segment/rules/last_order_test.rb +15 -3
  56. data/test/models/workarea/segment/rules/traffic_referrer_test.rb +10 -8
  57. data/test/models/workarea/segmentable_test.rb +18 -0
  58. data/test/queries/workarea/alerts_test.rb +11 -0
  59. data/test/queries/workarea/order_item_details_test.rb +4 -12
  60. data/test/services/workarea/direct_upload_test.rb +3 -0
  61. data/test/vcr_cassettes/get_latest_version.yml +90 -0
  62. data/test/workers/workarea/deactivate_stale_discounts_test.rb +2 -2
  63. data/workarea-core.gemspec +2 -3
  64. metadata +16 -25
  65. data/app/controllers/workarea/current_referrer.rb +0 -14
  66. data/app/models/workarea/fulfillment/policies/ship.rb +0 -15
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: c6ddffdd6e59cf8d313af5d6e66ce27d527aba9a8d9f226697b8291b16263c00
4
- data.tar.gz: 64a0dcf6ab8aee0fedd8a8f3e55c3ecfbcf552fc9b8e3418daa1cb5759c6dded
3
+ metadata.gz: 350a33b73048c0e24062f5bbd378518bfae1133b2e8d49b58907d4327191ffe9
4
+ data.tar.gz: 74ddf766b88286672ca1ac81924a09b753aa6e20efae0d9fa6eeb1ecbcf8bfa8
5
5
  SHA512:
6
- metadata.gz: 546e65d948e142a5ed31a3d57583690af677416a1ebb0368c6d0b7e36caaeb599ef80390fe9fa9c103ed0ac1747c97e5f05b0b07c05001c2da0655a396d178a2
7
- data.tar.gz: c5aea33c89c5a53ab66133d6a9b9181bf4dda114ee1dcc55fcb4a934e58548b0e6b70993a027f2c9659846b0b866dbe12bb7d60c0263f687aa908ffc427542bb
6
+ metadata.gz: 349c362c45e578680ef743051bc3554c5995013be7a1e348bcac2780fa6a9425b137fbfec9f6d7be3af354996a5fcc895be5af91241e1bf7b7ad1a878cf428d1
7
+ data.tar.gz: 260730f0b9e935712e47230aa9b3a103b971ec190afbadb7b0a15a2b78efd562928f48f990ed0db0a4ecf515084bd65f385145683b33ac2a66fab971cdbeda47
@@ -19,6 +19,10 @@ module Workarea
19
19
  current_visit.metrics
20
20
  end
21
21
 
22
+ def current_referrer
23
+ current_visit.referrer
24
+ end
25
+
22
26
  def update_tracking!(email: current_user&.email)
23
27
  if email.blank?
24
28
  cookies.delete(:email)
@@ -36,12 +36,12 @@ module Workarea
36
36
  # TODO deprecated, remove in v3.6
37
37
  return 'purchase!' if Workarea.config.auto_capture
38
38
 
39
- if @order.items.all?(&:requires_shipping?)
40
- Workarea.config.checkout_payment_action[:shipped]
41
- elsif @order.items.any?(&:requires_shipping?)
42
- Workarea.config.checkout_payment_action[:mixed]
39
+ if @order.items.all?(&:shipping?)
40
+ Workarea.config.checkout_payment_action[:shipping]
41
+ elsif @order.items.any?(&:shipping?)
42
+ Workarea.config.checkout_payment_action[:partial_shipping]
43
43
  else
44
- Workarea.config.checkout_payment_action[:not_shipped]
44
+ Workarea.config.checkout_payment_action[:no_shipping]
45
45
  end
46
46
  end
47
47
  end
@@ -37,6 +37,10 @@ module Workarea
37
37
  Workarea.config.data_file_formats.first
38
38
  end
39
39
 
40
+ def mime_type
41
+ "#{MIME::Types.type_for(file_type).first.to_s}; charset=utf-8"
42
+ end
43
+
40
44
  def process!
41
45
  raise NotImplementedError
42
46
  end
@@ -8,10 +8,6 @@ module Workarea
8
8
  @sku = sku
9
9
  end
10
10
 
11
- def requires_shipping?
12
- false
13
- end
14
-
15
11
  def process(order_item:, fulfillment: nil)
16
12
  raise(NotImplementedError)
17
13
  end
@@ -1,8 +1,8 @@
1
1
  module Workarea
2
2
  class Fulfillment
3
3
  module Policies
4
- class Ignore < Base
5
- def process(*args)
4
+ class Shipping < Base
5
+ def process(*)
6
6
  # no op
7
7
  end
8
8
  end
@@ -15,8 +15,6 @@ module Workarea
15
15
  dragonfly_accessor :file, app: :workarea
16
16
  validates :file, presence: true, if: -> { download? }
17
17
 
18
- delegate :requires_shipping?, to: :policy_object
19
-
20
18
  def self.policies
21
19
  Workarea.config.fulfillment_policies.map(&:demodulize).map(&:underscore)
22
20
  end
@@ -7,7 +7,13 @@ module Workarea
7
7
  end
8
8
 
9
9
  def generate_monthly!
10
- results = generate_results.map { |r| r.merge(query_id: r['_id']) }
10
+ results = generate_results.map do |result|
11
+ result.merge(
12
+ query_id: result['_id'],
13
+ query_string: result['query_string'].presence || result['_id']
14
+ )
15
+ end
16
+
11
17
  create!(results: results) if results.present?
12
18
  end
13
19
 
@@ -33,6 +39,7 @@ module Workarea
33
39
  {
34
40
  '$group' => {
35
41
  '_id' => '$query_id',
42
+ 'query_string' => { '$first' => '$query_string' },
36
43
  'improving_weeks' => { '$sum' => 1 },
37
44
  'revenue_changes' => { '$push' => '$revenue_change' },
38
45
  'orders' => { '$sum' => '$orders' }
@@ -17,6 +17,8 @@ module Workarea
17
17
  field :revenue, type: Float, default: 0.0
18
18
 
19
19
  index(reporting_on: 1, total_results: 1)
20
+ index(query_id: 1)
21
+
20
22
  scope :by_query_id, ->(id) { where(query_id: id) }
21
23
 
22
24
  def self.save_search(query_string, total_results, at: Time.current)
@@ -183,12 +183,22 @@ module Workarea
183
183
  save!
184
184
  end
185
185
 
186
- # Whether this order needs to be shipped
186
+ # Check to see if this order delivers with any of the fulfillment policies
187
+ # passed in.
188
+ #
189
+ # @param [String,Symbol]
190
+ # @return [Boolean]
191
+ #
192
+ def fulfilled_by?(*types)
193
+ items.any? { |i| i.fulfilled_by?(*types) }
194
+ end
195
+
196
+ # Whether any of the order's items require physical shipping.
187
197
  #
188
198
  # @return [Boolean]
189
199
  #
190
200
  def requires_shipping?
191
- items.present? && items.any?(&:requires_shipping?)
201
+ fulfilled_by?(:shipping)
192
202
  end
193
203
 
194
204
  # Whether this order can be purchased, which is defined here as the order
@@ -13,7 +13,7 @@ module Workarea
13
13
  field :total_value, type: Money, default: 0
14
14
  field :total_price, type: Money, default: 0
15
15
  field :via, type: String
16
- field :requires_shipping, type: Boolean, default: true
16
+ field :fulfillment, type: String, default: -> { Workarea.config.fulfillment_policies.first.demodulize.underscore }
17
17
 
18
18
  scope :by_newest, -> { desc(:created_at) }
19
19
 
@@ -30,6 +30,32 @@ module Workarea
30
30
  only_integer: true
31
31
  }
32
32
 
33
+ # To allow for custom policies defining their own methods here
34
+ Workarea.config.fulfillment_policies.each do |class_name|
35
+ define_method "#{class_name.demodulize.underscore}?" do
36
+ fulfillment == class_name.demodulize.underscore
37
+ end
38
+ end
39
+
40
+ # These methods exist for findability
41
+ def shipping?
42
+ fulfillment == 'shipping'
43
+ end
44
+
45
+ def download?
46
+ fulfillment == 'download'
47
+ end
48
+
49
+ # Whether this order has any items that need to be fulfilled by a particular
50
+ # fulfillment policy.
51
+ #
52
+ # @param [Array<String,Symbol>]
53
+ # @return [Boolean]
54
+ #
55
+ def fulfilled_by?(*types)
56
+ types.any? { |t| send("#{t}?") }
57
+ end
58
+
33
59
  # Whether this item is a digital (not-shipped) type of item.
34
60
  #
35
61
  # @return [Boolean]
@@ -39,7 +65,7 @@ module Workarea
39
65
  end
40
66
  Workarea.deprecation.deprecate_methods(
41
67
  Order::Item,
42
- digital?: 'use `!item.requires_shipping?` to determine if this item is shipped.'
68
+ digital?: :fulfilled_by?
43
69
  )
44
70
 
45
71
  # Adds a price adjustment to the item. Does not persist.
@@ -29,13 +29,13 @@ module Workarea
29
29
 
30
30
  def shipped_items_price_adjustments
31
31
  PriceAdjustmentSet.new(
32
- order.items.select(&:requires_shipping?).flat_map(&:price_adjustments)
32
+ order.items.select(&:shipping?).flat_map(&:price_adjustments)
33
33
  )
34
34
  end
35
35
 
36
36
  def not_shipped_items_price_adjustments
37
37
  PriceAdjustmentSet.new(
38
- order.items.reject(&:requires_shipping?).flat_map(&:price_adjustments)
38
+ order.items.reject(&:shipping?).flat_map(&:price_adjustments)
39
39
  )
40
40
  end
41
41
 
@@ -91,7 +91,8 @@ module Workarea
91
91
  has_many :redemptions,
92
92
  class_name: 'Workarea::Pricing::Discount::Redemption'
93
93
 
94
- index(created_at: 1) # for DeactivateStaleDiscounts
94
+ index(active: 1)
95
+ index(updated_at: 1) # for DeactivateStaleDiscounts
95
96
 
96
97
  validates :name, presence: true
97
98
 
@@ -242,7 +243,7 @@ module Workarea
242
243
  # @return [Time]
243
244
  #
244
245
  def auto_deactivates_at
245
- start = last_redemption.try(:created_at) || created_at
246
+ start = last_redemption.try(:created_at) || updated_at
246
247
  start + Workarea.config.discount_staleness_ttl
247
248
  end
248
249
 
@@ -32,11 +32,9 @@ module Workarea
32
32
  { releasable_type: self.class.name, releasable_id: id }
33
33
  )
34
34
 
35
- embedded_relations.each do |name, metadata|
36
- Array.wrap(send(name)).each do |child|
37
- if child.respond_to?(:changesets_with_children)
38
- criteria.merge!(child.changesets_with_children)
39
- end
35
+ embedded_children.each do |child|
36
+ if child.respond_to?(:changesets_with_children)
37
+ criteria.merge!(child.changesets_with_children)
40
38
  end
41
39
  end
42
40
 
@@ -0,0 +1,22 @@
1
+ module Workarea
2
+ module Reports
3
+ class CustomEvent
4
+ include ApplicationDocument
5
+
6
+ field :name, type: String
7
+ field :occurred_at, type: Time
8
+
9
+ index({ occurred_at: 1 });
10
+
11
+ validates :name, presence: true
12
+ validates :occurred_at, presence: true
13
+
14
+ scope :occurred_between, ->(starts_at: nil, ends_at: nil) do
15
+ where(
16
+ :occurred_at.gte => starts_at,
17
+ :occurred_at.lte => ends_at
18
+ )
19
+ end
20
+ end
21
+ end
22
+ end
@@ -3,7 +3,10 @@ module Workarea
3
3
  class Admin
4
4
  module Releasable
5
5
  def facets
6
- super.merge(upcoming_changes: upcoming_release_ids_with_changesets)
6
+ super.merge(
7
+ upcoming_changes: upcoming_release_ids_with_changesets,
8
+ active_by_segment: active_segment_ids
9
+ )
7
10
  end
8
11
 
9
12
  def status
@@ -25,16 +28,25 @@ module Workarea
25
28
  end
26
29
 
27
30
  def content_changesets
28
- return [] unless model.is_a?(Contentable)
29
-
30
- Workarea::Content.for(model)
31
- .changesets
32
- .any_in(release_id: upcoming_release_ids)
31
+ return [] unless content.present?
32
+ content.changesets.any_in(release_id: upcoming_release_ids)
33
33
  end
34
34
 
35
35
  def upcoming_release_ids
36
36
  @upcoming_release_ids ||= Workarea::Release.upcoming.map(&:id)
37
37
  end
38
+
39
+ def content
40
+ return unless model.is_a?(Contentable)
41
+ @content ||= Workarea::Content.for(model)
42
+ end
43
+
44
+ def active_segment_ids
45
+ result = model.active_segment_ids_with_children +
46
+ (content&.active_segment_ids_with_children || [])
47
+
48
+ result.uniq
49
+ end
38
50
  end
39
51
  end
40
52
  end
@@ -14,12 +14,16 @@ module Workarea
14
14
  end
15
15
 
16
16
  def instance
17
- first || create!(rules: default_rules)
17
+ first || create!(name: instance_name, rules: default_rules)
18
18
  end
19
19
 
20
20
  def instance_id
21
21
  name.demodulize.underscore.to_sym
22
22
  end
23
+
24
+ def instance_name
25
+ name.demodulize.underscore.titleize
26
+ end
23
27
  end
24
28
 
25
29
  def self.create!
@@ -32,10 +36,6 @@ module Workarea
32
36
  ].each(&:instance)
33
37
  end
34
38
 
35
- def name
36
- self.class.name.demodulize.underscore.titleize
37
- end
38
-
39
39
  def destroy
40
40
  false
41
41
  end
@@ -2,11 +2,18 @@ module Workarea
2
2
  class Segment
3
3
  module Rules
4
4
  class LastOrder < Base
5
+ field :within, type: Boolean, default: true
5
6
  field :days, type: Integer
6
7
 
7
8
  def qualifies?(visit)
8
- return false if visit.metrics.last_order_at.blank? || days.blank?
9
- visit.metrics.last_order_at >= days.days.ago
9
+ return false if days.blank?
10
+ return !within if visit.metrics.last_order_at.blank?
11
+
12
+ if within?
13
+ visit.metrics.last_order_at >= days.days.ago
14
+ else
15
+ visit.metrics.last_order_at < days.days.ago
16
+ end
10
17
  end
11
18
  end
12
19
  end
@@ -3,14 +3,25 @@ module Workarea
3
3
  module Rules
4
4
  class TrafficReferrer < Base
5
5
  field :medium, type: String
6
- field :source, type: String
6
+ field :source, type: Array, default: []
7
+ field :url, type: String
7
8
 
8
9
  def qualifies?(visit)
9
- return false if medium.blank? && source.blank?
10
- return false unless visit.referrer[:known]
10
+ medium_match?(visit.referrer) ||
11
+ source_match?(visit.referrer) ||
12
+ url_match?(visit.referrer)
13
+ end
14
+
15
+ def medium_match?(referrer)
16
+ medium.to_s.strip.casecmp?(referrer.medium)
17
+ end
18
+
19
+ def source_match?(referrer)
20
+ source.any? { |s| s.strip.casecmp?(referrer.source) }
21
+ end
11
22
 
12
- (medium.blank? || medium.strip.casecmp?(visit.referrer[:medium])) &&
13
- (source.blank? || visit.referrer[:source].to_s =~ /#{source.strip}/i)
23
+ def url_match?(referrer)
24
+ url.present? && referrer.uri.to_s =~ /#{url.strip}/i
14
25
  end
15
26
  end
16
27
  end
@@ -50,10 +50,23 @@ module Workarea
50
50
  end
51
51
  end
52
52
 
53
+ def segmented?
54
+ active_segment_ids.present?
55
+ end
56
+
53
57
  def segments
54
58
  @segments ||= active_segment_ids.blank? ? [] : Segment.in(id: active_segment_ids)
55
59
  end
56
60
 
61
+ def active_segment_ids_with_children
62
+ children = embedded_children.reduce([]) do |memo, child|
63
+ memo += child.active_segment_ids if child.respond_to?(:active_segment_ids)
64
+ memo
65
+ end
66
+
67
+ (active_segment_ids + children).uniq
68
+ end
69
+
57
70
  private
58
71
 
59
72
  def mark_segmented_content
@@ -2,8 +2,11 @@ module Workarea
2
2
  class TrafficReferrer
3
3
  include ApplicationDocument
4
4
 
5
+ field :known, type: Boolean, default: false
5
6
  field :source, type: String
6
7
  field :medium, type: String
7
8
  field :uri, type: String
9
+ field :domain, type: String
10
+ field :term, type: String
8
11
  end
9
12
  end
@@ -78,5 +78,26 @@ module Workarea
78
78
  .asc(:publish_at)
79
79
  .reject(&:has_changes?)
80
80
  end
81
+
82
+ def latest_workarea_version
83
+ return if Rails.env.test?
84
+ return if Rails.env.development? && ENV.fetch('SKIP_VERSION_CHECK', 'true') =~ /true/i
85
+
86
+ Workarea::LatestVersion.get
87
+ end
88
+
89
+ def missing_segments
90
+ @missing_segments ||= begin
91
+ search = Search::AdminSearch.new
92
+ active_by_segment_facet =
93
+ search.facets.detect { |f| f.name == 'active_by_segment' }
94
+
95
+ if active_by_segment_facet.present?
96
+ active_by_segment_facet.results.keys - Segment.pluck(:id).map(&:to_s)
97
+ else
98
+ []
99
+ end
100
+ end
101
+ end
81
102
  end
82
103
  end