workarea-core 3.5.13 → 3.5.18

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 (51) hide show
  1. checksums.yaml +4 -4
  2. data/app/controllers/workarea/impersonation.rb +2 -1
  3. data/app/mailers/workarea/application_mailer.rb +4 -1
  4. data/app/middleware/workarea/application_middleware.rb +5 -2
  5. data/app/models/workarea/checkout.rb +7 -7
  6. data/app/models/workarea/data_file/csv.rb +9 -1
  7. data/app/models/workarea/inquiry.rb +2 -1
  8. data/app/models/workarea/inventory/sku.rb +2 -2
  9. data/app/models/workarea/metrics/user.rb +24 -8
  10. data/app/models/workarea/order.rb +10 -0
  11. data/app/models/workarea/payment.rb +1 -6
  12. data/app/models/workarea/search/storefront/category_query.rb +1 -1
  13. data/app/queries/workarea/search/admin_search.rb +4 -0
  14. data/app/queries/workarea/search/admin_sorting.rb +1 -1
  15. data/app/services/workarea/index_release_schedule_previews.rb +37 -0
  16. data/app/workers/workarea/index_release_schedule_change.rb +32 -0
  17. data/app/workers/workarea/publish_release.rb +1 -0
  18. data/config/initializers/00_configuration.rb +3 -2
  19. data/config/locales/en.yml +2 -0
  20. data/lib/generators/workarea/install/install_generator.rb +13 -0
  21. data/lib/generators/workarea/install/templates/initializer.rb.erb +1 -13
  22. data/lib/tasks/cache.rake +3 -33
  23. data/lib/tasks/help.rake +4 -43
  24. data/lib/tasks/insights.rake +3 -35
  25. data/lib/tasks/migrate.rake +3 -96
  26. data/lib/tasks/search.rake +6 -68
  27. data/lib/tasks/services.rake +4 -54
  28. data/lib/workarea/configuration.rb +11 -2
  29. data/lib/workarea/configuration/administrable_options.rb +1 -5
  30. data/lib/workarea/core.rb +1 -0
  31. data/lib/workarea/core/engine.rb +4 -0
  32. data/lib/workarea/ext/jbuilder/jbuilder_cache.rb +29 -0
  33. data/lib/workarea/tasks/cache.rb +43 -0
  34. data/lib/workarea/tasks/help.rb +55 -0
  35. data/lib/workarea/tasks/insights.rb +47 -0
  36. data/lib/workarea/tasks/migrate.rb +106 -0
  37. data/lib/workarea/tasks/search.rb +105 -0
  38. data/lib/workarea/tasks/services.rb +71 -0
  39. data/lib/workarea/version.rb +1 -1
  40. data/test/generators/workarea/install_generator_test.rb +6 -2
  41. data/test/mailers/workarea/application_mailer_test.rb +10 -0
  42. data/test/models/workarea/checkout_test.rb +57 -0
  43. data/test/models/workarea/data_file/import_test.rb +40 -0
  44. data/test/models/workarea/search/storefront/category_query_test.rb +11 -0
  45. data/test/queries/workarea/search/admin_search_test.rb +10 -0
  46. data/test/services/workarea/index_release_schedule_previews_test.rb +28 -0
  47. data/test/workers/workarea/{reindex_release_test.rb → index_release_schedule_change_test.rb} +30 -4
  48. data/test/workers/workarea/publish_release_test.rb +24 -0
  49. data/workarea-core.gemspec +2 -2
  50. metadata +17 -8
  51. data/app/workers/workarea/reindex_release.rb +0 -42
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: b15a5824ab2746ddff9941773fefcaec1829d2a721a65893b3dac3a2fc7aad8a
4
- data.tar.gz: 90b49ae058315ad91c7427c0b94c39a86016ed1898bb44d4bad99c9377053d53
3
+ metadata.gz: 84d99f1dbf010e7d67b9dbc7785720bbed5cd17c22528dfe57e194dfbb660892
4
+ data.tar.gz: c5453a27cf96335ae9d35fe857ccc770a0412117579b37dc1cb84e9221cd6efe
5
5
  SHA512:
