workarea-core 3.5.17 → 3.5.22

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 (56) hide show
  1. checksums.yaml +4 -4
  2. data/app/controllers/workarea/current_tracking.rb +6 -2
  3. data/app/controllers/workarea/impersonation.rb +4 -2
  4. data/app/middleware/workarea/application_middleware.rb +5 -2
  5. data/app/models/workarea/metrics/user.rb +18 -8
  6. data/app/models/workarea/order/item.rb +4 -4
  7. data/app/models/workarea/release.rb +8 -0
  8. data/app/models/workarea/search/admin/inventory_sku.rb +5 -1
  9. data/app/models/workarea/search/storefront.rb +1 -5
  10. data/app/models/workarea/search/storefront/product.rb +1 -14
  11. data/app/models/workarea/shipping/service.rb +12 -5
  12. data/app/models/workarea/tax/rate.rb +3 -1
  13. data/app/queries/workarea/product_releases.rb +1 -11
  14. data/app/queries/workarea/search/admin_search.rb +4 -0
  15. data/app/queries/workarea/search/admin_sorting.rb +1 -1
  16. data/app/queries/workarea/search/product_entries.rb +12 -6
  17. data/app/workers/workarea/update_email.rb +33 -0
  18. data/config/initializers/00_configuration.rb +23 -8
  19. data/config/initializers/05_scheduled_jobs.rb +1 -1
  20. data/config/initializers/22_session_store.rb +1 -1
  21. data/config/locales/en.yml +1 -0
  22. data/lib/generators/workarea/install/templates/initializer.rb.erb +0 -12
  23. data/lib/tasks/cache.rake +3 -33
  24. data/lib/tasks/help.rake +4 -43
  25. data/lib/tasks/insights.rake +3 -35
  26. data/lib/tasks/migrate.rake +3 -96
  27. data/lib/tasks/search.rake +6 -68
  28. data/lib/tasks/services.rake +4 -54
  29. data/lib/workarea.rb +10 -0
  30. data/lib/workarea/changelog.rake +1 -1
  31. data/lib/workarea/configuration.rb +4 -3
  32. data/lib/workarea/configuration/administrable_options.rb +2 -1
  33. data/lib/workarea/core/engine.rb +4 -0
  34. data/lib/workarea/scheduled_jobs.rb +1 -1
  35. data/lib/workarea/tasks/cache.rb +43 -0
  36. data/lib/workarea/tasks/help.rb +55 -0
  37. data/lib/workarea/tasks/insights.rb +47 -0
  38. data/lib/workarea/tasks/migrate.rb +106 -0
  39. data/lib/workarea/tasks/search.rb +105 -0
  40. data/lib/workarea/tasks/services.rb +71 -0
  41. data/lib/workarea/version.rb +1 -1
  42. data/lib/workarea/visit.rb +8 -1
  43. data/lib/workarea/warnings.rb +1 -1
  44. data/test/generators/workarea/install_generator_test.rb +0 -2
  45. data/test/integration/workarea/authentication_test.rb +2 -1
  46. data/test/lib/workarea/scheduled_jobs_test.rb +1 -5
  47. data/test/models/workarea/data_file/csv_test.rb +2 -1
  48. data/test/models/workarea/metrics/user_test.rb +55 -52
  49. data/test/models/workarea/order/item_test.rb +9 -0
  50. data/test/models/workarea/shipping/service_test.rb +26 -0
  51. data/test/queries/workarea/search/admin_search_test.rb +10 -0
  52. data/test/workers/workarea/status_reporter_test.rb +3 -1
  53. data/test/workers/workarea/update_email_test.rb +39 -0
  54. metadata +10 -4
  55. data/app/workers/workarea/update_payment_profile_email.rb +0 -22
  56. data/test/workers/workarea/update_payment_profile_email_test.rb +0 -27
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 8d24b6af137b806311dbd5d13fb13a57f8693f99d767f94fb3144994fe9c7028
4
- data.tar.gz: dbbfb80358c11fc03b2e470ac741f615dc2d68e5f9c6c0d55b3b69567eaa4d02
3
+ metadata.gz: 48dfcf76f720a117fd8f3a16cdbef856816837692dc53f731b38729c5d37b9ef
4
+ data.tar.gz: a1ffa660f618f5d1b428ce4db9c6d768f0a12c3490ea45f02d699fa4b5ff441f
5
5
  SHA512:
6
- metadata.gz: a7f587ddc4e1e9c425eaace526f562f99ec5885002f833fd33a134164d3d20a405938a1e762b3cfd221824a77f435ebb79383591ae2bd0c3b81bd407ba0ceb12
7
- data.tar.gz: 61da7b2aa4a72d5e0b5456b401f9124769fa5f43c601d33e6e14b6a64bf820910bed36faf6992a1865e603455710b05647fa62b62488fa8fd973d33edac02564
6
+ metadata.gz: 509b598d21369c93345837d160e2a95d271c4c0d06276192f050a2997badec0b1542a012be9c1ed10d7bb176816372825abc08370d8fb67b3cd961a96ecb6246
7
+ data.tar.gz: 0cf582e5e981a1524f7b1cb2229f542c2d99f8b152802386aa68a42a09de7c84602ee8c07659011ecc3a1bec1ca6ddcfdb85c2e7aa8ecdcf6333c42808b81027
@@ -1,7 +1,6 @@
1
1
  module Workarea
2
2
  module CurrentTracking
3
3
  extend ActiveSupport::Concern
4
- include HttpCaching
5
4
 
6
5
  included do
7
6
  before_action :ensure_current_metrics
@@ -27,7 +26,12 @@ module Workarea
27
26
  if email.blank?
28
27
  cookies.delete(:email)
29
28
  elsif email != cookies.signed[:email]
30
- Metrics::User.find_or_initialize_by(id: email).merge!(current_visit&.metrics)
29
+ unless impersonating?
30
+ Metrics::User
31
+ .find_or_initialize_by(id: email)
32
+ .merge_views!(current_visit&.metrics)
33
+ end
34
+
31
35
  cookies.permanent.signed[:email] = email
32
36
  end
33
37
 
@@ -15,10 +15,11 @@ module Workarea
15
15
  session[:user_id] = user.id.to_s
16
16
 
17
17
  user.mark_impersonated_by!(current_user)
18
- @current_user = user
18
+ update_tracking!(email: user.email)
19
19
  end
20
20
 
21
21
  def stop_impersonation
22
+ update_tracking!(email: current_admin.email)
22
23
  session[:user_id] = current_admin.id.to_s
23
24
  session.delete(:admin_id)
24
25
  end
@@ -39,7 +40,8 @@ module Workarea
39
40
  end
40
41
 
41
42
  def current_impersonation
42
- @current_impersonation ||= User.find(session[:user_id])
43
+ return @current_impersonation if defined?(@current_impersonation)
44
+ @current_impersonation = User.find(session[:user_id]) rescue nil
43
45
  end
44
46
 
45
47
  def admin_browse_as_guest
@@ -1,12 +1,15 @@
1
1
  module Workarea
2
2
  class ApplicationMiddleware
3
+ ASSET_REGEX = /(jpe?g|png|ico|gif|bmp|webp|tif?f|css|js|svg|otf|ttf|woff|woff2)$/
4
+
3
5
  def initialize(app)
4
6
  @app = app
5
7
  end
6
8
 
7
9
  def call(env)
8
10
  request = Rack::Request.new(env)
9
- return @app.call(env) if request.path =~ /(jpe?g|png|ico|gif|css|js|svg)$/
11
+ env['workarea.asset_request'] = request.path =~ ASSET_REGEX
12
+ return @app.call(env) if env['workarea.asset_request']
10
13
 
11
14
  set_locale(env, request)
12
15
  setup_environment(env, request)
@@ -25,7 +28,7 @@ module Workarea
25
28
  env['workarea.visit'] = Visit.new(env)
26
29
  env['workarea.cache_varies'] = Cache::Varies.new(env['workarea.visit']).to_s
27
30
  env['rack-cache.cache_key'] = Cache::RackCacheKey
28
- env['rack-cache.force-pass'] = env['workarea.visit'].admin?
31
+ env['rack-cache.force-pass'] = env['workarea.visit'].admin? && !env['workarea.asset_request']
29
32
  end
30
33
 
31
34
  def set_segment_request_headers(env)
@@ -110,6 +110,8 @@ module Workarea
110
110
  end
111
111
 
112
112
  def merge!(other)
113
+ return if other.blank?
114
+
113
115
  # To recalculate average_order_value
114
116
  self.orders += other.orders
115
117
  self.revenue += other.revenue
