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
@@ -17,11 +17,11 @@ module Workarea
17
17
  import.process!
18
18
 
19
19
  ensure
20
- if import.error?
20
+ if import&.error?
21
21
  Admin::DataFileMailer.import_error(id).deliver_now
22
- elsif import.failure?
22
+ elsif import&.failure?
23
23
  Admin::DataFileMailer.import_failure(id).deliver_now
24
- else
24
+ elsif import.present?
25
25
  Admin::DataFileMailer.import(id).deliver_now
26
26
  end
27
27
  end
@@ -0,0 +1,42 @@
1
+ module Workarea
2
+ class ReindexRelease
3
+ include Sidekiq::Worker
4
+ include Sidekiq::CallbacksWorker
5
+
6
+ sidekiq_options(
7
+ enqueue_on: {
8
+ Release => :save,
9
+ only_if: -> { publish_at_changed? },
10
+ with: -> { [id, publish_at_was, publish_at] }
11
+ },
12
+ queue: 'high'
13
+ )
14
+
15
+ def perform(id, previous_publish_at, new_publish_at)
16
+ rescheduled_release = Release.find(id)
17
+ earlier, later = if previous_publish_at.present? && new_publish_at.present?
18
+ [previous_publish_at, new_publish_at].sort
19
+ elsif previous_publish_at.present?
20
+ [previous_publish_at, nil]
21
+ else
22
+ [new_publish_at, nil]
23
+ end
24
+
25
+ affected_releases = Release.scheduled(after: earlier, before: later).includes(:changesets).to_a
26
+ affected_releases += [rescheduled_release]
27
+ affected_releases.uniq!
28
+
29
+ affected_models = affected_releases.flat_map(&:changesets).flat_map(&:releasable)
30
+
31
+ affected_releases.each do |release|
32
+ affected_models.each do |releasable|
33
+ Search::Storefront.new(releasable.in_release(release)).destroy
34
+
35
+ # Different models have different indexing workers, running callbacks
36
+ # ensures the appropriate worker is triggered
37
+ releasable.run_callbacks(:save_release_changes)
38
+ end
39
+ end
40
+ end
41
+ end
42
+ end
@@ -19,8 +19,18 @@ module Workarea
19
19
 
20
20
  def perform(id)
21
21
  user = User.find(id)
22
- metrics = Metrics::User.find_or_create_by(id: user.email)
23
- metrics.set(admin: user.admin?, tags: user.tags)
22
+
23
+ Metrics::User.collection.update_one(
24
+ { _id: user.email },
25
+ {
26
+ '$set' => {
27
+ admin: user.admin?,
28
+ tags: user.tags,
29
+ updated_at: Time.current.utc
30
+ }
31
+ },
32
+ upsert: true
33
+ )
24
34
  end
25
35
  end
26
36
  end
@@ -16,7 +16,9 @@ namespace :workarea do
16
16
  task admin: :environment do
17
17
  setup
18
18
  puts 'Indexing admin...'
19
- Workarea::Search::Admin.reset_indexes!
19
+ Workarea::QueuesPauser.with_paused_queues do
20
+ Workarea::Search::Admin.reset_indexes!
21
+ end
20
22
 
21
23
  Mongoid.models.each do |klass|
22
24
  next unless Workarea::Search::Admin.for(klass.first).present?
@@ -36,8 +38,10 @@ namespace :workarea do
36
38
  setup
37
39
  puts 'Indexing storefront...'
38
40
 
39
- Workarea::Search::Storefront.reset_indexes!
40
- Workarea::Search::Storefront.ensure_dynamic_mappings
41
+ Workarea::QueuesPauser.with_paused_queues do
42
+ Workarea::Search::Storefront.reset_indexes!
43
+ Workarea::Search::Storefront.ensure_dynamic_mappings
44
+ end
41
45
 
42
46
  # This code finds all unique filters for products so we can index a sample
43
47
  # product for each to ensure the dynamic mappings get created.
@@ -83,7 +87,9 @@ namespace :workarea do
83
87
  setup
84
88
  puts 'Indexing help...'
85
89
 
86
- Workarea::Search::Help.reset_indexes!
90
+ Workarea::QueuesPauser.with_paused_queues do
91
+ Workarea::Search::Help.reset_indexes!
92
+ end
87
93
 
88
94
  Workarea::Help::Article.all.each_by(Workarea.config.bulk_index_batch_size) do |help_article|
89
95
  Workarea::Search::Help.new(help_article).save
