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

feat(cleanup): add bulk resource deletion script for integration tests (DEVX-433) #815

Open
wants to merge 1 commit into
base: master
Choose a base branch
from

Conversation

evansinho
Copy link
Contributor

This PR introduces a script to handle the bulk deletion of various resources, ensuring the system stays clean and within resource limits. Key features include:

  • Unpublishing and deleting all products.
  • Safe deletion of resources, skipping any referenced by others (e.g., tax categories, customer groups, stores).
  • Improved error handling and logging for easier tracking.
  • Resolved versioning conflicts during resource updates to prevent concurrency issues.
  • This will help maintain a cleaner environment and avoid hitting commercetools resource limits.

@evansinho evansinho requested a review from a team as a code owner October 4, 2024 15:22
Copy link

changeset-bot bot commented Oct 4, 2024

⚠️ No Changeset found

Latest commit: ba6f8f2

Merging this PR will not cause a version bump for any packages. If these changes should not result in a new version, you're good to go. If these changes should result in a version bump, you need to add a changeset.

This PR includes no changesets

When changesets are added to this PR, you'll see the packages that this PR includes changesets for and the associated semver types

Click here to learn what changesets are, and how to add one.

Click here if you're a maintainer who wants to add a changeset to this PR

@evansinho evansinho changed the title feat(cleanup): add bulk resource deletion script for commercetools (DEVX-433) feat(cleanup): add bulk resource deletion script for integration tests (DEVX-433) Oct 4, 2024
)
console.log(`Deleted ${productTypes.body.results.length} Product Types`)

// Delete all standalone products (not tied to a product type)

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry, can you clarify this point - I thought products need always to be tied to a product type? https://docs.commercetools.com/api/projects/products#product

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@cneijenhuis Thanks for pointing that out! Yes, you're right, products are always tied to a product type. However, when deleting a product, we don't need to reference the product type explicitly in the deletion process. In this case, I'm fetching all the products and deleting them directly. The link to the product type is part of the product’s metadata, but it doesn’t need to be called out specifically for the deletion to happen. I focus on unpublishing and then deleting the product itself.

Let me know if that makes sense!