@@ -132,14 +134,8 @@ module Workarea
132
134
  update['$max'] = { last_order_at: other.last_order_at.utc } if other.last_order_at.present?
133
135
 
134
136
  self.class.collection.update_one({ _id: id }, update, upsert: true)
137
+ other.delete
135
138
 
136
- self.class.save_affinity(
137
- id: id,
138
- action: 'viewed',
139
- product_ids: other.viewed.product_ids,
140
- category_ids: other.viewed.category_ids,
141
- search_ids: other.viewed.search_ids
142
- )
143
139
  self.class.save_affinity(
144
140
  id: id,
145
141
  action: 'purchased',
@@ -148,7 +144,21 @@ module Workarea
148
144
  search_ids: other.purchased.search_ids
149
145
  )
150
146
 
151
- reload
147
+ merge_views!(other)
148
+ end
149
+
150
+ def merge_views!(other)
151
+ return if other.blank?
152
+
153
+ self.class.save_affinity(
154
+ id: id,
155
+ action: 'viewed',
156
+ product_ids: other.viewed.product_ids,
157
+ category_ids: other.viewed.category_ids,
158
+ search_ids: other.viewed.search_ids
159
+ )
160
+
161
+ reload rescue self # save_affinity might not have actually persisted anything
152
162
  end
153
163
  end
154
164
  end
@@ -33,17 +33,17 @@ module Workarea
33
33
  # To allow for custom policies defining their own methods here
34
34
  Workarea.config.fulfillment_policies.each do |class_name|
35
35
  define_method "#{class_name.demodulize.underscore}?" do
36
- fulfillment == class_name.demodulize.underscore
36
+ fulfilled_by?(class_name.demodulize.underscore)
37
37
  end
38
38
  end
39
39
 
40
40
  # These methods exist for findability
41
41
  def shipping?
42
- fulfillment == 'shipping'
42
+ fulfilled_by?('shipping')
43
43
  end
44
44
 
45
45
  def download?
46
- fulfillment == 'download'
46
+ fulfilled_by?('download')
47
47
  end
48
48
 
49
49
  # Whether this order has any items that need to be fulfilled by a particular
@@ -53,7 +53,7 @@ module Workarea
53
53
  # @return [Boolean]
54
54
  #
55
55
  def fulfilled_by?(*types)
56
- types.any? { |t| send("#{t}?") }
56
+ types.map(&:to_s).include?(fulfillment)
57
57
  end
58
58
 
59
59
  # Whether this item is a digital (not-shipped) type of item.
@@ -129,6 +129,14 @@ module Workarea
129
129
  scoped.sort_by { |r| [r.publish_at, r.created_at] }
130
130
  end
131
131
 
132
+ def self.schedule_affected_by_changesets(changesets)
133
+ changesets
134
+ .uniq(&:release)
135
+ .reject { |cs| cs.release.blank? }
136
+ .flat_map { |cs| [cs.release] + cs.release.scheduled_after }
137
+ .uniq
138
+ end
139
+
132
140
  def as_current
133
141
  self.class.with_current(self) { yield }
134
142
  end
@@ -11,7 +11,11 @@ module Workarea
11
11
  end
12
12
 
13
13
  def jump_to_text
14
- "#{model.id} (#{model.available} available)"
14
+ I18n.t(
15
+ 'workarea.inventory_sku.jump_to_text',
16
+ id: model.id,
17
+ count: model.available_to_sell
18
+ )
15
19
  end
16
20
 
17
21
  def jump_to_position
@@ -83,11 +83,7 @@ module Workarea
83
83
  end
84
84
 
85
85
  def releases
86
- changesets
87
- .uniq(&:release)
88
- .reject { |cs| cs.release.blank? }
89
- .flat_map { |cs| [cs.release] + cs.release.scheduled_after }
90
- .uniq
86
+ Release.schedule_affected_by_changesets(changesets)
91
87
  end
92
88
 
93
89
  def as_document
@@ -122,21 +122,8 @@ module Workarea
122
122
  ProductPrimaryImageUrl.new(model).path
123
123
  end
124
124
 
125
- # All {Releasable}s that could affect the product's Elasticsearch document
126
- # should add their changesets to this method.
127
- #
128
- # @example Add to the changesets affecting a product in a decorator
129
- # def changesets
130
- # super.merge(SomeReleasable.for_product(product.id).changesets_with_children)
131
- # end
132
- #
133
- # @return [Mongoid::Criteria]
134
- #
135
125
  def changesets
136
- criteria = model.changesets_with_children
137
- pricing.each { |ps| criteria.merge!(ps.changesets_with_children) }
138
- criteria.merge!(FeaturedProducts.changesets(model.id))
139
- criteria.includes(:release)
126
+ ProductReleases.new(model).changesets
140
127
  end
141
128
 
142
129
  private
@@ -37,15 +37,22 @@ module Workarea
37
37
  end
38
38
 
39
39
  def self.by_price(price)
40
- cache.select do |method|
41
- (method.subtotal_min.nil? || method.subtotal_min <= price) &&
42
- (method.subtotal_max.nil? || method.subtotal_max >= price)
40
+ cache.select do |service|
41
+ (service.subtotal_min.nil? || service.subtotal_min <= price) &&
42
+ (service.subtotal_max.nil? || service.subtotal_max >= price)
43
43
  end
44
44
  end
45
45
 
46
46
  def self.find_tax_code(carrier, name)
47
- method = find_by(carrier: carrier, name: name) rescue nil
48
- method.try(:tax_code)
47
+ service = find_by(carrier: carrier, name: name) rescue nil
48
+ service.present? ? service.tax_code : default_tax_code(carrier, name)
49
+ end
50
+
51
+ def self.default_tax_code(carrier, name)
52
+ default = Workarea.config.default_shipping_service_tax_code
53
+ return default unless default.respond_to?(:call)
54
+
55
+ default.call(carrier, name)
49
56
  end
50
57
 
51
58
  def find_rate(price = 0.to_m)
@@ -57,7 +57,9 @@ module Workarea
57
57
  percentage_field = super
58
58
  return percentage_field unless percentage_field.zero?
59
59
 
60
- country_percentage + region_percentage + postal_code_percentage
60
+ [country_percentage, region_percentage, postal_code_percentage]
61
+ .compact
62
+ .sum
61
63
  end
62
64
  end
63
65
  end
@@ -1,10 +1,4 @@
1
1
  module Workarea
2
- #
3
- # TODO remove in v3.6
4
- #
5
- # This is no longer used, this logic was moved into the search models to allow
6
- # it to be used for any model (not just products).
7
- #
8
2
  class ProductReleases
9
3
  attr_reader :product
10
4
 
@@ -13,11 +7,7 @@ module Workarea
13
7
  end
14
8
 
15
9
  def releases
16
- changesets
17
- .uniq(&:release)
18
- .reject { |cs| cs.release.blank? }
19
- .flat_map { |cs| [cs.release] + cs.release.scheduled_after }
20
- .uniq
10
+ Release.schedule_affected_by_changesets(changesets)
21
11
  end
22
12
 
23
13
  # All {Releasable}s that could affect the product's Elasticsearch document
@@ -11,6 +11,10 @@ module Workarea
11
11
  def self.available_sorts
12
12
  AdminSorting.available_sorts
13
13
  end
14
+
15
+ def default_admin_sort
16
+ [{ _score: :desc }, { updated_at: :desc }]
17
+ end
14
18
  end
15
19
  end
16
20
  end
@@ -16,7 +16,7 @@ module Workarea
16
16
  end
17
17
 
18
18
  def default_admin_sort
19
- [{ _score: :desc }, { updated_at: :desc }]
19
+ [{ updated_at: :desc }, { _score: :desc }]
20
20
  end
21
21
 
22
22
  def user_selected_sort
@@ -18,17 +18,23 @@ module Workarea
18
18
 
19
19
  def live_entries
20
20
  @live_entries ||= @products.reduce([]) do |memo, product|
21
- memo + Array.wrap(index_entries_for(product.without_release))
21
+ memo + live_entries_for(product)
22
22
  end
23
23
  end
24
24
 
25
25
  def release_entries
26
- @release_entries ||= @products.reduce([]) do |results, product|
27
- releases = ProductReleases.new(product).releases
26
+ @release_entries ||= @products.reduce([]) do |memo, product|
27
+ memo + release_entries_for(product)
28
+ end
29
+ end
30
+
31
+ def live_entries_for(product)
32
+ Array.wrap(index_entries_for(product.without_release))
33
+ end
28
34
 
