workarea-core 3.5.17 → 3.5.22

Sign up to get free protection for your applications and to get access to all the features.
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
@@ -182,6 +182,16 @@ module Workarea
182
182
  def self.deprecation
183
183
  @deprecation ||= ActiveSupport::Deprecation.new('3.6', 'Workarea')
184
184
  end
185
+
186
+ # Whether the app should skip connecting to external services on boot,
187
+ # such as Mongo, Elasticsearch, or Redis. Note that this will break
188
+ # functionality relying on these services.
189
+ #
190
+ # @return [Boolean]
191
+ #
192
+ def self.skip_services?
193
+ !!(ENV['WORKAREA_SKIP_SERVICES'] =~ /true/)
194
+ end
185
195
  end
186
196
 
187
197
  require 'workarea/core'
@@ -38,7 +38,7 @@ namespace :workarea do
38
38
  author: author
39
39
  }
40
40
 
41
- if subject.start_with?('Revert')
41
+ if subject.start_with?('Revert "')
42
42
  reverts << body.match(/[a-f0-9]{40}/)
43
43
  reverts << sha
44
44
  end
@@ -26,8 +26,6 @@ module Workarea
26
26
 
27
27
  config.site_name = 'Workarea'
28
28
  config.host = 'www.example.com'
29
- config.email_to = 'customerservice@example.com'
30
- config.email_from = 'noreply@example.com'
31
29
 
32
30
  # Config sent to the ImageMagick through Dragonfly for optimizing jpgs
33
31
  # All metadata profiles are removed, comments cleared by comment -set ""
@@ -942,7 +940,10 @@ module Workarea
942
940
 
943
941
  # Whether the app should skip connecting to external services on boot,
944
942
  # such as Mongo, Elasticsearch, or Redis.
945
- config.skip_service_connections = ENV['WORKAREA_SKIP_SERVICES'].to_s =~ /true/
943
+ #
944
+ # @deprecated Use `Workarea.skip_services?` instead
945
+ #
946
+ config.skip_service_connections = Workarea.skip_services?
946
947
 
947
948
  # This is a feature flag, which enables localized active fields. If you're
948
949
  # upgrading, you can set this to false to avoid having to do a MongoDB
@@ -12,7 +12,8 @@ module Workarea
12
12
  private
13
13
 
14
14
  def check_fieldsets?(name)
15
- ::Mongoid.clients.any? &&
15
+ !Workarea.skip_services? &&
16
+ ::Mongoid.clients.any? &&
16
17
  Configuration::Admin.fields.keys.include?(name.to_s)
17
18
  end
18
19
  end
@@ -78,6 +78,10 @@ module Workarea
78
78
  # get autoloaded. Without this, admin actions like updating product
79
79
  # attributes raises a {NameError} "uninitialized constant BulkIndexProducts".
80
80
  require_dependency 'workarea/bulk_index_products'
81
+
82
+ # Fixes a constant error raised in middleware (when doing segmentation)
83
+ # No idea what the cause is. TODO revisit after Zeitwerk.
84
+ require_dependency 'workarea/metrics/user'
81
85
  end
82
86
  end
83
87
  end
@@ -5,7 +5,7 @@ module Workarea
5
5
  # removing a previously scheduled job from initializers doesn't
6
6
  # actually stop the job from being enqueued.
7
7
  def self.clean
8
- return if Workarea.config.skip_service_connections
8
+ return if Workarea.skip_services?
9
9
 
10
10
  Sidekiq::Cron::Job.all.each do |job|
11
11
  job.destroy unless const_defined?(job.klass)
