workarea-core 3.5.13 → 3.5.18

Sign up to get free protection for your applications and to get access to all the features.
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