@@ -70,7 +70,7 @@ module Workarea
70
70
  end
71
71
 
72
72
  def to_s
73
- @to_s ||= @varies.map { |v| visit.instance_exec(&v).to_s }.join(':')
73
+ @to_s ||= ([I18n.locale] + @varies.map { |v| visit.instance_exec(&v).to_s }).join(':')
74
74
  end
75
75
  end
76
76
  end
@@ -67,7 +67,7 @@ namespace :workarea do
67
67
  message << " #{line.strip}"
68
68
  end
69
69
  end
70
- message << " #{entry[:author]}\n"
70
+ message << "\n #{entry[:author]}\n"
71
71
  end
72
72
 
73
73
  # ensure and append to changelog
@@ -131,6 +131,8 @@ require 'workarea/ext/freedom_patches/dragonfly_callable_url_host'
131
131
  require 'workarea/ext/freedom_patches/active_support_duration'
132
132
  require 'workarea/ext/freedom_patches/premailer'
133
133
  require 'workarea/ext/freedom_patches/referer_parser'
134
+ require 'workarea/ext/freedom_patches/mongoid_localized_defaults'
135
+ require 'workarea/ext/freedom_patches/i18n_js'
134
136
  require 'workarea/ext/mongoid/list_field'
135
137
  require 'workarea/ext/mongoid/each_by'
136
138
  require 'workarea/ext/mongoid/except'
@@ -219,6 +221,7 @@ require 'workarea/mail_interceptor'
219
221
  require 'workarea/visit'
220
222
  require 'workarea/warnings'
221
223
  require 'workarea/latest_version'
224
+ require 'workarea/queues_pauser'
222
225
 
223
226
  #
224
227
  # Engines
@@ -44,22 +44,27 @@ module Workarea
44
44
 
45
45
  def save(document, options = {})
46
46
  options = options.merge(type: type)
47
- I18n.for_each_locale { current_index.save(document, options) }
47
+ current_index.save(document, options)
48
48
  end
49
49
 
50
- def bulk(documents, options = {})
50
+ def bulk(documents = [], options = {})
51
51
  options = options.merge(type: type)
52
- I18n.for_each_locale { current_index.bulk(documents, options) }
52
+
53
+ if block_given?
54
+ I18n.for_each_locale { current_index.bulk(Array.wrap(yield), options) }
55
+ else
56
+ current_index.bulk(documents, options)
57
+ end
53
58
  end
54
59
 
55
60
  def update(document, options = {})
56
61
  options = options.merge(type: type)
57
- I18n.for_each_locale { current_index.update(document, options) }
62
+ current_index.update(document, options)
58
63
  end
59
64
 
60
65
  def delete(id, options = {})
61
66
  options = options.merge(type: type)
62
- I18n.for_each_locale { current_index.delete(id, options) }
67
+ current_index.delete(id, options)
63
68
  end
64
69
 
65
70
  def count(query = nil, options = {})
@@ -97,12 +102,14 @@ module Workarea
97
102
  end
98
103
 
99
104
  def save(options = {})
100
- document = as_document.merge(Serializer.serialize(model))
101
- self.class.save(document, options)
105
+ I18n.for_each_locale do
106
+ document = as_document.merge(Serializer.serialize(model))
107
+ self.class.save(document, options)
108
+ end
102
109
  end
103
110
 
104
111
  def destroy(options = {})
105
- self.class.delete(id, options)
112
+ I18n.for_each_locale { self.class.delete(id, options) }
106
113
  end
107
114
  end
108
115
  end
