workarea-core 3.5.8 → 3.5.13

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 (61) hide show
  1. checksums.yaml +4 -4
  2. data/app/controllers/workarea/application_controller.rb +0 -5
  3. data/app/controllers/workarea/authentication.rb +6 -0
  4. data/app/helpers/workarea/i18n_helper.rb +1 -1
  5. data/app/middleware/workarea/application_middleware.rb +18 -0
  6. data/app/models/workarea/bulk_action/product_edit.rb +6 -6
  7. data/app/models/workarea/order.rb +3 -3
  8. data/app/models/workarea/releasable.rb +3 -1
  9. data/app/models/workarea/release/changeset.rb +1 -0
  10. data/app/models/workarea/search/admin/pricing_discount.rb +1 -1
  11. data/app/models/workarea/search/storefront.rb +9 -1
  12. data/app/models/workarea/search/storefront/category_query.rb +2 -2
  13. data/app/models/workarea/search/storefront/product.rb +11 -2
  14. data/app/models/workarea/user/password_reset.rb +3 -1
  15. data/app/queries/workarea/product_releases.rb +6 -0
  16. data/app/queries/workarea/search/pagination.rb +4 -1
  17. data/app/queries/workarea/search/product_entries.rb +7 -5
  18. data/app/queries/workarea/search/storefront_search.rb +1 -1
  19. data/app/services/workarea/direct_upload.rb +17 -13
  20. data/app/services/workarea/hash_update.rb +15 -1
  21. data/app/workers/workarea/bulk_index_admin.rb +1 -1
  22. data/app/workers/workarea/bulk_index_products.rb +3 -2
  23. data/app/workers/workarea/bulk_index_searches.rb +4 -4
  24. data/app/workers/workarea/index_category_changes.rb +16 -3
  25. data/app/workers/workarea/process_import.rb +3 -3
  26. data/app/workers/workarea/reindex_release.rb +42 -0
  27. data/app/workers/workarea/synchronize_user_metrics.rb +12 -2
  28. data/lib/tasks/search.rake +10 -4
  29. data/lib/workarea/cache.rb +1 -1
  30. data/lib/workarea/changelog.rake +1 -1
  31. data/lib/workarea/core.rb +3 -0
  32. data/lib/workarea/elasticsearch/document.rb +15 -8
  33. data/lib/workarea/ext/freedom_patches/i18n_js.rb +27 -0
  34. data/lib/workarea/ext/freedom_patches/mongoid_localized_defaults.rb +25 -0
  35. data/lib/workarea/geolocation.rb +1 -9
  36. data/lib/workarea/queues_pauser.rb +26 -0
  37. data/lib/workarea/version.rb +1 -1
  38. data/lib/workarea/visit.rb +8 -1
  39. data/lib/workarea/warnings.rb +3 -4
  40. data/test/helpers/workarea/i18n_helper_test.rb +2 -0
  41. data/test/integration/workarea/authentication_test.rb +10 -0
  42. data/test/integration/workarea/cache_varies_integration_test.rb +31 -0
  43. data/test/lib/workarea/elasticsearch/document_test.rb +20 -0
  44. data/test/lib/workarea/ext/freedom_patches/mongoid_localized_defaults_test.rb +25 -0
  45. data/test/lib/workarea/geolocation_test.rb +3 -3
  46. data/test/models/workarea/releasable_test.rb +13 -0
  47. data/test/models/workarea/search/storefront/product_releases_test.rb +60 -0
  48. data/test/models/workarea/search/storefront_test.rb +13 -0
  49. data/test/models/workarea/segment/rules/geolocation_test.rb +9 -7
  50. data/test/models/workarea/user/password_reset_test.rb +12 -4
  51. data/test/queries/workarea/search/category_browse_test.rb +23 -0
  52. data/test/queries/workarea/search/pagination_test.rb +9 -0
  53. data/test/queries/workarea/search/product_entries_test.rb +11 -0
  54. data/test/queries/workarea/search/product_search_test.rb +16 -0
  55. data/test/services/workarea/direct_upload_test.rb +20 -3
  56. data/test/services/workarea/hash_update_test.rb +12 -12
  57. data/test/workers/workarea/process_import_test.rb +6 -0
  58. data/test/workers/workarea/reindex_release_test.rb +81 -0
  59. data/workarea-core.gemspec +4 -3
  60. metadata +29 -9
  61. data/test/queries/workarea/product_releases_test.rb +0 -56
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 1c2fa2d437187cdfa6f9630002c94d5822cc51f5befb62060e6fd219203d4285
4
- data.tar.gz: 2a9bb9faea3d1d1ed34cbf48938fcee9d1d2673ebcd3acf1cc976fda0370acd5
3
+ metadata.gz: b15a5824ab2746ddff9941773fefcaec1829d2a721a65893b3dac3a2fc7aad8a
4
+ data.tar.gz: 90b49ae058315ad91c7427c0b94c39a86016ed1898bb44d4bad99c9377053d53
5
5
  SHA512:
