From 9d29078b0d5f9c23e137b9eb59a0c2b0ab713052 Mon Sep 17 00:00:00 2001 From: Adrien de Peretti Date: Sun, 11 May 2025 16:21:41 +0200 Subject: [PATCH] fix(workflow-engine-*): q text search (#12435) --- .../workflow-engine/admin/index.spec.ts | 94 +++++++++++++++++++ .../http/workflow-execution/admin/queries.ts | 4 + .../core/types/src/workflows-sdk/common.ts | 11 ++- .../admin/workflows-executions/validators.ts | 1 + .../src/services/workflows-module.ts | 66 +++++++++++++ .../src/services/workflows-module.ts | 66 +++++++++++++ 6 files changed, 237 insertions(+), 5 deletions(-) create mode 100644 integration-tests/http/__tests__/workflow-engine/admin/index.spec.ts diff --git a/integration-tests/http/__tests__/workflow-engine/admin/index.spec.ts b/integration-tests/http/__tests__/workflow-engine/admin/index.spec.ts new file mode 100644 index 0000000000..2a5596fe80 --- /dev/null +++ b/integration-tests/http/__tests__/workflow-engine/admin/index.spec.ts @@ -0,0 +1,94 @@ +import { + createStep, + createWorkflow, + StepResponse, + WorkflowData, + WorkflowResponse, +} from "@medusajs/framework/workflows-sdk" +import { medusaIntegrationTestRunner } from "@medusajs/test-utils" +import { Modules } from "@medusajs/utils" +import { + adminHeaders, + createAdminUser, +} from "../../../../helpers/create-admin-user" + +jest.setTimeout(300000) + +medusaIntegrationTestRunner({ + testSuite: ({ dbConnection, getContainer, api }) => { + let container + + beforeEach(async () => { + container = getContainer() + await createAdminUser(dbConnection, adminHeaders, container) + }) + + describe("GET /admin/workflow-executions", () => { + it("should filter using q", async () => { + const step1 = createStep( + { + name: "my-step", + }, + async (_) => { + return new StepResponse({ result: "success" }) + } + ) + + const workflowName = "workflow-admin/workflow-executions" + createWorkflow( + { + name: workflowName, + retentionTime: 50, + }, + function (input: WorkflowData<{ initial: string }>) { + const stepRes = step1() + + return new WorkflowResponse(stepRes) + } + ) + + const engine = container.resolve(Modules.WORKFLOW_ENGINE) + + const transactionId = "test-transaction-id" + await engine.run(workflowName, { + transactionId, + input: { + initial: "test", + }, + }) + + const transactionId2 = "unknown" + await engine.run(workflowName, { + transactionId: transactionId2, + input: { + initial: "test", + }, + }) + + const q = "transaction-id" + const response = await api.get( + `/admin/workflows-executions?q=${q}`, + adminHeaders + ) + + expect(response.status).toEqual(200) + expect(response.data.workflow_executions.length).toEqual(1) + expect(response.data.workflow_executions[0].transaction_id).toEqual( + transactionId + ) + + const q2 = "known" + const response2 = await api.get( + `/admin/workflows-executions?q=${q2}`, + adminHeaders + ) + + expect(response2.status).toEqual(200) + expect(response2.data.workflow_executions.length).toEqual(1) + expect(response2.data.workflow_executions[0].transaction_id).toEqual( + transactionId2 + ) + }) + }) + }, +}) diff --git a/packages/core/types/src/http/workflow-execution/admin/queries.ts b/packages/core/types/src/http/workflow-execution/admin/queries.ts index 58aa1009e4..58b448c57b 100644 --- a/packages/core/types/src/http/workflow-execution/admin/queries.ts +++ b/packages/core/types/src/http/workflow-execution/admin/queries.ts @@ -1,6 +1,10 @@ import { FindParams } from "../../common" export interface AdminGetWorkflowExecutionsParams extends FindParams { + /** + * Filter using a search query. + */ + q?: string /** * Filter by the ID of the transaction to retrieve workflow executions for a specific transaction. */ diff --git a/packages/core/types/src/workflows-sdk/common.ts b/packages/core/types/src/workflows-sdk/common.ts index 025f7e25a7..ab42eff15d 100644 --- a/packages/core/types/src/workflows-sdk/common.ts +++ b/packages/core/types/src/workflows-sdk/common.ts @@ -1,19 +1,20 @@ import { BaseFilterable, OperatorMap } from "../dal" - +import { TransactionState } from "../http" export interface WorkflowExecutionDTO { id: string workflow_id: string transaction_id: string - execution: string - context: string - state: any + execution: Record | null + context: Record | null + state: TransactionState created_at: Date updated_at: Date - deleted_at: Date + deleted_at: Date | null } export interface FilterableWorkflowExecutionProps extends BaseFilterable { + q?: string id?: string | string[] | OperatorMap workflow_id?: string | string[] | OperatorMap transaction_id?: string | string[] | OperatorMap diff --git a/packages/medusa/src/api/admin/workflows-executions/validators.ts b/packages/medusa/src/api/admin/workflows-executions/validators.ts index 6fec7312eb..f60f541abd 100644 --- a/packages/medusa/src/api/admin/workflows-executions/validators.ts +++ b/packages/medusa/src/api/admin/workflows-executions/validators.ts @@ -16,6 +16,7 @@ export const AdminGetWorkflowExecutionsParams = createFindParams({ limit: 100, }).merge( z.object({ + q: z.string().optional(), transaction_id: z.union([z.string(), z.array(z.string())]).optional(), workflow_id: z.union([z.string(), z.array(z.string())]).optional(), }) diff --git a/packages/modules/workflow-engine-inmemory/src/services/workflows-module.ts b/packages/modules/workflow-engine-inmemory/src/services/workflows-module.ts index 75868e5dda..305cd14b20 100644 --- a/packages/modules/workflow-engine-inmemory/src/services/workflows-module.ts +++ b/packages/modules/workflow-engine-inmemory/src/services/workflows-module.ts @@ -1,12 +1,16 @@ import { Context, DAL, + FilterableWorkflowExecutionProps, + FindConfig, InferEntityType, InternalModuleDeclaration, ModulesSdkTypes, + WorkflowExecutionDTO, WorkflowsSdkTypes, } from "@medusajs/framework/types" import { + InjectManager, InjectSharedContext, isDefined, MedusaContext, @@ -74,6 +78,68 @@ export class WorkflowsModuleService< }, } + static prepareFilters(filters: T & { q?: string }) { + const filters_ = { ...filters } // shallow copy + if (filters_?.q) { + const q = filters_.q + delete filters_.q + + const textSearch = { $ilike: `%${q}%` } + const textSearchFilters = { + $or: [ + { + transaction_id: textSearch, + }, + { + workflow_id: textSearch, + }, + { + state: textSearch, + }, + { + execution: { + runId: textSearch, + }, + }, + ], + } + + if (!Object.keys(filters_).length) { + return textSearchFilters + } else { + return { $and: [filters, textSearchFilters] } + } + } + + return filters + } + + @InjectManager() + // @ts-expect-error + async listWorkflowExecutions( + filters: FilterableWorkflowExecutionProps = {}, + config?: FindConfig, + @MedusaContext() sharedContext?: Context + ) { + const filters_ = WorkflowsModuleService.prepareFilters(filters) + return await super.listWorkflowExecutions(filters_, config, sharedContext) + } + + @InjectManager() + // @ts-expect-error + async listAndCountWorkflowExecutions( + filters: FilterableWorkflowExecutionProps = {}, + config?: FindConfig, + @MedusaContext() sharedContext?: Context + ) { + const filters_ = WorkflowsModuleService.prepareFilters(filters) + return await super.listAndCountWorkflowExecutions( + filters_, + config, + sharedContext + ) + } + @InjectSharedContext() async run>( workflowIdOrWorkflow: TWorkflow, diff --git a/packages/modules/workflow-engine-redis/src/services/workflows-module.ts b/packages/modules/workflow-engine-redis/src/services/workflows-module.ts index 33e7caee89..858151695f 100644 --- a/packages/modules/workflow-engine-redis/src/services/workflows-module.ts +++ b/packages/modules/workflow-engine-redis/src/services/workflows-module.ts @@ -1,12 +1,16 @@ import { Context, DAL, + FilterableWorkflowExecutionProps, + FindConfig, InferEntityType, InternalModuleDeclaration, ModulesSdkTypes, + WorkflowExecutionDTO, WorkflowsSdkTypes, } from "@medusajs/framework/types" import { + InjectManager, InjectSharedContext, isDefined, MedusaContext, @@ -86,6 +90,68 @@ export class WorkflowsModuleService< }, } + static prepareFilters(filters: T & { q?: string }) { + const filters_ = { ...filters } // shallow copy + if (filters_?.q) { + const q = filters_.q + delete filters_.q + + const textSearch = { $ilike: `%${q}%` } + const textSearchFilters = { + $or: [ + { + transaction_id: textSearch, + }, + { + workflow_id: textSearch, + }, + { + state: textSearch, + }, + { + execution: { + runId: textSearch, + }, + }, + ], + } + + if (!Object.keys(filters_).length) { + return textSearchFilters + } else { + return { $and: [filters, textSearchFilters] } + } + } + + return filters + } + + @InjectManager() + // @ts-expect-error + async listWorkflowExecutions( + filters: FilterableWorkflowExecutionProps = {}, + config?: FindConfig, + @MedusaContext() sharedContext?: Context + ) { + const filters_ = WorkflowsModuleService.prepareFilters(filters) + return await super.listWorkflowExecutions(filters_, config, sharedContext) + } + + @InjectManager() + // @ts-expect-error + async listAndCountWorkflowExecutions( + filters: FilterableWorkflowExecutionProps = {}, + config?: FindConfig, + @MedusaContext() sharedContext?: Context + ) { + const filters_ = WorkflowsModuleService.prepareFilters(filters) + return await super.listAndCountWorkflowExecutions( + filters_, + config, + sharedContext + ) + } + @InjectSharedContext() async run>( workflowIdOrWorkflow: TWorkflow,