6
- metadata.gz: 7ee0b818334a756cbeceacf3be95347e8fa0d46090c0fd3729073c67a04e4c2e17530adc50e772e3bc4f759fdc5d62d7d775e5e2cb0281a5541e063d82fe7526
7
- data.tar.gz: bd4eb10892770ef2b332a25203f54e703940cec8d45fce3f13a3316636b4ec3c78c0ce4088e504845e7f017ebf75eaa8a0036251f37ed9207d90f3fe6a86be5e
6
+ metadata.gz: 4605fd9a72741e6cac8ab7a32d2408ba1b8f2bfb79ac875875461ab59fe660ffd5254e3b0eb69d5534222217de0ec2fc3d60351bee5e7575d93d2393fb8d9221
7
+ data.tar.gz: e2946da1f9618bc92f65ceca4281c426747f4c75d162b33e68a6fe629008507c43e07dcde39e12f871b2f0011225f40bfd5d9ecdc8d7f563586b90900f43c447
@@ -39,7 +39,8 @@ module Workarea
39
39
  end
40
40
 
41
41
  def current_impersonation
42
- @current_impersonation ||= User.find(session[:user_id])
42
+ return @current_impersonation if defined?(@current_impersonation)
43
+ @current_impersonation = User.find(session[:user_id]) rescue nil
43
44
  end
44
45
 
45
46
  def admin_browse_as_guest
@@ -8,7 +8,10 @@ module Workarea
8
8
  default from: -> (*) { Workarea.config.email_from }
9
9
 
10
10
  def default_url_options(options = {})
11
- super.merge(host: Workarea.config.host)
11
+ # super isn't returning the configured options, so manually merge them in
12
+ super
13
+ .merge(Rails.application.config.action_mailer.default_url_options.to_h)
14
+ .merge(host: Workarea.config.host)
12
15
  end
13
16
  end
14
17
  end
@@ -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)
@@ -47,11 +47,7 @@ module Workarea
47
47
  def inventory
48
48
  @inventory ||= Inventory::Transaction.from_order(
49
49
  order.id,
50
- order.items.inject({}) do |memo, item|
51
- memo[item.sku] ||= 0
52
- memo[item.sku] += item.quantity
53
- memo
54
- end
50
+ order.sku_quantities
55
51
  )
56
52
  end
57
53
 
@@ -158,10 +154,14 @@ module Workarea
158
154
  # Used in auto completing an order for a logged in user.
159
155
  #
160
156
  # @param [Hash] parameters for updating
161
- # @return [self]
157
+ # @return [Boolean] whether the update was successful.
162
158
  #
163
159
  def update(params = {})
164
- steps.each { |s| s.new(self).update(params) }
160
+ return true if params.blank?
161
+
162
+ steps.reduce(true) do |result, step|
163
+ result &= step.new(self).update(params)
164
+ end
165
165
  end
166
166
 
167
167
  # Whether this checkout needs any further information
@@ -17,7 +17,15 @@ module Workarea
17
17
  assign_attributes(root, attrs)
18
18
  assign_embedded_attributes(root, attrs)
19
19
 
20
- if root.save || failed_new_record_ids.exclude?(id)
20
+ possibly_affected_models = root.embedded_children + [root]
21
+ was_successful = true
22
+
23
+ possibly_affected_models.each do |model|
24
+ meaningful_changes = model.changes.except('updated_at')
25
+ was_successful &= model.save if model.changed? && meaningful_changes.present?
26
+ end
27
+
28
+ if was_successful || failed_new_record_ids.exclude?(id)
21
29
  log(index, root)
22
30
  else
23
31
  operation.total += 1 # ensure line numbers remain consistent
@@ -15,7 +15,8 @@ module Workarea
15
15
  validate :subject_exists
16
16
 
17
17
  def full_subject
18
- Workarea.config.inquiry_subjects[subject]
18
+ I18n.t('workarea.inquiry.subjects')[subject.optionize.to_sym].presence ||
19
+ Workarea.config.inquiry_subjects[subject]
19
20
  end
20
21
 
21
22
  private
@@ -142,11 +142,11 @@ module Workarea
142
142
  end
143
143
 
144
144
  def policy_class
