-
Notifications
You must be signed in to change notification settings - Fork 2.2k
refactor: create archiveProducts GQL mutation to replace meteor methods
#5680
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
Merged
aldeed
merged 18 commits into
develop
from
refactor-kieckhafer-convertCatalogMethodsToGQL
Oct 8, 2019
Merged
Changes from all commits
Commits
Show all changes
18 commits
Select commit
Hold shift + click to select a range
5d14eef
feat: create archiveProducts mutation
kieckhafer ddea7d6
remove archiveProduct and deleteVariant meteor methods
kieckhafer 10fcbdc
refactor: update media record code to fix deleting media
kieckhafer 8f296e9
refactor: update client to use new GQL archiveProducts mutation insta…
kieckhafer a72d090
Merge remote-tracking branch 'origin/develop' into refactor-kieckhafe…
kieckhafer 82ccb18
tests: add tests for archiveProducts
kieckhafer dc93ec2
refactor: update file name
kieckhafer 1ce5f5a
refactor: return only original products, not all variants and options
kieckhafer 2355ab0
refactor: add metadata field to updated
kieckhafer bbc7404
refactor: small code style updates
kieckhafer b233c75
feat: create archiveProductVariants resolver
kieckhafer a2c725b
feat: add client code for archiveProductVariants
kieckhafer bee4786
style: graphql lint fix
kieckhafer 6ec86b9
Merge remote-tracking branch 'origin/develop' into refactor-kieckhafe…
kieckhafer 9f0af7f
refactor: make all test.stop() await
kieckhafer fdf3c04
Merge remote-tracking branch 'origin/develop' into refactor-kieckhafe…
kieckhafer a2456e5
Merge branch 'develop' into refactor-kieckhafer-convertCatalogMethods…
aldeed 0a132bd
feat: ability to disable background workers
aldeed File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
7 changes: 7 additions & 0 deletions
7
imports/node-app/core-services/product/mutations/__snapshots__/archiveProducts.test.js.snap
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,7 @@ | ||
| // Jest Snapshot v1, https://goo.gl/fbAQLP | ||
|
|
||
| exports[`throws if permission check fails 1`] = `"Access Denied"`; | ||
|
|
||
| exports[`throws if the productIds isn't supplied 1`] = `"Product ids is required"`; | ||
|
|
||
| exports[`throws if the shopId isn't supplied 1`] = `"Shop ID is required"`; |
123 changes: 123 additions & 0 deletions
123
imports/node-app/core-services/product/mutations/archiveProducts.js
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,123 @@ | ||
| import SimpleSchema from "simpl-schema"; | ||
| import ReactionError from "@reactioncommerce/reaction-error"; | ||
|
|
||
| const inputSchema = new SimpleSchema({ | ||
| "productIds": Array, | ||
| "productIds.$": { | ||
| type: String | ||
| }, | ||
| "shopId": String | ||
| }); | ||
|
|
||
| /** | ||
| * | ||
| * @method archiveProducts | ||
| * @summary archives a product | ||
| * @description the method archives products, but will also archive | ||
| * child variants and options | ||
| * @param {Object} context - an object containing the per-request state | ||
| * @param {Object} input - Input arguments for the bulk operation | ||
| * @param {String} input.productIds - an array of decoded product IDs to archive | ||
| * @param {String} input.shopId - shop these products belong to | ||
| * @return {Array} array with archived products | ||
| */ | ||
| export default async function archiveProducts(context, input) { | ||
| inputSchema.validate(input); | ||
| const { appEvents, collections, userHasPermission, userId } = context; | ||
| const { MediaRecords, Products } = collections; | ||
| const { productIds, shopId } = input; | ||
|
|
||
| if (!userHasPermission(["createProduct", "product/admin", "product/archive"], shopId)) { | ||
| throw new ReactionError("access-denied", "Access Denied"); | ||
| } | ||
|
|
||
| // Check to make sure all products are on the same shop | ||
| const count = await Products.find({ _id: { $in: productIds }, shopId }).count(); | ||
| if (count !== productIds.length) throw new ReactionError("not-found", "One or more products do not exist"); | ||
|
|
||
| // Find all products that aren't deleted, and all their variants variants | ||
| const productsWithVariants = await Products.find({ | ||
| // Don't "archive" products that are already marked deleted. | ||
| isDeleted: { | ||
| $ne: true | ||
| }, | ||
| $or: [ | ||
| { | ||
| _id: { | ||
| $in: productIds | ||
| } | ||
| }, | ||
| { | ||
| ancestors: { | ||
| $in: productIds | ||
| } | ||
| } | ||
| ] | ||
| }).toArray(); | ||
|
|
||
| // Get ID's of all products to archive | ||
| const productIdsToArchive = productsWithVariants.map((product) => product._id); | ||
|
|
||
|
|
||
| const archivedProducts = await Promise.all(productIdsToArchive.map(async (productId) => { | ||
| const { value: archivedProduct } = await Products.findOneAndUpdate( | ||
| { | ||
| _id: productId | ||
| }, | ||
| { | ||
| $set: { | ||
| isDeleted: true | ||
| } | ||
| }, { | ||
| returnOriginal: false | ||
| } | ||
| ); | ||
|
|
||
| if (archivedProduct.type === "variant") { | ||
| appEvents.emit("afterVariantSoftDelete", { | ||
| variant: { | ||
| ...archivedProduct | ||
| }, | ||
| deletedBy: userId | ||
| }); | ||
| } else { | ||
| appEvents.emit("afterProductSoftDelete", { | ||
| product: { | ||
| ...archivedProduct | ||
| }, | ||
| deletedBy: userId | ||
| }); | ||
| } | ||
| return archivedProduct; | ||
| })); | ||
|
|
||
| if (archivedProducts && archivedProducts.length) { | ||
| // Flag associated MediaRecords as deleted. | ||
| await MediaRecords.updateMany( | ||
| { | ||
| $or: [ | ||
| { | ||
| "metadata.productId": { | ||
| $in: productIdsToArchive | ||
| } | ||
| }, | ||
| { | ||
| "metadata.variantId": { | ||
| $in: productIdsToArchive | ||
| } | ||
| } | ||
| ] | ||
| }, | ||
| { | ||
| $set: { | ||
| "metadata.isDeleted": true, | ||
| "metadata.workflow": "archived" | ||
| } | ||
| } | ||
| ); | ||
| } | ||
|
|
||
| // Return only originally supplied product(s), | ||
| // not variants and options also archived | ||
| return archivedProducts.filter((archivedProduct) => productIds.includes(archivedProduct._id)); | ||
| } | ||
33 changes: 33 additions & 0 deletions
33
imports/node-app/core-services/product/mutations/archiveProducts.test.js
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,33 @@ | ||
| import mockContext from "@reactioncommerce/api-utils/tests/mockContext.js"; | ||
| import archiveProducts from "./archiveProducts.js"; | ||
|
|
||
| mockContext.mutations.archiveProducts = jest.fn().mockName("mutations.archiveProducts"); | ||
|
|
||
| test("throws if permission check fails", async () => { | ||
| mockContext.userHasPermission.mockReturnValueOnce(false); | ||
|
|
||
| await expect(archiveProducts(mockContext, { | ||
| productIds: ["PRODUCT_ID_1", "PRODUCT_ID_2"], | ||
| shopId: "SHOP_ID" | ||
| })).rejects.toThrowErrorMatchingSnapshot(); | ||
|
|
||
| expect(mockContext.userHasPermission).toHaveBeenCalledWith(["createProduct", "product/admin", "product/archive"], "SHOP_ID"); | ||
| }); | ||
|
|
||
| test("throws if the productIds isn't supplied", async () => { | ||
| mockContext.userHasPermission.mockReturnValueOnce(true); | ||
|
|
||
| await expect(archiveProducts(mockContext, { | ||
| productIds: undefined, | ||
| shopId: "SHOP_ID" | ||
| })).rejects.toThrowErrorMatchingSnapshot(); | ||
| }); | ||
|
|
||
| test("throws if the shopId isn't supplied", async () => { | ||
| mockContext.userHasPermission.mockReturnValueOnce(true); | ||
|
|
||
| await expect(archiveProducts(mockContext, { | ||
| productIds: ["PRODUCT_ID_1", "PRODUCT_ID_2"], | ||
| shopId: undefined | ||
| })).rejects.toThrowErrorMatchingSnapshot(); | ||
| }); |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
37 changes: 37 additions & 0 deletions
37
imports/node-app/core-services/product/resolvers/Mutation/archiveProductVariants.js
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,37 @@ | ||
| import { decodeProductOpaqueId } from "@reactioncommerce/reaction-graphql-xforms/product"; | ||
| import { decodeShopOpaqueId } from "@reactioncommerce/reaction-graphql-xforms/shop"; | ||
|
|
||
| /** | ||
| * | ||
| * @method archiveProductVariants | ||
| * @summary Takes an array of variant IDs and archives variants | ||
| * @param {Object} _ - unused | ||
| * @param {Object} args - The input arguments | ||
| * @param {Object} args.input - mutation input object | ||
| * @param {String} args.input.shopId - shop these variants belong to | ||
| * @param {String} args.input.variantIds - an array of variant IDs to archive | ||
| * @param {Object} context - an object containing the per-request state | ||
| * @return {Array} array with archived variants | ||
| */ | ||
| export default async function archiveProductVariants(_, { input }, context) { | ||
| const { | ||
| clientMutationId, | ||
| shopId, | ||
| variantIds | ||
| } = input; | ||
|
|
||
| const decodedVariantIds = variantIds.map((variantId) => decodeProductOpaqueId(variantId)); | ||
|
|
||
| // This `archiveProductVariants` resolver calls the `archiveProducts` mutation | ||
| // as we don't have the need to separate this into `archiveProductVariants` at this time. | ||
| // In the future, we can create a `archiveProductVariants` mutation if needed. | ||
| const archivedVariants = await context.mutations.archiveProducts(context, { | ||
| productIds: decodedVariantIds, | ||
| shopId: decodeShopOpaqueId(shopId) | ||
| }); | ||
|
|
||
| return { | ||
| clientMutationId, | ||
| variants: archivedVariants | ||
| }; | ||
| } |
34 changes: 34 additions & 0 deletions
34
imports/node-app/core-services/product/resolvers/Mutation/archiveProducts.js
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,34 @@ | ||
| import { decodeProductOpaqueId } from "@reactioncommerce/reaction-graphql-xforms/product"; | ||
| import { decodeShopOpaqueId } from "@reactioncommerce/reaction-graphql-xforms/shop"; | ||
|
|
||
| /** | ||
| * | ||
| * @method archiveProducts | ||
| * @summary Takes an array of product IDs and archives products | ||
| * @param {Object} _ - unused | ||
| * @param {Object} args - The input arguments | ||
| * @param {Object} args.input - mutation input object | ||
| * @param {String} args.input.productIds - an array of decoded product IDs to archive | ||
| * @param {String} args.input.shopId - shop these products belong to | ||
| * @param {Object} context - an object containing the per-request state | ||
| * @return {Array} array with archived products | ||
| */ | ||
| export default async function archiveProducts(_, { input }, context) { | ||
| const { | ||
| clientMutationId, | ||
| productIds, | ||
| shopId | ||
| } = input; | ||
|
|
||
| const decodedProductIds = productIds.map((productId) => decodeProductOpaqueId(productId)); | ||
|
|
||
| const archivedProducts = await context.mutations.archiveProducts(context, { | ||
| productIds: decodedProductIds, | ||
| shopId: decodeShopOpaqueId(shopId) | ||
| }); | ||
|
|
||
| return { | ||
| clientMutationId, | ||
| products: archivedProducts | ||
| }; | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,12 @@ | ||
| import envalid, { bool, str } from "envalid"; | ||
|
|
||
| export default envalid.cleanEnv(process.env, { | ||
| // This is necessary to override the envalid default | ||
| // validation for NODE_ENV, which uses | ||
| // str({ choices: ['development', 'test', 'production'] }) | ||
| // | ||
| // We currently need to set NODE_ENV to "jesttest" when | ||
| // integration tests run. | ||
| NODE_ENV: str(), | ||
| REACTION_WORKERS_ENABLED: bool({ default: true }) | ||
| }); |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.