diff --git a/integration-tests/http/__tests__/product/admin/product-export.spec.ts b/integration-tests/http/__tests__/product/admin/product-export.spec.ts index d9a3e08930..4ad050df52 100644 --- a/integration-tests/http/__tests__/product/admin/product-export.spec.ts +++ b/integration-tests/http/__tests__/product/admin/product-export.spec.ts @@ -168,8 +168,8 @@ medusaIntegrationTestRunner({ adminHeaders ) - const workflowId = batchJobRes.data.workflow_id - expect(workflowId).toBeTruthy() + const transactionId = batchJobRes.data.transaction_id + expect(transactionId).toBeTruthy() await subscriberExecution const notifications = ( @@ -214,8 +214,8 @@ medusaIntegrationTestRunner({ adminHeaders ) - const workflowId = batchJobRes.data.workflow_id - expect(workflowId).toBeTruthy() + const transactionId = batchJobRes.data.transaction_id + expect(transactionId).toBeTruthy() await subscriberExecution const notifications = ( diff --git a/integration-tests/http/__tests__/product/admin/product-import.spec.ts b/integration-tests/http/__tests__/product/admin/product-import.spec.ts index 98e6e82429..873a507206 100644 --- a/integration-tests/http/__tests__/product/admin/product-import.spec.ts +++ b/integration-tests/http/__tests__/product/admin/product-import.spec.ts @@ -101,8 +101,18 @@ medusaIntegrationTestRunner({ // BREAKING: The batch endpoints moved to the domain routes (admin/batch-jobs -> /admin/products/import). The payload and response changed as well. const batchJobRes = await api.post("/admin/products/import", form, meta) - const workflowId = batchJobRes.data.workflow_id - expect(workflowId).toBeTruthy() + const transactionId = batchJobRes.data.transaction_id + expect(transactionId).toBeTruthy() + expect(batchJobRes.data.summary).toEqual({ + toCreate: 1, + toUpdate: 1, + }) + + await api.post( + `/admin/products/import/${transactionId}/confirm`, + {}, + meta + ) await subscriberExecution const notifications = ( diff --git a/packages/core/core-flows/src/product/steps/index.ts b/packages/core/core-flows/src/product/steps/index.ts index a5b59dfe0d..37e7f0ec67 100644 --- a/packages/core/core-flows/src/product/steps/index.ts +++ b/packages/core/core-flows/src/product/steps/index.ts @@ -23,3 +23,4 @@ export * from "./delete-product-tags" export * from "./generate-product-csv" export * from "./parse-product-csv" export * from "./group-products-for-batch" +export * from "./wait-confirmation-product-import" diff --git a/packages/core/core-flows/src/product/steps/wait-confirmation-product-import.ts b/packages/core/core-flows/src/product/steps/wait-confirmation-product-import.ts new file mode 100644 index 0000000000..b1e3af9834 --- /dev/null +++ b/packages/core/core-flows/src/product/steps/wait-confirmation-product-import.ts @@ -0,0 +1,13 @@ +import { createStep } from "@medusajs/workflows-sdk" + +export const waitConfirmationProductImportStepId = + "wait-confirmation-product-import" +export const waitConfirmationProductImportStep = createStep( + { + name: waitConfirmationProductImportStepId, + async: true, + // After an hour we want to timeout and cancel the import so we don't have orphaned workflows + timeout: 60 * 60 * 1 * 1000, + }, + async () => {} +) diff --git a/packages/core/core-flows/src/product/workflows/import-products.ts b/packages/core/core-flows/src/product/workflows/import-products.ts index e3ecb58133..c0b8ffec12 100644 --- a/packages/core/core-flows/src/product/workflows/import-products.ts +++ b/packages/core/core-flows/src/product/workflows/import-products.ts @@ -5,7 +5,11 @@ import { } from "@medusajs/workflows-sdk" import { WorkflowTypes } from "@medusajs/types" import { sendNotificationsStep } from "../../notification" -import { groupProductsForBatchStep, parseProductCsvStep } from "../steps" +import { + waitConfirmationProductImportStep, + groupProductsForBatchStep, + parseProductCsvStep, +} from "../steps" import { batchProductsWorkflow } from "./batch-products" export const importProductsWorkflowId = "import-products" @@ -13,11 +17,18 @@ export const importProductsWorkflow = createWorkflow( importProductsWorkflowId, ( input: WorkflowData - ): WorkflowData => { + ): WorkflowData => { const products = parseProductCsvStep(input.fileContent) const batchRequest = groupProductsForBatchStep(products) - // TODO: Add async confirmation step here + const summary = transform({ batchRequest }, (data) => { + return { + toCreate: data.batchRequest.create.length, + toUpdate: data.batchRequest.update.length, + } + }) + + waitConfirmationProductImportStep() batchProductsWorkflow.runAsStep({ input: batchRequest }) @@ -37,5 +48,6 @@ export const importProductsWorkflow = createWorkflow( }) sendNotificationsStep(notifications) + return summary } ) diff --git a/packages/core/types/src/http/product/admin/responses.ts b/packages/core/types/src/http/product/admin/responses.ts index 096f5f900e..9f81655ceb 100644 --- a/packages/core/types/src/http/product/admin/responses.ts +++ b/packages/core/types/src/http/product/admin/responses.ts @@ -32,11 +32,15 @@ export interface AdminProductVariantDeleteResponse extends DeleteResponse<"variant", AdminProduct> {} export interface AdminExportProductResponse { - workflow_id: string + transaction_id: string } export interface AdminImportProductResponse { - workflow_id: string + transaction_id: string + summary: { + toCreate: number + toUpdate: number + } } export interface AdminBatchProductVariantResponse diff --git a/packages/core/types/src/workflow/product/import-products.ts b/packages/core/types/src/workflow/product/import-products.ts index e184cfa206..14de827620 100644 --- a/packages/core/types/src/workflow/product/import-products.ts +++ b/packages/core/types/src/workflow/product/import-products.ts @@ -2,3 +2,8 @@ export interface ImportProductsDTO { fileContent: string filename: string } + +export interface ImportProductsSummary { + toCreate: number + toUpdate: number +} diff --git a/packages/medusa/src/api/admin/products/export/route.ts b/packages/medusa/src/api/admin/products/export/route.ts index 23a9112d6c..81309c7872 100644 --- a/packages/medusa/src/api/admin/products/export/route.ts +++ b/packages/medusa/src/api/admin/products/export/route.ts @@ -17,5 +17,5 @@ export const POST = async ( input, }) - res.status(202).json({ workflow_id: transaction.transactionId }) + res.status(202).json({ transaction_id: transaction.transactionId }) } diff --git a/packages/medusa/src/api/admin/products/import/[transaction_id]/confirm/route.ts b/packages/medusa/src/api/admin/products/import/[transaction_id]/confirm/route.ts new file mode 100644 index 0000000000..0b468cf6bf --- /dev/null +++ b/packages/medusa/src/api/admin/products/import/[transaction_id]/confirm/route.ts @@ -0,0 +1,34 @@ +import { + AuthenticatedMedusaRequest, + MedusaResponse, +} from "../../../../../../types/routing" + +import { IWorkflowEngineService } from "@medusajs/types" +import { ModuleRegistrationName, TransactionHandlerType } from "@medusajs/utils" +import { + importProductsWorkflowId, + waitConfirmationProductImportStepId, +} from "@medusajs/core-flows" +import { StepResponse } from "@medusajs/workflows-sdk" + +export const POST = async ( + req: AuthenticatedMedusaRequest, + res: MedusaResponse +) => { + const workflowEngineService: IWorkflowEngineService = req.scope.resolve( + ModuleRegistrationName.WORKFLOW_ENGINE + ) + const transactionId = req.params.transaction_id + + await workflowEngineService.setStepSuccess({ + idempotencyKey: { + action: TransactionHandlerType.INVOKE, + transactionId, + stepId: waitConfirmationProductImportStepId, + workflowId: importProductsWorkflowId, + }, + stepResponse: new StepResponse(true), + }) + + res.status(202).json({}) +} diff --git a/packages/medusa/src/api/admin/products/import/route.ts b/packages/medusa/src/api/admin/products/import/route.ts index 66f4dbd42b..8c112e16f7 100644 --- a/packages/medusa/src/api/admin/products/import/route.ts +++ b/packages/medusa/src/api/admin/products/import/route.ts @@ -19,12 +19,14 @@ export const POST = async ( ) } - const { transaction } = await importProductsWorkflow(req.scope).run({ + const { result, transaction } = await importProductsWorkflow(req.scope).run({ input: { filename: input.originalname, fileContent: input.buffer.toString("utf-8"), }, }) - res.status(202).json({ workflow_id: transaction.transactionId }) + res + .status(202) + .json({ transaction_id: transaction.transactionId, summary: result }) } diff --git a/packages/medusa/src/api/admin/products/middlewares.ts b/packages/medusa/src/api/admin/products/middlewares.ts index 468991772c..0e557b2359 100644 --- a/packages/medusa/src/api/admin/products/middlewares.ts +++ b/packages/medusa/src/api/admin/products/middlewares.ts @@ -92,6 +92,11 @@ export const adminProductRoutesMiddlewares: MiddlewareRoute[] = [ matcher: "/admin/products/import", middlewares: [upload.single("file")], }, + { + method: ["POST"], + matcher: "/admin/products/import/:transaction_id/confirm", + middlewares: [], + }, { method: ["GET"], matcher: "/admin/products/:id",