workarea-google_product_feed 3.1.3

Sign up to get free protection for your applications and to get access to all the features.
Files changed (80) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +14 -0
  3. data/CHANGELOG.md +99 -0
  4. data/Gemfile +10 -0
  5. data/README.md +61 -0
  6. data/Rakefile +53 -0
  7. data/app/models/workarea/catalog/category.decorator +7 -0
  8. data/app/models/workarea/catalog/product.decorator +7 -0
  9. data/app/models/workarea/feed/google.rb +16 -0
  10. data/app/view_models/workarea/storefront/google_product_feed_sku_view_model.rb +26 -0
  11. data/app/view_models/workarea/storefront/google_product_feed_view_model.rb +125 -0
  12. data/app/workers/workarea/exporting/google_feed.rb +90 -0
  13. data/bin/rails +18 -0
  14. data/config/initializers/00_scheduled_jobs.rb +6 -0
  15. data/config/initializers/01_configuration.rb +7 -0
  16. data/config/routes.rb +6 -0
  17. data/lib/workarea/google_product_feed.rb +32 -0
  18. data/lib/workarea/google_product_feed/engine.rb +8 -0
  19. data/lib/workarea/google_product_feed/version.rb +5 -0
  20. data/script/admin_ci +9 -0
  21. data/script/ci +11 -0
  22. data/script/core_ci +9 -0
  23. data/script/plugins_ci +9 -0
  24. data/script/storefront_ci +9 -0
  25. data/test/dummy/Rakefile +6 -0
  26. data/test/dummy/app/assets/config/manifest.js +4 -0
  27. data/test/dummy/app/assets/images/.keep +0 -0
  28. data/test/dummy/app/assets/javascripts/application.js +13 -0
  29. data/test/dummy/app/assets/stylesheets/application.css +15 -0
  30. data/test/dummy/app/controllers/application_controller.rb +3 -0
  31. data/test/dummy/app/controllers/concerns/.keep +0 -0
  32. data/test/dummy/app/helpers/application_helper.rb +2 -0
  33. data/test/dummy/app/jobs/application_job.rb +2 -0
  34. data/test/dummy/app/mailers/application_mailer.rb +4 -0
  35. data/test/dummy/app/models/concerns/.keep +0 -0
  36. data/test/dummy/app/views/layouts/application.html.erb +14 -0
  37. data/test/dummy/app/views/layouts/mailer.html.erb +13 -0
  38. data/test/dummy/app/views/layouts/mailer.text.erb +1 -0
  39. data/test/dummy/bin/bundle +3 -0
  40. data/test/dummy/bin/rails +4 -0
  41. data/test/dummy/bin/rake +4 -0
  42. data/test/dummy/bin/setup +34 -0
  43. data/test/dummy/bin/update +29 -0
  44. data/test/dummy/config.ru +5 -0
  45. data/test/dummy/config/application.rb +20 -0
  46. data/test/dummy/config/boot.rb +5 -0
  47. data/test/dummy/config/cable.yml +9 -0
  48. data/test/dummy/config/environment.rb +5 -0
  49. data/test/dummy/config/environments/development.rb +54 -0
  50. data/test/dummy/config/environments/production.rb +86 -0
  51. data/test/dummy/config/environments/test.rb +43 -0
  52. data/test/dummy/config/initializers/application_controller_renderer.rb +6 -0
  53. data/test/dummy/config/initializers/assets.rb +11 -0
  54. data/test/dummy/config/initializers/backtrace_silencers.rb +7 -0
  55. data/test/dummy/config/initializers/cookies_serializer.rb +5 -0
  56. data/test/dummy/config/initializers/filter_parameter_logging.rb +4 -0
  57. data/test/dummy/config/initializers/inflections.rb +16 -0
  58. data/test/dummy/config/initializers/mime_types.rb +4 -0
  59. data/test/dummy/config/initializers/new_framework_defaults.rb +21 -0
  60. data/test/dummy/config/initializers/session_store.rb +3 -0
  61. data/test/dummy/config/initializers/workarea.rb +5 -0
  62. data/test/dummy/config/initializers/wrap_parameters.rb +14 -0
  63. data/test/dummy/config/locales/en.yml +23 -0
  64. data/test/dummy/config/puma.rb +47 -0
  65. data/test/dummy/config/routes.rb +5 -0
  66. data/test/dummy/config/secrets.yml +22 -0
  67. data/test/dummy/config/spring.rb +6 -0
  68. data/test/dummy/lib/assets/.keep +0 -0
  69. data/test/dummy/log/.keep +0 -0
  70. data/test/dummy/public/404.html +67 -0
  71. data/test/dummy/public/422.html +67 -0
  72. data/test/dummy/public/500.html +66 -0
  73. data/test/dummy/public/apple-touch-icon-precomposed.png +0 -0
  74. data/test/dummy/public/apple-touch-icon.png +0 -0
  75. data/test/dummy/public/favicon.ico +0 -0
  76. data/test/test_helper.rb +13 -0
  77. data/test/view_models/workarea/storefront/google_product_feed_view_model_test.rb +46 -0
  78. data/test/workers/workarea/exporting/google_feed_test.rb +181 -0
  79. data/workarea-google_product_feed.gemspec +16 -0
  80. metadata +140 -0
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: cd02b4d78fddea69ede52bdb1f96282debd4d8f21c4c82c104c4af08b23325e3
4
+ data.tar.gz: 407d2275d33690d402795ff6ca127b3761ab63d5c7fc77a3f90c586e163602a2
5
+ SHA512:
6
+ metadata.gz: fcd0ef976f661a4aa9386878c6117d28df6c279a43db090ebe2ea79e43d0a437c601b112d8c74f6b65daed9dbbb08cebc48950ac51cd86ea5bf596fe03e01bfd
7
+ data.tar.gz: 01671aef8ac70fe1cf0586e46cc1d46421d31d2936ad7e47949bdc5005e2c7d758e7ad2ed4cb513d740dcef595c31f847d53d000a69ff3607573fbfdfdb9f3fe
data/.gitignore ADDED
@@ -0,0 +1,14 @@
1
+ .bundle/
2
+ Gemfile.lock
3
+ .sass-cache/
4
+ .DS_Store
5
+ log/*.log
6
+ pkg/
7
+ tmp/
8
+ test/dummy/public/system/*
9
+ test/dummy/db/*.sqlite3
10
+ test/dummy/log/*.log
11
+ test/dummy/tmp/
12
+ test/dummy/.sass-cache
13
+ coverage
14
+ .byebug_history
data/CHANGELOG.md ADDED
@@ -0,0 +1,99 @@
1
+ Workarea Google Product Feed 3.1.3 (2019-08-21)
2
+ --------------------------------------------------------------------------------
3
+
4
+ * Open Source!
5
+
6
+
7
+
8
+ Workarea Google Product Feed 3.1.2 (2019-06-25)
9
+ --------------------------------------------------------------------------------
10
+
11
+ * Update image urls in google product feed
12
+
13
+ * change image size to be :detail so the images are larger than 250
14
+ (googles requirement for apparel products)
15
+ * fix problems with tests failing with multi-site
16
+
17
+ GOOGPROD-22
18
+ Eric Pigeon
19
+
20
+
21
+
22
+ Workarea Google Product Feed 3.1.1 (2018-09-19)
23
+ --------------------------------------------------------------------------------
24
+
25
+ * Fix config values not being reset in tests
26
+
27
+ Tests can fail because values aren't reset properly.
28
+
29
+ GOOGPROD-20
30
+ Ben Crouse
31
+
32
+
33
+
34
+ Google Product Feed 3.1.0 (2018-03-21)
35
+ --------------------------------------------------------------------------------
36
+
37
+ * Add a configuration for static values in google merchant feed
38
+
39
+ GOOGPROD-16
40
+ Eric Pigeon
41
+
42
+ * Use a product's default category first
43
+
44
+ update the google taxonomy to prefer using it's default category first
45
+
46
+ GOOGPROD-18
47
+ Eric Pigeon
48
+
49
+
50
+ Google Product Feed 3.0.2 (2018-01-11)
51
+ --------------------------------------------------------------------------------
52
+
53
+ * Product feed does not include inactive products
54
+
55
+ GOOGPROD-10
56
+ Jake Beresford
57
+
58
+ * Improve implementation of Google category field
59
+
60
+ * check product, then category, then fall back to a configuration
61
+
62
+ GOOGPROD-11
63
+ Jake Beresford
64
+
65
+ * Do not export non-displayable skus to google feed
66
+
67
+ GOOGPROD-10
68
+ Jake Beresford
69
+
70
+
71
+ Google Product Feed 3.0.0 (2017-05-25)
72
+ --------------------------------------------------------------------------------
73
+
74
+ * Fixes final test.
75
+
76
+ GOOGPROD-7
77
+ Beresford, Jake
78
+
79
+ * Upgrade plugin for v3 compatibility
80
+
81
+ * Rename
82
+ * Convert rspec to minitest
83
+ * Use Variant#active? instead of Variant#purchasable?
84
+
85
+ GOOGPROD-7
86
+ Beresford, Jake
87
+
88
+
89
+ Google Product Feed 2.0.5 (2016-12-07)
90
+ --------------------------------------------------------------------------------
91
+
92
+ * Send the correct price for a variant
93
+
94
+ If a product has different pricers per variant we should use the correct
95
+ sell price for the sku rather than the minimal selling price of the
96
+ product
97
+
98
+ GOOGPROD-6
99
+ Eric Pigeon
data/Gemfile ADDED
@@ -0,0 +1,10 @@
1
+ source 'https://rubygems.org'
2
+
3
+ gemspec
4
+
5
+ gem 'workarea', source: 'https://gems.weblinc.com'
6
+
7
+ group :test do
8
+ gem 'simplecov', require: false
9
+ gem 'simplecov-bamboo', require: false
10
+ end
data/README.md ADDED
@@ -0,0 +1,61 @@
1
+ Workarea Google Production Feed
2
+ ================================================================================
3
+ Google Production Feed for the Workarea Platform
4
+
5
+
6
+ Google category
7
+ --------------------------------------------------------------------------------
8
+ The google product feed includes a field for google category. This field must map to one of the pre-defined categories.
9
+ Documentation can be found here [https://support.google.com/merchants/answer/6324436?hl=en](https://support.google.com/merchants/answer/6324436?hl=en)
10
+
11
+ The workarea platform implements this field by:
12
+ 1) Look for google\_category on the product, use this if found.
13
+ 2) Look for google\_name on the products default category
14
+ 3) Look for google\_name on categories the product is in, use this if found
15
+ 4) Use the default google category from Workarea::GoogleProductFeed.default\_category
16
+
17
+ Getting Started
18
+ --------------------------------------------------------------------------------
19
+
20
+ You must have access to a WebLinc gems server to use this gem. Add your gems server credentials to Bundler:
21
+
22
+ ```bash
23
+ $ bundle config gems.weblinc.com my_username:my_password
24
+ ```
25
+
26
+ Or set the appropriate environment variable in a shell startup file:
27
+
28
+ ```bash
29
+ $ export BUNDLE_GEMS__WEBLINC__COM='my_username:my_password'
30
+ ```
31
+
32
+ Then add the gem to your application's Gemfile specifying the source:
33
+
34
+ ```ruby
35
+ # ...
36
+ gem 'workarea-google_product_feed', source: 'https://gems.weblinc.com'
37
+ # ...
38
+ ```
39
+
40
+ Or use a source block:
41
+
42
+ ```ruby
43
+ # ...
44
+ source 'https://gems.weblinc.com' do
45
+ gem 'workarea-google_product_feed'
46
+ end
47
+ # ...
48
+ ```
49
+
50
+ Update your application's bundle.
51
+
52
+ ```bash
53
+ $ cd path/to/application ; bundle
54
+ ```
55
+
56
+ Copyright & Licensing
57
+ --------------------------------------------------------------------------------
58
+
59
+ Copyright WebLinc 2018. All rights reserved.
60
+
61
+ For licensing, contact sales@workarea.com.
data/Rakefile ADDED
@@ -0,0 +1,53 @@
1
+ #!/usr/bin/env rake
2
+ begin
3
+ require 'bundler/setup'
4
+ rescue LoadError
5
+ puts 'You must `gem install bundler` and `bundle install` to run rake tasks'
6
+ end
7
+
8
+ require 'rdoc/task'
9
+
10
+ RDoc::Task.new(:rdoc) do |rdoc|
11
+ rdoc.rdoc_dir = 'rdoc'
12
+ rdoc.title = 'Tasker'
13
+ rdoc.options << '--line-numbers'
14
+ rdoc.rdoc_files.include('README.md')
15
+ rdoc.rdoc_files.include('lib/**/*.rb')
16
+ end
17
+
18
+ APP_RAKEFILE = File.expand_path('../test/dummy/Rakefile', __FILE__)
19
+ load 'rails/tasks/engine.rake'
20
+ load 'rails/tasks/statistics.rake'
21
+ load 'workarea/changelog.rake'
22
+
23
+ require 'rake/testtask'
24
+
25
+ Rake::TestTask.new(:test) do |t|
26
+ t.libs << 'lib'
27
+ t.libs << 'test'
28
+ t.pattern = 'test/**/*_test.rb'
29
+ t.verbose = false
30
+ end
31
+
32
+ task default: :test
33
+
34
+ $LOAD_PATH.unshift File.expand_path('../lib', __FILE__)
35
+ require 'workarea/google_product_feed/version'
36
+
37
+ desc "Release version #{Workarea::GoogleProductFeed::VERSION} of the gem"
38
+ task :release do
39
+ host = "https://#{ENV['BUNDLE_GEMS__WEBLINC__COM']}@gems.weblinc.com"
40
+
41
+ #Rake::Task['workarea:changelog'].execute
42
+ #system 'git add CHANGELOG.md'
43
+ #system 'git commit -m "Update CHANGELOG"'
44
+ #system 'git push origin HEAD'
45
+
46
+ system "git tag -a v#{Workarea::GoogleProductFeed::VERSION} -m 'Tagging #{Workarea::GoogleProductFeed::VERSION}'"
47
+ system 'git push --tags'
48
+
49
+ system 'gem build workarea-google_product_feed.gemspec'
50
+ system "gem push workarea-google_product_feed-#{Workarea::GoogleProductFeed::VERSION}.gem"
51
+ system "gem push workarea-google_product_feed-#{Workarea::GoogleProductFeed::VERSION}.gem --host #{host}"
52
+ system "rm workarea-google_product_feed-#{Workarea::GoogleProductFeed::VERSION}.gem"
53
+ end
@@ -0,0 +1,7 @@
1
+ module Workarea
2
+ decorate Catalog::Category, with: :google_product_feed do
3
+ decorated do
4
+ field :google_name, type: String
5
+ end
6
+ end
7
+ end
@@ -0,0 +1,7 @@
1
+ module Workarea
2
+ decorate Catalog::Product, with: :google_product_feed do
3
+ decorated do
4
+ field :google_category, type: String
5
+ end
6
+ end
7
+ end
@@ -0,0 +1,16 @@
1
+ module Workarea
2
+ class Feed::Google
3
+ class NoFeedError < StandardError; end
4
+
5
+ include ApplicationDocument
6
+ extend Dragonfly::Model
7
+
8
+ field :feed_uid, type: String
9
+
10
+ dragonfly_accessor :feed, app: :workarea
11
+
12
+ def self.find_first_or_initialize
13
+ first || new
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,26 @@
1
+ module Workarea
2
+ module Storefront
3
+ class GoogleProductFeedSkuViewModel < ApplicationViewModel
4
+ def status
5
+ model.active? ? 'in stock' : 'out of stock'
6
+ end
7
+
8
+ def color
9
+ Array.wrap(model.fetch_detail('color')).first
10
+ end
11
+
12
+ def condition
13
+ 'new'
14
+ end
15
+
16
+ def size
17
+ Array.wrap(model.fetch_detail('size')).first
18
+ end
19
+
20
+ def displayable?
21
+ return false unless options[:inventory_sku].present?
22
+ options[:inventory_sku].displayable?
23
+ end
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,125 @@
1
+ module Workarea
2
+ module Storefront
3
+ class GoogleProductFeedViewModel < ApplicationViewModel
4
+ module ProductImageUrl
5
+ include Workarea::ApplicationHelper
6
+ include Workarea::I18n::DefaultUrlOptions
7
+ include ActionView::Helpers::AssetUrlHelper
8
+ include Core::Engine.routes.url_helpers
9
+ extend self
10
+
11
+ def mounted_core
12
+ self
13
+ end
14
+ end
15
+ include ActionView::Helpers::SanitizeHelper
16
+ include ActionView::Helpers::TextHelper
17
+
18
+ def self.fields
19
+ [
20
+ 'item_group_id',
21
+ 'id',
22
+ 'title',
23
+ 'description',
24
+ 'link',
25
+ 'image_link',
26
+ 'availability',
27
+ 'condition',
28
+ 'brand',
29
+ 'color',
30
+ 'size',
31
+ 'google_product_category',
32
+ 'product_type',
33
+ 'price'
34
+ ] + GoogleProductFeed.static_feed_values.keys
35
+ end
36
+
37
+ delegate :sell_min_price, to: :pricing
38
+
39
+ def brand
40
+ return '' unless model.filters['brand'].present?
41
+ model.filters['brand'].join(' ')
42
+ end
43
+
44
+ def category_name
45
+ model.google_category.presence ||
46
+ category&.google_name.presence ||
47
+ Workarea::GoogleProductFeed.default_category
48
+ end
49
+
50
+ # TODO this should be changed to return sku images if present
51
+ def image
52
+ ProductImageUrl.product_image_url(images.primary, GoogleProductFeed.image_size)
53
+ end
54
+
55
+ def meta_description
56
+ if model.meta_description.present?
57
+ sanitize_description(model.meta_description)
58
+ else
59
+ sanitize_description(model.description)
60
+ end
61
+ end
62
+
63
+ def product_type
64
+ return '' unless model.filters['product_type'].present?
65
+ model.filters['product_type'].first
66
+ end
67
+
68
+ def variants
69
+ @variants ||= model.variants.map do |variant|
70
+ GoogleProductFeedSkuViewModel.wrap(
71
+ variant,
72
+ inventory_sku: inventory.for_sku(variant.sku)
73
+ )
74
+ end
75
+ end
76
+
77
+ def sku_price(sku)
78
+ pricing.for_sku(sku).sell
79
+ end
80
+
81
+ private
82
+
83
+ def category
84
+ return categorization.default_model if categorization.default_model&.google_name.present?
85
+
86
+ categorization.to_models.detect { |c| c.google_name.present? } ||
87
+ categorization.to_models.first
88
+ end
89
+
90
+ def categorization
91
+ @categorization ||= Workarea::Categorization.new(model)
92
+ end
93
+
94
+ def host
95
+ Workarea.config.host
96
+ end
97
+
98
+ def images
99
+ @images ||= Storefront::ProductViewModel::ImageCollection.new(model, options)
100
+ end
101
+
102
+ def inventory
103
+ @inventory ||= Inventory::Collection.new(model.variants.map(&:sku))
104
+ end
105
+
106
+ def pricing
107
+ @pricing ||= Pricing::Collection.new(model.variants.map(&:sku))
108
+ end
109
+
110
+ def primary_image
111
+ model.images.asc(:position).first
112
+ end
113
+
114
+ def sanitize_description(description)
115
+ return if description.nil?
116
+
117
+ # strip html and shorten to the google max of 5k chars
118
+ sanitized = truncate(strip_tags(description), length: 5000)
119
+
120
+ # strip tabs and new lines
121
+ sanitized.tr("\t\r\n", '')
122
+ end
123
+ end
124
+ end
125
+ end
@@ -0,0 +1,90 @@
1
+ module Workarea
2
+ module Exporting
3
+ class GoogleFeed
4
+ include Sidekiq::Worker
5
+ include I18n::DefaultUrlOptions
6
+ include Storefront::Engine.routes.url_helpers
7
+
8
+ cattr_accessor :execution_block_size, :temp_file
9
+ self.execution_block_size = 100
10
+ self.temp_file = 'tmp/export/google_feed.txt'
11
+
12
+ sidekiq_options queue: 'low'
13
+
14
+ def perform(*args)
15
+ generate_csv
16
+ save_google_feed
17
+ end
18
+
19
+ def generate_csv
20
+ ensure_directory
21
+
22
+ @feed = CSV.open(GoogleFeed.temp_file, 'w', col_sep: "\t")
23
+ write_header
24
+ write_google_feed
25
+
26
+ ensure
27
+ @feed.close if defined?(@feed)
28
+ end
29
+
30
+ private
31
+
32
+ def ensure_directory
33
+ directory = File.dirname(GoogleFeed.temp_file)
34
+ FileUtils.mkdir_p(directory)
35
+ end
36
+
37
+ def write_header
38
+ @feed << Storefront::GoogleProductFeedViewModel.fields
39
+ end
40
+
41
+ def write_google_feed
42
+ Catalog::Product.all.each_by(GoogleFeed.execution_block_size) do |p|
43
+ product = Storefront::GoogleProductFeedViewModel.wrap(p)
44
+
45
+ product.variants.each do |sku|
46
+ next unless item_valid(product, sku)
47
+ @feed << values_array(product, sku)
48
+ end
49
+ end
50
+ end
51
+
52
+ def save_google_feed
53
+ google_feed = Feed::Google.find_first_or_initialize
54
+ google_feed.feed = @feed
55
+ google_feed.save!
56
+ end
57
+
58
+ def host
59
+ Workarea.config.host
60
+ end
61
+
62
+ def item_valid(product, sku)
63
+ sku.active? &&
64
+ sku.displayable? &&
65
+ product.active? &&
66
+ product.sku_price(sku.sku).present? &&
67
+ product.sku_price(sku.sku) > 0
68
+ end
69
+
70
+ def values_array(product, sku)
71
+ [
72
+ product.id,
73
+ sku.sku,
74
+ product.name,
75
+ product.meta_description,
76
+ product_url(product, host: host, sku: sku.sku),
77
+ product.image,
78
+ sku.status,
79
+ sku.condition,
80
+ product.brand,
81
+ sku.color,
82
+ sku.size,
83
+ product.category_name,
84
+ product.product_type,
85
+ product.sku_price(sku.sku)
86
+ ] + GoogleProductFeed.static_feed_values.values
87
+ end
88
+ end
89
+ end
90
+ end