saleor-apps-redis_apl/packages/webhook-utils/src/update-webhooks.ts
Krzysztof Wolski 7e0755ec9e
Webhook helpers (#949)
* WIP

* Added script and making implementation more roboust

* Added rollback on issues with the migration

* Cleanup the code

* Use allSettled instead of all

* Do not check spelling in schema files.
Schema is pulled from the API and is not controlled by our team

* Update the pkg json

* Fix typo on log message

* Add dedupe to the ignored words.
Its used by codegen

* Add changesets
2023-09-07 13:04:23 +02:00

166 lines
5.4 KiB
TypeScript

import { WebhookManifest } from "@saleor/app-sdk/types";
import { Client } from "urql";
import { webhooksToRemove } from "./filters/webhooks-to-remove";
import { getWebhookIdsAndManifestsToUpdate } from "./filters/get-webhook-ids-and-manifests-to-update";
import { webhooksToAdd } from "./filters/webhooks-to-add";
import { createLogger } from "@saleor/apps-shared";
import { removeAppWebhook } from "./operations/remove-app-webhook";
import { WebhookDetailsFragment } from "../generated/graphql";
import { createAppWebhookFromManifest } from "./create-app-webhook-from-manifest";
import { modifyAppWebhookFromManifest } from "./modify-app-webhook-from-manifest";
import { createAppWebhookFromWebhookDetailsFragment } from "./create-app-webhook-from-webhook-details-fragment";
import { modifyAppWebhookFromWebhookDetails } from "./modify-app-webhook-from-webhook-details";
const logger = createLogger({ name: "updateWebhooks" });
interface RollbackArgs {
client: Client;
webhookManifests: Array<WebhookManifest>;
existingWebhooksData: Array<WebhookDetailsFragment>;
addedWebhooks: Array<WebhookDetailsFragment>;
modifiedWebhooks: Array<WebhookDetailsFragment>;
removedWebhooks: Array<WebhookDetailsFragment>;
}
const rollback = async ({
client,
addedWebhooks,
modifiedWebhooks,
existingWebhooksData,
removedWebhooks,
}: RollbackArgs) => {
if (addedWebhooks.length) {
logger.info("Removing added webhooks");
await Promise.allSettled(
addedWebhooks.map((webhook) => removeAppWebhook({ client, webhookId: webhook.id })),
);
}
if (modifiedWebhooks.length) {
logger.info("Rollback modified webhooks");
await Promise.allSettled(
modifiedWebhooks.map((modifiedWebhook) => {
const webhookDetails = existingWebhooksData.find(
(existingWebhook) => existingWebhook.id === modifiedWebhook.id,
);
if (!webhookDetails) {
logger.error("This should not happen");
throw new Error("This should not happen");
}
return modifyAppWebhookFromWebhookDetails({ client, webhookDetails });
}),
);
}
if (removedWebhooks.length) {
logger.debug("Rollback removed webhooks");
await Promise.allSettled(
modifiedWebhooks.map((webhookDetails) => {
return createAppWebhookFromWebhookDetailsFragment({
client,
webhookDetails,
});
}),
);
}
};
interface UpdateWebhooksArgs {
client: Client;
webhookManifests: Array<WebhookManifest>;
existingWebhooksData: Array<WebhookDetailsFragment>;
dryRun?: boolean;
}
/*
* Based on given list of existing and new webhooks:
* - remove the ones which are not in the new list
* - create the ones which are not in the existing list
* - update queries the ones which are in both lists
*
* If any of the operations fails, rollback all changes to the initial state
*/
export const updateWebhooks = async ({
client,
webhookManifests,
existingWebhooksData,
dryRun,
}: UpdateWebhooksArgs) => {
const addedWebhooks = [];
const modifiedWebhooks = [];
const removedWebhooks = [];
try {
logger.debug("Preparing list of changes to be executed");
// Based on names, find the ones which should be added
const webhookManifestsToBeAdded = webhooksToAdd({
existingWebhooksPartial: existingWebhooksData,
newWebhookManifests: webhookManifests,
});
// Based on names, find the ones which should be updated
const webhookIdsAndManifestsToBeUpdated = getWebhookIdsAndManifestsToUpdate({
existingWebhooksPartial: existingWebhooksData,
newWebhookManifests: webhookManifests,
});
// Based on names, find the ones which should be removed
const webhookToBeRemoved = webhooksToRemove({
existingWebhooksPartial: existingWebhooksData,
newWebhookManifests: webhookManifests,
});
logger.info(
`Scheduled changes: ${webhookIdsAndManifestsToBeUpdated.length} to be updated, ${webhookManifestsToBeAdded.length} to be added, ${webhookToBeRemoved.length} to be removed`,
);
if (dryRun) {
logger.info("Dry run mode, changes will not be executed. Exiting.");
return;
}
for (const webhookManifest of webhookManifestsToBeAdded) {
logger.debug(`Adding webhook ${webhookManifest.name}`);
const createdWebhook = await createAppWebhookFromManifest({ client, webhookManifest });
logger.debug("Webhook added");
addedWebhooks.push(createdWebhook);
}
for (const updateData of webhookIdsAndManifestsToBeUpdated) {
const { webhookId, webhookManifest } = updateData;
logger.debug(`Updating webhook ${webhookManifest.name}`);
const response = await modifyAppWebhookFromManifest({ client, webhookId, webhookManifest });
logger.debug("Webhook updated");
modifiedWebhooks.push(response);
}
for (const webhookDetails of webhookToBeRemoved) {
logger.debug(`Removing webhook ${webhookDetails.name}`);
await removeAppWebhook({ client, webhookId: webhookDetails.id });
logger.debug("Webhook removed");
removedWebhooks.push(webhookDetails);
}
logger.info("Migration finished successfully");
} catch (e) {
logger.error(e, "Error during update procedure, rolling back changes");
await rollback({
client,
addedWebhooks,
existingWebhooksData,
modifiedWebhooks,
webhookManifests,
removedWebhooks,
});
logger.info("Changes rolled back");
}
};