145
- "Workarea::Inventory::Policies::#{policy.classify}".constantize
145
+ "Workarea::Inventory::Policies::#{policy.camelize}".constantize
146
146
  rescue NameError
147
147
  raise(
148
148
  InvalidPolicy,
149
- "Workarea::Inventory::Policies::#{policy.classify} must be a policy class"
149
+ "Workarea::Inventory::Policies::#{policy.camelize} must be a policy class"
150
150
  )
151
151
  end
152
152
 
@@ -110,14 +110,28 @@ module Workarea
110
110
  end
111
111
 
112
112
  def merge!(other)
113
- %w(orders revenue discounts cancellations refund).each do |field|
114
- self.send("#{field}=", send(field) + other.send(field))
115
- end
116
-
117
- self.first_order_at = [first_order_at, other.first_order_at].compact.min
118
- self.last_order_at = [last_order_at, other.last_order_at].compact.max
119
- self.average_order_value = average_order_value
120
- save!
113
+ # To recalculate average_order_value
114
+ self.orders += other.orders
115
+ self.revenue += other.revenue
116
+
117
+ update = {
118
+ '$set' => {
119
+ average_order_value: average_order_value,
120
+ updated_at: Time.current.utc
121
+ },
122
+ '$inc' => {
123
+ orders: other.orders,
124
+ revenue: other.revenue,
125
+ discounts: other.discounts,
126
+ cancellations: other.cancellations,
127
+ refund: other.refund
128
+ }
129
+ }
130
+
131
+ update['$min'] = { first_order_at: other.first_order_at.utc } if other.first_order_at.present?
132
+ update['$max'] = { last_order_at: other.last_order_at.utc } if other.last_order_at.present?
133
+
134
+ self.class.collection.update_one({ _id: id }, update, upsert: true)
121
135
 
122
136
  self.class.save_affinity(
123
137
  id: id,
@@ -133,6 +147,8 @@ module Workarea
133
147
  category_ids: other.purchased.category_ids,
134
148
  search_ids: other.purchased.search_ids
135
149
  )
150
+
151
+ reload
136
152
  end
137
153
  end
138
154
  end
@@ -375,6 +375,16 @@ module Workarea
375
375
  )
376
376
  end
377
377
 
378
+ # A hash with the quantity of each SKU in the order
379
+ #
380
+ # @return [Hash]
381
+ #
382
+ def sku_quantities
383
+ items.each_with_object(Hash.new(0)) do |item, quantities|
384
+ quantities[item.sku] += item.quantity
385
+ end
386
+ end
387
+
378
388
  private
379
389
 
380
390
  def item_count_limit
@@ -80,12 +80,7 @@ module Workarea
80
80
  build_credit_card unless credit_card
81
81
  credit_card.saved_card_id = nil
82
82
  credit_card.attributes = attrs.slice(
83
- :month,
84
- :year,
85
- :saved_card_id,
86
- :number,
87
- :cvv,
88
- :amount
83
+ *Workarea.config.credit_card_attributes
89
84
  )
90
85
  save
91
86
  end
@@ -144,7 +144,7 @@ module Workarea
144
144
  .where(releasable_type: ProductRule.name)
145
145
  .any_in(releasable_id: category.product_rules.map(&:id))
146
146
  .includes(:release)
147
- .to_a
147
+ .select(&:release)
148
148
  end
149
149
  end
150
150
  end
@@ -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
@@ -0,0 +1,37 @@
1
+ module Workarea
2
+ class IndexReleaseSchedulePreviews
3
+ attr_reader :release, :starts_at, :ends_at
4
+
5
+ def initialize(release: nil, starts_at: nil, ends_at: nil)
6
+ @release = release
7
+ @starts_at = starts_at
8
+ @ends_at = ends_at
9
+ end
10
+
11
+ def affected_releases
12
+ result = Release
13
+ .scheduled(after: starts_at, before: ends_at)
14
+ .includes(:changesets)
15
+ .to_a
16
+
17
+ result << release if release.present?
18
+ result.uniq
19
+ end
20
+
21
+ def affected_models
22
+ affected_releases.flat_map(&:changesets).flat_map(&:releasable).compact
23
+ end
24
+
25
+ def perform
26
+ affected_releases.each do |release|
27
+ affected_models.each do |releasable|
28
+ Search::Storefront.new(releasable.in_release(release)).destroy
29
+
30
+ # Different models have different indexing workers, running callbacks
31
+ # ensures the appropriate worker is triggered
32
+ releasable.run_callbacks(:save_release_changes)
33
+ end
34
+ end
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,32 @@
1
+ module Workarea
2
+ class IndexReleaseScheduleChange
3
+ include Sidekiq::Worker
4
+ include Sidekiq::CallbacksWorker
5
+
6
+ sidekiq_options(
7
+ enqueue_on: {
8
+ Release => [:save, :destroy],
9
+ only_if: -> { publish_at_changed? || destroyed? },
10
+ with: -> { [id, publish_at_was, publish_at] }
11
+ },
12
+ queue: 'releases'
13
+ )
14
+
15
+ def perform(id, previous_publish_at, new_publish_at)
16
+ # When destroyed, changesets for the release ID will still exist and be used to update the index
17
+ rescheduled_release = Release.find_or_initialize_by(id: id)
18
+
19
+ earlier, later = if rescheduled_release.persisted? && previous_publish_at.present? && new_publish_at.present?
20
+ [previous_publish_at, new_publish_at].sort
21
+ elsif previous_publish_at.present?
22
+ [previous_publish_at, nil]
23
+ else
24
+ [new_publish_at, nil]
25
+ end
26
+
27
+ IndexReleaseSchedulePreviews
28
+ .new(release: rescheduled_release, starts_at: earlier, ends_at: later)
29
+ .perform
30
+ end
31
+ end
32
+ end
@@ -8,6 +8,7 @@ module Workarea
8
8
  system_user = User.find_system_user!(release.name, 'Release')
9
9
 
10
10
  Mongoid::AuditLog.record(system_user) { release.publish! }
11
+ IndexReleaseSchedulePreviews.new(release: release).perform
11
12
 
12
13
  rescue Mongoid::Errors::DocumentNotFound
13
14
  # Doesn't matter, release has been removed
@@ -156,14 +156,15 @@ Workarea::Configuration.define_fields do
156
156
  end
157
157
 
158
158
  fieldset 'Communication', namespaced: false do
159
+
159
160
  field 'Email From',
160
161
  type: :string,
161
- default: 'noreply@example.com',
162
+ default: -> { "#{Workarea.config.site_name} <noreply@#{Workarea.config.host}>" },
162
163
  description: 'The email address used as the sender of system emails'
163
164
 
164
165
  field 'Email To',
165
166
  type: :string,
166
- default: 'customerservice@example.com',
167
+ default: -> { "#{Workarea.config.site_name} <customerservice@#{Workarea.config.host}>" },
167
168
  description: 'The email address that receives user generated emails, e.g. contact us inquiries'
168
169
 
169
170
  field 'Inquiry Subjects',
@@ -102,6 +102,8 @@ en:
102
102
  name: "Fulfillment SKU %{id}"
103
103
  inventory_sku:
104
104
  name: "Inventory %{id}"
105
+ inquiry:
106
+ subjects: {}
105
107
  order:
106
108
  name: "Order %{id}"
107
109
  traffic_referrer:
@@ -55,6 +55,19 @@ module Workarea
55
55
  remove_file 'public/favicon.ico'
56
56
  end
57
57
 
58
+ def add_development_mailer_port
59
+ development_port = <<-CODE
60
+
61
+ config.action_mailer.default_url_options = { port: 3000 }
62
+ CODE
63
+
64
+ inject_into_file(
65
+ 'config/environments/development.rb',
66
+ development_port,
67
+ before: /^end/
68
+ )
69
+ end
70
+
58
71
  private
59
72
 
60
73
  def app_name
@@ -4,19 +4,7 @@ Workarea.configure do |config|
4
4
 
5
5
  config.host = {
6
6
  'test' => 'www.example.com',
7
- 'development' => 'localhost:3000',
7
+ 'development' => 'localhost',
8
8
  'production' => 'www.<%= app_name.dasherize %>.com' # TODO
9
9
  }[Rails.env]
