workarea-core 3.5.12 → 3.5.17
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.
- checksums.yaml +4 -4
- data/app/mailers/workarea/application_mailer.rb +4 -1
- data/app/models/workarea/checkout.rb +7 -7
- data/app/models/workarea/data_file/csv.rb +9 -1
- data/app/models/workarea/inquiry.rb +2 -1
- data/app/models/workarea/inventory/sku.rb +2 -2
- data/app/models/workarea/metrics/user.rb +24 -8
- data/app/models/workarea/order.rb +13 -3
- data/app/models/workarea/payment.rb +1 -6
- data/app/models/workarea/releasable.rb +3 -1
- data/app/models/workarea/release/changeset.rb +1 -0
- data/app/models/workarea/search/admin/pricing_discount.rb +1 -1
- data/app/models/workarea/search/storefront.rb +9 -1
- data/app/models/workarea/search/storefront/category_query.rb +1 -1
- data/app/models/workarea/search/storefront/product.rb +11 -2
- data/app/queries/workarea/product_releases.rb +6 -0
- data/app/services/workarea/direct_upload.rb +6 -1
- data/app/services/workarea/index_release_schedule_previews.rb +37 -0
- data/app/workers/workarea/index_category_changes.rb +16 -3
- data/app/workers/workarea/index_release_schedule_change.rb +32 -0
- data/app/workers/workarea/publish_release.rb +1 -0
- data/config/locales/en.yml +2 -0
- data/lib/generators/workarea/install/install_generator.rb +13 -0
- data/lib/generators/workarea/install/templates/initializer.rb.erb +1 -1
- data/lib/tasks/search.rake +10 -4
- data/lib/workarea/configuration.rb +11 -0
- data/lib/workarea/configuration/administrable_options.rb +1 -5
- data/lib/workarea/core.rb +2 -0
- data/lib/workarea/ext/jbuilder/jbuilder_cache.rb +29 -0
- data/lib/workarea/queues_pauser.rb +26 -0
- data/lib/workarea/version.rb +1 -1
- data/test/generators/workarea/install_generator_test.rb +6 -0
- data/test/mailers/workarea/application_mailer_test.rb +10 -0
- data/test/models/workarea/checkout_test.rb +57 -0
- data/test/models/workarea/data_file/import_test.rb +40 -0
- data/test/models/workarea/releasable_test.rb +13 -0
- data/test/models/workarea/search/storefront/category_query_test.rb +11 -0
- data/test/models/workarea/search/storefront/product_releases_test.rb +60 -0
- data/test/models/workarea/search/storefront_test.rb +13 -0
- data/test/queries/workarea/search/category_browse_test.rb +23 -0
- data/test/services/workarea/direct_upload_test.rb +20 -0
- data/test/services/workarea/index_release_schedule_previews_test.rb +28 -0
- data/test/workers/workarea/index_release_schedule_change_test.rb +107 -0
- data/test/workers/workarea/publish_release_test.rb +24 -0
- data/workarea-core.gemspec +6 -5
- metadata +33 -13
- 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:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 8d24b6af137b806311dbd5d13fb13a57f8693f99d767f94fb3144994fe9c7028
|
4
|
+
data.tar.gz: dbbfb80358c11fc03b2e470ac741f615dc2d68e5f9c6c0d55b3b69567eaa4d02
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: a7f587ddc4e1e9c425eaace526f562f99ec5885002f833fd33a134164d3d20a405938a1e762b3cfd221824a77f435ebb79383591ae2bd0c3b81bd407ba0ceb12
|
7
|
+
data.tar.gz: 61da7b2aa4a72d5e0b5456b401f9124769fa5f43c601d33e6e14b6a64bf820910bed36faf6992a1865e603455710b05647fa62b62488fa8fd973d33edac02564
|
@@ -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
|
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
|
@@ -47,11 +47,7 @@ module Workarea
|
|
47
47
|
def inventory
|
48
48
|
@inventory ||= Inventory::Transaction.from_order(
|
49
49
|
order.id,
|
50
|
-
order.
|
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 [
|
157
|
+
# @return [Boolean] whether the update was successful.
|
162
158
|
#
|
163
159
|
def update(params = {})
|
164
|
-
|
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
|
-
|
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
|
@@ -142,11 +142,11 @@ module Workarea
|
|
142
142
|
end
|
143
143
|
|
144
144
|
def policy_class
|
145
|
-
"Workarea::Inventory::Policies::#{policy.
|
145
|
+
"Workarea::Inventory::Policies::#{policy.camelize}".constantize
|
146
146
|
rescue NameError
|
147
147
|
raise(
|
148
148
|
InvalidPolicy,
|
149
|
-
"Workarea::Inventory::Policies::#{policy.
|
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
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
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
|
@@ -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: '
|
56
|
+
name: 'abandoned_order_email_with_fraud_index_v2',
|
57
57
|
background: true
|
58
58
|
}
|
59
59
|
)
|
@@ -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
|
-
|
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
|
@@ -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)
|
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
|
@@ -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:
|
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,
|
@@ -122,12 +122,21 @@ module Workarea
|
|
122
122
|
ProductPrimaryImageUrl.new(model).path
|
123
123
|
end
|
124
124
|
|
125
|
-
#
|
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
|
-
|
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
|
@@ -8,7 +8,12 @@ module Workarea
|
|
8
8
|
url += ":#{uri.port}" unless uri.port.in? [80, 443]
|
9
9
|
id = "direct_upload_#{url}"
|
10
10
|
|
11
|
-
response =
|
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
|
16
|
+
|
12
17
|
cors = response.data[:body]
|
13
18
|
|
14
19
|
unless cors['CORSConfiguration'].pluck('ID').include?(id)
|
@@ -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
|
@@ -4,16 +4,29 @@ module Workarea
|
|
4
4
|
include Sidekiq::CallbacksWorker
|
5
5
|
|
6
6
|
sidekiq_options(
|
7
|
-
enqueue_on: {
|
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 =
|
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) }
|
@@ -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
|
data/config/locales/en.yml
CHANGED
@@ -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
|