@@ -0,0 +1,27 @@
1
+ module I18n
2
+ module JS
3
+ class FallbackLocales
4
+ # i18n-js uses just the second part of this check out-of-the-box. This
5
+ # causes the I18n fallbacks to get autoloaded without the developer
6
+ # knowing.
7
+ #
8
+ # This surfaces in tests. System or integration tests will do this check
9
+ # for compiling assets, then I18n fallbacks get autoloaded. So this shows
10
+ # as some tests not having fallbacks if they run before one of those tests
11
+ # or magically having fallbacks if they run after one of those types of
12
+ # tests.
13
+ #
14
+ # Adding the `respond_to?` check doesn't cause autoload, but will return
15
+ # `true` if fallbacks are enabled. Retain the original check because we
16
+ # want the current I18n::JS backend to be checked, once fallbacks are
17
+ # `require`d `I18n.respond_to?(:fallbacks)` will always return `true`.
18
+ #
19
+ # See also: https://github.com/fnando/i18n-js/blob/master/lib/i18n/js/fallback_locales.rb#L49-L58
20
+ #
21
+ def using_i18n_fallbacks_module?
22
+ I18n.respond_to?(:fallbacks) &&
23
+ I18n::JS.backend.class.included_modules.include?(I18n::Backend::Fallbacks)
24
+ end
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,25 @@
1
+ module Mongoid
2
+ module Fields
3
+ module LocalizedDefaults
4
+ def create_accessors(name, meth, options = {})
5
+ super
6
+
7
+ if options[:localize]
8
+ field = fields[name]
9
+
10
+ define_method meth do |*args|
11
+ result = super(*args)
12
+ return result unless result.nil?
13
+
14
+ default_name = field.send(:default_name)
15
+ return send(default_name) if respond_to?(default_name)
16
+
17
+ field.default_val
18
+ end
19
+ end
20
+ end
21
+ end
22
+
23
+ ClassMethods.prepend(LocalizedDefaults)
24
+ end
25
+ end
@@ -57,15 +57,7 @@ module Workarea
57
57
  end
58
58
 
59
59
  def names
60
- @names ||= [
61
- postal_code,
62
- city,
63
- region,
64
- subdivision&.name,
65
- country&.alpha2,
66
- country&.alpha3,
67
- country&.name
68
- ].reject(&:blank?)
60
+ @names ||= [postal_code, city, subdivision&.name, country&.name].reject(&:blank?)
69
61
  end
70
62
 
71
63
  private
@@ -0,0 +1,26 @@
1
+ module Workarea
2
+ module QueuesPauser
3
+ extend self
4
+
5
+ def pause_queues!
6
+ pauser = Sidekiq::Throttled::QueuesPauser.instance
7
+ queues.each { |queue| pauser.pause!(queue) }
8
+ end
9
+
10
+ def resume_queues!
11
+ pauser = Sidekiq::Throttled::QueuesPauser.instance
12
+ queues.each { |queue| pauser.resume!(queue) }
13
+ end
14
+
15
+ def with_paused_queues(&block)
16
+ pause_queues!
17
+ yield
18
+ ensure
19
+ resume_queues!
20
+ end
21
+
22
+ def queues
23
+ Configuration::Sidekiq.queues
24
+ end
25
+ end
26
+ end
@@ -2,7 +2,7 @@ module Workarea
2
2
  module VERSION
3
3
  MAJOR = 3
4
4
  MINOR = 5
5
- PATCH = 8
5
+ PATCH = 13
6
6
  PRE = nil
7
7
  STRING = [MAJOR, MINOR, PATCH, PRE].compact.join('.')
8
8
 
@@ -38,7 +38,10 @@ module Workarea
38
38
  end
39
39
 
40
40
  def current_email
41
- cookies.signed[:email]
41
+ # For performance, prefer to use the cookie. The fallback to looking it up
42
+ # by user is a failsafe against a blank email cookie (e.g. from a raised
43
+ # error or poor application coding).
44
+ cookies.signed[:email].presence || (email_from_user_id if logged_in?)
42
45
  end
43
46
 
44
47
  def metrics
@@ -91,5 +94,9 @@ module Workarea
91
94
  def blank_metrics
92
95
  @blank_metrics ||= Metrics::User.new
93
96
  end
97
+
98
+ def email_from_user_id
99
+ User.find(session[:user_id]).email rescue nil
100
+ end
94
101
  end
95
102
  end
@@ -51,13 +51,12 @@ db.getSiblingDB("admin").runCommand( { setParameter: 1, notablescan: 0 } )
51
51
  **************************************************
52
52
  ⛔️ WARNING: Dragonfly is configured to use the filesystem.
53
53
 
54
- This means all dragonfly assets (assets, product images, etc.) will be stored
54
+ This means all Dragonfly assets (assets, product images, etc.) will be stored
55
55
  locally and not accessible to all servers within your environment.
56
56
 
57
57
  We recommend using S3 when running in a live environment by setting
58
- WORKAREA_S3_REGION and WORKAREA_S3_BUCKET_NAME in your environment variables.
59
- Workarea will automatically configure Dragonfly to use S3 if those values
60
- are present.
58
+ WORKAREA_S3_REGION and WORKAREA_S3_BUCKET_NAME in your environment variables,
59
+ and setting `Workarea.config.asset_store = :s3` in an initializer.
61
60
  **************************************************
62
61
  eos
63
62
  end
@@ -7,12 +7,14 @@ module Workarea
7
7
  params[:controller] = 'controller'
