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
Deploy multiple webhook subscription modules #3764
base: main
Are you sure you want to change the base?
Conversation
We detected some changes at either packages/*/src or packages/cli-kit/assets/cli-ruby/** and there are no updates in the .changeset. |
31d8d7b
to
0f7bb15
Compare
Coverage report
Show files with reduced coverage 🔻
Test suite run success1641 tests passing in 766 suites. Report generated by 🧪jest coverage report action from 017baff |
53ef185
to
e9bc505
Compare
if (topics) | ||
return topics.map((topic) => { | ||
return {api_version, uri, topic, ...optionalFields} | ||
}) | ||
}) | ||
|
||
return webhookSubscriptions.length > 0 ? webhookSubscriptions : [] | ||
return webhookSubscriptions.length > 0 ? webhookSubscriptions.filter(Boolean) : [] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
fancy way of filtering out undefined
values we get from subscription
objects that might not have topics
(ie. in the case of privacy compliance webhooks)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We have some utils for this kind of operations. To filter undefineds from an array, use getArrayRejectingUndefined
:)
@@ -50,14 +50,15 @@ function transformFromWebhookSubscriptionConfig(content: object) { | |||
const {api_version, subscriptions = []} = webhooks | |||
|
|||
const webhookSubscriptions = subscriptions.flatMap((subscription) => { | |||
const {uri, topics, ...optionalFields} = subscription | |||
// compliance_topics gets handled by privacy_compliance_webhooks | |||
const {uri, topics, compliance_topics: _, ...optionalFields} = subscription |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
needed compliance_topics: _
to prevent compliance_topics
to get pushed as part of subscription
in the webhook_subscription
config, which gives us errors
@@ -150,7 +150,7 @@ export class ExtensionInstance<TConfiguration extends BaseConfigType = BaseConfi | |||
} | |||
|
|||
isUuidManaged() { | |||
return !this.isAppConfigExtension && !this.specification.extensionManagedInToml | |||
return !this.isAppConfigExtension && this.specification.uidStrategy === 'uuid' |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
should this also be !this.specification.uidStrategy === 'dynamic'
?
@@ -274,7 +274,9 @@ async function ensureExtensionIdsForExtensionsManagedInToml( | |||
developerPlatformClient: DeveloperPlatformClient, | |||
appId: string, | |||
) { | |||
const extensionsManagedInToml = localExtensionRegistrations.filter((ext) => ext.specification.extensionManagedInToml) | |||
const extensionsManagedInToml = localExtensionRegistrations.filter( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
we should probably rename these vars as well
548764f
to
0b58ba2
Compare
Unused types (1)
|
…dInToml, it's not needed since if there are no matches, a new extension + id + uuid is created
017baff
to
63432d0
Compare
@@ -301,12 +303,19 @@ async function ensureExtensionIdsForExtensionsManagedInToml( | |||
localConfigArray?.forEach((localConfigObj: any) => { | |||
// eslint-disable-next-line @typescript-eslint/no-explicit-any | |||
const hasMatch = possibleMatches?.some((possibleMatch: any) => { | |||
const remoteConfigString = possibleMatch.activeVersion?.config | |||
const remoteConfigObj = remoteConfigString ? JSON.parse(remoteConfigString) : '' | |||
const remoteActiveVersionConfig = possibleMatch.activeVersion?.config |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
since we were only checking against the activeVersion
(which is null
in cases when we add a new subscription then run dev
), we were creating new webhook-subscription
drafts, so added new checks for to check against the draftVersion
too
const configContent = await extension.commonDeployConfig('') | ||
return Array.isArray(configContent) ? configContent : [configContent] | ||
} | ||
|
||
function mapExtensionsIdsNonUuidManaged(extensionsIdsNonUuidManaged: {[key: string]: string[]}) { | ||
const result: {[key: string]: string} = {} | ||
for (const key in extensionsIdsNonUuidManaged) { | ||
if (extensionsIdsNonUuidManaged[key]!.length > 0) { | ||
if (extensionsIdsNonUuidManaged[key]!.length > 1) { | ||
result[key] = extensionsIdsNonUuidManaged[key]!.toString() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
kind of a hack :/ basically if there's more than 1 id we turn the entire array into a string (then we turn it back into an array later when we're updating the drafts 😬 )
there might be some other downstream effects that this causes
stdout, | ||
stderr, | ||
}: UpdateExtensionDraftOptions) { | ||
const registrationIdArr = registrationId.split(',') |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this is such a hack lol.. basically if there are multiple registration ids, the data shape is something like:
{ webhook-subscription: '1250,1251'}
(bc I just turned the entire array into a string in mapExtensionsIdsNonUuidManaged
)
So the split
here turns this back into an array
[1250, 1251]
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I will modify the type here to be [key: string]: string[]. Non multiples extensions will only have one element inside the array
Anyway, I have a question here.
- When we deploy the subscriptions, if the local content doesn't match the content of any remote module, then we create a new registrationId (I mean there's no update, the subscriptions are only created or deleted)
- This updateDraft, is triggered by the
dev
command when the user modifies the content of the toml, and in this case we are reusing the registrationIds
|
||
// eslint-disable-next-line @typescript-eslint/no-misused-promises | ||
registrationIdArr.forEach(async (registrationId, index) => { | ||
const config = configArr[index] as object |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
thoughts on this? 😬 feels a bit hacky /prone to bugs - just the quickest way I could get individual subscriptions stored in config
in the draft versions of webhook-subscription
if ( | ||
specificationIdentifier === WebhookSubscriptionSpecIdentifier && | ||
remoteConfigObj.topic === localConfig.topic && | ||
remoteConfigObj.uri === localConfig.uri | ||
) | ||
return true | ||
return false |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
if ( | |
specificationIdentifier === WebhookSubscriptionSpecIdentifier && | |
remoteConfigObj.topic === localConfig.topic && | |
remoteConfigObj.uri === localConfig.uri | |
) | |
return true | |
return false | |
return specificationIdentifier === WebhookSubscriptionSpecIdentifier && | |
remoteConfigObj.topic === localConfig.topic && | |
remoteConfigObj.uri === localConfig.uri |
@@ -318,6 +318,7 @@ export class App implements AppInterface { | |||
return configuration as CurrentAppConfiguration & SpecsAppConfiguration | |||
} | |||
|
|||
// karen.xie: do we need to also filter the webhook_subscription modules here? | |||
private filterDeclarativeWebhooksConfig() { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If we don't want to manage extension from these specs:
- privacy_compliance_webhooks
- webhook_subscription
You should gate those specs behind the flag Flag.DeclarativeWebhooks in partners like this one
That way the final toml schema won't include them so the extensions are not loaded (in fact, you'll receive an parsing error in case the content is there an the beta is not enabled)
I thing that you could also remove fetching the Flag.DeclarativeWebhooks because you don't use it in elsewhere
const remoteActiveVersionConfigObj = remoteActiveVersionConfig ? JSON.parse(remoteActiveVersionConfig) : '' | ||
const remoteDraftVersionConfigObj = remoteDraftVersionConfig ? JSON.parse(remoteDraftVersionConfig) : '' | ||
if ( | ||
matchesRemoteConfigForWebhookSubscriptions( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why do we need an explicit matchesRemoteConfigForWebhookSubscriptions
?, I mean we could just check the .config
regardless the type of extension if they have the uidStrategy === 'dynamic'
right?.
@@ -40,8 +41,19 @@ export const pushUpdatesForDraftableExtensions: DevProcessFunction<DraftableExte | |||
await extension.build({app, stdout, stderr, useTasks: false, signal, environment: 'development'}) | |||
const registrationId = remoteExtensions[extension.localIdentifier] | |||
if (!registrationId) throw new AbortError(`Extension ${extension.localIdentifier} not found on remote app.`) | |||
// Initial draft update for each extension | |||
await updateExtensionDraft({extension, developerPlatformClient, apiKey, registrationId, stdout, stderr}) | |||
if (extension.specification.externalIdentifier === WebhookSubscriptionSpecIdentifier) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I wouldn't do this inside.
I will create a function that returns the list of final extension instances before the Promise.all
The extension instances that include multipleConfig, will return a copy of the ExtensionInstance for each item, and the only difference could be the config right?
This way you don't need to deal with multiple methods
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Anyway this
if (extension.specification.externalIdentifier === WebhookSubscriptionSpecIdentifier)
should be if (await multipleConfigs(extension))
right?
stdout, | ||
stderr, | ||
}: UpdateExtensionDraftOptions) { | ||
const registrationIdArr = registrationId.split(',') |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I will modify the type here to be [key: string]: string[]. Non multiples extensions will only have one element inside the array
Anyway, I have a question here.
- When we deploy the subscriptions, if the local content doesn't match the content of any remote module, then we create a new registrationId (I mean there's no update, the subscriptions are only created or deleted)
- This updateDraft, is triggered by the
dev
command when the user modifies the content of the toml, and in this case we are reusing the registrationIds
WHY are these changes introduced?
Fixes https://github.com/Shopify/develop-app-management/issues/1720 and https://github.com/Shopify/develop-app-management/issues/1724
See https://github.com/Shopify/develop-app-management/issues/1720 for more details and testing instructions
WHAT is this pull request doing?
How to test your changes?
Post-release steps
Measuring impact
How do we know this change was effective? Please choose one:
Checklist
dev
ordeploy
have been reflected in the internal flowchart.