fix(orchestration): fix set step failure (#10031)

What:
 - copy data before saving checkpoint
 - removed unused data format function
 - properly handle registerStepFailure to not throw
 - emit onFinish event even when execution failed
This commit is contained in:
Carlos R. L. Rodrigues
2024-11-12 07:06:36 -03:00
committed by GitHub
parent 7794faf49e
commit 1eef324af3
10 changed files with 217 additions and 212 deletions

View File

@@ -1,144 +0,0 @@
import { createMedusaContainer } from "@medusajs/utils"
import { MedusaWorkflow } from "../../medusa-workflow"
import { exportWorkflow } from "../workflow-export"
jest.mock("@medusajs/orchestration", () => {
return {
TransactionHandlerType: {
INVOKE: "invoke",
COMPENSATE: "compensate",
},
TransactionState: {
FAILED: "failed",
REVERTED: "reverted",
},
LocalWorkflow: jest.fn(() => {
return {
run: jest.fn(() => {
return {
getErrors: jest.fn(),
getState: jest.fn(() => "done"),
getContext: jest.fn(() => {
return {
invoke: { result_step: "invoke_test" },
}
}),
}
}),
registerStepSuccess: jest.fn(() => {
return {
getErrors: jest.fn(),
getState: jest.fn(() => "done"),
getContext: jest.fn(() => {
return {
invoke: { result_step: "invoke_test" },
}
}),
}
}),
registerStepFailure: jest.fn(() => {
return {
getErrors: jest.fn(),
getState: jest.fn(() => "done"),
getContext: jest.fn(() => {
return {
invoke: { result_step: "invoke_test" },
}
}),
}
}),
cancel: jest.fn(() => {
return {
getErrors: jest.fn(),
getState: jest.fn(() => "reverted"),
getContext: jest.fn(() => {
return {
invoke: { result_step: "invoke_test" },
}
}),
}
}),
}
}),
}
})
describe("Export Workflow", function () {
afterEach(() => {
MedusaWorkflow.workflows = {}
})
it("should prepare the input data before initializing the transaction", async function () {
let transformedInput
const prepare = jest.fn().mockImplementation(async (data) => {
data.__transformed = true
transformedInput = data
return data
})
const work = exportWorkflow("id" as any, "result_step", prepare)
const container = createMedusaContainer()
const wfHandler = work(container)
const input = {
test: "payload",
}
const { result } = await wfHandler.run({
input,
})
expect(input).toEqual({
test: "payload",
})
expect(transformedInput).toEqual({
test: "payload",
__transformed: true,
})
expect(result).toEqual("invoke_test")
})
describe("Using the exported workflow run", function () {
afterEach(() => {
MedusaWorkflow.workflows = {}
})
it("should prepare the input data before initializing the transaction", async function () {
let transformedInput
const prepare = jest.fn().mockImplementation(async (data) => {
data.__transformed = true
transformedInput = data
return data
})
const work = exportWorkflow("id" as any, "result_step", prepare)
const input = {
test: "payload",
}
const container = createMedusaContainer()
const { result } = await work.run({
input,
container,
})
expect(input).toEqual({
test: "payload",
})
expect(transformedInput).toEqual({
test: "payload",
__transformed: true,
})
expect(result).toEqual("invoke_test")
})
})
})

View File

@@ -3,7 +3,6 @@ import {
DistributedTransactionEvents,
DistributedTransactionType,
LocalWorkflow,
TransactionHandlerType,
TransactionState,
} from "@medusajs/orchestration"
import {
@@ -18,6 +17,7 @@ import {
isPresent,
MedusaContextType,
Modules,
TransactionHandlerType,
} from "@medusajs/utils"
import { EOL } from "os"
import { ulid } from "ulid"
@@ -41,13 +41,11 @@ function createContextualWorkflowRunner<
>({
workflowId,
defaultResult,
dataPreparation,
options,
container,
}: {
workflowId: string
defaultResult?: string | Symbol
dataPreparation?: (data: TData) => Promise<unknown>
options?: {
wrappedInput?: boolean
sourcePath?: string
@@ -110,7 +108,10 @@ function createContextualWorkflowRunner<
events,
flowMetadata,
]
const transaction = await method.apply(method, args) as DistributedTransactionType
const transaction = (await method.apply(
method,
args
)) as DistributedTransactionType
let errors = transaction.getErrors(TransactionHandlerType.INVOKE)
@@ -118,16 +119,24 @@ function createContextualWorkflowRunner<
const isCancelled =
isCancel && transaction.getState() === TransactionState.REVERTED
const isRegisterStepFailure =
method === originalRegisterStepFailure &&
transaction.getState() === TransactionState.REVERTED
let thrownError = null
if (
!isCancelled &&
failedStatus.includes(transaction.getState()) &&
throwOnError
!isCancelled &&
!isRegisterStepFailure
) {
/*const errorMessage = errors
?.map((err) => `${err.error?.message}${EOL}${err.error?.stack}`)
?.join(`${EOL}`)*/
const firstError = errors?.[0]?.error ?? new Error("Unknown error")
throw firstError
thrownError = firstError
if (throwOnError) {
throw firstError
}
}
let result
@@ -135,6 +144,8 @@ function createContextualWorkflowRunner<
result = resolveValue(resultFrom, transaction.getContext())
if (result instanceof Promise) {
result = await result.catch((e) => {
thrownError = e
if (throwOnError) {
throw e
}
@@ -151,6 +162,7 @@ function createContextualWorkflowRunner<
errors,
transaction,
result,
thrownError,
}
}
@@ -175,22 +187,6 @@ function createContextualWorkflowRunner<
context.transactionId ??= ulid()
context.eventGroupId ??= ulid()
if (typeof dataPreparation === "function") {
try {
const copyInput = input ? JSON.parse(JSON.stringify(input)) : input
input = await dataPreparation(copyInput as TData)
} catch (err) {
if (throwOnError) {
throw new Error(
`Data preparation failed: ${err.message}${EOL}${err.stack}`
)
}
return {
errors: [err],
}
}
}
return await originalExecution(
originalRun,
{
@@ -339,7 +335,6 @@ function createContextualWorkflowRunner<
export const exportWorkflow = <TData = unknown, TResult = unknown>(
workflowId: string,
defaultResult?: string | Symbol,
dataPreparation?: (data: TData) => Promise<unknown>,
options?: {
wrappedInput?: boolean
sourcePath?: string
@@ -364,7 +359,6 @@ export const exportWorkflow = <TData = unknown, TResult = unknown>(
>({
workflowId,
defaultResult,
dataPreparation,
options,
container,
})
@@ -390,7 +384,6 @@ export const exportWorkflow = <TData = unknown, TResult = unknown>(
>({
workflowId,
defaultResult,
dataPreparation,
options,
container,
})

View File

@@ -152,15 +152,10 @@ export function createWorkflow<TData, TResult, THooks extends any[]>(
WorkflowManager.register(name, context.flow, handlers, options)
}
const workflow = exportWorkflow<TData, TResult>(
name,
returnedStep,
undefined,
{
wrappedInput: true,
sourcePath: fileSourcePath,
}
)
const workflow = exportWorkflow<TData, TResult>(name, returnedStep, {
wrappedInput: true,
sourcePath: fileSourcePath,
})
const mainFlow = <TDataOverride = undefined, TResultOverride = undefined>(
container?: LoadedModule[] | MedusaContainer