29
- results + releases.reduce([]) do |memo, release|
30
- memo + Array.wrap(index_entries_for(product.in_release(release)))
31
- end
35
+ def release_entries_for(product)
36
+ ProductReleases.new(product).releases.reduce([]) do |memo, release|
37
+ memo + Array.wrap(index_entries_for(product.in_release(release)))
32
38
  end
33
39
  end
34
40
 
@@ -0,0 +1,33 @@
1
+ module Workarea
2
+ class UpdateEmail
3
+ include Sidekiq::Worker
4
+ include Sidekiq::CallbacksWorker
5
+
6
+ sidekiq_options(
7
+ enqueue_on: { User => :update, with: -> { [id, changes] } }
8
+ )
9
+
10
+ def perform(id, changes)
11
+ return unless changes['email'].present? && changes['email'].first.present?
12
+
13
+ old_email, new_email = changes['email']
14
+ update_payment_profile(id, old_email, new_email)
15
+ update_metrics(old_email, new_email)
16
+ end
17
+
18
+ def update_payment_profile(id, old_email, new_email)
19
+ user = User.find(id)
20
+ user.email = old_email # set old email so we lookup by old email value
21
+
22
+ Payment::Profile.update_email(PaymentReference.new(user), new_email)
23
+ end
24
+
25
+ def update_metrics(old_email, new_email)
26
+ old_metrics = Metrics::User.find(old_email) rescue nil
27
+ return if old_metrics.blank?
28
+
29
+ new_metrics = Metrics::User.find_or_initialize_by(id: new_email)
30
+ new_metrics.merge!(old_metrics)
31
+ end
32
+ end
33
+ end
@@ -76,6 +76,17 @@ Workarea::Configuration.define_fields do
76
76
  zip: '19106'
77
77
  },
78
78
  description: 'Origin location for calculating shipping costs'
79
+
80
+ # This can be overwritten within the app to use a proc for more complex
81
+ # scenarios.
82
+ field 'Default Shipping Service Tax Code',
83
+ type: String,
84
+ allow_blank: true,
85
+ description: %(
86
+ Tax code assigned to shipping options when an existing service does
87
+ not exist. This is useful for third-party gateways to assign tax codes
88
+ to dynamically generated options.
89
+ ).squish
79
90
  end
80
91
 
81
92
  fieldset 'Payment', namespaced: false do
@@ -116,11 +127,6 @@ Workarea::Configuration.define_fields do
116
127
  end
117
128
 
118
129
  fieldset 'Search', namespaced: false do
119
- field 'Default Search Facet Result Sizes',
120
- type: :integer,
121
- default: 10,
122
- description: 'The number of filter results returned for each filter type.'
123
-
124
130
  field 'Search Facet Result Sizes',
125
131
  type: :hash,
126
132
  values_type: :integer,
@@ -128,7 +134,15 @@ Workarea::Configuration.define_fields do
128
134
  description: %(
129
135
  The number of filter results returned for any specified filter type. If no
130
136
  size is defined for a filter type, the default will be what is specified
131
- in the default config above.
137
+ in the default config below.
138
+ ).squish
139
+
140
+ field 'Default Search Facet Result Sizes',
141
+ type: :integer,
142
+ default: 10,
143
+ description: %(
144
+ The number of filter results returned for each filter type when not
145
+ specified above.
132
146
  ).squish
133
147
 
134
148
  field 'Search Size Facet Sort',
@@ -156,14 +170,15 @@ Workarea::Configuration.define_fields do
156
170
  end
157
171
 
158
172
  fieldset 'Communication', namespaced: false do
173
+
159
174
  field 'Email From',
160
175
  type: :string,
161
- default: 'noreply@example.com',
176
+ default: -> { "#{Workarea.config.site_name} <noreply@#{Workarea.config.host}>" },
162
177
  description: 'The email address used as the sender of system emails'
163
178
 
164
179
  field 'Email To',
165
180
  type: :string,
166
- default: 'customerservice@example.com',
181
+ default: -> { "#{Workarea.config.site_name} <customerservice@#{Workarea.config.host}>" },
167
182
  description: 'The email address that receives user generated emails, e.g. contact us inquiries'
168
183
 
169
184
  field 'Inquiry Subjects',
@@ -1,4 +1,4 @@
1
- unless Workarea.config.skip_service_connections
1
+ unless Workarea.skip_services?
2
2
  Sidekiq::Cron::Job.create(
3
3
  name: 'Workarea::CleanInventoryTransactions',
4
4
  klass: 'Workarea::CleanInventoryTransactions',