@@ -0,0 +1,43 @@
1
+ module Workarea
2
+ module Tasks
3
+ module Cache
4
+ extend self
5
+
6
+ def prime_images
7
+ include Rails.application.routes.url_helpers
8
+ include Workarea::Storefront::ProductsHelper
9
+ include Workarea::Core::Engine.routes.url_helpers
10
+
11
+ built_in_jobs = [:thumb, :gif, :jpg, :png, :strip, :convert, :optimized]
12
+
13
+ jobs = Dragonfly.app(:workarea).processor_methods.reject do |job|
14
+ built_in_jobs.include?(job)
15
+ end
16
+
17
+ Workarea::Catalog::Product.all.each_by(50) do |product|
18
+ product.images.each do |image|
19
+ jobs.each do |job|
20
+ url = URI.join(
21
+ "https://#{Workarea.config.host}",
22
+ dynamic_product_image_url(
23
+ image.product.slug,
24
+ image.option,
25
+ image.id,
26
+ job,
27
+ only_path: true
28
+ )
29
+ ).to_s
30
+
31
+ begin
32
+ `curl #{url}`
33
+ puts "Downloaded image #{url}"
34
+ rescue StandardError => e
35
+ puts e.inspect
36
+ end
37
+ end
38
+ end
39
+ end
40
+ end
41
+ end
42
+ end
43
+ end
@@ -0,0 +1,55 @@
1
+ module Workarea
2
+ module Tasks
3
+ module Help
4
+ extend self
5
+
6
+ def reload
7
+ Workarea::Help::Article.delete_all
8
+ Workarea::Help::Asset.delete_all
9
+ Workarea::HelpSeeds.new.perform
10
+ end
11
+
12
+ def dump
13
+ Workarea::Help::Article.all.each_by(50) do |article|
14
+ article_root = Rails.root.join(
15
+ 'data',
16
+ 'help',
17
+ article.category.systemize,
18
+ article.name.systemize
19
+ )
20
+
21
+ asset_path = article_root.join('assets')
22
+
23
+ FileUtils.mkdir_p(article_root)
24
+
25
+ if article.thumbnail.present?
26
+ article.thumbnail.to_file(article_root.join(article.thumbnail.name))
27
+ end
28
+
29
+ Workarea::Help::Asset.all.each_by(50) do |asset|
30
+ if article.summary.include?(asset.url) || article.body.include?(asset.url)
31
+ FileUtils.mkdir_p(asset_path)
32
+ asset.to_file(asset_path.join(asset.name))
33
+ reference = "<%= #{asset.name.split('.').first} %>"
34
+
35
+ article.summary.gsub!(asset.url, reference)
36
+ article.body.gsub!(asset.url, reference)
37
+ end
38
+ end
39
+
40
+ if article.summary.present?
41
+ File.open(article_root.join('summary.md'), 'w') do |file|
42
+ file.write(article.summary)
43
+ end
44
+ end
45
+
46
+ if article.body.present?
47
+ File.open(article_root.join('body.md'), 'w') do |file|
48
+ file.write(article.body)
49
+ end
50
+ end
51
+ end
52
+ end
53
+ end
54
+ end
55
+ end
@@ -0,0 +1,47 @@
1
+ module Workarea
2
+ module Tasks
3
+ module Insights
4
+ extend self
5
+
6
+ def generate
7
+ require 'active_support/testing/time_helpers'
8
+ include ActiveSupport::Testing::TimeHelpers
9
+ batch_size = ENV['WORKAREA_INSIGHTS_BATCH_SIZE'].presence || 1000
10
+
11
+ Workarea::Order
12
+ .placed
13
+ .each_by(batch_size.to_i) { |o| Workarea::SaveOrderMetrics.perform(o) }
14
+
15
+ 8.times do |i|
16
+ travel_to (i.weeks.ago.beginning_of_week + 1.hour)
17
+ Workarea::GenerateInsights.generate_all!
18
+ end
19
+ end
20
+
21
+ # Clear the metrics/insights environment - deletes lots of data, this task
22
+ # is very dangerous! Useful for testing/debugging.
23
+ def reset!
24
+ Workarea::Order
25
+ .where(:metrics_saved_at.gt => 0)
26
+ .update_all(metrics_saved_at: nil)
27
+
28
+ Workarea::Metrics::CategoryByDay.delete_all
29
+ Workarea::Metrics::CountryByDay.delete_all
30
+ Workarea::Metrics::DiscountByDay.delete_all
31
+ Workarea::Metrics::MenuByDay.delete_all
32
+ Workarea::Metrics::ProductByDay.delete_all
33
+ Workarea::Metrics::ProductByWeek.delete_all
34
+ Workarea::Metrics::ProductForLastWeek.delete_all
35
+ Workarea::Metrics::SalesByDay.delete_all
36
+ Workarea::Metrics::SearchByDay.delete_all
37
+ Workarea::Metrics::SearchByWeek.delete_all
38
+ Workarea::Metrics::SearchForLastWeek.delete_all
39
+ Workarea::Metrics::SkuByDay.delete_all
40
+ Workarea::Metrics::TenderByDay.delete_all
41
+ Workarea::Metrics::TrafficReferrerByDay.delete_all
42
+ Workarea::Metrics::User.delete_all
43
+ Workarea::Insights::Base.delete_all
44
+ end
45
+ end
46
+ end
47
+ end
@@ -0,0 +1,106 @@
1
+ module Workarea
2
+ module Tasks
3
+ module Migrate
4
+ extend self
5
+
6
+ def v3_5
7
+ count = 0
8
+
9
+ Workarea::Release.where(:undo_at.gte => Time.current).each do |release|
10
+ undo = release.build_undo(publish_at: release.undo_at).tap(&:save!)
11
+
12
+ release.changesets.each do |changeset|
13
+ changeset.build_undo(release: undo).save!
14
+ end
15
+
16
+ Workarea::Scheduler.delete(release.undo_job_id)
17
+
18
+ release.update_attributes!(undo_at: nil, undo_job_id: nil)
19
+ count += 1
20
+ end
21
+
22
+ Workarea::Release.all.each { |r| Workarea::IndexAdminSearch.perform(r) }
23
+
24
+ puts "✅ #{count} undo releases have been created."
25
+
26
+ count = 0
27
+
28
+ Workarea::Tax::Category.all.each_by(100) do |category|
29
+ category.rates.each_by(500) do |rate|
30
+ rate.postal_code_percentage = rate.percentage
31
+ rate.percentage = nil
32
+ end
33
+
34
+ category.save!
35
+ count += 1
36
+ end
37
+
38
+ puts "✅ #{count} tax categories updated."
39
+
40
+ count = 0
41
+ failed_ids = []
42
+ backup = Mongo::Collection.new(Mongoid::Clients.default.database, 'workarea_legacy_segments')
43
+
44
+ legacy_segments = Workarea::Segment.collection.find.to_a
45
+ legacy_segments.each do |doc|
46
+ backup.insert_one(doc)
47
+ Workarea::Segment.collection.delete_one(doc.slice('_id'))
48
+
49
+ segment = Workarea::Segment.new(
50
+ id: doc['_id'],
51
+ name: doc['name'],
52
+ subscribed_user_ids: doc['subscribed_user_ids'],
53
+ created_at: doc['created_at'],
54
+ updated_at: doc['updated_at']
55
+ )
56
+
57
+ doc['conditions'].each do |condition|
58
+ if condition['_type'] =~ /UserTag/
59
+ segment.rules << Workarea::Segment::Rules::Tags.new(tags: condition['tags'])
60
+ elsif condition['_type'] =~ /TotalSpent/
61
+ rule = Workarea::Segment::Rules::Revenue.new
62
+
63
+ if condition['operator'] == 'equals'
64
+ rule.minimum = rule.maximum = Money.demongoize(condition['amount'])
65
+ elsif condition['operator'] == 'less_than_or_equals'
66
+ rule.maximum = Money.demongoize(condition['amount'])
67
+ elsif condition['operator'] == 'less_than'
68
+ rule.maximum = (Money.demongoize(condition['amount']) - 0.01.to_m)
69
+ elsif condition['operator'] == 'greater_than_or_equals'
70
+ rule.minimum = Money.demongoize(condition['amount'])
71
+ elsif condition['operator'] == 'greater_than'
72
+ rule.minimum = (Money.demongoize(condition['amount']) + 0.01.to_m)
73
+ end
74
+
75
+ segment.rules << rule
76
+ end
77
+ end
78
+
79
+ if doc['conditions'].size == segment.rules.size && segment.save
80
+ count += 1
81
+ else
82
+ failed_ids << doc['_id']
83
+ end
84
+ end
85
+
86
+ puts "✅ #{count} segments have been migrated." if count > 0
87
+ if failed_ids.any?
88
+ puts "⛔️ #{failed_ids.count} segments failed to migrate."
89
+ puts "You can find copies of the original segments in the workarea_legacy_segments collection."
90
+ puts "The segments that failed are #{failed_ids.to_sentence}."
91
+ end
92
+
93
+ Workarea::Segment::LifeCycle.create!
94
+ puts "✅ Life cycle segments have been created."
95
+
96
+ admin_ids = Workarea::User.admins.pluck(:id)
97
+ admin_ids.each do |id|
98
+ Workarea::SynchronizeUserMetrics.new.perform(id)
99
+ end
100
+ puts "✅ #{admin_ids.count} admins have had their metrics synchronized." if admin_ids.count > 0
101
+
102
+ puts "\nMigration complete!"
103
+ end
104
+ end
105
+ end
106
+ end
@@ -0,0 +1,105 @@
1
+ module Workarea
2
+ module Tasks
3
+ module Search
4
+ extend self
5
+
6
+ def setup
7
+ require 'sidekiq/testing/inline' unless ENV['INLINE'] == 'false'
8
+ Workarea.config.bulk_index_batch_size = ENV['BATCH_SIZE'].to_i if ENV['BATCH_SIZE'].present?
9
+ end
10
+
11
+ def index_admin
12
+ Workarea::QueuesPauser.with_paused_queues do
13
+ Workarea::Search::Admin.reset_indexes!
14
+ end
15
+
16
+ Mongoid.models.each do |klass|
17
+ next unless Workarea::Search::Admin.for(klass.first).present?
18
+
19
+ klass.all.each_slice_of(Workarea.config.bulk_index_batch_size) do |models|
20
+ Workarea::BulkIndexAdmin.perform_by_models(models)
21
+ end
22
+ end
23
+
24
+ Workarea.config.jump_to_navigation.to_a.each do |tuple|
25
+ Workarea::Search::Admin::Navigation.new(tuple).save
26
+ end
27
+ end
28
+
29
+ def index_storefront
30
+ Workarea::QueuesPauser.with_paused_queues do
31
+ Workarea::Search::Storefront.reset_indexes!
32
+ Workarea::Search::Storefront.ensure_dynamic_mappings
33
+ end
34
+
35
+ ensure_dynamic_mappings_for_current_product_filters
36
+
37
+ index_storefront_categories
38
+ index_storefront_content_pages
39
+ index_storefront_products
40
+ index_storefront_searches
41
+ end
42
+
43
+ def index_help
44
+ Workarea::QueuesPauser.with_paused_queues do
45
+ Workarea::Search::Help.reset_indexes!
46
+ end
47
+
48
+ Workarea::Help::Article.all.each_by(Workarea.config.bulk_index_batch_size) do |help_article|
49
+ Workarea::Search::Help.new(help_article).save
50
+ end
51
+ end
52
+
53
+ private
54
+
55
+ # This code finds all unique filters for products so we can index a sample
56
+ # product for each to ensure the dynamic mappings get created.
57
+ #
58
+ # This is necessary to fix mapping errors from Elasticsearch when trying
59
+ # to index category percolations against fields which have no mapping.
60
+ #
61
+ def ensure_dynamic_mappings_for_current_product_filters
62
+ map = %{
63
+ function() {
64
+ for (var key in this.filters.#{I18n.locale}) {
65
+ emit(key, null);
66
+ }
67
+ }
68
+ }
69
+ reduce = 'function(key) { return null; }'
70
+ results = Workarea::Catalog::Product.map_reduce(map, reduce).out(inline: 1)
71
+ unique_filters = results.map { |r| r['_id'] }
72
+
73
+ sample_products = unique_filters.reduce([]) do |memo, filter|
74
+ filter = "filters.#{I18n.locale}.#{filter}"
75
+ memo << Workarea::Catalog::Product.exists(filter => true).sample
76
+ end
77
+
78
+ sample_products.each do |product|
79
+ Workarea::Search::Storefront::Product.new(product).save
80
+ end
81
+ end
82
+
83
+ def index_storefront_categories
84
+ Workarea::Catalog::Category.all.each_by(Workarea.config.bulk_index_batch_size) do |category|
85
+ Workarea::Search::Storefront::CategoryQuery.new(category).create
86
+ Workarea::Search::Storefront::Category.new(category).save
87
+ end
88
+ end
89
+
90
+ def index_storefront_content_pages
91
+ Workarea::Content::Page.all.each_by(Workarea.config.bulk_index_batch_size) do |page|
92
+ Workarea::Search::Storefront::Page.new(page).save
93
+ end
94
+ end
95
+
96
+ def index_storefront_products
97
+ Workarea::BulkIndexProducts.perform
98
+ end
99
+
100
+ def index_storefront_searches
101
+ Workarea::BulkIndexSearches.perform
102
+ end
103
+ end
104
+ end
105
+ end
@@ -0,0 +1,71 @@
1
+ module Workarea
2
+ module Tasks
3
+ module Services
4
+ extend self
5
+
6
+ def assert_docker_compose_installed!
7
+ unless system('docker-compose -v > /dev/null 2>&1')
8
+ STDERR.puts <<~eos
9
+ **************************************************
10
+ ⛔️ ERROR: workarea:services tasks depend on Docker Compose being installed. \
11
+ See https://docs.docker.com/compose/install/ for how to install.
12
+ **************************************************
13
+ eos
14
+ exit
15
+ end
16
+ end
17
+
18
+ def compose_file_path
19
+ File.join(Gem::Specification.find_by_name('workarea').gem_dir, 'docker-compose.yml')
20
+ end
21
+
22
+ def compose_env
23
+ require 'workarea/version'
24
+
25
+ {
26
+ 'COMPOSE_FILE' => compose_file_path,
27
+ 'COMPOSE_PROJECT_NAME' => File.basename(Dir.pwd),
28
+
29
+ 'MONGODB_VERSION' => Workarea::VERSION::MONGODB::STRING,
30
+ 'MONGODB_PORT' => ENV['WORKAREA_MONGODB_PORT'] || '27017',
31
+
32
+ 'REDIS_VERSION' => Workarea::VERSION::REDIS::STRING,
33
+ 'REDIS_PORT' => ENV['WORKAREA_REDIS_PORT'] || '6379',
34
+
35
+ 'ELASTICSEARCH_VERSION' => Workarea::VERSION::ELASTICSEARCH::STRING,
36
+ 'ELASTICSEARCH_PORT' => ENV['WORKAREA_ELASTICSEARCH_PORT'] || '9200'
37
+ }
38
+ end
39
+
40
+ def up
41
+ assert_docker_compose_installed!
42
+
43
+ if system(compose_env, "docker-compose up -d #{ENV['COMPOSE_ARGUMENTS']} #{ENV['WORKAREA_SERVICES']}")
44
+ puts '✅ Success! Workarea services are running in the background. Run workarea:services:down to stop them.'
45
+ else
46
+ STDERR.puts '⛔️ Error! There was an error starting Workarea services.'
47
+ end
48
+ end
49
+
50
+ def down
51
+ assert_docker_compose_installed!
52
+
53
+ if system(compose_env, "docker-compose down #{ENV['COMPOSE_ARGUMENTS']}")
54
+ puts '✅ Success! Workarea services are stopped. Run workarea:services:up to start them.'
55
+ else
56
+ STDERR.puts '⛔️ Error! There was an error stopping Workarea services.'
57
+ end
58
+ end
59
+
60
+ def clean
61
+ assert_docker_compose_installed!
62
+
63
+ if system(compose_env, "docker-compose down -v #{ENV['COMPOSE_ARGUMENTS']}")
64
+ puts '✅ Success! Workarea service volumes have been removed. Run workarea:services:up to start services and recreate volumes.'
65
+ else
66
+ STDERR.puts '⛔️ Error! There was an error removing Workarea service volumes.'
67
+ end
68
+ end
69
+ end
70
+ end
71
+ end