8
8
  params[:action] = 'action'
9
9
  params[:foo] = 'bar'
10
+ params[:locale] = 'es'
10
11
 
11
12
  assert_includes(switch_locale_fields, 'foo')
12
13
  assert_includes(switch_locale_fields, 'bar')
13
14
  refute_includes(switch_locale_fields, 'utf8')
14
15
  refute_includes(switch_locale_fields, 'controller')
15
16
  refute_includes(switch_locale_fields, 'action')
17
+ refute_includes(switch_locale_fields, 'locale')
16
18
  end
17
19
  end
18
20
  end
@@ -185,5 +185,15 @@ module Workarea
185
185
  get '/test_logout'
186
186
  refute(cookies[:email].present?)
187
187
  end
188
+
189
+ def test_ensures_locale_passthrough_for_return_to
190
+ set_locales(available: [:en, :es], default: :en, current: :en)
191
+
192
+ get '/login_required', params: { locale: 'es', return_to: '/blah?foo=bar' }
193
+ get '/test_login', params: { user_id: @user.id }
194
+ assert(response.redirect?)
195
+ assert_match('locale=es', response.location)
196
+ assert_match('foo=bar', response.location)
197
+ end
188
198
  end
189
199
  end
@@ -25,6 +25,10 @@ module Workarea
25
25
  render plain: session[:foo].presence || 'nil'
26
26
  end
27
27
 
28
+ def varies
29
+ render plain: request.env['workarea.cache_varies']
30
+ end
31
+
28
32
  def current_user
29
33
  nil
30
34
  end
@@ -34,6 +38,11 @@ module Workarea
34
38
  Rails.application.routes.prepend do
35
39
  post 'cache_varies_test_set_session', to: 'workarea/cache_varies_integration_test/caching#set_session'
36
40
  get 'cache_varies_test_foo', to: 'workarea/cache_varies_integration_test/caching#foo'
41
+
42
+ scope '(:locale)', constraints: Workarea::I18n.routes_constraint do
43
+ get 'cache_varies_test_varies', to: 'workarea/cache_varies_integration_test/caching#varies'
44
+ patch 'cache_varies_test_varies', to: 'workarea/cache_varies_integration_test/caching#varies'
45
+ end
37
46
  end
38
47
 
39
48
  Rails.application.reload_routes!
@@ -74,5 +83,27 @@ module Workarea
74
83
  assert_equal('baz', response.body)
75
84
  assert_equal('fresh', response.headers['X-Rack-Cache'])
76
85
  end
86
+
87
+ def test_varies_includes_locale
88
+ set_locales(available: [:en, :es], default: :en, current: :en)
89
+
90
+ get '/cache_varies_test_varies'
91
+ assert_includes(response.body, 'en')
92
+
93
+ get '/cache_varies_test_varies', params: { locale: 'es' }
94
+ assert_includes(response.body, 'es')
95
+
96
+ get '/es/cache_varies_test_varies'
97
+ assert_includes(response.body, 'es')
98
+
99
+ patch '/cache_varies_test_varies'
100
+ assert_includes(response.body, 'en')
101
+
102
+ patch '/cache_varies_test_varies', params: { locale: 'es' }
103
+ assert_includes(response.body, 'es')
104
+
105
+ patch '/es/cache_varies_test_varies'
106
+ assert_includes(response.body, 'es')
107
+ end
77
108
  end
78
109
  end
@@ -67,6 +67,26 @@ module Workarea
67
67
  assert_equal({ 'id' => '1' }, results.first['_source'])
68
68
  end
69
69
 
70
+ def test_bulk_with_block
71
+ set_locales(available: [:en, :es], default: :en, current: :en)
72
+ Foo.bulk { { id: I18n.locale.to_s, bulk_action: 'index' } }
73
+
74
+ find_results = -> do
75
+ Foo
76
+ .current_index
77
+ .search({ query: { match_all: {} } }, type: 'foo')
78
+ .dig('hits', 'hits')
79
+ end
80
+
81
+ I18n.locale = :en
82
+ assert(1, Foo.count)
83
+ assert_equal({ 'id' => 'en' }, find_results.call.first['_source'])
84
+
85
+ I18n.locale = :es
86
+ assert(1, Foo.count)
87
+ assert_equal({ 'id' => 'es' }, find_results.call.first['_source'])
88
+ end
89
+
70
90
  def test_update
71
91
  Foo.save(id: '1')
72
92
  Foo.update(id: '1', foo: 'bar')