From c98ca8d3b74227ab1129e78ade5f3945ae36eab9 Mon Sep 17 00:00:00 2001 From: Gavin Ballard Date: Sat, 4 Apr 2020 17:19:12 +1100 Subject: [PATCH 01/34] rbenv -> asdf --- README.md | 62 +++++++++++++++++++++++++++---------------------------- 1 file changed, 31 insertions(+), 31 deletions(-) diff --git a/README.md b/README.md index ba9eeb09..05b10fe0 100644 --- a/README.md +++ b/README.md @@ -26,16 +26,16 @@ are: - You should have set up a Shopify Partner account to allow you to create development stores and applications; - [asdf][] is recommended for Ruby version management; -- You should have the latest version of Ruby 2.5 installed locally, along with +- You should have the latest version of Ruby 2.6 installed locally, along with the `rails` and `bundler` gems (make sure you have the version of Rails you'd like to use installed - use `gem install rails -v VERSION` for this); -- You should have [ngrok] installed for HTTP tunnelling; +- You should have [ngrok] installed for HTTP tunnelling; - You should have followed the instructions in the Development Configuration Notion card for configuring Bundler with credentials to access Disco's private Gemfury server. - You should have followed the instructions in the Development Configuration Notion page to have generated a personal access token on Github and added it to your development configuration. - + [Development Setup]: https://www.notion.so/discolabs/Development-Setup-97199ecca84343c18f29efec6fd841ab [Development Configuration]: https://www.notion.so/discolabs/Development-Configuration-9ac4e3d77da7454480d750bde3323a0a [asdf]: https://github.com/asdf-vm/asdf @@ -61,7 +61,7 @@ example, to use Rails 4.2 with Ruby 2.4.1 and DiscoApp version 0.13.8, the last line of the command above should read: ``` - | bash -s example_app 4.2.0 2.4.1 0.13.8 + | bash -s example_app 4.2.0 2.4.1 0.13.8 ``` Once this is complete, you'll have a new Rails app created in `/example_app`, @@ -377,13 +377,13 @@ specific webhooks are received. They are: received. By default, this task keeps the metadata attributes on the relevant `DiscoApp::Shop` model up to date. - `DiscoApp::SubscriptionChangedJob`, called whenever a shop changes the plan - that they are subscribed to. + that they are subscribed to. - `DiscoApp::SynchroniseWebhooksJob`, called by the installation job but also enqueued by the `webhooks:sync` rake task to allow for re-synchronisation of webhooks after installation. - `DiscoApp::SynchroniseCarrierServiceJob`, called by the installation job but also enqueued by the `carrier_service:sync` rake task to allow for - re-synchronisation of the carrier service after installation. + re-synchronisation of the carrier service after installation. The default jobs that come with the engine can be extended through the use of Concerns in a similar way to the models that come with the engine. See @@ -420,7 +420,7 @@ Webhooks should generally be created inside the `perform` method of the `DiscoApp::AppInstalledJob` background task. By default, webhooks are set up to listen for the `app/uninstalled` and `shop/update` webhook topics. -To check which webhooks are registered by your app run `shop.with_api_context{ShopifyAPI::Webhook.find(:all)}` from your console, where `shop = DiscoApp::Shop.find(your_shop_id)`. +To check which webhooks are registered by your app run `shop.with_api_context{ShopifyAPI::Webhook.find(:all)}` from your console, where `shop = DiscoApp::Shop.find(your_shop_id)`. ### Shopify Flow The gem provides support for [Shopify Flow Connectors][], allowing applications @@ -529,7 +529,7 @@ code to support Shopify Flow Actions and Triggers are: should be fired; 2. Create a `Flow::Actions::ActionName` service object class for each action you'd like your application to be able to process. - + Assuming you've configured your application's Flow integration correctly from the Shopify Partner dashboard, the sending of triggers and receiving of actions should then "just work". @@ -537,7 +537,7 @@ should then "just work". However, to help maintain an overview of the actions and triggers supported by your application with its codebase, it's recommended to maintain two additional initializers in your application's configuration that describe them. These -files should then be treated as the source of truth for your application's +files should then be treated as the source of truth for your application's actions and triggers, and should be referenced when setting up or updating your application's Flow configuration from the Partner Dashboard. @@ -597,9 +597,9 @@ model change. Here's an example: ``` # app/models/widget_configuration.rb class WidgetConfiguration < ActiveRecord::Base - include DiscoApp::Concerns::RendersAssets - renders_assets :js_asset, assets: 'assets/widgets.js', triggered_by: 'locale' -end + include DiscoApp::Concerns::RendersAssets + renders_assets :js_asset, assets: 'assets/widgets.js', triggered_by: 'locale' +end ``` With this simple declaration, any time the `locale` attribute on a particular @@ -624,8 +624,8 @@ like this: ``` # app/models/widget_configuration.rb class WidgetConfiguration < ActiveRecord::Base - include DiscoApp::Concerns::RendersAssets - renders_assets :widget_assets, assets: ['assets/widgets.scss', 'assets/widgets.js'], triggered_by: ['locale', 'background_color'] + include DiscoApp::Concerns::RendersAssets + renders_assets :widget_assets, assets: ['assets/widgets.scss', 'assets/widgets.js'], triggered_by: ['locale', 'background_color'] end ``` @@ -650,8 +650,8 @@ rendered asset: ``` # app/models/widget_configuration.rb class WidgetConfiguration < ActiveRecord::Base - include DiscoApp::Concerns::RendersAssets - renders_assets :widget_assets, assets: ['assets/widgets.scss', 'assets/widgets.js'], script_tags: 'widgets.js', triggered_by: ['locale', 'background_color'] + include DiscoApp::Concerns::RendersAssets + renders_assets :widget_assets, assets: ['assets/widgets.scss', 'assets/widgets.js'], script_tags: 'widgets.js', triggered_by: ['locale', 'background_color'] end ``` @@ -722,7 +722,7 @@ application proxy, you would have the following in your application's class MyModel < ActiveRecord::Base include DiscoApp::Concerns::CanBeLiquified - ... rest of model definition ... + ... rest of model definition ... end ``` @@ -858,8 +858,8 @@ implementation of this inside the dummy app used for testing Disco App in ```ruby class Product < ActiveRecord::Base - include DiscoApp::Concerns::Synchronises - belongs_to :shop, class_name: 'DiscoApp::Shop' + include DiscoApp::Concerns::Synchronises + belongs_to :shop, class_name: 'DiscoApp::Shop' end ``` 3. Add background jobs to handle possible webhook calls we could receive to keep @@ -869,7 +869,7 @@ implementation of this inside the dummy app used for testing Disco App in `synchronise_deletion` method as appropriate, eg: ```ruby - class ProductsCreateJob < DiscoApp::ShopJob + class ProductsCreateJob < DiscoApp::ShopJob def perform(_shop, product_data) Product.synchronise(@shop, product_data) end @@ -882,7 +882,7 @@ implementation of this inside the dummy app used for testing Disco App in ``` class Product < ActiveRecord::Base - include DiscoApp::Concerns::Synchronises + include DiscoApp::Concerns::Synchronises belongs_to :shop, class_name: 'DiscoApp::Shop' SHOPIFY_API_CLASS = ShopifyAPI::Product end @@ -898,7 +898,7 @@ wanted to synchronise products of a particular type, you could implement: ```ruby class Product < ActiveRecord::Base - include DiscoApp::Concerns::Synchronises + include DiscoApp::Concerns::Synchronises belongs_to :shop, class_name: 'DiscoApp::Shop' def should_synchronise?(shop, data) @@ -914,7 +914,7 @@ can include `DiscoApp::Concerns::HasMetafields` to gain access to a convenient on your class and away you go: ``` -class Product < ActiveRecord::Base +class Product < ActiveRecord::Base include DiscoApp::Concerns::HasMetafields SHOPIFY_API_CLASS = ShopifyAPI::Product @@ -1005,9 +1005,9 @@ Check that: - You've correctly listed the redirect URI in the app on the partner dashboard. ### Scheduled tasks aren't running -Check that you've added the tasks to the server. This will look something like: +Check that you've added the tasks to the server. This will look something like: -``` +``` dokku_apps: - name: app-name plugins: ['redis'] @@ -1020,17 +1020,17 @@ Check that you've added the tasks to the server. This will look something like: minute: "*/5" ``` -Don't forget to provision the server after making changes: `./provision.sh server-name`. +Don't forget to provision the server after making changes: `./provision.sh server-name`. ### Webhooks aren't firing This is a pretty common problem and can be cause by a number of things. You can check if your webhook has registered by running `shop.with_api_context{ShopifyAPI::Webhook.find(:all)}` in a Rails console, where `shop = DiscoApp::Shop.find(your_shop_id)`. If it isn't registered, check the following things: -1. Check you've run the `rake webhooks:sync` task -2. Check you've added the webhook topic to `config/initializers/disco_app.rb` and it's spelled correctly -3. Ensure you have a background job set up and named correctly with a `perform` method -4. Run `DiscoApp::Shop.installed.has_active_shopify_plan` from a console. If this doesn't return an active plan make sure `shop.status` is set to `'installed'`. +1. Check you've run the `rake webhooks:sync` task +2. Check you've added the webhook topic to `config/initializers/disco_app.rb` and it's spelled correctly +3. Ensure you have a background job set up and named correctly with a `perform` method +4. Run `DiscoApp::Shop.installed.has_active_shopify_plan` from a console. If this doesn't return an active plan make sure `shop.status` is set to `'installed'`. -If you encounter other speedbumps with webhooks please add then to this list. +If you encounter other speedbumps with webhooks please add then to this list. ## Contributing While developing Shopify applications using the DiscoApp Engine, you may see From 9c3e7573ba321bc024b4ccdfe02366dda388c31b Mon Sep 17 00:00:00 2001 From: Gavin Ballard Date: Sat, 4 Apr 2020 17:48:30 +1100 Subject: [PATCH 02/34] Add Node version to initialise.sh --- README.md | 12 ++++++------ initialise.sh | 8 +++++--- 2 files changed, 11 insertions(+), 9 deletions(-) diff --git a/README.md b/README.md index 05b10fe0..bc99ab51 100644 --- a/README.md +++ b/README.md @@ -54,14 +54,14 @@ curl -H "Authorization: token $GITHUB_PERSONAL_ACCESS_TOKEN" \ Be sure to change `example_app` to the desired name of your actual application. -By default, the `initialise.sh` script uses the latest version of Ruby, Rails -and the DiscoApp framework. If for any reason you need to specify which version -of each of these to use, you can provide them as arguments on the last line. For -example, to use Rails 4.2 with Ruby 2.4.1 and DiscoApp version 0.13.8, the last -line of the command above should read: +By default, the `initialise.sh` script uses the latest version of Ruby, Rails, +Node and the DiscoApp framework. If for any reason you need to specify which +version of each of these to use, you can provide them as arguments on the last +line. For example, to use Rails 4.2 with Ruby 2.4.1, Node 10.19 and DiscoApp +version 0.13.8, the last line of the command above should read: ``` - | bash -s example_app 4.2.0 2.4.1 0.13.8 + | bash -s example_app 4.2.0 2.4.1 10.19 0.13.8 ``` Once this is complete, you'll have a new Rails app created in `/example_app`, diff --git a/initialise.sh b/initialise.sh index 2d42b99e..fdafa985 100755 --- a/initialise.sh +++ b/initialise.sh @@ -2,13 +2,14 @@ # Usage: initialise.sh example_app APP_NAME="$1" -RAILS_VERSION="${RAILS_VERSION:-6.0.1}" +RAILS_VERSION="${RAILS_VERSION:-6.0.2}" RUBY_VERSION="${RUBY_VERSION:-2.6.5}" +NODE_VERSION="${NODE_VERSION:-13.7.0}" DISCO_APP_VERSION="${DISCO_APP_VERSION:-0.18.0}" if [ -z $APP_NAME ]; then echo "Usage: ./initialise.sh app_name (rails_version) (ruby_version) (disco_app_version)" - echo "Only app_name is required, defaults to Rails $RAILS_VERSION, Ruby $RUBY_VERSION, Disco App $DISCO_APP_VERSION." + echo "Only app_name is required, defaults to Rails $RAILS_VERSION, Ruby $RUBY_VERSION, Node $NODE_VERSION, Disco App $DISCO_APP_VERSION." exit fi @@ -17,8 +18,9 @@ cd $APP_NAME echo "source 'https://rubygems.org'" > Gemfile echo "gem 'rails', '~> $RAILS_VERSION'" >> Gemfile echo "ruby $RUBY_VERSION" > .tool-versions +echo "nodejs $NODE_VERSION" >> .tool-versions bundle install -bundle exec rails _"$RAILS_VERSION"_ new . --force --skip-bundle +bundle exec rails _"$RAILS_VERSION"_ new . --force echo "gem 'disco_app', '$DISCO_APP_VERSION', source: \"https://gem.fury.io/discolabs/\"" >> Gemfile bundle update bundle exec rails generate disco_app:install --force From ddbd81c3e9091afd9d5db3380aa011d24340cfd7 Mon Sep 17 00:00:00 2001 From: Gavin Ballard Date: Sun, 5 Apr 2020 11:01:00 +1000 Subject: [PATCH 03/34] Add TriggerUsage model to track status of flow triggers --- app/models/disco_app/concerns/shop.rb | 1 + .../disco_app/flow/concerns/trigger_usage.rb | 17 +++++++++++++++++ app/models/disco_app/flow/trigger_usage.rb | 9 +++++++++ ...20200405000000_create_flow_trigger_usages.rb | 16 ++++++++++++++++ 4 files changed, 43 insertions(+) create mode 100644 app/models/disco_app/flow/concerns/trigger_usage.rb create mode 100644 app/models/disco_app/flow/trigger_usage.rb create mode 100644 db/migrate/20200405000000_create_flow_trigger_usages.rb diff --git a/app/models/disco_app/concerns/shop.rb b/app/models/disco_app/concerns/shop.rb index b33b13e6..5073717d 100644 --- a/app/models/disco_app/concerns/shop.rb +++ b/app/models/disco_app/concerns/shop.rb @@ -19,6 +19,7 @@ module DiscoApp::Concerns::Shop # Define relationship to Flow actions and triggers. has_many :flow_actions, class_name: 'DiscoApp::Flow::Action', dependent: :destroy has_many :flow_triggers, class_name: 'DiscoApp::Flow::Trigger', dependent: :destroy + has_many :flow_trigger_usages, class_name: 'DiscoApp::Flow::TriggerUsage', dependent: :destroy # Define possible installation statuses as an enum. enum status: { diff --git a/app/models/disco_app/flow/concerns/trigger_usage.rb b/app/models/disco_app/flow/concerns/trigger_usage.rb new file mode 100644 index 00000000..6277c5b8 --- /dev/null +++ b/app/models/disco_app/flow/concerns/trigger_usage.rb @@ -0,0 +1,17 @@ +module DiscoApp + module Flow + module Concerns + module TriggerUsage + + extend ActiveSupport::Concern + + included do + belongs_to :shop + + self.table_name = :disco_app_flow_trigger_usages + end + + end + end + end +end diff --git a/app/models/disco_app/flow/trigger_usage.rb b/app/models/disco_app/flow/trigger_usage.rb new file mode 100644 index 00000000..ff08b62b --- /dev/null +++ b/app/models/disco_app/flow/trigger_usage.rb @@ -0,0 +1,9 @@ +module DiscoApp + module Flow + class TriggerUsage < ApplicationRecord + + include DiscoApp::Flow::Concerns::TriggerUsage + + end + end +end diff --git a/db/migrate/20200405000000_create_flow_trigger_usages.rb b/db/migrate/20200405000000_create_flow_trigger_usages.rb new file mode 100644 index 00000000..abfdf65f --- /dev/null +++ b/db/migrate/20200405000000_create_flow_trigger_usages.rb @@ -0,0 +1,16 @@ +class CreateFlowTriggerUsages < ActiveRecord::Migration[5.2] + + def change + create_table :disco_app_flow_trigger_usages do |t| + t.integer :shop_id, limit: 8 + t.string :flow_trigger_definition_id + t.boolean :has_enabled_flow, default: true + t.datetime :timestamp, null: true + t.timestamps null: false + end + + add_foreign_key :disco_app_flow_trigger_usages, :disco_app_shops, column: :shop_id, on_delete: :cascade + add_index :disco_app_flow_actions, [:shop_id, :flow_trigger_definition_id], unique: true + end + +end From 130a90d2da0dbef46d9555618d533c671133b56a Mon Sep 17 00:00:00 2001 From: Gavin Ballard Date: Sun, 5 Apr 2020 11:01:30 +1000 Subject: [PATCH 04/34] Add UpdateTriggerUsage service method --- .../disco_app/flow/update_trigger_usage.rb | 46 +++++++++++++++++++ 1 file changed, 46 insertions(+) create mode 100644 app/services/disco_app/flow/update_trigger_usage.rb diff --git a/app/services/disco_app/flow/update_trigger_usage.rb b/app/services/disco_app/flow/update_trigger_usage.rb new file mode 100644 index 00000000..32d95242 --- /dev/null +++ b/app/services/disco_app/flow/update_trigger_usage.rb @@ -0,0 +1,46 @@ +require 'interactor' + +module DiscoApp + module Flow + class UpdateTriggerUsage + + include Interactor + + delegate :shop, :flow_trigger_definition_id, :has_enabled_flow, :timestamp, to: :context + delegate :trigger_usage, to: :context + + def call + find_or_create_trigger_usage + update_trigger_usage + end + + private + + def find_or_create_trigger_usage + context.trigger_usage = shop.flow_trigger_usages.create_or_find_by!( + flow_trigger_definition_id: flow_trigger_definition_id + ) + rescue ActiveRecord::RecordNotUnique, PG::UniqueViolation + context.fail! + end + + def update_trigger_usage + return if existing_timestamp_is_newer? + + trigger_usage.update( + has_enabled_flow: has_enabled_flow, + timestamp: parsed_timestamp + ) + end + + def existing_timestamp_is_newer? + trigger_usage.timestamp.present? && parsed_timestamp < trigger_usage.timestamp + end + + def parsed_timestamp + @parsed_timestamp ||= Time.parse(timestamp) + end + + end + end +end From 7d5bd02e78bce5a4dc7858814a685f03d2f306fa Mon Sep 17 00:00:00 2001 From: Gavin Ballard Date: Sun, 5 Apr 2020 11:01:42 +1000 Subject: [PATCH 05/34] Add trigger usage controller --- .../flow/concerns/actions_controller.rb | 29 +---------- .../flow/concerns/trigger_usage_controller.rb | 23 +++++++++ .../flow/concerns/verifies_flow_payload.rb | 50 +++++++++++++++++++ .../flow/trigger_usage_controller.rb | 9 ++++ config/routes.rb | 4 ++ 5 files changed, 87 insertions(+), 28 deletions(-) create mode 100644 app/controllers/disco_app/flow/concerns/trigger_usage_controller.rb create mode 100644 app/controllers/disco_app/flow/concerns/verifies_flow_payload.rb create mode 100644 app/controllers/disco_app/flow/trigger_usage_controller.rb diff --git a/app/controllers/disco_app/flow/concerns/actions_controller.rb b/app/controllers/disco_app/flow/concerns/actions_controller.rb index d06342e3..22bb9263 100644 --- a/app/controllers/disco_app/flow/concerns/actions_controller.rb +++ b/app/controllers/disco_app/flow/concerns/actions_controller.rb @@ -4,12 +4,7 @@ module Concerns module ActionsController extend ActiveSupport::Concern - - included do - before_action :verify_flow_action - before_action :find_shop - protect_from_forgery with: :null_session - end + include DiscoApp::Flow::Concerns::VerifiesFlowPayload def create_flow_action DiscoApp::Flow::CreateAction.call( @@ -22,28 +17,6 @@ def create_flow_action head :ok end - private - - def verify_flow_action - return head :unauthorized unless flow_action_is_valid? - - request.body.rewind - end - - # Shopify Flow action endpoints use the same verification method as webhooks, which is why we reuse this - # service method here. - def flow_action_is_valid? - DiscoApp::WebhookService.valid_hmac?( - request.body.read.to_s, - ShopifyApp.configuration.secret, - request.headers['HTTP_X_SHOPIFY_HMAC_SHA256'] - ) - end - - def find_shop - @shop = DiscoApp::Shop.find_by!(shopify_domain: params[:shopify_domain]) - end - end end end diff --git a/app/controllers/disco_app/flow/concerns/trigger_usage_controller.rb b/app/controllers/disco_app/flow/concerns/trigger_usage_controller.rb new file mode 100644 index 00000000..982c36cb --- /dev/null +++ b/app/controllers/disco_app/flow/concerns/trigger_usage_controller.rb @@ -0,0 +1,23 @@ +module DiscoApp + module Flow + module Concerns + module TriggerUsageController + + extend ActiveSupport::Concern + include DiscoApp::Flow::Concerns::VerifiesFlowPayload + + def update_trigger_usage + DiscoApp::Flow::UpdateTriggerUsage.call( + shop: @shop, + flow_trigger_definition_id: params[:flow_trigger_definition_id], + has_enabled_flow: params[:has_enabled_flow], + timestamp: params[:timestamp] + ) + + head :ok + end + + end + end + end +end diff --git a/app/controllers/disco_app/flow/concerns/verifies_flow_payload.rb b/app/controllers/disco_app/flow/concerns/verifies_flow_payload.rb new file mode 100644 index 00000000..d550c413 --- /dev/null +++ b/app/controllers/disco_app/flow/concerns/verifies_flow_payload.rb @@ -0,0 +1,50 @@ +module DiscoApp + module Flow + module Concerns + module VerifiesFlowPayload + + extend ActiveSupport::Concern + + included do + before_action :verify_flow_payload + before_action :find_shop + protect_from_forgery with: :null_session + end + + def create_flow_action + DiscoApp::Flow::CreateAction.call( + shop: @shop, + action_id: params[:id], + action_run_id: params[:action_run_id], + properties: params[:properties] + ) + + head :ok + end + + private + + def verify_flow_payload + return head :unauthorized unless flow_payload_is_valid? + + request.body.rewind + end + + # Shopify Flow action endpoints use the same verification method as webhooks, which is why we reuse this + # service method here. + def flow_payload_is_valid? + DiscoApp::WebhookService.valid_hmac?( + request.body.read.to_s, + ShopifyApp.configuration.secret, + request.headers['HTTP_X_SHOPIFY_HMAC_SHA256'] + ) + end + + def find_shop + @shop = DiscoApp::Shop.find_by!(shopify_domain: params[:shopify_domain]) + end + + end + end + end +end diff --git a/app/controllers/disco_app/flow/trigger_usage_controller.rb b/app/controllers/disco_app/flow/trigger_usage_controller.rb new file mode 100644 index 00000000..fad04182 --- /dev/null +++ b/app/controllers/disco_app/flow/trigger_usage_controller.rb @@ -0,0 +1,9 @@ +module DiscoApp + module Flow + class TriggerUsageController < ActionController::Base + + include DiscoApp::Flow::Concerns::TriggerUsageController + + end + end +end diff --git a/config/routes.rb b/config/routes.rb index f6b4d0ce..c198ece5 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -12,6 +12,10 @@ controller :actions do post 'actions/:id' => :create_flow_action, as: :flow_actions end + + controller :trigger_usage do + post 'trigger_usage' => :update_trigger_usage, as: :flow_trigger_usage + end end resources :user_sessions, only: [:new, :create, :destroy] From 2dca22070136aa91f6e9c768f2c2dd6b2e89f7c4 Mon Sep 17 00:00:00 2001 From: Gavin Ballard Date: Sun, 5 Apr 2020 11:04:41 +1000 Subject: [PATCH 06/34] Fix long index name --- db/migrate/20200405000000_create_flow_trigger_usages.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/db/migrate/20200405000000_create_flow_trigger_usages.rb b/db/migrate/20200405000000_create_flow_trigger_usages.rb index abfdf65f..90116de9 100644 --- a/db/migrate/20200405000000_create_flow_trigger_usages.rb +++ b/db/migrate/20200405000000_create_flow_trigger_usages.rb @@ -10,7 +10,7 @@ def change end add_foreign_key :disco_app_flow_trigger_usages, :disco_app_shops, column: :shop_id, on_delete: :cascade - add_index :disco_app_flow_actions, [:shop_id, :flow_trigger_definition_id], unique: true + add_index :disco_app_flow_trigger_usages, [:shop_id, :flow_trigger_definition_id], unique: true, name: :index_disco_app_flow_actions_on_shop_id_and_trigger_id end end From b2bff8221afc3d9a075cfbcec7e32db17eb81a20 Mon Sep 17 00:00:00 2001 From: Gavin Ballard Date: Sun, 5 Apr 2020 11:04:52 +1000 Subject: [PATCH 07/34] Update test schema --- test/dummy/db/schema.rb | 380 +++++++++++++++++++++------------------- 1 file changed, 196 insertions(+), 184 deletions(-) diff --git a/test/dummy/db/schema.rb b/test/dummy/db/schema.rb index e6494a73..38bea8f6 100644 --- a/test/dummy/db/schema.rb +++ b/test/dummy/db/schema.rb @@ -2,192 +2,204 @@ # of editing this file, please use the migrations feature of Active Record to # incrementally modify your database, and then regenerate this schema definition. # -# Note that this schema.rb definition is the authoritative source for your -# database schema. If you need to create the application database on another -# system, you should be using db:schema:load, not running all the migrations -# from scratch. The latter is a flawed and unsustainable approach (the more migrations -# you'll amass, the slower it'll run and the greater likelihood for issues). +# This file is the source Rails uses to define your schema when running `rails +# db:schema:load`. When creating a new database, `rails db:schema:load` tends to +# be faster and is potentially less error prone than running all of your +# migrations from scratch. Old migrations may fail to apply correctly if those +# migrations use external dependencies or application code. # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema.define(version: 2018_12_29_100327) do +ActiveRecord::Schema.define(version: 2020_04_05_000000) do + # These are extensions that must be enabled in order to support this database - enable_extension 'plpgsql' - - create_table 'carts', id: :serial, force: :cascade do |t| - t.bigint 'shop_id' - t.string 'token' - t.jsonb 'data' - t.datetime 'created_at', null: false - t.datetime 'updated_at', null: false - t.index ['token'], name: 'index_carts_on_token', unique: true - end - - create_table 'disco_app_app_settings', id: :serial, force: :cascade do |t| - t.datetime 'created_at', null: false - t.datetime 'updated_at', null: false - end - - create_table 'disco_app_application_charges', id: :serial, force: :cascade do |t| - t.bigint 'shop_id' - t.bigint 'subscription_id' - t.integer 'status', default: 0 - t.datetime 'created_at', null: false - t.datetime 'updated_at', null: false - t.bigint 'shopify_id' - t.string 'confirmation_url' - end - - create_table 'disco_app_flow_actions', force: :cascade do |t| - t.bigint 'shop_id' - t.string 'action_id' - t.string 'action_run_id' - t.jsonb 'properties', default: {} - t.integer 'status', default: 0 - t.datetime 'processed_at' - t.jsonb 'processing_errors', default: [] - t.datetime 'created_at', null: false - t.datetime 'updated_at', null: false - t.index ['action_run_id'], name: 'index_disco_app_flow_actions_on_action_run_id', unique: true - end - - create_table 'disco_app_flow_triggers', force: :cascade do |t| - t.bigint 'shop_id' - t.string 'title' - t.string 'resource_name' - t.string 'resource_url' - t.jsonb 'properties', default: {} - t.integer 'status', default: 0 - t.datetime 'processed_at' - t.jsonb 'processing_errors', default: [] - t.datetime 'created_at', null: false - t.datetime 'updated_at', null: false - end - - create_table 'disco_app_plan_codes', id: :serial, force: :cascade do |t| - t.bigint 'plan_id' - t.string 'code' - t.integer 'trial_period_days' - t.integer 'amount' - t.datetime 'created_at', null: false - t.datetime 'updated_at', null: false - t.integer 'status', default: 0 - end - - create_table 'disco_app_plans', id: :serial, force: :cascade do |t| - t.integer 'status', default: 0 - t.string 'name' - t.integer 'plan_type', default: 0 - t.integer 'trial_period_days' - t.datetime 'created_at', null: false - t.datetime 'updated_at', null: false - t.integer 'amount', default: 0 - t.string 'currency', default: 'USD' - t.integer 'interval', default: 0 - t.integer 'interval_count', default: 1 - end - - create_table 'disco_app_recurring_application_charges', id: :serial, force: :cascade do |t| - t.bigint 'shop_id' - t.bigint 'subscription_id' - t.integer 'status', default: 0 - t.datetime 'created_at', null: false - t.datetime 'updated_at', null: false - t.bigint 'shopify_id' - t.string 'confirmation_url' - end - - create_table 'disco_app_sessions', id: :serial, force: :cascade do |t| - t.string 'session_id', null: false - t.text 'data' - t.datetime 'created_at', null: false - t.datetime 'updated_at', null: false - t.integer 'shop_id' - t.index ['session_id'], name: 'index_disco_app_sessions_on_session_id', unique: true - t.index ['updated_at'], name: 'index_disco_app_sessions_on_updated_at' - end - - create_table 'disco_app_shops', id: :serial, force: :cascade do |t| - t.string 'shopify_domain', null: false - t.string 'shopify_token', null: false - t.datetime 'created_at', null: false - t.datetime 'updated_at', null: false - t.integer 'status', default: 0 - t.string 'domain' - t.string 'plan_name' - t.string 'name' - t.jsonb 'data', default: {} - t.index ['shopify_domain'], name: 'index_disco_app_shops_on_shopify_domain', unique: true - end - - create_table 'disco_app_sources', id: :serial, force: :cascade do |t| - t.string 'source' - t.string 'name' - t.datetime 'created_at', null: false - t.datetime 'updated_at', null: false - t.index ['source'], name: 'index_disco_app_sources_on_source' - end - - create_table 'disco_app_subscriptions', id: :serial, force: :cascade do |t| - t.integer 'shop_id' - t.integer 'plan_id' - t.integer 'status' - t.integer 'subscription_type' - t.datetime 'created_at', null: false - t.datetime 'updated_at', null: false - t.datetime 'trial_start_at' - t.datetime 'trial_end_at' - t.datetime 'cancelled_at' - t.integer 'amount', default: 0 - t.bigint 'plan_code_id' - t.integer 'trial_period_days' - t.bigint 'source_id' - t.index ['plan_id'], name: 'index_disco_app_subscriptions_on_plan_id' - t.index ['shop_id'], name: 'index_disco_app_subscriptions_on_shop_id' - end - - create_table 'disco_app_users', id: :serial, force: :cascade do |t| - t.bigint 'shop_id' - t.string 'first_name' - t.string 'last_name' - t.string 'email' - t.datetime 'created_at', null: false - t.datetime 'updated_at', null: false - t.index ['id', 'shop_id'], name: 'index_disco_app_users_on_id_and_shop_id', unique: true - end - - create_table 'js_configurations', id: :serial, force: :cascade do |t| - t.bigint 'shop_id' - t.string 'label', default: 'Default' - t.string 'locale', default: 'en' - end - - create_table 'products', id: :serial, force: :cascade do |t| - t.bigint 'shop_id' - t.jsonb 'data' - t.datetime 'created_at', null: false - t.datetime 'updated_at', null: false - end - - create_table 'widget_configurations', id: :serial, force: :cascade do |t| - t.bigint 'shop_id' - t.string 'label', default: 'Default' - t.string 'locale', default: 'en' - t.string 'background_color', default: '#FFFFFF' - end - - add_foreign_key 'carts', 'disco_app_shops', column: 'shop_id' - add_foreign_key 'disco_app_application_charges', 'disco_app_shops', column: 'shop_id' - add_foreign_key 'disco_app_application_charges', 'disco_app_subscriptions', column: 'subscription_id' - add_foreign_key 'disco_app_flow_actions', 'disco_app_shops', column: 'shop_id', on_delete: :cascade - add_foreign_key 'disco_app_flow_triggers', 'disco_app_shops', column: 'shop_id', on_delete: :cascade - add_foreign_key 'disco_app_plan_codes', 'disco_app_plans', column: 'plan_id' - add_foreign_key 'disco_app_recurring_application_charges', 'disco_app_shops', column: 'shop_id' - add_foreign_key 'disco_app_recurring_application_charges', 'disco_app_subscriptions', column: 'subscription_id' - add_foreign_key 'disco_app_sessions', 'disco_app_shops', column: 'shop_id', on_delete: :cascade - add_foreign_key 'disco_app_subscriptions', 'disco_app_plan_codes', column: 'plan_code_id' - add_foreign_key 'disco_app_subscriptions', 'disco_app_sources', column: 'source_id' - add_foreign_key 'js_configurations', 'disco_app_shops', column: 'shop_id' - add_foreign_key 'products', 'disco_app_shops', column: 'shop_id' - add_foreign_key 'widget_configurations', 'disco_app_shops', column: 'shop_id' + enable_extension "plpgsql" + + create_table "carts", id: :serial, force: :cascade do |t| + t.bigint "shop_id" + t.string "token" + t.jsonb "data" + t.datetime "created_at", null: false + t.datetime "updated_at", null: false + t.index ["token"], name: "index_carts_on_token", unique: true + end + + create_table "disco_app_app_settings", force: :cascade do |t| + t.datetime "created_at", null: false + t.datetime "updated_at", null: false + end + + create_table "disco_app_application_charges", force: :cascade do |t| + t.bigint "shop_id" + t.bigint "subscription_id" + t.integer "status", default: 0 + t.datetime "created_at", null: false + t.datetime "updated_at", null: false + t.bigint "shopify_id" + t.string "confirmation_url" + end + + create_table "disco_app_flow_actions", force: :cascade do |t| + t.bigint "shop_id" + t.string "action_id" + t.string "action_run_id" + t.jsonb "properties", default: {} + t.integer "status", default: 0 + t.datetime "processed_at" + t.jsonb "processing_errors", default: [] + t.datetime "created_at", null: false + t.datetime "updated_at", null: false + t.index ["action_run_id"], name: "index_disco_app_flow_actions_on_action_run_id", unique: true + end + + create_table "disco_app_flow_trigger_usages", force: :cascade do |t| + t.bigint "shop_id" + t.string "flow_trigger_definition_id" + t.boolean "has_enabled_flow", default: true + t.datetime "timestamp" + t.datetime "created_at", null: false + t.datetime "updated_at", null: false + t.index ["shop_id", "flow_trigger_definition_id"], name: "index_disco_app_flow_actions_on_shop_id_and_trigger_id", unique: true + end + + create_table "disco_app_flow_triggers", force: :cascade do |t| + t.bigint "shop_id" + t.string "title" + t.string "resource_name" + t.string "resource_url" + t.jsonb "properties", default: {} + t.integer "status", default: 0 + t.datetime "processed_at" + t.jsonb "processing_errors", default: [] + t.datetime "created_at", null: false + t.datetime "updated_at", null: false + end + + create_table "disco_app_plan_codes", force: :cascade do |t| + t.bigint "plan_id" + t.string "code" + t.integer "trial_period_days" + t.integer "amount" + t.datetime "created_at", null: false + t.datetime "updated_at", null: false + t.integer "status", default: 0 + end + + create_table "disco_app_plans", force: :cascade do |t| + t.integer "status", default: 0 + t.string "name" + t.integer "plan_type", default: 0 + t.integer "trial_period_days" + t.datetime "created_at", null: false + t.datetime "updated_at", null: false + t.integer "amount", default: 0 + t.string "currency", default: "USD" + t.integer "interval", default: 0 + t.integer "interval_count", default: 1 + end + + create_table "disco_app_recurring_application_charges", force: :cascade do |t| + t.bigint "shop_id" + t.bigint "subscription_id" + t.integer "status", default: 0 + t.datetime "created_at", null: false + t.datetime "updated_at", null: false + t.bigint "shopify_id" + t.string "confirmation_url" + end + + create_table "disco_app_sessions", force: :cascade do |t| + t.string "session_id", null: false + t.text "data" + t.datetime "created_at", null: false + t.datetime "updated_at", null: false + t.integer "shop_id" + t.index ["session_id"], name: "index_disco_app_sessions_on_session_id", unique: true + t.index ["updated_at"], name: "index_disco_app_sessions_on_updated_at" + end + + create_table "disco_app_shops", force: :cascade do |t| + t.string "shopify_domain", null: false + t.string "shopify_token", null: false + t.datetime "created_at", null: false + t.datetime "updated_at", null: false + t.integer "status", default: 0 + t.string "domain" + t.string "plan_name" + t.string "name" + t.jsonb "data", default: {} + t.index ["shopify_domain"], name: "index_disco_app_shops_on_shopify_domain", unique: true + end + + create_table "disco_app_sources", force: :cascade do |t| + t.string "source" + t.string "name" + t.datetime "created_at", null: false + t.datetime "updated_at", null: false + t.index ["source"], name: "index_disco_app_sources_on_source" + end + + create_table "disco_app_subscriptions", force: :cascade do |t| + t.integer "shop_id" + t.integer "plan_id" + t.integer "status" + t.integer "subscription_type" + t.datetime "created_at", null: false + t.datetime "updated_at", null: false + t.datetime "trial_start_at" + t.datetime "trial_end_at" + t.datetime "cancelled_at" + t.integer "amount", default: 0 + t.bigint "plan_code_id" + t.integer "trial_period_days" + t.bigint "source_id" + t.index ["plan_id"], name: "index_disco_app_subscriptions_on_plan_id" + t.index ["shop_id"], name: "index_disco_app_subscriptions_on_shop_id" + end + + create_table "disco_app_users", force: :cascade do |t| + t.bigint "shop_id" + t.string "first_name" + t.string "last_name" + t.string "email" + t.datetime "created_at", null: false + t.datetime "updated_at", null: false + t.index ["id", "shop_id"], name: "index_disco_app_users_on_id_and_shop_id", unique: true + end + + create_table "js_configurations", id: :serial, force: :cascade do |t| + t.bigint "shop_id" + t.string "label", default: "Default" + t.string "locale", default: "en" + end + + create_table "products", id: :serial, force: :cascade do |t| + t.bigint "shop_id" + t.jsonb "data" + t.datetime "created_at", null: false + t.datetime "updated_at", null: false + end + + create_table "widget_configurations", id: :serial, force: :cascade do |t| + t.bigint "shop_id" + t.string "label", default: "Default" + t.string "locale", default: "en" + t.string "background_color", default: "#FFFFFF" + end + + add_foreign_key "carts", "disco_app_shops", column: "shop_id" + add_foreign_key "disco_app_application_charges", "disco_app_shops", column: "shop_id" + add_foreign_key "disco_app_application_charges", "disco_app_subscriptions", column: "subscription_id" + add_foreign_key "disco_app_flow_actions", "disco_app_shops", column: "shop_id", on_delete: :cascade + add_foreign_key "disco_app_flow_trigger_usages", "disco_app_shops", column: "shop_id", on_delete: :cascade + add_foreign_key "disco_app_flow_triggers", "disco_app_shops", column: "shop_id", on_delete: :cascade + add_foreign_key "disco_app_plan_codes", "disco_app_plans", column: "plan_id" + add_foreign_key "disco_app_recurring_application_charges", "disco_app_shops", column: "shop_id" + add_foreign_key "disco_app_recurring_application_charges", "disco_app_subscriptions", column: "subscription_id" + add_foreign_key "disco_app_sessions", "disco_app_shops", column: "shop_id", on_delete: :cascade + add_foreign_key "disco_app_subscriptions", "disco_app_plan_codes", column: "plan_code_id" + add_foreign_key "disco_app_subscriptions", "disco_app_sources", column: "source_id" + add_foreign_key "js_configurations", "disco_app_shops", column: "shop_id" + add_foreign_key "products", "disco_app_shops", column: "shop_id" + add_foreign_key "widget_configurations", "disco_app_shops", column: "shop_id" end From 4f7618ebacd7d6cba36ffae66e8cc5bda75df22e Mon Sep 17 00:00:00 2001 From: Gavin Ballard Date: Sun, 5 Apr 2020 11:20:02 +1000 Subject: [PATCH 08/34] Change service call to expect a parsed Time object --- .../disco_app/flow/concerns/trigger_usage_controller.rb | 8 +++++++- app/services/disco_app/flow/update_trigger_usage.rb | 8 ++------ 2 files changed, 9 insertions(+), 7 deletions(-) diff --git a/app/controllers/disco_app/flow/concerns/trigger_usage_controller.rb b/app/controllers/disco_app/flow/concerns/trigger_usage_controller.rb index 982c36cb..72f68314 100644 --- a/app/controllers/disco_app/flow/concerns/trigger_usage_controller.rb +++ b/app/controllers/disco_app/flow/concerns/trigger_usage_controller.rb @@ -11,12 +11,18 @@ def update_trigger_usage shop: @shop, flow_trigger_definition_id: params[:flow_trigger_definition_id], has_enabled_flow: params[:has_enabled_flow], - timestamp: params[:timestamp] + timestamp: parsed_timestamp ) head :ok end + private + + def parsed_timestamp + Time.parse(params[:timestamp]) + end + end end end diff --git a/app/services/disco_app/flow/update_trigger_usage.rb b/app/services/disco_app/flow/update_trigger_usage.rb index 32d95242..99c09083 100644 --- a/app/services/disco_app/flow/update_trigger_usage.rb +++ b/app/services/disco_app/flow/update_trigger_usage.rb @@ -29,16 +29,12 @@ def update_trigger_usage trigger_usage.update( has_enabled_flow: has_enabled_flow, - timestamp: parsed_timestamp + timestamp: timestamp ) end def existing_timestamp_is_newer? - trigger_usage.timestamp.present? && parsed_timestamp < trigger_usage.timestamp - end - - def parsed_timestamp - @parsed_timestamp ||= Time.parse(timestamp) + trigger_usage.timestamp.present? && timestamp < trigger_usage.timestamp end end From 36620a0293f64814552bfa369e9c132f212b7d22 Mon Sep 17 00:00:00 2001 From: Gavin Ballard Date: Sun, 5 Apr 2020 11:20:15 +1000 Subject: [PATCH 09/34] Add test for trigger usage service --- .../flow/update_trigger_usage_test.rb | 87 +++++++++++++++++++ 1 file changed, 87 insertions(+) create mode 100644 test/services/disco_app/flow/update_trigger_usage_test.rb diff --git a/test/services/disco_app/flow/update_trigger_usage_test.rb b/test/services/disco_app/flow/update_trigger_usage_test.rb new file mode 100644 index 00000000..b16881c7 --- /dev/null +++ b/test/services/disco_app/flow/update_trigger_usage_test.rb @@ -0,0 +1,87 @@ +require 'test_helper' + +module DiscoApp + module Flow + class UpdateTriggerUsageTest < ActiveSupport::TestCase + + include ActiveJob::TestHelper + + def setup + @shop = disco_app_shops(:widget_store) + end + + def teardown + @shop = nil + end + + test 'when no usage record exists and usage is true, record is persisted as expected' do + result = UpdateTriggerUsage.call( + shop: @shop, + flow_trigger_definition_id: flow_trigger_definition_id, + has_enabled_flow: true, + timestamp: timestamp + ) + + assert result.success? + assert result.trigger_usage.persisted? + assert result.trigger_usage.has_enabled_flow? + assert_equal timestamp, result.trigger_usage.timestamp + end + + test 'when usage record exists and timestamp is older, record is updated as expected' do + older_timestamp = timestamp - 7.days + + trigger_usage = @shop.flow_trigger_usages.create!( + flow_trigger_definition_id: flow_trigger_definition_id, + has_enabled_flow: true, + timestamp: older_timestamp + ) + + result = UpdateTriggerUsage.call( + shop: @shop, + flow_trigger_definition_id: flow_trigger_definition_id, + has_enabled_flow: false, + timestamp: timestamp + ) + + assert result.success? + assert_equal trigger_usage, result.trigger_usage + assert_not result.trigger_usage.has_enabled_flow? + assert_equal timestamp, result.trigger_usage.timestamp + end + + test 'when usage record exists and timestamp is newer, record is not updated' do + newer_timestamp = timestamp + 7.days + + trigger_usage = @shop.flow_trigger_usages.create!( + flow_trigger_definition_id: flow_trigger_definition_id, + has_enabled_flow: true, + timestamp: newer_timestamp + ) + + result = UpdateTriggerUsage.call( + shop: @shop, + flow_trigger_definition_id: flow_trigger_definition_id, + has_enabled_flow: false, + timestamp: timestamp + ) + + assert result.success? + assert_equal trigger_usage, result.trigger_usage + assert result.trigger_usage.has_enabled_flow? + assert_equal newer_timestamp, result.trigger_usage.timestamp + end + + private + + def flow_trigger_definition_id + 'Test trigger' + end + + def timestamp + @timestamp ||= Time.parse('2020-04-05T00:34:00Z') + end + + end + end +end From 0866152fc635603aea240bd8a01aea4c11fddfb1 Mon Sep 17 00:00:00 2001 From: Gavin Ballard Date: Sun, 5 Apr 2020 11:37:43 +1000 Subject: [PATCH 10/34] Add controller tests for trigger usage updates --- .../flow/trigger_usage_controller_test.rb | 41 +++++++++++++++++++ .../fixtures/webhooks/flow/trigger_usage.json | 7 ++++ .../flow/update_trigger_usage_test.rb | 14 +++---- 3 files changed, 55 insertions(+), 7 deletions(-) create mode 100644 test/controllers/disco_app/flow/trigger_usage_controller_test.rb create mode 100644 test/fixtures/webhooks/flow/trigger_usage.json diff --git a/test/controllers/disco_app/flow/trigger_usage_controller_test.rb b/test/controllers/disco_app/flow/trigger_usage_controller_test.rb new file mode 100644 index 00000000..5dda9cbf --- /dev/null +++ b/test/controllers/disco_app/flow/trigger_usage_controller_test.rb @@ -0,0 +1,41 @@ +require 'test_helper' + +module DiscoApp + module Flow + class TriggerUsageControllerTest < ActionController::TestCase + + def setup + @shop = disco_app_shops(:widget_store) + @routes = DiscoApp::Engine.routes + end + + def teardown + @shop = nil + end + + test 'trigger usage request with no hmac returns unauthorized and does not process' do + body = webhook_fixture('flow/trigger_usage') + post :update_trigger_usage, body: body, as: :json + assert_response :unauthorized + assert_equal 0, @shop.flow_trigger_usages.count + end + + test 'trigger usage request with invalid hmac returns unauthorized and does not process' do + body = webhook_fixture('flow/trigger_usage') + @request.headers['HTTP_X_SHOPIFY_HMAC_SHA256'] = '4b5a5762352c9e9a8f4307f0e0ce6919370f1602bdedb331db3b7203de5ade6f' + post :update_trigger_usage, body: body, as: :json + assert_response :unauthorized + assert_equal 0, @shop.flow_trigger_usages.count + end + + test 'trigger usage request with valid hmac returns ok and does process' do + body = webhook_fixture('flow/trigger_usage') + @request.headers['HTTP_X_SHOPIFY_HMAC_SHA256'] = DiscoApp::WebhookService.calculated_hmac(body, ShopifyApp.configuration.secret) + post :update_trigger_usage, body: body, as: :json + assert_response :ok + assert_equal 1, @shop.flow_trigger_usages.count + end + + end + end +end diff --git a/test/fixtures/webhooks/flow/trigger_usage.json b/test/fixtures/webhooks/flow/trigger_usage.json new file mode 100644 index 00000000..79bd5b88 --- /dev/null +++ b/test/fixtures/webhooks/flow/trigger_usage.json @@ -0,0 +1,7 @@ +{ + "flow_trigger_definition_id": "Test trigger", + "has_enabled_flow": false, + "shop_id": "690933842", + "shopify_domain": "widgets.myshopify.com", + "timestamp": "2020-04-05T00:34:00.001Z" +} diff --git a/test/services/disco_app/flow/update_trigger_usage_test.rb b/test/services/disco_app/flow/update_trigger_usage_test.rb index b16881c7..6e9b1bcb 100644 --- a/test/services/disco_app/flow/update_trigger_usage_test.rb +++ b/test/services/disco_app/flow/update_trigger_usage_test.rb @@ -32,16 +32,16 @@ def teardown older_timestamp = timestamp - 7.days trigger_usage = @shop.flow_trigger_usages.create!( - flow_trigger_definition_id: flow_trigger_definition_id, - has_enabled_flow: true, - timestamp: older_timestamp + flow_trigger_definition_id: flow_trigger_definition_id, + has_enabled_flow: true, + timestamp: older_timestamp ) result = UpdateTriggerUsage.call( - shop: @shop, - flow_trigger_definition_id: flow_trigger_definition_id, - has_enabled_flow: false, - timestamp: timestamp + shop: @shop, + flow_trigger_definition_id: flow_trigger_definition_id, + has_enabled_flow: false, + timestamp: timestamp ) assert result.success? From a79275b3d8a751149f01392679352b6fa35215b0 Mon Sep 17 00:00:00 2001 From: Gavin Ballard Date: Sun, 5 Apr 2020 11:37:49 +1000 Subject: [PATCH 11/34] Cleanup --- .../flow/concerns/verifies_flow_payload.rb | 15 ++------------- 1 file changed, 2 insertions(+), 13 deletions(-) diff --git a/app/controllers/disco_app/flow/concerns/verifies_flow_payload.rb b/app/controllers/disco_app/flow/concerns/verifies_flow_payload.rb index d550c413..6e1e7260 100644 --- a/app/controllers/disco_app/flow/concerns/verifies_flow_payload.rb +++ b/app/controllers/disco_app/flow/concerns/verifies_flow_payload.rb @@ -11,17 +11,6 @@ module VerifiesFlowPayload protect_from_forgery with: :null_session end - def create_flow_action - DiscoApp::Flow::CreateAction.call( - shop: @shop, - action_id: params[:id], - action_run_id: params[:action_run_id], - properties: params[:properties] - ) - - head :ok - end - private def verify_flow_payload @@ -30,8 +19,8 @@ def verify_flow_payload request.body.rewind end - # Shopify Flow action endpoints use the same verification method as webhooks, which is why we reuse this - # service method here. + # Shopify Flow action and trigger usage update endpoints use the same + # verification as webhooks, which is why we reuse this service method here. def flow_payload_is_valid? DiscoApp::WebhookService.valid_hmac?( request.body.read.to_s, From 10a3df3a82def39478438a8bf3b489bb5f32ba53 Mon Sep 17 00:00:00 2001 From: Gavin Ballard Date: Sun, 5 Apr 2020 11:43:58 +1000 Subject: [PATCH 12/34] Add (failing) test for skipping triggers --- test/services/disco_app/flow/process_trigger_test.rb | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/test/services/disco_app/flow/process_trigger_test.rb b/test/services/disco_app/flow/process_trigger_test.rb index ae9f113e..a4db4273 100644 --- a/test/services/disco_app/flow/process_trigger_test.rb +++ b/test/services/disco_app/flow/process_trigger_test.rb @@ -37,6 +37,18 @@ def teardown assert_not result.success? end + test 'call to process trigger that we know is not being used fails with skipped status logged' do + @shop.flow_trigger_usages.create( + flow_trigger_definition_id: @trigger.title, + has_enabled_flow: false, + timestamp: @now + ) + result = ProcessTrigger.call(trigger: @trigger) + assert_not result.success? + assert @trigger.skipped? + assert_equal @now, @trigger.processed_at + end + test 'processing valid pending trigger succeeds and makes the expected api call' do VCR.use_cassette('flow_trigger_valid') do result = ProcessTrigger.call(trigger: @trigger) From d1c5b5e4b48f9b53ff6060750702a44232b2d6a4 Mon Sep 17 00:00:00 2001 From: Gavin Ballard Date: Sun, 5 Apr 2020 11:54:03 +1000 Subject: [PATCH 13/34] Update processing to skip unused triggers --- app/models/disco_app/flow/concerns/trigger.rb | 3 +- .../disco_app/flow/process_trigger.rb | 28 ++++++++++++++++--- .../disco_app/flow/process_trigger_test.rb | 4 +-- 3 files changed, 28 insertions(+), 7 deletions(-) diff --git a/app/models/disco_app/flow/concerns/trigger.rb b/app/models/disco_app/flow/concerns/trigger.rb index a1c48ca1..f5267516 100644 --- a/app/models/disco_app/flow/concerns/trigger.rb +++ b/app/models/disco_app/flow/concerns/trigger.rb @@ -13,7 +13,8 @@ module Trigger enum status: { pending: 0, succeeded: 1, - failed: 2 + failed: 2, + skipped: 3 } def properties diff --git a/app/services/disco_app/flow/process_trigger.rb b/app/services/disco_app/flow/process_trigger.rb index c7292f09..d656b4e1 100644 --- a/app/services/disco_app/flow/process_trigger.rb +++ b/app/services/disco_app/flow/process_trigger.rb @@ -11,7 +11,7 @@ class ProcessTrigger def call validate_trigger - make_api_request + make_api_request unless trigger_not_in_use? update_trigger fail_if_errors_present end @@ -33,20 +33,40 @@ def make_api_request def update_trigger trigger.update!( - status: api_success ? Trigger.statuses[:succeeded] : Trigger.statuses[:failed], - processing_errors: api_success ? [] : api_errors, + status: trigger_status, + processing_errors: processing_errors, processed_at: Time.current ) end + def trigger_status + return Trigger.statuses[:skipped] if trigger_not_in_use? + + api_success ? Trigger.statuses[:succeeded] : Trigger.statuses[:failed] + end + + def processing_errors + return [] if trigger_not_in_use? + return [] if api_success + api_errors + end + def fail_if_errors_present - context.fail! unless api_success + context.fail! unless trigger_not_in_use? || api_success end def api_client @api_client ||= DiscoApp::GraphqlClient.new(trigger.shop) end + def trigger_not_in_use? + trigger_usage.present? && !trigger_usage.has_enabled_flow? + end + + def trigger_usage + @trigger_usage ||= TriggerUsage.find_by(shop: trigger.shop, flow_trigger_definition_id: trigger.title) + end + end end end diff --git a/test/services/disco_app/flow/process_trigger_test.rb b/test/services/disco_app/flow/process_trigger_test.rb index a4db4273..9e46802b 100644 --- a/test/services/disco_app/flow/process_trigger_test.rb +++ b/test/services/disco_app/flow/process_trigger_test.rb @@ -37,14 +37,14 @@ def teardown assert_not result.success? end - test 'call to process trigger that we know is not being used fails with skipped status logged' do + test 'call to process trigger that we know is not being used succeeds with skipped status logged' do @shop.flow_trigger_usages.create( flow_trigger_definition_id: @trigger.title, has_enabled_flow: false, timestamp: @now ) result = ProcessTrigger.call(trigger: @trigger) - assert_not result.success? + assert result.success? assert @trigger.skipped? assert_equal @now, @trigger.processed_at end From d1c0abd5c00c3f345f4baaed788b2be257b0ed8f Mon Sep 17 00:00:00 2001 From: Gavin Ballard Date: Sun, 5 Apr 2020 11:55:48 +1000 Subject: [PATCH 14/34] Add additional flow trigger tests --- .../disco_app/flow/process_trigger_test.rb | 21 +++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/test/services/disco_app/flow/process_trigger_test.rb b/test/services/disco_app/flow/process_trigger_test.rb index 9e46802b..84f7257d 100644 --- a/test/services/disco_app/flow/process_trigger_test.rb +++ b/test/services/disco_app/flow/process_trigger_test.rb @@ -37,6 +37,12 @@ def teardown assert_not result.success? end + test 'call to process trigger that has already been skipped fails' do + @trigger.skipped! + result = ProcessTrigger.call(trigger: @trigger) + assert_not result.success? + end + test 'call to process trigger that we know is not being used succeeds with skipped status logged' do @shop.flow_trigger_usages.create( flow_trigger_definition_id: @trigger.title, @@ -58,6 +64,21 @@ def teardown end end + test 'processing valid pending trigger that we know is being used succeeds and makes the expected api call' do + @shop.flow_trigger_usages.create( + flow_trigger_definition_id: @trigger.title, + has_enabled_flow: true, + timestamp: @now + ) + + VCR.use_cassette('flow_trigger_valid') do + result = ProcessTrigger.call(trigger: @trigger) + assert result.success? + assert @trigger.succeeded? + assert_equal @now, @trigger.processed_at + end + end + test 'processing invalid pending trigger makes the expected api call with errors logged' do VCR.use_cassette('flow_trigger_invalid_title') do result = ProcessTrigger.call(trigger: @trigger) From c130f09fb078f7bea5a5ab7be8080bd5a9316713 Mon Sep 17 00:00:00 2001 From: Gavin Ballard Date: Sun, 5 Apr 2020 12:26:16 +1000 Subject: [PATCH 15/34] Add Flow Trigger Usage info to README etc --- CHANGELOG.md | 1 + README.md | 21 +++++++++++++++++++++ UPGRADING.md | 9 ++++++++- 3 files changed, 30 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8acf015b..3f8da276 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -18,6 +18,7 @@ All notable changes to this project will be documented in this file. - rubocop-performance and rubocop-rails plugins - Support for AppSignal - Generator for React applications +- Support for monitoring Shopify Flow trigger usage to minimise redundant requests ### Changed - Switch from Minitest to RSpec as the testing library for generated apps diff --git a/README.md b/README.md index bc99ab51..f571e2b5 100644 --- a/README.md +++ b/README.md @@ -480,6 +480,27 @@ The arguments passed to the `CreateTrigger` method are: [defined a trigger]: https://help.shopify.com/en/api/embedded-apps/app-extensions/flow/create-triggers +#### Trigger Usage Monitoring +After the initial introduction of Shopify Flow, Shopify added support for +sending a special [Shopify Flow webhook] to applications to let them know when +their Flow triggers were or weren't being actively used by merchants. Having +this information means apps can avoid making redundant Flow trigger API calls. + +Disco App provides built-in support for processing the information provided in +these webhooks, keeping track of current trigger usage for each installed shop, +and skipping the trigger API call in situations where we know it isn't being +used by a merchant. It does this by providing a +`DiscoApp::Flow::TriggerUsageController` endpoint, alongside a +`DiscoApp::Flow::TriggerUsage` model to track usage in the database. + +The only thing you'll have to do if you'd like your app to take advantage of +this functionality is update the "Extensions... Shopify Flow... Webhook +configuration" setting for your application in the Shopify Partner dashboard. +You should set the webhook URL to point to the `/flow/trigger_usage` path for +your app, eg `https://example.discolabs.com/flow/trigger_usage`. + +[Shopify Flow webhook]: https://shopify.dev/tutorials/create-a-shopify-flow-webhook + #### Actions Shopify Flow Actions are the operations a Shopify application can perform as part of a workflow. Like Triggers, [Actions must be defined][] within the diff --git a/UPGRADING.md b/UPGRADING.md index c2c96699..cd2d325c 100644 --- a/UPGRADING.md +++ b/UPGRADING.md @@ -28,11 +28,18 @@ Set your `shopify_api` version to 6.0: gem 'shopify_api', '~> 6.0' ``` +Ensure new Shopify Flow Trigger Usage database migrations are brought across and run: + +``` +bundle exec rake disco_app:install:migrations +bundle exec rake db:migrate +``` + ## Upgrading from 0.16.0 to 0.16.1 Ensure new Shopify Flow database migrations are brought across and run: ``` -bundle exec rake disco_app:install:migrations` +bundle exec rake disco_app:install:migrations bundle exec rake db:migrate ``` From 3e7aad2591e1d7809d041c35fba229d6b4fa7f5d Mon Sep 17 00:00:00 2001 From: Gavin Ballard Date: Mon, 6 Apr 2020 17:55:17 +1000 Subject: [PATCH 16/34] Parse shop timezone from iana_timezone attribute --- UPGRADING.md | 5 +++++ app/models/disco_app/concerns/shop.rb | 6 +++--- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/UPGRADING.md b/UPGRADING.md index cd2d325c..e29c2dc6 100644 --- a/UPGRADING.md +++ b/UPGRADING.md @@ -35,6 +35,11 @@ bundle exec rake disco_app:install:migrations bundle exec rake db:migrate ``` +Shop timezones are now calculated based on the `iana_timezone` attribute in the shop's +`data` attribute, rather than the old text-based `timezone` attribute. If you have an +app with many old installs, you may need to ensure the `iana_timezone` attribute is +set. + ## Upgrading from 0.16.0 to 0.16.1 Ensure new Shopify Flow database migrations are brought across and run: diff --git a/app/models/disco_app/concerns/shop.rb b/app/models/disco_app/concerns/shop.rb index 5073717d..cfe94877 100644 --- a/app/models/disco_app/concerns/shop.rb +++ b/app/models/disco_app/concerns/shop.rb @@ -87,9 +87,9 @@ def installed_duration # shop's "data" hash, return the default Rails zone (which should be UTC). def time_zone @time_zone ||= begin - Time.find_zone!(data[:timezone].to_s.gsub(/^\(.+\)\s/, '')) - rescue ArgumentError - Time.zone + Time.find_zone!(data[:iana_timezone]) + rescue ArgumentError + Time.zone end end From 9aaa713b6116d71900cb199b18bd68b23192904a Mon Sep 17 00:00:00 2001 From: Gavin Ballard Date: Mon, 13 Apr 2020 17:12:09 +1000 Subject: [PATCH 17/34] Tweak dependencies --- app/models/disco_app/concerns/shop.rb | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/app/models/disco_app/concerns/shop.rb b/app/models/disco_app/concerns/shop.rb index cfe94877..dd4a3359 100644 --- a/app/models/disco_app/concerns/shop.rb +++ b/app/models/disco_app/concerns/shop.rb @@ -79,6 +79,11 @@ def admin_url "https://#{shopify_domain}/admin/api/#{api_version}" end + # Return the absolutely URL to the shop's admin. + def shopify_url + "https://#{shopify_domain}" + end + def installed_duration distance_of_time_in_words_to_now(created_at.time) end From 1e92991f8e2d5e7c2d321a829c86cefc3ed50c7b Mon Sep 17 00:00:00 2001 From: Gavin Ballard Date: Thu, 16 Apr 2020 12:03:22 +1000 Subject: [PATCH 18/34] Update PULL_REQUEST_TEMPLATE.md --- .github/PULL_REQUEST_TEMPLATE.md | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index 0ed54edb..e66b950a 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -1,7 +1,7 @@ -Jira: [XXX-1](https://discodev.atlassian.net/browse/XXX-1) +Clubhouse: [ch1234](https://app.clubhouse.io/disco/story/1234/do-something) ### Description -A brief overview of the work done. Be sure to provide some context to the issue/feature, so that the reviewer can jump right on in and review your sweet, sweet code. +A brief overview of the work done. Be sure to provide some context to the issue/feature, so that the reviewer can jump right on in and review your sweet, sweet code. Highlight any interesting design decisions that you may have taken. @@ -14,5 +14,7 @@ Highlight any interesting design decisions that you may have taken. - [ ] Updated README and any other relevant documentation. - [ ] Tested changes locally. - [ ] Updated test suite and made sure that it all passes. +- [ ] Updated test matrix. - [ ] Ensured that Rubocop and friends are happy. - [ ] Checked that this PR is referencing the correct base. +- [ ] Logged all time in Clockify. From fd4a1e4b5c4abf0ab2603a2f7f1d764a618895ec Mon Sep 17 00:00:00 2001 From: Gavin Ballard Date: Thu, 16 Apr 2020 12:04:52 +1000 Subject: [PATCH 19/34] Update CHANGELOG.md --- CHANGELOG.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3f8da276..4d7798c1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,10 @@ # Change Log All notable changes to this project will be documented in this file. +## Unreleased +### Added +- Support for Shopify Flow trigger usage monitoring + ## 0.18.0 - 2020-04-15 ### Changed - Upgraded to Rails 6 From 743b369e531a8344add7313e142a0e8620473557 Mon Sep 17 00:00:00 2001 From: Gavin Ballard Date: Thu, 16 Apr 2020 12:11:26 +1000 Subject: [PATCH 20/34] Update UPGRADING.md --- UPGRADING.md | 25 +++++++++++++------------ 1 file changed, 13 insertions(+), 12 deletions(-) diff --git a/UPGRADING.md b/UPGRADING.md index e29c2dc6..4e6842f9 100644 --- a/UPGRADING.md +++ b/UPGRADING.md @@ -3,6 +3,19 @@ This file contains more detailed instructions on what's required when updating an application between one release version of the gem to the next. It's intended as more in-depth accompaniment to the notes in `CHANGELOG.md` for each version. +## Upgrading from 0.18.0 to Unreleased +Ensure new Shopify Flow Trigger Usage database migrations are brought across and run: + +``` +bundle exec rake disco_app:install:migrations +bundle exec rake db:migrate +``` + +Shop timezones are now calculated based on the `iana_timezone` attribute in the shop's +`data` attribute, rather than the old text-based `timezone` attribute. If you have an +app with many old installs, you may need to ensure the `iana_timezone` attribute is +set. + ## Upgrading from 0.17.0 to 0.18.0 Upgrade to Rails 6 ([guide](https://edgeguides.rubyonrails.org/upgrading_ruby_on_rails.html#upgrading-from-rails-5-2-to-rails-6-0)). @@ -28,18 +41,6 @@ Set your `shopify_api` version to 6.0: gem 'shopify_api', '~> 6.0' ``` -Ensure new Shopify Flow Trigger Usage database migrations are brought across and run: - -``` -bundle exec rake disco_app:install:migrations -bundle exec rake db:migrate -``` - -Shop timezones are now calculated based on the `iana_timezone` attribute in the shop's -`data` attribute, rather than the old text-based `timezone` attribute. If you have an -app with many old installs, you may need to ensure the `iana_timezone` attribute is -set. - ## Upgrading from 0.16.0 to 0.16.1 Ensure new Shopify Flow database migrations are brought across and run: From 20c4e04c27e119960c33d9733dea8ba3430a41bc Mon Sep 17 00:00:00 2001 From: Gavin Ballard Date: Thu, 16 Apr 2020 12:16:41 +1000 Subject: [PATCH 21/34] Remove test matrix line from PR template --- .github/PULL_REQUEST_TEMPLATE.md | 1 - 1 file changed, 1 deletion(-) diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index e66b950a..eb49f997 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -14,7 +14,6 @@ Highlight any interesting design decisions that you may have taken. - [ ] Updated README and any other relevant documentation. - [ ] Tested changes locally. - [ ] Updated test suite and made sure that it all passes. -- [ ] Updated test matrix. - [ ] Ensured that Rubocop and friends are happy. - [ ] Checked that this PR is referencing the correct base. - [ ] Logged all time in Clockify. From 32fc33b45d863aa847acd0e504e4ce2ca10e6ffa Mon Sep 17 00:00:00 2001 From: Gavin Ballard Date: Thu, 16 Apr 2020 12:32:53 +1000 Subject: [PATCH 22/34] Add nodejs version to .tool-versions --- .tool-versions | 1 + 1 file changed, 1 insertion(+) diff --git a/.tool-versions b/.tool-versions index 78115053..74f99cf9 100644 --- a/.tool-versions +++ b/.tool-versions @@ -1 +1,2 @@ ruby 2.6.5 +nodejs 13.7.0 From 165ad886fb1f36b38cb9a642dd1557f2a7105998 Mon Sep 17 00:00:00 2001 From: Gavin Ballard Date: Thu, 16 Apr 2020 12:33:28 +1000 Subject: [PATCH 23/34] Add nodejs to .tool-versions template --- lib/generators/disco_app/install/templates/root/.tool-versions | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/generators/disco_app/install/templates/root/.tool-versions b/lib/generators/disco_app/install/templates/root/.tool-versions index 78115053..74f99cf9 100644 --- a/lib/generators/disco_app/install/templates/root/.tool-versions +++ b/lib/generators/disco_app/install/templates/root/.tool-versions @@ -1 +1,2 @@ ruby 2.6.5 +nodejs 13.7.0 From b445f24a0dfd03bda2f4224913c74f6b57e875aa Mon Sep 17 00:00:00 2001 From: Gavin Ballard Date: Thu, 16 Apr 2020 12:49:10 +1000 Subject: [PATCH 24/34] Fix some tests for new timezone method --- app/models/disco_app/concerns/shop.rb | 6 +----- test/fixtures/api/subscriptions/valid_request.json | 2 +- test/fixtures/disco_app/shops.yml | 1 + test/models/disco_app/shop_test.rb | 2 +- 4 files changed, 4 insertions(+), 7 deletions(-) diff --git a/app/models/disco_app/concerns/shop.rb b/app/models/disco_app/concerns/shop.rb index dd4a3359..3ff9d79d 100644 --- a/app/models/disco_app/concerns/shop.rb +++ b/app/models/disco_app/concerns/shop.rb @@ -91,11 +91,7 @@ def installed_duration # Return the shop's configured timezone. If none can be parsed from the # shop's "data" hash, return the default Rails zone (which should be UTC). def time_zone - @time_zone ||= begin - Time.find_zone!(data[:iana_timezone]) - rescue ArgumentError - Time.zone - end + @time_zone ||= Time.find_zone!(data[:iana_timezone]) || Time.zone end # Return the shop's configured locale as a symbol. If none exists for some diff --git a/test/fixtures/api/subscriptions/valid_request.json b/test/fixtures/api/subscriptions/valid_request.json index 9f3e0e35..0a248a9b 100644 --- a/test/fixtures/api/subscriptions/valid_request.json +++ b/test/fixtures/api/subscriptions/valid_request.json @@ -9,7 +9,7 @@ "domain": null, "plan_name": null, "name": null, - "data": {"timezone": "(GMT+10:00) Melbourne", "country_name": "Australia"} + "data": {"timezone": "(GMT+10:00) Melbourne", "country_name": "Australia", "iana_timezone": "Australia/Melbourne"} }, "subscription": { "id": 304261385, diff --git a/test/fixtures/disco_app/shops.yml b/test/fixtures/disco_app/shops.yml index 1d1455b9..60c3e589 100644 --- a/test/fixtures/disco_app/shops.yml +++ b/test/fixtures/disco_app/shops.yml @@ -6,6 +6,7 @@ widget_store: data: country_name: 'Australia' timezone: '(GMT+10:00) Melbourne' + iana_timezone: 'Australia/Melbourne' widget_store_dev: shopify_domain: widgets-dev.myshopify.com diff --git a/test/models/disco_app/shop_test.rb b/test/models/disco_app/shop_test.rb index bbabdb78..f1a16bba 100644 --- a/test/models/disco_app/shop_test.rb +++ b/test/models/disco_app/shop_test.rb @@ -25,7 +25,7 @@ def teardown end test 'time_zone helper returns correct time zone instance when known timezone defined' do - assert_equal 'Melbourne', @shop.time_zone.name + assert_equal 'Australia/Melbourne', @shop.time_zone.name end test 'time_zone helper returns default Rails timezone when no known timezone defined' do From c0753de1ec34c72cafd5b2744b1a99329f8730a0 Mon Sep 17 00:00:00 2001 From: Tom Collins Date: Thu, 16 Apr 2020 17:23:44 +1000 Subject: [PATCH 25/34] use sass-rails version 6 to match rails version --- disco_app.gemspec | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/disco_app.gemspec b/disco_app.gemspec index 0d2e6710..b56ef634 100644 --- a/disco_app.gemspec +++ b/disco_app.gemspec @@ -40,7 +40,7 @@ Gem::Specification.new do |s| s.add_runtime_dependency 'rails', '~> 6.0' s.add_runtime_dependency 'rails_12factor', '~> 0.0.3' s.add_runtime_dependency 'react-rails', '~> 2.5' - s.add_runtime_dependency 'sass-rails', '~> 5.0' + s.add_runtime_dependency 'sass-rails', '~> 6.0' s.add_runtime_dependency 'shopify_api', '~> 9.0' s.add_runtime_dependency 'shopify_app', '~> 12.0.7' s.add_runtime_dependency 'sidekiq', '~> 6.0' From af3d05a9e62b5f886ea11df4caf580e1df098ced Mon Sep 17 00:00:00 2001 From: Tom Collins Date: Thu, 16 Apr 2020 17:43:27 +1000 Subject: [PATCH 26/34] sass-rails 6 seems to require manifest.js --- test/dummy/app/assets/config/manifest.js | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 test/dummy/app/assets/config/manifest.js diff --git a/test/dummy/app/assets/config/manifest.js b/test/dummy/app/assets/config/manifest.js new file mode 100644 index 00000000..a3d7d420 --- /dev/null +++ b/test/dummy/app/assets/config/manifest.js @@ -0,0 +1,2 @@ +//= link application.css +//= link application.js From 813e0062b6c8f41e6f6c36aca16b5aeb07dab8c9 Mon Sep 17 00:00:00 2001 From: Gavin Ballard Date: Mon, 20 Apr 2020 15:50:42 +1000 Subject: [PATCH 27/34] Add default jobs and concerns for GDPR webhooks --- .../concerns/customers_data_request_job.rb | 13 +++++++++++++ app/jobs/disco_app/concerns/customers_redact_job.rb | 13 +++++++++++++ app/jobs/disco_app/concerns/shop_redact_job.rb | 13 +++++++++++++ app/jobs/disco_app/customers_data_request_job.rb | 5 +++++ app/jobs/disco_app/customers_redact_job.rb | 5 +++++ app/jobs/disco_app/shop_redact_job.rb | 5 +++++ 6 files changed, 54 insertions(+) create mode 100644 app/jobs/disco_app/concerns/customers_data_request_job.rb create mode 100644 app/jobs/disco_app/concerns/customers_redact_job.rb create mode 100644 app/jobs/disco_app/concerns/shop_redact_job.rb create mode 100644 app/jobs/disco_app/customers_data_request_job.rb create mode 100644 app/jobs/disco_app/customers_redact_job.rb create mode 100644 app/jobs/disco_app/shop_redact_job.rb diff --git a/app/jobs/disco_app/concerns/customers_data_request_job.rb b/app/jobs/disco_app/concerns/customers_data_request_job.rb new file mode 100644 index 00000000..5a441668 --- /dev/null +++ b/app/jobs/disco_app/concerns/customers_data_request_job.rb @@ -0,0 +1,13 @@ +module DiscoApp + module Concerns + module CustomersDataRequestJob + + extend ActiveSupport::Concern + + def perform(_shop, data_request_data) + # See https://shopify.dev/tutorials/add-gdpr-webhooks-to-your-app#customers-data_request + end + + end + end +end diff --git a/app/jobs/disco_app/concerns/customers_redact_job.rb b/app/jobs/disco_app/concerns/customers_redact_job.rb new file mode 100644 index 00000000..ee0f214d --- /dev/null +++ b/app/jobs/disco_app/concerns/customers_redact_job.rb @@ -0,0 +1,13 @@ +module DiscoApp + module Concerns + module CustomersRedactJob + + extend ActiveSupport::Concern + + def perform(_shop, redaction_request) + # See https://shopify.dev/tutorials/add-gdpr-webhooks-to-your-app#customers-redact + end + + end + end +end diff --git a/app/jobs/disco_app/concerns/shop_redact_job.rb b/app/jobs/disco_app/concerns/shop_redact_job.rb new file mode 100644 index 00000000..601aa02a --- /dev/null +++ b/app/jobs/disco_app/concerns/shop_redact_job.rb @@ -0,0 +1,13 @@ +module DiscoApp + module Concerns + module ShopRedactJob + + extend ActiveSupport::Concern + + def perform(_shop, redaction_request) + # See https://shopify.dev/tutorials/add-gdpr-webhooks-to-your-app#shop-redact + end + + end + end +end diff --git a/app/jobs/disco_app/customers_data_request_job.rb b/app/jobs/disco_app/customers_data_request_job.rb new file mode 100644 index 00000000..a83461cf --- /dev/null +++ b/app/jobs/disco_app/customers_data_request_job.rb @@ -0,0 +1,5 @@ +class DiscoApp::CustomersDataRequestJob < DiscoApp::ShopJob + + include DiscoApp::Concerns::CustomersDataRequestJob + +end diff --git a/app/jobs/disco_app/customers_redact_job.rb b/app/jobs/disco_app/customers_redact_job.rb new file mode 100644 index 00000000..fdc198a5 --- /dev/null +++ b/app/jobs/disco_app/customers_redact_job.rb @@ -0,0 +1,5 @@ +class DiscoApp::CustomersRedactJob < DiscoApp::ShopJob + + include DiscoApp::Concerns::CustomersRedactJob + +end diff --git a/app/jobs/disco_app/shop_redact_job.rb b/app/jobs/disco_app/shop_redact_job.rb new file mode 100644 index 00000000..7a9d2308 --- /dev/null +++ b/app/jobs/disco_app/shop_redact_job.rb @@ -0,0 +1,5 @@ +class DiscoApp::ShopRedactJob < DiscoApp::ShopJob + + include DiscoApp::Concerns::ShopRedactJob + +end From 5a9300f2997e0ba6bd46b76b7649a277f3215bdd Mon Sep 17 00:00:00 2001 From: Gavin Ballard Date: Mon, 20 Apr 2020 17:03:01 +1000 Subject: [PATCH 28/34] Add ability to specify fields for webhooks --- .../concerns/synchronise_webhooks_job.rb | 28 ++++++++++++++----- lib/disco_app/configuration.rb | 3 ++ .../templates/initializers/disco_app.rb | 5 ++++ test/dummy/config/initializers/disco_app.rb | 7 +++++ .../synchronise_webhooks_job_test.rb | 13 +++++++-- test/vcr/webhook_failure.yml | 12 ++++---- 6 files changed, 52 insertions(+), 16 deletions(-) diff --git a/app/jobs/disco_app/concerns/synchronise_webhooks_job.rb b/app/jobs/disco_app/concerns/synchronise_webhooks_job.rb index 912db856..282f82c8 100644 --- a/app/jobs/disco_app/concerns/synchronise_webhooks_job.rb +++ b/app/jobs/disco_app/concerns/synchronise_webhooks_job.rb @@ -2,7 +2,7 @@ module DiscoApp::Concerns::SynchroniseWebhooksJob extend ActiveSupport::Concern - COMMON_WEBHOOKS = [:'app/uninstalled', :'shop/update'].freeze + COMMON_WEBHOOKS = %i[app/uninstalled shop/update] # Ensure the webhooks registered with our shop are the same as those listed # in our application configuration. @@ -12,6 +12,7 @@ def perform(_shop) with_verbose_output(topic) do ShopifyAPI::Webhook.create( topic: topic, + fields: topic_fields(topic), address: webhooks_url, format: 'json' ) @@ -25,10 +26,13 @@ def perform(_shop) # Ensure webhook addresses are current. current_webhooks.each do |webhook| - unless webhook.address == webhooks_url - webhook.address = webhooks_url - webhook.save - end + expected_fields = topic_fields(webhook.topic.to_sym).map(&:to_s) + + next if webhook.address == webhooks_url && webhook.fields == expected_fields + + webhook.address = webhooks_url + webhook.fields = expected_fields + webhook.save end end @@ -36,12 +40,12 @@ def perform(_shop) # Get the full list of expected webhook topics. def expected_topics - COMMON_WEBHOOKS + (DiscoApp.configuration.webhook_topics || []) + @expected_topics ||= (COMMON_WEBHOOKS + (DiscoApp.configuration.webhook_topics || [])).map(&:to_sym) end # Return a list of currently registered topics. def current_topics - current_webhooks.map(&:topic).map(&:to_sym) + @current_topics ||= current_webhooks.map(&:topic).map(&:to_sym) end # Return a list of current registered webhooks. @@ -49,6 +53,16 @@ def current_webhooks @current_webhooks ||= ShopifyAPI::Webhook.find(:all) end + # Return a list of requested fields for the given topic. + def topic_fields(topic) + webhook_fields[topic] || [] + end + + # Return a list of topic-to-field mappings. + def webhook_fields + @webhook_fields ||= (DiscoApp.configuration.webhook_fields || {}).symbolize_keys + end + # Return the absolute URL to the webhooks endpoint. def webhooks_url DiscoApp::Engine.routes.url_helpers.webhooks_url diff --git a/lib/disco_app/configuration.rb b/lib/disco_app/configuration.rb index 7abd05bf..e8a9c0fb 100644 --- a/lib/disco_app/configuration.rb +++ b/lib/disco_app/configuration.rb @@ -8,6 +8,9 @@ class Configuration # Set the list of Shopify webhook topics to register. attr_accessor :webhook_topics + # Define the list of fields to receive for each webhook topic. + attr_accessor :webhook_fields + # Set Flow configuration attr_accessor :flow_actions attr_accessor :flow_triggers diff --git a/lib/generators/disco_app/install/templates/initializers/disco_app.rb b/lib/generators/disco_app/install/templates/initializers/disco_app.rb index cc823022..76353e97 100644 --- a/lib/generators/disco_app/install/templates/initializers/disco_app.rb +++ b/lib/generators/disco_app/install/templates/initializers/disco_app.rb @@ -8,6 +8,11 @@ # See https://help.shopify.com/api/reference/webhook. config.webhook_topics = [] + # By default, webhooks request all fields to be provided in the payload. + # You can override this behaviour by providing an entry in this hash with the + # key set to a webhook topic and the value set to an array of fields. + # config.webhook_fields = {} + # Set the below if using an application proxy. config.app_proxy_prefix = ENV['SHOPIFY_APP_PROXY_PREFIX'] diff --git a/test/dummy/config/initializers/disco_app.rb b/test/dummy/config/initializers/disco_app.rb index 160c11b9..73570ecf 100644 --- a/test/dummy/config/initializers/disco_app.rb +++ b/test/dummy/config/initializers/disco_app.rb @@ -8,6 +8,13 @@ # See https://help.shopify.com/api/reference/webhook. config.webhook_topics = %i[orders/create orders/paid carts/create carts/update] + # By default, webhooks request all fields to be provided in the payload. + # You can override this behaviour by providing an entry in this hash with the + # key set to a webhook topic and the value set to an array of fields. + config.webhook_fields = { + 'orders/paid': [:id] + } + # Set the below if using an application proxy. config.app_proxy_prefix = ENV['SHOPIFY_APP_PROXY_PREFIX'] diff --git a/test/jobs/disco_app/synchronise_webhooks_job_test.rb b/test/jobs/disco_app/synchronise_webhooks_job_test.rb index 99fe9481..1e5ccd95 100644 --- a/test/jobs/disco_app/synchronise_webhooks_job_test.rb +++ b/test/jobs/disco_app/synchronise_webhooks_job_test.rb @@ -22,9 +22,16 @@ def teardown DiscoApp::SynchroniseWebhooksJob.perform_later(@shop) end - # Assert that all 4 expected webhook topics were POSTed to. - ['app/uninstalled', 'shop/update', 'orders/create', 'orders/paid'].each do |expected_webhook_topic| - assert_requested(:post, "#{@shop.admin_url}/webhooks.json", times: 1) { |request| request.body.include?(expected_webhook_topic) } + # Assert that all 3 webhook topics without field lists were POSTed to. + ['app/uninstalled', 'shop/update', 'orders/create'].each do |expected_webhook_topic| + assert_requested(:post, "#{@shop.admin_url}/webhooks.json", times: 1) do |request| + request.body.include?(%Q("topic":"#{expected_webhook_topic}")) && request.body.include?('"fields":[]') + end + end + + # Assert that the orders/paid webhook topic was posted to with a field restriction. + assert_requested(:post, "#{@shop.admin_url}/webhooks.json", times: 1) do |request| + request.body.include?('"topic":"orders/paid"') && request.body.include?('"fields":["id"]') end end end diff --git a/test/vcr/webhook_failure.yml b/test/vcr/webhook_failure.yml index 1e15859a..c87f6549 100644 --- a/test/vcr/webhook_failure.yml +++ b/test/vcr/webhook_failure.yml @@ -98,7 +98,7 @@ http_interactions: uri: https://widgets.myshopify.com/admin/api/2019-10/webhooks.json body: encoding: UTF-8 - string: '{"webhook":{"topic":"app/uninstalled","address":"https://test.example.com/webhooks","format":"json"}}' + string: '{"webhook":{"topic":"app/uninstalled","fields":[],"address":"https://test.example.com/webhooks","format":"json"}}' headers: Content-Type: - application/json @@ -188,7 +188,7 @@ http_interactions: uri: https://widgets.myshopify.com/admin/api/2019-10/webhooks.json body: encoding: UTF-8 - string: '{"webhook":{"topic":"shop/update","address":"https://test.example.com/webhooks","format":"json"}}' + string: '{"webhook":{"topic":"shop/update","fields":[],"address":"https://test.example.com/webhooks","format":"json"}}' headers: Content-Type: - application/json @@ -278,7 +278,7 @@ http_interactions: uri: https://widgets.myshopify.com/admin/api/2019-10/webhooks.json body: encoding: UTF-8 - string: '{"webhook":{"topic":"orders/create","address":"https://test.example.com/webhooks","format":"json"}}' + string: '{"webhook":{"topic":"orders/create","fields":[],"address":"https://test.example.com/webhooks","format":"json"}}' headers: Content-Type: - application/json @@ -369,7 +369,7 @@ http_interactions: uri: https://widgets.myshopify.com/admin/api/2019-10/webhooks.json body: encoding: UTF-8 - string: '{"webhook":{"topic":"orders/paid","address":"https://test.example.com/webhooks","format":"json"}}' + string: '{"webhook":{"topic":"orders/paid","fields":["id"],"address":"https://test.example.com/webhooks","format":"json"}}' headers: Content-Type: - application/json @@ -460,7 +460,7 @@ http_interactions: uri: https://widgets.myshopify.com/admin/api/2019-10/webhooks.json body: encoding: UTF-8 - string: '{"webhook":{"topic":"carts/create","address":"https://test.example.com/webhooks","format":"json"}}' + string: '{"webhook":{"topic":"carts/create","fields":[],"address":"https://test.example.com/webhooks","format":"json"}}' headers: Content-Type: - application/json @@ -551,7 +551,7 @@ http_interactions: uri: https://widgets.myshopify.com/admin/api/2019-10/webhooks.json body: encoding: UTF-8 - string: '{"webhook":{"topic":"carts/update","address":"https://test.example.com/webhooks","format":"json"}}' + string: '{"webhook":{"topic":"carts/update","fields":[],"address":"https://test.example.com/webhooks","format":"json"}}' headers: Content-Type: - application/json From ab5008b0dfb38fc1fa1d817c7f457615ad05de4a Mon Sep 17 00:00:00 2001 From: Gavin Ballard Date: Mon, 20 Apr 2020 17:25:57 +1000 Subject: [PATCH 29/34] Add more robust testing for updating webhooks when needed --- .../api/widget_store/empty_webhooks.json | 3 ++ .../api/widget_store/existing_webhooks.json | 43 +++++++++++++++++++ test/fixtures/api/widget_store/webhooks.json | 1 - .../synchronise_webhooks_job_test.rb | 35 ++++++++++++++- 4 files changed, 80 insertions(+), 2 deletions(-) create mode 100644 test/fixtures/api/widget_store/empty_webhooks.json create mode 100644 test/fixtures/api/widget_store/existing_webhooks.json delete mode 100644 test/fixtures/api/widget_store/webhooks.json diff --git a/test/fixtures/api/widget_store/empty_webhooks.json b/test/fixtures/api/widget_store/empty_webhooks.json new file mode 100644 index 00000000..4dd3bda8 --- /dev/null +++ b/test/fixtures/api/widget_store/empty_webhooks.json @@ -0,0 +1,3 @@ +{ + "webhooks": [] +} diff --git a/test/fixtures/api/widget_store/existing_webhooks.json b/test/fixtures/api/widget_store/existing_webhooks.json new file mode 100644 index 00000000..2212d803 --- /dev/null +++ b/test/fixtures/api/widget_store/existing_webhooks.json @@ -0,0 +1,43 @@ +{ + "webhooks": [ + { + "id": 748073353265, + "address": "https://test.example.com/webhooks", + "topic": "shop/update", + "created_at": "2020-04-01T00:00:00Z", + "updated_at": "2020-04-01T00:00:00Z", + "format": "json", + "fields": [], + "metafield_namespaces": [], + "api_version": "2020-04", + "private_metafield_namespaces": [] + }, + { + "id": 748073353266, + "address": "https://old.example.com/webhooks", + "topic": "orders/create", + "created_at": "2020-04-01T00:00:00Z", + "updated_at": "2020-04-01T00:00:00Z", + "format": "json", + "fields": [], + "metafield_namespaces": [], + "api_version": "2020-04", + "private_metafield_namespaces": [] + }, + { + "id": 748073353267, + "address": "https://test.example.com/webhooks", + "topic": "orders/paid", + "created_at": "2020-04-01T00:00:00Z", + "updated_at": "2020-04-01T00:00:00Z", + "format": "json", + "fields": [ + "id", + "financial_status" + ], + "metafield_namespaces": [], + "api_version": "2020-04", + "private_metafield_namespaces": [] + } + ] +} diff --git a/test/fixtures/api/widget_store/webhooks.json b/test/fixtures/api/widget_store/webhooks.json deleted file mode 100644 index fe51488c..00000000 --- a/test/fixtures/api/widget_store/webhooks.json +++ /dev/null @@ -1 +0,0 @@ -[] diff --git a/test/jobs/disco_app/synchronise_webhooks_job_test.rb b/test/jobs/disco_app/synchronise_webhooks_job_test.rb index 1e5ccd95..d77b1c81 100644 --- a/test/jobs/disco_app/synchronise_webhooks_job_test.rb +++ b/test/jobs/disco_app/synchronise_webhooks_job_test.rb @@ -15,7 +15,7 @@ def teardown test 'webhook synchronisation job creates webhooks for all expected topics' do with_suppressed_output do - stub_request(:get, "#{@shop.admin_url}/webhooks.json").to_return(status: 200, body: api_fixture('widget_store/webhooks').to_json) + stub_request(:get, "#{@shop.admin_url}/webhooks.json").to_return(status: 200, body: api_fixture('widget_store/empty_webhooks').to_json) stub_request(:post, "#{@shop.admin_url}/webhooks.json").to_return(status: 200) perform_enqueued_jobs do @@ -36,6 +36,39 @@ def teardown end end + test 'webhook synchronisation job only creates and updates webhooks when required' do + with_suppressed_output do + stub_request(:get, "#{@shop.admin_url}/webhooks.json").to_return(status: 200, body: api_fixture('widget_store/existing_webhooks').to_json) + stub_request(:put, "#{@shop.admin_url}/webhooks/748073353266.json").to_return(status: 200) + stub_request(:put, "#{@shop.admin_url}/webhooks/748073353267.json").to_return(status: 200) + stub_request(:post, "#{@shop.admin_url}/webhooks.json").to_return(status: 200) + + perform_enqueued_jobs do + DiscoApp::SynchroniseWebhooksJob.perform_later(@shop) + end + + # Assert that a missing webhook was created. + assert_requested(:post, "#{@shop.admin_url}/webhooks.json", times: 1) do |request| + request.body.include?('"topic":"app/uninstalled"') + end + + # Assert that no request was made to update an existing webhook with the expected values. + assert_requested(:post, "#{@shop.admin_url}/webhooks.json", times: 0) do |request| + request.body.include?('"topic":"shop/update"') + end + + # Assert that a request was made to update the URL of a webhook with an out of date URL. + assert_requested(:put, "#{@shop.admin_url}/webhooks/748073353266.json", times: 1) do |request| + request.body.include?('"topic":"orders/create"') && request.body.include?('"address":"https://test.example.com/webhooks"') + end + + # Assert that a request was made to update the fields of a webhook with out of date fields. + assert_requested(:put, "#{@shop.admin_url}/webhooks/748073353267.json", times: 1) do |request| + request.body.include?('"topic":"orders/paid"') && request.body.include?('"fields":["id"]') + end + end + end + test 'returns error messages for webhooks that cannot be registered' do VCR.use_cassette('webhook_failure') do with_suppressed_output do From 2d4829bef4c17b000599d4fdc3791cb214e48b98 Mon Sep 17 00:00:00 2001 From: Gavin Ballard Date: Mon, 20 Apr 2020 17:31:16 +1000 Subject: [PATCH 30/34] Add documentation for GDPR webhooks --- README.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/README.md b/README.md index f571e2b5..6765f342 100644 --- a/README.md +++ b/README.md @@ -376,6 +376,12 @@ specific webhooks are received. They are: - `DiscoApp::ShopUpdateJob`, triggered when the `shop/update` webhook is received. By default, this task keeps the metadata attributes on the relevant `DiscoApp::Shop` model up to date. +- `DiscoApp::CustomersDataRequestJob`, triggered when the + `customers/data_request` webhook is received. By default, this does nothing. +- `DiscoApp::CustomersRedactJob`, triggered when the `customers/redact` webhook + is received. By default, this does nothing. +- `DiscoApp::ShopRedactJob`, triggered when the `shop/redact` webhook is + received. By default, this does nothing. - `DiscoApp::SubscriptionChangedJob`, called whenever a shop changes the plan that they are subscribed to. - `DiscoApp::SynchroniseWebhooksJob`, called by the installation job but also From 18abfa5f9bf02dc8238c66dc86fcc1adc0db03bd Mon Sep 17 00:00:00 2001 From: Gavin Ballard Date: Mon, 20 Apr 2020 17:44:59 +1000 Subject: [PATCH 31/34] Document new webhook_fields option --- README.md | 42 ++++++++++++++++++++++++++++++++++++++---- 1 file changed, 38 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 6765f342..8f69019d 100644 --- a/README.md +++ b/README.md @@ -422,11 +422,45 @@ There shouldn't be any need to extend or override `DiscoApp::WebhooksController` inside an application - all application logic should simply be placed inside the relevant `*Job` class. -Webhooks should generally be created inside the `perform` method of the -`DiscoApp::AppInstalledJob` background task. By default, webhooks are set up to -listen for the `app/uninstalled` and `shop/update` webhook topics. +The registration of webhooks for a shop is typically handled automatically by +the `DiscoApp::SynchroniseWebhooksJob` background job, which is either queued +automatically on installation (the default `DiscoApp::AppInstalledJob`) does +this) or by the `webhooks:sync` rake task. -To check which webhooks are registered by your app run `shop.with_api_context{ShopifyAPI::Webhook.find(:all)}` from your console, where `shop = DiscoApp::Shop.find(your_shop_id)`. +To check which webhooks are currently registered by your app for any given +shop, you can run the following from the Rails console: + +```ruby +shop = DiscoApp::Shop.find(your_shop_id) +shop.with_api_context { ShopifyAPI::Webhook.find(:all) } +``` + +All application are configured to register webhooks for the `app/uninstalled` +and `shop/update` webhook topics, as these are almost always required. +Additional webhook topics can be defined in `config/initializers/disco_app.rb`, +with: + +```ruby +DiscoApp.configure do |config| + + config.webhook_topics = [:'orders/create', :'orders/paid'] + +end +``` + +By default, webhooks will be registered with an empty `fields` attribute +(meaning all object fields will be sent in the payload). This can be overridden +on a per-topic basis with the optional `webhook_fields` configuration option: + +```ruby +DiscoApp.configure do |config| + + config.webhook_fields = { + 'orders/paid': [:id, :financial_status] + } + +end +``` ### Shopify Flow The gem provides support for [Shopify Flow Connectors][], allowing applications From ba6ccaadf5e56d158261df1caf091b6de8faa866 Mon Sep 17 00:00:00 2001 From: Gavin Ballard Date: Mon, 20 Apr 2020 17:47:35 +1000 Subject: [PATCH 32/34] Update UPGRADING and CHANGELOG --- CHANGELOG.md | 1 + UPGRADING.md | 11 +++++++++++ 2 files changed, 12 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4d7798c1..184c77f6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,7 @@ All notable changes to this project will be documented in this file. ## Unreleased ### Added - Support for Shopify Flow trigger usage monitoring +- Ability to minimise webhook payloads through `config.webhook_fields` ## 0.18.0 - 2020-04-15 ### Changed diff --git a/UPGRADING.md b/UPGRADING.md index 4e6842f9..854e054e 100644 --- a/UPGRADING.md +++ b/UPGRADING.md @@ -16,6 +16,17 @@ Shop timezones are now calculated based on the `iana_timezone` attribute in the app with many old installs, you may need to ensure the `iana_timezone` attribute is set. +You can now easily restrict the fields that are sent in webhook payloads with the new +`webhook_fields` configuration option: + +``` +DiscoApp.configure do |config| + config.webhook_fields = { + 'orders/paid': [:id, :financial_status] + } +end +``` + ## Upgrading from 0.17.0 to 0.18.0 Upgrade to Rails 6 ([guide](https://edgeguides.rubyonrails.org/upgrading_ruby_on_rails.html#upgrading-from-rails-5-2-to-rails-6-0)). From ae1b454d2ab98c5236cedd3618d5b14ab6f21a17 Mon Sep 17 00:00:00 2001 From: Gavin Ballard Date: Mon, 20 Apr 2020 18:31:59 +1000 Subject: [PATCH 33/34] Fix for fixture --- test/jobs/disco_app/app_installed_job_test.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/jobs/disco_app/app_installed_job_test.rb b/test/jobs/disco_app/app_installed_job_test.rb index bdd550b0..2e840dec 100644 --- a/test/jobs/disco_app/app_installed_job_test.rb +++ b/test/jobs/disco_app/app_installed_job_test.rb @@ -7,7 +7,7 @@ class DiscoApp::AppInstalledJobTest < ActionController::TestCase def setup @shop = disco_app_shops(:widget_store) - stub_request(:get, "#{@shop.admin_url}/webhooks.json").to_return(status: 200, body: api_fixture('widget_store/webhooks').to_json) + stub_request(:get, "#{@shop.admin_url}/webhooks.json").to_return(status: 200, body: api_fixture('widget_store/empty_webhooks').to_json) stub_request(:post, "#{@shop.admin_url}/webhooks.json").to_return(status: 200) stub_request(:get, "#{@shop.admin_url}/shop.json").to_return(status: 200, body: api_fixture('widget_store/shop').to_json) stub_request(:get, "#{@shop.admin_url}/carrier_services.json").to_return(status: 200, body: api_fixture('widget_store/carrier_services').to_json) From c05315c1bcb07587e335805ad7f6c853aad2d530 Mon Sep 17 00:00:00 2001 From: Gavin Ballard Date: Tue, 21 Apr 2020 06:15:50 +1000 Subject: [PATCH 34/34] Bump version --- CHANGELOG.md | 2 +- UPGRADING.md | 2 +- VERSION | 2 +- initialise.sh | 2 +- lib/disco_app/version.rb | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 184c77f6..3a2aa124 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,7 +1,7 @@ # Change Log All notable changes to this project will be documented in this file. -## Unreleased +## 0.18.1 - 2020-04-21 ### Added - Support for Shopify Flow trigger usage monitoring - Ability to minimise webhook payloads through `config.webhook_fields` diff --git a/UPGRADING.md b/UPGRADING.md index 854e054e..382dc770 100644 --- a/UPGRADING.md +++ b/UPGRADING.md @@ -3,7 +3,7 @@ This file contains more detailed instructions on what's required when updating an application between one release version of the gem to the next. It's intended as more in-depth accompaniment to the notes in `CHANGELOG.md` for each version. -## Upgrading from 0.18.0 to Unreleased +## Upgrading from 0.18.0 to 0.18.1 Ensure new Shopify Flow Trigger Usage database migrations are brought across and run: ``` diff --git a/VERSION b/VERSION index 66333910..249afd51 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -0.18.0 +0.18.1 diff --git a/initialise.sh b/initialise.sh index fdafa985..4517e911 100755 --- a/initialise.sh +++ b/initialise.sh @@ -5,7 +5,7 @@ APP_NAME="$1" RAILS_VERSION="${RAILS_VERSION:-6.0.2}" RUBY_VERSION="${RUBY_VERSION:-2.6.5}" NODE_VERSION="${NODE_VERSION:-13.7.0}" -DISCO_APP_VERSION="${DISCO_APP_VERSION:-0.18.0}" +DISCO_APP_VERSION="${DISCO_APP_VERSION:-0.18.1}" if [ -z $APP_NAME ]; then echo "Usage: ./initialise.sh app_name (rails_version) (ruby_version) (disco_app_version)" diff --git a/lib/disco_app/version.rb b/lib/disco_app/version.rb index bc3c7b21..39bd46e0 100644 --- a/lib/disco_app/version.rb +++ b/lib/disco_app/version.rb @@ -1,5 +1,5 @@ module DiscoApp - VERSION = '0.18.0'.freeze + VERSION = '0.18.1'.freeze end