6
- metadata.gz: 71f63a2c7b0d0824a173279e01c17ddcc10ea4ef7fb2a232874cd54198a867b5fb6cc3735673d7fe41223f1815e28fdc3794d820d25a74eea03d7f381b4e47eb
7
- data.tar.gz: fc5503c7cb0ee41b481c04cf3682b002e019e095c4a6a1e27c9279a40c78012c3712312e82e5c8c2878c5be680aca7137acb9d706af71bf6cf95c54c4112ec52
6
+ metadata.gz: 7ee0b818334a756cbeceacf3be95347e8fa0d46090c0fd3729073c67a04e4c2e17530adc50e772e3bc4f759fdc5d62d7d775e5e2cb0281a5541e063d82fe7526
7
+ data.tar.gz: bd4eb10892770ef2b332a25203f54e703940cec8d45fce3f13a3316636b4ec3c78c0ce4088e504845e7f017ebf75eaa8a0036251f37ed9207d90f3fe6a86be5e
@@ -7,7 +7,6 @@ module Workarea
7
7
 
8
8
  protect_from_forgery
9
9
 
10
- before_action :set_locale
11
10
  after_action :set_flash_header
12
11
 
13
12
  # Cache templates within the scope of a request for development
@@ -33,10 +32,6 @@ module Workarea
33
32
 
34
33
  private
35
34
 
36
- def set_locale
37
- I18n.locale = params[:locale] || I18n.default_locale
38
- end
39
-
40
35
  def set_flash_header
41
36
  messages = flash.map { |k, v| [k, ERB::Util.h(v)] }
42
37
  response.headers['X-Flash-Messages'] = Hash[messages].to_json
@@ -89,6 +89,12 @@ module Workarea
89
89
  else
90
90
  uri = URI.parse(params[:return_to])
91
91
 
92
+ if I18n.locale != I18n.default_locale
93
+ query_hash = Rack::Utils.parse_nested_query(uri.query)
94
+ query_hash['locale'] ||= I18n.locale
95
+ uri.query = query_hash.to_query
96
+ end
97
+
92
98
  if uri.fragment.present?
93
99
  "#{uri.request_uri}##{uri.fragment}"
94
100
  else
@@ -10,7 +10,7 @@ module Workarea
10
10
  result = ''
11
11
 
12
12
  params
13
- .except(:utf8, :controller, :action)
13
+ .except(:utf8, :controller, :action, :locale)
14
14
  .each_pair { |key, value| result << hidden_field_tag(key, value, id: nil) }
15
15
 
16
16
  result.html_safe
@@ -8,6 +8,7 @@ module Workarea
8
8
  request = Rack::Request.new(env)
9
9
  return @app.call(env) if request.path =~ /(jpe?g|png|ico|gif|css|js|svg)$/
10
10
 
11
+ set_locale(env, request)
11
12
  setup_environment(env, request)
12
13
  set_segment_request_headers(env)
13
14
  status, headers, body = @app.call(env)
@@ -16,6 +17,10 @@ module Workarea
16
17
  [status, headers, body]
17
18
  end
18
19
 
20
+ def set_locale(env, request)
21
+ I18n.locale = locale_from_request(env, request) || I18n.default_locale
22
+ end
23
+
19
24
  def setup_environment(env, request)
20
25
  env['workarea.visit'] = Visit.new(env)
21
26
  env['workarea.cache_varies'] = Cache::Varies.new(env['workarea.visit']).to_s
@@ -37,6 +42,19 @@ module Workarea
37
42
 
38
43
  private
39
44
 