10
-
11
- config.email_to = {
12
- 'test' => "#{config.site_name} <customerservice@example.com>",
13
- 'development' => "#{config.site_name} <customerservice@<%= app_name %>.test>",
14
- 'production' => "#{config.site_name} <customerservice@<%= app_name.dasherize %>.com>" # TODO
15
- }[Rails.env]
16
-
17
- config.email_from = {
18
- 'test' => "#{config.site_name} <noreply@example.com>",
19
- 'development' => "#{config.site_name} <noreply@<%= app_name %>.test",
20
- 'production' => "#{config.site_name} <noreply@<%= app_name.dasherize %>.com>" # TODO
21
- }[Rails.env]
22
10
  end
@@ -1,40 +1,10 @@
1
+ require 'workarea/tasks/cache'
2
+
1
3
  namespace :workarea do
2
4
  namespace :cache do
3
5
  desc 'Prime images cache'
4
6
  task prime_images: :environment do
5
- include Rails.application.routes.url_helpers
6
- include Workarea::Storefront::ProductsHelper
7
- include Workarea::Core::Engine.routes.url_helpers
8
-
9
- built_in_jobs = [:thumb, :gif, :jpg, :png, :strip, :convert, :optimized]
10
-
11
- jobs = Dragonfly.app(:workarea).processor_methods.reject do |job|
12
- built_in_jobs.include?(job)
13
- end
14
-
15
- Workarea::Catalog::Product.all.each_by(50) do |product|
16
- product.images.each do |image|
17
- jobs.each do |job|
18
- url = URI.join(
19
- "https://#{Workarea.config.host}",
20
- dynamic_product_image_url(
21
- image.product.slug,
22
- image.option,
23
- image.id,
24
- job,
25
- only_path: true
26
- )
27
- ).to_s
28
-
29
- begin
30
- `curl #{url}`
31
- puts "Downloaded image #{url}"
32
- rescue StandardError => e
33
- puts e.inspect
34
- end
35
- end
36
- end
37
- end
7
+ Workarea::Tasks::Cache.prime_images
38
8
  end
39
9
  end
40
10
  end
@@ -1,11 +1,10 @@
1
+ require 'workarea/tasks/help'
2
+
1
3
  namespace :workarea do
2
4
  desc 'Drop and recreate help articles (Warning: all current help will be deleted!)'
3
5
  task reload_help: :environment do
4
6
  puts 'Deleting help articles...'
5
- Workarea::Help::Article.delete_all
6
- Workarea::Help::Asset.delete_all
7
-
8
- Workarea::HelpSeeds.new.perform
7
+ Workarea::Tasks::Help.reload
9
8
  Rake::Task['workarea:search_index:help'].invoke
10
9
  end
11
10
 
@@ -16,44 +15,6 @@ namespace :workarea do
16
15
  end
17
16
 
18
17
  task dump_help: :environment do
19
- Workarea::Help::Article.all.each_by(50) do |article|
20
- article_root = Rails.root.join(
21
- 'data',
22
- 'help',
23
- article.category.systemize,
24
- article.name.systemize
25
- )
26
-
27
- asset_path = article_root.join('assets')
28
-
29
- FileUtils.mkdir_p(article_root)
30
-
31
- if article.thumbnail.present?
32
- article.thumbnail.to_file(article_root.join(article.thumbnail.name))
33
- end
34
-
35
- Workarea::Help::Asset.all.each_by(50) do |asset|
36
- if article.summary.include?(asset.url) || article.body.include?(asset.url)
37
- FileUtils.mkdir_p(asset_path)
38
- asset.to_file(asset_path.join(asset.name))
39
- reference = "<%= #{asset.name.split('.').first} %>"
40
-
41
- article.summary.gsub!(asset.url, reference)
42
- article.body.gsub!(asset.url, reference)
43
- end
44
- end
45
-
46
- if article.summary.present?
47
- File.open(article_root.join('summary.md'), 'w') do |file|
48
- file.write(article.summary)
49
- end
50
- end
51
-
52
- if article.body.present?
53
- File.open(article_root.join('body.md'), 'w') do |file|
54
- file.write(article.body)
55
- end
56
- end
57
- end
18
+ Workarea::Tasks::Help.dump
58
19
  end
59
20
  end