workarea-core 3.5.0.beta.1 → 3.5.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.
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