Skip to content

Commit

Permalink
Merge branch 'main' into liz/config-to-disable-rest
Browse files Browse the repository at this point in the history
  • Loading branch information
lizkenyon authored Nov 28, 2024
2 parents 2c54020 + 240be28 commit c8a8c09
Show file tree
Hide file tree
Showing 13 changed files with 264 additions and 26 deletions.
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,9 @@
Note: For changes to the API, see https://shopify.dev/changelog?filter=api
## Unreleased

## 14.7.0

- [#1347](https://github.com/Shopify/shopify-api-ruby/pull/1347) Extend webhook registration to support filters
- [#1344](https://github.com/Shopify/shopify-api-ruby/pull/1344) Allow ShopifyAPI::Webhooks::Registry to update a webhook when fields or metafield_namespaces are changed.
- [#1343](https://github.com/Shopify/shopify-api-ruby/pull/1343) Make ShopifyAPI::Context::scope parameter optional. `scope` defaults to empty list `[]`.
- [#1348](https://github.com/Shopify/shopify-api-ruby/pull/1348) Add config option that will disable the REST API client and REST resources. New apps should use the GraphQL Admin API
Expand Down
19 changes: 12 additions & 7 deletions Gemfile.lock
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
PATH
remote: .
specs:
shopify_api (14.6.0)
shopify_api (14.7.0)
activesupport
concurrent-ruby
hash_diff
Expand All @@ -16,20 +16,24 @@ PATH
GEM
remote: https://rubygems.org/
specs:
activesupport (7.1.4)
activesupport (7.1.5)
base64
benchmark (>= 0.3)
bigdecimal
concurrent-ruby (~> 1.0, >= 1.0.2)
connection_pool (>= 2.2.5)
drb
i18n (>= 1.6, < 2)
logger (>= 1.4.2)
minitest (>= 5.1)
mutex_m
securerandom (>= 0.3)
tzinfo (~> 2.0)
addressable (2.8.0)
public_suffix (>= 2.0.2, < 5.0)
ast (2.4.2)
base64 (0.2.0)
benchmark (0.4.0)
bigdecimal (3.1.8)
byebug (11.1.3)
coderay (1.1.3)
Expand All @@ -50,21 +54,22 @@ GEM
i18n (1.14.6)
concurrent-ruby (~> 1.0)
json (2.7.1)
jwt (2.9.1)
jwt (2.9.3)
base64
language_server-protocol (3.17.0.3)
logger (1.6.1)
method_source (1.0.0)
mini_mime (1.1.5)
minitest (5.15.0)
mocha (1.13.0)
multi_xml (0.6.0)
mutex_m (0.2.0)
mutex_m (0.3.0)
netrc (0.11.0)
oj (3.16.6)
oj (3.16.7)
bigdecimal (>= 3.0)
ostruct (>= 0.2)
openssl (3.2.0)
ostruct (0.6.0)
ostruct (0.6.1)
parallel (1.24.0)
parser (3.3.0.5)
ast (~> 2.4.1)
Expand Down Expand Up @@ -104,7 +109,7 @@ GEM
rubocop-sorbet (0.6.11)
rubocop (>= 0.90.0)
ruby-progressbar (1.13.0)
securerandom (0.3.1)
securerandom (0.3.2)
sorbet (0.5.11230)
sorbet-static (= 0.5.11230)
sorbet-runtime (0.5.11230)
Expand Down
11 changes: 11 additions & 0 deletions docs/usage/webhooks.md
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,17 @@ registration = ShopifyAPI::Webhooks::Registry.add_registration(
)
```

If you need to filter the webhooks you want to receive, you can use a [webhooks filter](https://shopify.dev/docs/apps/build/webhooks/customize/filters), which can be specified on registration through the `filter` parameter.

```ruby
registration = ShopifyAPI::Webhooks::Registry.add_registration(
topic: "products/update",
delivery_method: :http,
handler: WebhookHandler,
filter: "variants.price:>=10.00"
)
```

**Note**: The webhooks you register with Shopify are saved in the Shopify platform, but the local `ShopifyAPI::Webhooks::Registry` needs to be reloaded whenever your server restarts.

### EventBridge and PubSub Webhooks
Expand Down
1 change: 1 addition & 0 deletions lib/shopify_api/rest/resources/2024_07/webhook.rb
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ def initialize(session: ShopifyAPI::Context.active_session, from_hash: nil)
@api_version = T.let(nil, T.nilable(String))
@created_at = T.let(nil, T.nilable(String))
@fields = T.let(nil, T.nilable(T::Array[T.untyped]))
@filter = T.let(nil, T.nilable(String))
@format = T.let(nil, T.nilable(String))
@id = T.let(nil, T.nilable(Integer))
@metafield_namespaces = T.let(nil, T.nilable(T::Array[T.untyped]))
Expand Down
1 change: 1 addition & 0 deletions lib/shopify_api/rest/resources/2024_10/webhook.rb
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ def initialize(session: ShopifyAPI::Context.active_session, from_hash: nil)
@api_version = T.let(nil, T.nilable(String))
@created_at = T.let(nil, T.nilable(String))
@fields = T.let(nil, T.nilable(T::Array[T.untyped]))
@filter = T.let(nil, T.nilable(String))
@format = T.let(nil, T.nilable(String))
@id = T.let(nil, T.nilable(Integer))
@metafield_namespaces = T.let(nil, T.nilable(T::Array[T.untyped]))
Expand Down
2 changes: 1 addition & 1 deletion lib/shopify_api/version.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,5 @@
# frozen_string_literal: true

module ShopifyAPI
VERSION = "14.6.0"
VERSION = "14.7.0"
end
11 changes: 9 additions & 2 deletions lib/shopify_api/webhooks/registration.rb
Original file line number Diff line number Diff line change
Expand Up @@ -22,18 +22,23 @@ class Registration
sig { returns(T.nilable(T::Array[String])) }
attr_reader :metafield_namespaces

sig { returns(T.nilable(String)) }
attr_reader :filter

sig do
params(topic: String, path: String, handler: T.nilable(T.any(Handler, WebhookHandler)),
fields: T.nilable(T.any(String, T::Array[String])),
metafield_namespaces: T.nilable(T::Array[String])).void
metafield_namespaces: T.nilable(T::Array[String]),
filter: T.nilable(String)).void
end
def initialize(topic:, path:, handler: nil, fields: nil, metafield_namespaces: nil)
def initialize(topic:, path:, handler: nil, fields: nil, metafield_namespaces: nil, filter: nil)
@topic = T.let(topic.gsub("/", "_").upcase, String)
@path = path
@handler = handler
fields_array = fields.is_a?(String) ? fields.split(FIELDS_DELIMITER) : fields
@fields = T.let(fields_array&.map(&:strip)&.compact, T.nilable(T::Array[String]))
@metafield_namespaces = T.let(metafield_namespaces&.map(&:strip)&.compact, T.nilable(T::Array[String]))
@filter = filter
end

sig { abstract.returns(String) }
Expand All @@ -54,6 +59,7 @@ def build_check_query; end
current_address: T.nilable(String),
fields: T::Array[String],
metafield_namespaces: T::Array[String],
filter: T.nilable(String),
})
end
def parse_check_result(body); end
Expand Down Expand Up @@ -88,6 +94,7 @@ def subscription_response_attributes
attributes = ["id"]
attributes << "includeFields" if @fields
attributes << "metafieldNamespaces" if @metafield_namespaces
attributes << "filter" if @filter
attributes
end
end
Expand Down
9 changes: 7 additions & 2 deletions lib/shopify_api/webhooks/registrations/event_bridge.rb
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,8 @@ def callback_address

sig { override.returns(T::Hash[Symbol, String]) }
def subscription_args
{ arn: callback_address, includeFields: fields, metafieldNamespaces: metafield_namespaces }.compact
{ arn: callback_address, includeFields: fields,
metafieldNamespaces: metafield_namespaces, filter: filter, }.compact
end

sig { override.params(webhook_id: T.nilable(String)).returns(String) }
Expand All @@ -32,6 +33,7 @@ def build_check_query
id
includeFields
metafieldNamespaces
filter
endpoint {
__typename
... on WebhookEventBridgeEndpoint {
Expand All @@ -51,23 +53,26 @@ def build_check_query
current_address: T.nilable(String),
fields: T::Array[String],
metafield_namespaces: T::Array[String],
filter: T.nilable(String),
})
end
def parse_check_result(body)
edges = body.dig("data", "webhookSubscriptions", "edges") || {}
webhook_id = nil
fields = []
metafield_namespaces = []
filter = nil
current_address = nil
unless edges.empty?
node = edges[0]["node"]
webhook_id = node["id"].to_s
current_address = node["endpoint"]["arn"].to_s
fields = node["includeFields"] || []
metafield_namespaces = node["metafieldNamespaces"] || []
filter = node["filter"].to_s
end
{ webhook_id: webhook_id, current_address: current_address, fields: fields,
metafield_namespaces: metafield_namespaces, }
metafield_namespaces: metafield_namespaces, filter: filter, }
end
end
end
Expand Down
9 changes: 7 additions & 2 deletions lib/shopify_api/webhooks/registrations/http.rb
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,8 @@ def callback_address

sig { override.returns(T::Hash[Symbol, String]) }
def subscription_args
{ callbackUrl: callback_address, includeFields: fields, metafieldNamespaces: metafield_namespaces }.compact
{ callbackUrl: callback_address, includeFields: fields,
metafieldNamespaces: metafield_namespaces, filter: filter, }.compact
end

sig { override.params(webhook_id: T.nilable(String)).returns(String) }
Expand All @@ -38,6 +39,7 @@ def build_check_query
id
includeFields
metafieldNamespaces
filter
endpoint {
__typename
... on WebhookHttpEndpoint {
Expand All @@ -57,13 +59,15 @@ def build_check_query
current_address: T.nilable(String),
fields: T::Array[String],
metafield_namespaces: T::Array[String],
filter: T.nilable(String),
})
end
def parse_check_result(body)
edges = body.dig("data", "webhookSubscriptions", "edges") || {}
webhook_id = nil
fields = []
metafield_namespaces = []
filter = nil
current_address = nil
unless edges.empty?
node = edges[0]["node"]
Expand All @@ -76,9 +80,10 @@ def parse_check_result(body)
end
fields = node["includeFields"] || []
metafield_namespaces = node["metafieldNamespaces"] || []
filter = node["filter"].to_s
end
{ webhook_id: webhook_id, current_address: current_address, fields: fields,
metafield_namespaces: metafield_namespaces, }
metafield_namespaces: metafield_namespaces, filter: filter, }
end
end
end
Expand Down
8 changes: 6 additions & 2 deletions lib/shopify_api/webhooks/registrations/pub_sub.rb
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ def subscription_args
project = project_topic_pair[0]
topic = project_topic_pair[1]
{ pubSubProject: project, pubSubTopic: topic, includeFields: fields,
metafieldNamespaces: metafield_namespaces, }.compact
metafieldNamespaces: metafield_namespaces, filter: filter, }.compact
end

sig { override.params(webhook_id: T.nilable(String)).returns(String) }
Expand All @@ -36,6 +36,7 @@ def build_check_query
id
includeFields
metafieldNamespaces
filter
endpoint {
__typename
... on WebhookPubSubEndpoint {
Expand All @@ -56,13 +57,15 @@ def build_check_query
current_address: T.nilable(String),
fields: T::Array[String],
metafield_namespaces: T::Array[String],
filter: T.nilable(String),
})
end
def parse_check_result(body)
edges = body.dig("data", "webhookSubscriptions", "edges") || {}
webhook_id = nil
fields = []
metafield_namespaces = []
filter = nil
current_address = nil
unless edges.empty?
node = edges[0]["node"]
Expand All @@ -71,9 +74,10 @@ def parse_check_result(body)
"pubsub://#{node["endpoint"]["pubSubProject"]}:#{node["endpoint"]["pubSubTopic"]}"
fields = node["includeFields"] || []
metafield_namespaces = node["metafieldNamespaces"] || []
filter = node["filter"].to_s
end
{ webhook_id: webhook_id, current_address: current_address, fields: fields,
metafield_namespaces: metafield_namespaces, }
metafield_namespaces: metafield_namespaces, filter: filter, }
end
end
end
Expand Down
14 changes: 9 additions & 5 deletions lib/shopify_api/webhooks/registry.rb
Original file line number Diff line number Diff line change
Expand Up @@ -19,23 +19,25 @@ class << self
path: String,
handler: T.nilable(T.any(Handler, WebhookHandler)),
fields: T.nilable(T.any(String, T::Array[String])),
filter: T.nilable(String),
metafield_namespaces: T.nilable(T::Array[String])).void
end
def add_registration(topic:, delivery_method:, path:, handler: nil, fields: nil, metafield_namespaces: nil)
def add_registration(topic:, delivery_method:, path:, handler: nil, fields: nil, filter: nil,
metafield_namespaces: nil)
@registry[topic] = case delivery_method
when :pub_sub
Registrations::PubSub.new(topic: topic, path: path, fields: fields,
metafield_namespaces: metafield_namespaces)
metafield_namespaces: metafield_namespaces, filter: filter)
when :event_bridge
Registrations::EventBridge.new(topic: topic, path: path, fields: fields,
metafield_namespaces: metafield_namespaces)
metafield_namespaces: metafield_namespaces, filter: filter)
when :http
unless handler
raise Errors::InvalidWebhookRegistrationError, "Cannot create an Http registration without a handler."
end

Registrations::Http.new(topic: topic, path: path, handler: handler,
fields: fields, metafield_namespaces: metafield_namespaces)
fields: fields, metafield_namespaces: metafield_namespaces, filter: filter)
else
raise Errors::InvalidWebhookRegistrationError,
"Unsupported delivery method #{delivery_method}. Allowed values: {:http, :pub_sub, :event_bridge}."
Expand Down Expand Up @@ -223,10 +225,12 @@ def webhook_registration_needed?(client, registration)
parsed_check_result = registration.parse_check_result(T.cast(check_response.body, T::Hash[String, T.untyped]))
registration_fields = registration.fields || []
registration_metafield_namespaces = registration.metafield_namespaces || []
registration_filter = registration.filter

must_register = parsed_check_result[:current_address] != registration.callback_address ||
parsed_check_result[:fields].sort != registration_fields.sort ||
parsed_check_result[:metafield_namespaces].sort != registration_metafield_namespaces.sort
parsed_check_result[:metafield_namespaces].sort != registration_metafield_namespaces.sort ||
parsed_check_result[:filter] != registration_filter

{ webhook_id: parsed_check_result[:webhook_id], must_register: must_register }
end
Expand Down
Loading

0 comments on commit c8a8c09

Please sign in to comment.