taxCategory.id,
taxCategory.version,
async () => {
const productsReferencingTaxCategory = await apiRoot

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not following this for several reasons:

  • At this point, we should have already deleted all products - why should we double-check here?
  • Why are you trying to duplicate the "safe deletion" logic here? If it isn't safe to delete, the API should refuse to delete (and you should react to the error). E.g. for tax categories, shipping methods also reference them, and the API will refuse the request. Isn't it better to react to that error?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  • You're right that by the time we get to tax categories, we should have already deleted all products, and ideally, there wouldn't be any remaining references. The reason I added that check was to be extra cautious in case anything slipped through or there were edge cases where products might still reference a tax category. It’s more of a precaution to prevent unexpected errors during the cleanup.
  • As for duplicating the "safe deletion" logic, I agree...it’s better to rely on the API’s error handling when trying to delete something that’s still referenced, like in the case of shipping methods referencing tax categories. I can adjust the code to simply handle those errors directly rather than pre-checking, which will make the process cleaner and more efficient.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@evansinho I would recommend to delete straightaway and not checking.
Reasons:

  • At this point products are all delete. If not, then it should fail and you should investigate why. It could be an error in the script and then we have to fix it. If it's a platform error, then we should report it.
  • Checking this requires extra request/response. This script could be used as a reference for customers, so we should avoid doing unnecessary actions.

Please check also for other resources that use safeDeleteResource().

@ajimae
Copy link
Contributor

ajimae commented Oct 10, 2024

Nice job 👍🏽

I was looking at the jira ticket and I could see that other methods were also hinted. I think that aside this standalone script solution, we should also have in addition test hooks for relevant tests suites such as a beforeAll and afterAll hooks that does this just as we have here

We created the resource we needed for the test and immediately delete the resource(s) after the tests finished running. This way we are always sure of a clean slate anytime we are running the integration tests.

What do you think?

Copy link

codecov bot commented Oct 10, 2024

Codecov Report

Attention: Patch coverage is 0% with 88 lines in your changes missing coverage. Please review.

Project coverage is 71.35%. Comparing base (1f26569) to head (ba6f8f2).
Report is 10 commits behind head on master.

Files with missing lines Patch % Lines
...ntegration-tests/CleanEverythingIntegrationTest.ts 0.00% 88 Missing ⚠️
Additional details and impacted files
@@             Coverage Diff             @@
##           master     #815       +/-   ##
===========================================
- Coverage   93.07%   71.35%   -21.73%     
===========================================
  Files          25       26        +1     
  Lines         289      377       +88     
  Branches       14       17        +3     
===========================================
  Hits          269      269               
- Misses         20      108       +88     
Flag Coverage Δ
integrationtests 71.35% <0.00%> (-21.73%) ⬇️

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

@evansinho
Copy link
Contributor Author

@ajimae Thanks for the feedback! 👍🏽 I completely agree. Having the beforeAll and afterAll hooks for the test suites to handle resource creation and deletion will ensure a clean slate for each test run, making the process more robust. I'll create a new branch and start implementing this approach alongside the standalone script solution.

Copy link
Contributor

@lojzatran lojzatran left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good job 🔥

Since this is part of our SDK and we might later reference our customers how to do the proper clean up, I added some comments how to improve the script in general.

.withId({ ID: resourceId })
.delete({ queryArgs: { version } })
.execute()
console.log(`Deleted ${resourceType.slice(0, -1)} with ID: ${resourceId}`)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since the resource is deleted, you cannot do anything with the ID outputted here, so I would recommend removing this log.

Rather than this log it's more useful to output a summary in the end of the script - which resources were deleted and how many successfully and how many errors (maybe how long). Also output some kind of counting for long running deletion process - e.g. every 100 resources output Deleted ${count} ${resourceType.slice(0, -1)}. This way user has a way to follow the execution.

In general we should minimize logging things you cannot act upon - if it's info that sth is successfully deleted, just log the summary in the end of the whole process. On the other hand, the errors should be logged immediately as you have it here. Like this you could see the errors immediately and they're not lost in many logs.

// Function to unpublish and delete a product
const unpublishAndDeleteProduct = async (product: Product) => {
if (product.masterData.published) {
console.log(`Unpublishing product: ${product.id}`)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Remove this and write instead a one-line summary in the end of the deletion process like I suggested above.

await deleteProductsForProductType(productType.id)
})
)
console.log(`Deleted ${productTypes.body.results.length} Product Types`)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This log is not correct. In the lines above only products were deleted, product types were not deleted.
Actually product types are not deleted anywhere.

const productTypes = await apiRoot.productTypes().get().execute()
await Promise.all(
productTypes.body.results.map(async (productType) => {
await deleteProductsForProductType(productType.id)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's not necessary to fetch products per product type if we're going to delete all products. Just fetch all products without where query and delete. This could save some requests/responses in case there are less products.


// Function to delete all products in the system
const deleteAllProducts = async () => {
const products = await apiRoot.products().get().execute()
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This won't work if there are more than 20 (default limit number) products. You need to use process() on the client to iterate through all the products. (

, https://docs.commercetools.com/api/performance-tips - last bullet point). This applies to all other resources as well.

Additionally raise the limit to maximum 500 so there are less requests/responses in total (

)

console.log(
`Found ${products.body.results.length} products for product type ${productTypeId}...`
)
return Promise.all(
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's not recommended to use Promise.all with requests to commercetools. Rather use Promise.map and limit the concurrency to some lower number like 20. The reason is CTP cannot handle well the deletion and deletion usually triggers some backend work as well. I know this because I did Promise.all as well and caused the whole platform outage in the past 😅

taxCategory.id,
taxCategory.version,
async () => {
const productsReferencingTaxCategory = await apiRoot
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@evansinho I would recommend to delete straightaway and not checking.
Reasons:

  • At this point products are all delete. If not, then it should fail and you should investigate why. It could be an error in the script and then we have to fix it. If it's a platform error, then we should report it.
  • Checking this requires extra request/response. This script could be used as a reference for customers, so we should avoid doing unnecessary actions.

Please check also for other resources that use safeDeleteResource().

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants