Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Extend webhook registration to support metafield_namespaces #1186

Merged
merged 1 commit into from
Oct 10, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ Note: For changes to the API, see https://shopify.dev/changelog?filter=api
- [#1183](https://github.com/Shopify/shopify-api-ruby/pull/1189) Added string array support for fields parameter in Webhook::Registry
- [1208](https://github.com/Shopify/shopify-api-ruby/pull/1208) Fix CustomerAddress and FulfillmentRequest methods
- [1225](https://github.com/Shopify/shopify-api-ruby/pull/1225) Support for 2023_10 API version
- [#1186](https://github.com/Shopify/shopify-api-ruby/pull/1186) Extend webhook registration to support metafield_namespaces

## 13.1.0

Expand Down
11 changes: 11 additions & 0 deletions docs/usage/webhooks.md
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,17 @@ registration = ShopifyAPI::Webhooks::Registry.add_registration(
)
```

If you are storing metafields on an object you are receiving webhooks for, you can specify them on registration to make sure that they are also sent through the `metafieldNamespaces` parameter. Note if you are also using the `fields` parameter you will need to add `metafields` into that as well.

```ruby
registration = ShopifyAPI::Webhooks::Registry.add_registration(
topic: "orders/create",
delivery_method: :http,
handler: WebhookHandler,
metafieldNamespaces: ["custom"]
)
```

**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
23 changes: 19 additions & 4 deletions lib/shopify_api/webhooks/registration.rb
Original file line number Diff line number Diff line change
Expand Up @@ -19,16 +19,21 @@ class Registration
sig { returns(T.nilable(T::Array[String])) }
attr_reader :fields

sig { returns(T.nilable(T::Array[String])) }
attr_reader :metafield_namespaces

sig do
params(topic: String, path: String, handler: T.nilable(Handler),
fields: T.nilable(T.any(String, T::Array[String]))).void
fields: T.nilable(T.any(String, T::Array[String])),
metafield_namespaces: T.nilable(T::Array[String])).void
end
def initialize(topic:, path:, handler: nil, fields: nil)
def initialize(topic:, path:, handler: nil, fields: nil, metafield_namespaces: 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]))
end

sig { abstract.returns(String) }
Expand All @@ -51,7 +56,7 @@ def build_register_query(webhook_id: nil)
identifier = webhook_id ? "id: \"#{webhook_id}\"" : "topic: #{@topic}"

subscription_args_string = subscription_args.map do |k, v|
"#{k}: #{k == :includeFields ? v : '"' + v + '"'}"
"#{k}: #{[:includeFields, :metafieldNamespaces].include?(k) ? v : %("#{v}")}"
end.join(", ")

<<~QUERY
Expand All @@ -62,12 +67,22 @@ def build_register_query(webhook_id: nil)
message
}
webhookSubscription {
id#{@fields.nil? ? "" : "\n includeFields"}
#{subscription_response_attributes.join("\n ")}
}
}
}
QUERY
end

private

sig { returns(T::Array[String]) }
def subscription_response_attributes
attributes = ["id"]
attributes << "includeFields" if @fields
attributes << "metafieldNamespaces" if @metafield_namespaces
attributes
end
end
end
end
2 changes: 1 addition & 1 deletion lib/shopify_api/webhooks/registrations/event_bridge.rb
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ def callback_address

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

sig { override.params(webhook_id: T.nilable(String)).returns(String) }
Expand Down
2 changes: 1 addition & 1 deletion lib/shopify_api/webhooks/registrations/http.rb
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ def callback_address

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

sig { override.params(webhook_id: T.nilable(String)).returns(String) }
Expand Down
3 changes: 2 additions & 1 deletion lib/shopify_api/webhooks/registrations/pub_sub.rb
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,8 @@ def subscription_args
project_topic_pair = callback_address.gsub(%r{^pubsub://}, "").split(":")
project = project_topic_pair[0]
topic = project_topic_pair[1]
{ pubSubProject: project, pubSubTopic: topic, includeFields: fields }.compact
{ pubSubProject: project, pubSubTopic: topic, includeFields: fields,
metafieldNamespaces: metafield_namespaces, }.compact
end

sig { override.params(webhook_id: T.nilable(String)).returns(String) }
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 @@ -13,20 +13,24 @@ class << self
delivery_method: Symbol,
path: String,
handler: T.nilable(Handler),
fields: T.nilable(T.any(String, T::Array[String]))).void
fields: T.nilable(T.any(String, T::Array[String])),
metafield_namespaces: T.nilable(T::Array[String])).void
end
def add_registration(topic:, delivery_method:, path:, handler: nil, fields: nil)
def add_registration(topic:, delivery_method:, path:, handler: nil, fields: nil, metafield_namespaces: nil)
@registry[topic] = case delivery_method
when :pub_sub
Registrations::PubSub.new(topic: topic, path: path, fields: fields)
Registrations::PubSub.new(topic: topic, path: path, fields: fields,
metafield_namespaces: metafield_namespaces)
when :event_bridge
Registrations::EventBridge.new(topic: topic, path: path, fields: fields)
Registrations::EventBridge.new(topic: topic, path: path, fields: fields,
metafield_namespaces: metafield_namespaces)
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)
Registrations::Http.new(topic: topic, path: path, handler: handler,
fields: fields, metafield_namespaces: metafield_namespaces)
else
raise Errors::InvalidWebhookRegistrationError,
"Unsupported delivery method #{delivery_method}. Allowed values: {:http, :pub_sub, :event_bridge}."
Expand Down
33 changes: 30 additions & 3 deletions test/webhooks/registry_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,10 @@ def test_http_registration_with_fields_array_add_and_update
do_registration_test(:http, "test-webhooks", fields: ["field1", "field2"])
end

def test_http_registration_with_metafield_namespaces_add_and_update
do_registration_test(:http, "test-webhooks", metafield_namespaces: ["namespace1", "namespace2"])
end

def test_raises_on_http_registration_check_error
do_registration_check_error_test(:http, "test-webhooks")
end
Expand All @@ -114,6 +118,11 @@ def test_pubsub_registration_with_fields_array_add_and_update
do_registration_test(:pub_sub, "pubsub://my-project-id:my-topic-id", fields: ["field1", "field2"])
end

def test_pubsub_registration_with_metafield_namespaces_add_and_update
do_registration_test(:pub_sub, "pubsub://my-project-id:my-topic-id",
metafield_namespaces: ["namespace1", "namespace2"])
end

def test_raises_on_pubsub_registration_check_error
do_registration_check_error_test(:pub_sub, "pubsub://my-project-id:my-topic-id")
end
Expand All @@ -130,6 +139,10 @@ def test_eventbridge_registration_with_fields_array_add_and_update
do_registration_test(:event_bridge, "test-webhooks", fields: ["field1", "field2"])
end

def test_eventbridge_registration_with_metafield_namespaces_add_and_update
do_registration_test(:event_bridge, "test-webhooks", metafield_namespaces: ["namespace1", "namespace2"])
end

def test_raises_on_eventbridge_registration_check_error
do_registration_check_error_test(:event_bridge, "test-webhooks")
end
Expand Down Expand Up @@ -251,7 +264,7 @@ def test_get_webhook_id_with_graphql_errors

private

def do_registration_test(delivery_method, path, fields: nil)
def do_registration_test(delivery_method, path, fields: nil, metafield_namespaces: nil)
ShopifyAPI::Webhooks::Registry.clear

check_query_body = { query: queries[delivery_method][:check_query], variables: nil }
Expand All @@ -260,8 +273,21 @@ def do_registration_test(delivery_method, path, fields: nil)
.with(body: JSON.dump(check_query_body))
.to_return({ status: 200, body: JSON.dump(queries[delivery_method][:check_empty_response]) })

add_query_type = fields.nil? ? :register_add_query : :register_add_query_with_fields
add_response_type = fields.nil? ? :register_add_response : :register_add_with_fields_response
add_query_type = if fields.present?
:register_add_query_with_fields
elsif metafield_namespaces.present?
:register_add_query_with_metafield_namespaces
else
:register_add_query
end
add_response_type = if fields.present?
:register_add_with_fields_response
elsif metafield_namespaces.present?
:register_add_with_metafield_namespaces_response
else
:register_add_response
end

stub_request(:post, @url)
.with(body: JSON.dump({ query: queries[delivery_method][add_query_type], variables: nil }))
.to_return({ status: 200, body: JSON.dump(queries[delivery_method][add_response_type]) })
Expand All @@ -275,6 +301,7 @@ def do_registration_test(delivery_method, path, fields: nil)
end,
),
fields: fields,
metafield_namespaces: metafield_namespaces,
)
registration_response = ShopifyAPI::Webhooks::Registry.register_all(
session: @session,
Expand Down
78 changes: 78 additions & 0 deletions test/webhooks/webhook_registration_queries.rb
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,21 @@ def queries
}
}
QUERY
register_add_query_with_metafield_namespaces:
<<~QUERY,
mutation webhookSubscription {
webhookSubscriptionCreate(topic: SOME_TOPIC, webhookSubscription: {callbackUrl: "https://app-address.com/test-webhooks", metafieldNamespaces: ["namespace1", "namespace2"]}) {
userErrors {
field
message
}
webhookSubscription {
id
metafieldNamespaces
}
}
}
QUERY

register_add_response: {
"data" => {
Expand All @@ -82,6 +97,17 @@ def queries
},
},
},
register_add_with_metafield_namespaces_response: {
"data" => {
"webhookSubscriptionCreate" => {
"userErrors" => [],
"webhookSubscription" => {
"id" => "gid://shopify/WebhookSubscription/12345",
"metafieldNamespaces" => ["namespace1", "namespace2"],
},
},
},
},
check_existing_response: {
"data" => {
"webhookSubscriptions" => {
Expand Down Expand Up @@ -175,6 +201,21 @@ def queries
}
}
QUERY
register_add_query_with_metafield_namespaces:
<<~QUERY,
mutation webhookSubscription {
eventBridgeWebhookSubscriptionCreate(topic: SOME_TOPIC, webhookSubscription: {arn: "test-webhooks", metafieldNamespaces: ["namespace1", "namespace2"]}) {
userErrors {
field
message
}
webhookSubscription {
id
metafieldNamespaces
}
}
}
QUERY
register_add_response: {
"data" => {
"eventBridgeWebhookSubscriptionCreate" => {
Expand All @@ -194,6 +235,17 @@ def queries
},
},
},
register_add_with_metafield_namespaces_response: {
"data" => {
"eventBridgeWebhookSubscriptionCreate" => {
"userErrors" => [],
"webhookSubscription" => {
"id" => "gid://shopify/WebhookSubscription/12345",
"metafieldNamespaces" => ["namespace1", "namespace2"],
},
},
},
},
check_existing_response: {
"data" => {
"webhookSubscriptions" => {
Expand Down Expand Up @@ -288,6 +340,21 @@ def queries
}
}
QUERY
register_add_query_with_metafield_namespaces:
<<~QUERY,
mutation webhookSubscription {
pubSubWebhookSubscriptionCreate(topic: SOME_TOPIC, webhookSubscription: {pubSubProject: "my-project-id", pubSubTopic: "my-topic-id", metafieldNamespaces: ["namespace1", "namespace2"]}) {
userErrors {
field
message
}
webhookSubscription {
id
metafieldNamespaces
}
}
}
QUERY
register_add_response: {
"data" => {
"pubSubWebhookSubscriptionCreate" => {
Expand All @@ -307,6 +374,17 @@ def queries
},
},
},
register_add_with_metafield_namespaces_response: {
"data" => {
"pubSubWebhookSubscriptionCreate" => {
"userErrors" => [],
"webhookSubscription" => {
"id" => "gid://shopify/WebhookSubscription/12345",
"metafieldNamespaces" => ["namespace1", "namespace2"],
},
},
},
},
check_existing_response: {
"data" => {
"webhookSubscriptions" => {
Expand Down
Loading