45
+ def locale_from_request(env, request)
46
+ return request.params['locale'] if request.params['locale'].present?
47
+
48
+ env_with_method = env.merge(
49
+ method: request.params[Rack::MethodOverride::METHOD_OVERRIDE_PARAM_KEY].presence ||
50
+ request.request_method
51
+ )
52
+ Rails.application.routes.recognize_path(request.path, env_with_method)[:locale]
53
+
54
+ rescue ActionController::RoutingError
55
+ # Return nil since we can't get locale out of this request
56
+ end
57
+
40
58
  def normalize_segment_ids(visit)
41
59
  visit.applied_segments.map(&:id).sort.join(',')
42
60
  end
@@ -36,15 +36,15 @@ module Workarea
36
36
  end
37
37
 
38
38
  def apply_details!(product)
39
- HashUpdate
40
- .new(adds: add_details, removes: remove_details)
41
- .apply(product.details)
39
+ product.details = HashUpdate
40
+ .new(original: product.details, adds: add_details, removes: remove_details)
41
+ .result
42
42
  end
43
43
 
44
44
  def apply_filters!(product)
45
- HashUpdate
46
- .new(adds: add_filters, removes: remove_filters)
47
- .apply(product.filters)
45
+ product.filters = HashUpdate
46
+ .new(original: product.filters, adds: add_filters, removes: remove_filters)
47
+ .result
48
48
  end
49
49
 
50
50
  def apply_pricing!(product)
@@ -47,13 +47,13 @@ module Workarea
47
47
  {
48
48
  placed_at: 1,
49
49
  reminded_at: 1,
50
+ fraud_suspected_at: 1,
50
51
  checkout_started_at: 1,
51
52
  email: 1,
52
- "items[0]._id": 1,
53
- fraud_suspected_at: 1
53
+ "items[0]._id": 1
54
54
  },
55
55
  {
56
- name: 'abandoned_order_email_index',
56
+ name: 'abandoned_order_email_with_fraud_index_v2',
57
57
  background: true
58
58
  }
59
59
  )
@@ -72,7 +72,9 @@ module Workarea
72
72
  release.preview.changesets_for(self).each { |cs| cs.apply_to(result) }
73
73
  result
74
74
  else
75
- Release.with_current(release) { self.class.find(id) }
75
+ Release.with_current(release) do
76
+ Mongoid::QueryCache.uncached { self.class.find(id) }
77
+ end
76
78
  end
77
79
  end
78
80
 
@@ -18,6 +18,7 @@ module Workarea
18
18
  index({ 'document_path.type' => 1, 'document_path.document_id' => 1 })
19
19
  index('changeset.product_ids' => 1)
20
20
  index('original.product_ids' => 1)
21
+ index('releasable_type' => 1, 'releasable_id' => 1)
21
22
 
22
23
  # Finds changeset by whether the passed document is in the document
23
24
  # path of the changeset. Useful for showing embedded changes in the
@@ -21,7 +21,7 @@ module Workarea
21
21
  end
22
22
 
23
23
  def keywords
24
- super + model.promo_codes
24
+ super + Array.wrap(model.try(:promo_codes))
25
25
  end
26
26
 
27
27
  def facets
@@ -82,6 +82,14 @@ module Workarea
82
82
  @changesets ||= Array.wrap(model.try(:changesets_with_children))
83
83
  end
84
84
 
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
91
+ end
92
+
85
93
  def as_document
86
94
  Release.with_current(release_id) do
87
95
  {
@@ -91,7 +99,7 @@ module Workarea
91
99
  active: active,
92
100
  active_segment_ids: active_segment_ids,
93
101
  release_id: release_id,
94
- changeset_release_ids: changesets.map(&:release_id),
102
+ changeset_release_ids: releases.map(&:id),
95
103
  suggestion_content: suggestion_content,
96
104
  created_at: model.created_at,
97
105
  updated_at: model.updated_at,
@@ -113,7 +113,7 @@ module Workarea
113
113
  id: category.id,
114
114
  release_id: 'live',
115
115
  changeset_release_ids: changesets.map(&:release_id).uniq,
116
- query: Categorization.new(rules: category.product_rules).query
116
+ query: Workarea::Search::Categorization.new(rules: category.product_rules).query
117
117
  }
118
118
 
119
119
  Storefront.current_index.save(document, type: 'category')
@@ -130,7 +130,7 @@ module Workarea
130
130
  document = {
131
131
  id: "#{category.id}-#{changeset.release_id}",
132
132
  release_id: changeset.release_id,
133
- query: Categorization.new(rules: category.product_rules).query
133
+ query: Workarea::Search::Categorization.new(rules: category.product_rules).query
134
134
  }
135
135
 
136
136
  Storefront.current_index.save(document, type: 'category')
@@ -122,12 +122,21 @@ module Workarea
122
122
  ProductPrimaryImageUrl.new(model).path
123
123
  end
124
124
 
125
- # Override to include release changesets for pricing, featured products, etc.
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
126
132
  #
127
133
  # @return [Mongoid::Criteria]
128
134
  #
129
135
  def changesets
130
- @product_changesets ||= ProductReleases.new(model).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)
131
140
  end
132
141
 
133
142
  private
@@ -4,7 +4,7 @@ module Workarea
4
4
  include ApplicationDocument
5
5
  include UrlToken
6
6
 
7
- belongs_to :user, class_name: 'Workarea::User'
7
+ belongs_to :user, class_name: 'Workarea::User', index: true
8
8
 
9
9
  index(
10
10
  { created_at: 1 },
@@ -14,6 +14,8 @@ module Workarea
14
14
  def self.setup!(email)
15
15
  user = User.find_by_email(email)
16
16
  return nil unless user
17
+
18
+ where(user_id: user.id).destroy_all
17
19
  create!(user: user)
18
20
  end
19
21
 
@@ -1,4 +1,10 @@
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
+ #
2
8
  class ProductReleases
3
9
  attr_reader :product
4
10
 
@@ -7,7 +7,10 @@ module Workarea
7
7
  end
8
8
 
9
9
  def per_page
10
- params[:per_page].presence || Workarea.config.per_page
10
+ return Workarea.config.per_page if params[:per_page].blank?
11
+
12
+ tmp = params[:per_page].to_i
13
+ tmp > 0 ? tmp : Workarea.config.per_page
11
14
  end
12
15
 
13
16
  def size
@@ -17,15 +17,17 @@ module Workarea
17
17
  end
18
18
 
19
19
  def live_entries
20
- @live_entries ||= @products.flat_map do |product|
21
- index_entries_for(product.without_release)
20
+ @live_entries ||= @products.reduce([]) do |memo, product|
21
+ memo + Array.wrap(index_entries_for(product.without_release))
22
22
  end
23
23
  end
24
24
 
25
25
  def release_entries
26
- @release_entries ||= @products.flat_map do |product|
27
- ProductReleases.new(product).releases.map do |release|
28
- index_entries_for(product.in_release(release))
26
+ @release_entries ||= @products.reduce([]) do |results, product|
27
+ releases = ProductReleases.new(product).releases
28
+
29
+ results + releases.reduce([]) do |memo, release|
30
+ memo + Array.wrap(index_entries_for(product.in_release(release)))
29
31
  end
30
32
  end
31
33
  end
@@ -4,7 +4,7 @@ module Workarea
4
4
  attr_reader :params
5
5
 
6
6
  def initialize(params)
7
- @params = params
7
+ @params = params.with_indifferent_access.except(:per_page)
8
8
  @used_middleware = []
9
9
  end
10
10
 
@@ -6,22 +6,26 @@ module Workarea
6
6
  uri = URI.parse(request_url)
7
7
  url = "#{uri.scheme}://#{uri.host}"
8
8
  url += ":#{uri.port}" unless uri.port.in? [80, 443]
9
+ id = "direct_upload_#{url}"
9
10
 
10
- redis_key = "cors_#{url.optionize}"
11
- return if Workarea.redis.get(redis_key) == 'true'
11
+ response = begin
12
+ Workarea.s3.get_bucket_cors(Configuration::S3.bucket)
13
+ rescue Excon::Error::NotFound
14
+ Excon::Response.new(body: { 'CORSConfiguration' => [] })
15
+ end
12
16
 
13
- response = Workarea.s3.get_bucket_cors(Configuration::S3.bucket)
14
17
  cors = response.data[:body]
15
- cors['CORSConfiguration'] << {
16
- 'ID' => "direct_upload_#{url}",
17
- 'AllowedMethod' => 'PUT',
18
- 'AllowedOrigin' => url,
19
- 'AllowedHeader' => '*'
20
- }
21
- cors['CORSConfiguration'].uniq!
22
-
23
- Workarea.s3.put_bucket_cors(Configuration::S3.bucket, cors)
24
- Workarea.redis.set(redis_key, 'true')
18
+
19
+ unless cors['CORSConfiguration'].pluck('ID').include?(id)
20
+ cors['CORSConfiguration'] << {
21
+ 'ID' => id,
22
+ 'AllowedMethod' => 'PUT',
23
+ 'AllowedOrigin' => url,
24
+ 'AllowedHeader' => '*'
25
+ }
26
+
27
+ Workarea.s3.put_bucket_cors(Configuration::S3.bucket, cors)
28
+ end
25
29
  end
26
30
 
27
31
  attr_reader :type, :filename
@@ -5,13 +5,27 @@ module Workarea
5
5
  parsed.map(&:to_s).map(&:strip).reject(&:blank?) if parsed.present?
6
6
  end
7
7
 
8
- def initialize(adds: [], updates: [], removes: [])
8
+ def initialize(original: {}, adds: [], updates: [], removes: [])
9
+ @original = original
9
10
  @adds = Array(adds).flatten.each_slice(2).to_a
10
11
  @updates = Array(updates).flatten.each_slice(2).to_a
11
12
  @removes = Array(removes).flatten
12
13
  end
13
14
 
15
+ def result
16
+ apply_to(@original.deep_dup)
17
+ end
18
+
19
+ # TODO v3.6 remove this method, doesn't work when the field is localized
20
+ # @deprecated
14
21
  def apply(hash)
22
+ Workarea.deprecation.deprecate_methods(self.class, apply: :result)
23
+ apply_to(hash)
24
+ end
25
+
26
+ private
27
+
28
+ def apply_to(hash)
15
29
  @adds.each do |tuple|
16
30
  key, value = *tuple
17
31
  hash[key] = self.class.parse_values(value)
@@ -13,7 +13,7 @@ module Workarea
13
13
 
14
14
  def perform_by_models(models)
15
15
  return if models.empty?
16
- Workarea::Search::Admin.bulk(documents_for(models))
16
+ Workarea::Search::Admin.bulk { documents_for(models) }
17
17
  end
18
18
 
19
19
  private
@@ -15,8 +15,9 @@ module Workarea
15
15
  return if products.blank?
16
16
  products = Array.wrap(products)
17
17
 
18
- documents = Search::ProductEntries.new(products).map(&:as_bulk_document)
19
- Search::Storefront.bulk(documents)
18
+ Search::Storefront.bulk do
19
+ Search::ProductEntries.new(products).map(&:as_bulk_document)
20
+ end
20
21
 
21
22
  Catalog::Product.in(id: products.map(&:id)).set(last_indexed_at: Time.current)
22
23
  end
@@ -18,11 +18,11 @@ module Workarea
18
18
  end
19
19
 
20
20
  def perform_by_models(searches)
21
- documents = searches.map do |model|
22
- Search::Storefront::Search.new(model).as_bulk_document
21
+ Search::Storefront.bulk do
22
+ searches.map do |model|
23
+ Search::Storefront::Search.new(model).as_bulk_document
24
+ end
23
25
  end
24
-
25
- Search::Storefront.bulk(documents)
26
26
  end
27
27
  end
28
28
 
@@ -4,16 +4,29 @@ module Workarea
4
4
  include Sidekiq::CallbacksWorker
5
5
 
6
6
  sidekiq_options(
7
- enqueue_on: { Catalog::Category => [:save, :save_release_changes], with: -> { [changes] } },
7
+ enqueue_on: {
8
+ Catalog::Category => [:save, :save_release_changes],
9
+ with: -> { [changes, Release.current.present?] }
10
+ },
8
11
  ignore_if: -> { changes['product_ids'].blank? },
9
12
  lock: :until_executing,
10
13
  query_cache: true
11
14
  )
12
15
 
13
- def perform(changes)
16
+ def perform(changes, for_release = false)
14
17
  return unless changes['product_ids'].present?
15
18
 
16
- ids = require_index_ids(*changes['product_ids'])
19
+ ids = if for_release
20
+ # This is a shortcut because if you're resorting products within a release,
21
+ # the `changes` hash doesn't reflect the repositioning within the release,
22
+ # only the difference between what's live and what's in the release.
23
+ #
24
+ # Reindexing all of them is a shortcut to having to manually build a diff
25
+ # between the changesets in the possible affected releases.
26
+ changes['product_ids'].flatten.uniq
27
+ else
28
+ require_index_ids(*changes['product_ids'])
29
+ end
17
30
 
18
31
  if ids.size > max_count
19
32
  ids.each { |id| IndexProduct.perform_async(id) }