From 8c3b6a8408294190d3436ceffefdd500affc995f Mon Sep 17 00:00:00 2001 From: Yannis Moser Date: Mon, 11 May 2026 13:17:59 +0200 Subject: [PATCH 01/13] updated the csnBUilder and reimported test-processes --- lib/processImport/csnBuilder.ts | 31 +++++- ...ybridtest.annotation_Lifecycle_Process.cds | 33 +++++-- ...dtest.annotation_Lifecycle_Process_Two.cds | 33 +++++-- ...t.importProcess_Attributes_And_Outputs.cds | 33 +++++-- ...ybridtest.importProcess_Complex_Inputs.cds | 33 +++++-- ...hybridtest.importProcess_Simple_Inputs.cds | 33 +++++-- ...ridtest.programmatic_Lifecycle_Process.cds | 33 +++++-- ...hybridtest.programmatic_Output_Process.cds | 33 +++++-- ....importProcess_Attributes_And_Outputs.json | 94 ++++++++----------- ...bridtest.importProcess_Complex_Inputs.json | 73 +++++++------- ...ybridtest.importProcess_Simple_Inputs.json | 36 ++++--- ...t.importProcess_Attributes_And_Outputs.cds | 33 +++++-- ...ybridtest.importProcess_Complex_Inputs.cds | 33 +++++-- ...hybridtest.importProcess_Simple_Inputs.cds | 33 +++++-- ...ationproject.authorVerificationProcess.cds | 12 ++- ...applicationproject.bookApprovalProcess.cds | 12 ++- 16 files changed, 404 insertions(+), 184 deletions(-) diff --git a/lib/processImport/csnBuilder.ts b/lib/processImport/csnBuilder.ts index a700aabd..82ee4dd4 100644 --- a/lib/processImport/csnBuilder.ts +++ b/lib/processImport/csnBuilder.ts @@ -123,12 +123,15 @@ function addProcessTypes( kind: 'type', name: instanceName, elements: { + id: { type: csn.CdsBuiltinType.String }, definitionId: { type: csn.CdsBuiltinType.String }, definitionVersion: { type: csn.CdsBuiltinType.String }, - id: { type: csn.CdsBuiltinType.String }, status: { type: csn.CdsBuiltinType.String }, - startedAt: { type: csn.CdsBuiltinType.String }, + startedAt: { type: csn.CdsBuiltinType.Timestamp }, + completedAt: { type: csn.CdsBuiltinType.Timestamp }, startedBy: { type: csn.CdsBuiltinType.String }, + subject: { type: csn.CdsBuiltinType.String }, + businessKey: { type: csn.CdsBuiltinType.String }, }, }; @@ -191,12 +194,30 @@ function addProcessActions( returns: { type: outputsType }, }; - definitions[fqn(serviceName, 'getInstancesByBusinessKey')] = { + definitions[fqn(serviceName, 'getInstances')] = { kind: 'function', - name: fqn(serviceName, 'getInstancesByBusinessKey'), + name: fqn(serviceName, 'getInstances'), params: { - businessKey: { type: csn.CdsBuiltinType.String, notNull: true }, + id: { type: csn.CdsBuiltinType.String }, + businessKey: { type: csn.CdsBuiltinType.String }, status: { items: { type: csn.CdsBuiltinType.String } }, + definitionId: { type: csn.CdsBuiltinType.String }, + definitionVersion: { type: csn.CdsBuiltinType.String }, + startedAt: { type: csn.CdsBuiltinType.Timestamp }, + startedFrom: { type: csn.CdsBuiltinType.Timestamp }, + startedUpTo: { type: csn.CdsBuiltinType.Timestamp }, + completedAt: { type: csn.CdsBuiltinType.Timestamp }, + completedFrom: { type: csn.CdsBuiltinType.Timestamp }, + completedUpTo: { type: csn.CdsBuiltinType.Timestamp }, + startedBy: { type: csn.CdsBuiltinType.String }, + subject: { type: csn.CdsBuiltinType.String }, + containsText: { type: csn.CdsBuiltinType.String }, + rootInstanceId: { type: csn.CdsBuiltinType.String }, + parentInstanceId: { type: csn.CdsBuiltinType.String }, + top: { type: csn.CdsBuiltinType.Integer }, + skip: { type: csn.CdsBuiltinType.Integer }, + orderBy: { type: csn.CdsBuiltinType.String }, + inlinecount: { type: csn.CdsBuiltinType.String }, }, returns: { type: instancesType }, }; diff --git a/tests/bookshop/srv/external/eu12.cdsmunich.capprocesspluginhybridtest.annotation_Lifecycle_Process.cds b/tests/bookshop/srv/external/eu12.cdsmunich.capprocesspluginhybridtest.annotation_Lifecycle_Process.cds index 02198360..3d877d9d 100644 --- a/tests/bookshop/srv/external/eu12.cdsmunich.capprocesspluginhybridtest.annotation_Lifecycle_Process.cds +++ b/tests/bookshop/srv/external/eu12.cdsmunich.capprocesspluginhybridtest.annotation_Lifecycle_Process.cds @@ -1,4 +1,4 @@ -/* checksum : 2d9b04f7d100bb10cefeaa255ec0b188 */ +/* checksum : faa9a4eb5a9c2d28e4c44f34f3faaa20 */ namespace eu12.cdsmunich.capprocesspluginhybridtest; /** DO NOT EDIT. THIS IS A GENERATED SERVICE THAT WILL BE OVERRIDDEN ON NEXT IMPORT. */ @@ -21,12 +21,15 @@ service Annotation_Lifecycle_ProcessService { type ProcessAttributes : many ProcessAttribute; type ProcessInstance { + id : String; definitionId : String; definitionVersion : String; - id : String; status : String; - startedAt : String; + startedAt : Timestamp; + completedAt : Timestamp; startedBy : String; + subject : String; + businessKey : String; }; type ProcessInstances : many ProcessInstance; @@ -43,9 +46,27 @@ service Annotation_Lifecycle_ProcessService { processInstanceId : String not null ) returns ProcessOutputs; - function getInstancesByBusinessKey( - businessKey : String not null, - status : many String + function getInstances( + id : String, + businessKey : String, + status : many String, + definitionId : String, + definitionVersion : String, + startedAt : Timestamp, + startedFrom : Timestamp, + startedUpTo : Timestamp, + completedAt : Timestamp, + completedFrom : Timestamp, + completedUpTo : Timestamp, + startedBy : String, + subject : String, + containsText : String, + rootInstanceId : String, + parentInstanceId : String, + top : Integer, + skip : Integer, + orderBy : String, + inlinecount : String ) returns ProcessInstances; action suspend( diff --git a/tests/bookshop/srv/external/eu12.cdsmunich.capprocesspluginhybridtest.annotation_Lifecycle_Process_Two.cds b/tests/bookshop/srv/external/eu12.cdsmunich.capprocesspluginhybridtest.annotation_Lifecycle_Process_Two.cds index 5329f538..6f39a2a8 100644 --- a/tests/bookshop/srv/external/eu12.cdsmunich.capprocesspluginhybridtest.annotation_Lifecycle_Process_Two.cds +++ b/tests/bookshop/srv/external/eu12.cdsmunich.capprocesspluginhybridtest.annotation_Lifecycle_Process_Two.cds @@ -1,4 +1,4 @@ -/* checksum : 15602da859ed8169e46688286553aafe */ +/* checksum : 8b3ac902513493de35fb2054ebe2f4bf */ namespace eu12.cdsmunich.capprocesspluginhybridtest; /** DO NOT EDIT. THIS IS A GENERATED SERVICE THAT WILL BE OVERRIDDEN ON NEXT IMPORT. */ @@ -21,12 +21,15 @@ service Annotation_Lifecycle_Process_TwoService { type ProcessAttributes : many ProcessAttribute; type ProcessInstance { + id : String; definitionId : String; definitionVersion : String; - id : String; status : String; - startedAt : String; + startedAt : Timestamp; + completedAt : Timestamp; startedBy : String; + subject : String; + businessKey : String; }; type ProcessInstances : many ProcessInstance; @@ -43,9 +46,27 @@ service Annotation_Lifecycle_Process_TwoService { processInstanceId : String not null ) returns ProcessOutputs; - function getInstancesByBusinessKey( - businessKey : String not null, - status : many String + function getInstances( + id : String, + businessKey : String, + status : many String, + definitionId : String, + definitionVersion : String, + startedAt : Timestamp, + startedFrom : Timestamp, + startedUpTo : Timestamp, + completedAt : Timestamp, + completedFrom : Timestamp, + completedUpTo : Timestamp, + startedBy : String, + subject : String, + containsText : String, + rootInstanceId : String, + parentInstanceId : String, + top : Integer, + skip : Integer, + orderBy : String, + inlinecount : String ) returns ProcessInstances; action suspend( diff --git a/tests/bookshop/srv/external/eu12.cdsmunich.capprocesspluginhybridtest.importProcess_Attributes_And_Outputs.cds b/tests/bookshop/srv/external/eu12.cdsmunich.capprocesspluginhybridtest.importProcess_Attributes_And_Outputs.cds index c11851f6..3fca6d2c 100644 --- a/tests/bookshop/srv/external/eu12.cdsmunich.capprocesspluginhybridtest.importProcess_Attributes_And_Outputs.cds +++ b/tests/bookshop/srv/external/eu12.cdsmunich.capprocesspluginhybridtest.importProcess_Attributes_And_Outputs.cds @@ -1,4 +1,4 @@ -/* checksum : b2be28c9da2617d511526b2f68e5e6b0 */ +/* checksum : 99dc8a7a72e25f64ab1b6bdec8fbafa8 */ namespace eu12.cdsmunich.capprocesspluginhybridtest; /** DO NOT EDIT. THIS IS A GENERATED SERVICE THAT WILL BE OVERRIDDEN ON NEXT IMPORT. */ @@ -57,12 +57,15 @@ service ImportProcess_Attributes_And_OutputsService { type ProcessAttributes : many ProcessAttribute; type ProcessInstance { + id : String; definitionId : String; definitionVersion : String; - id : String; status : String; - startedAt : String; + startedAt : Timestamp; + completedAt : Timestamp; startedBy : String; + subject : String; + businessKey : String; }; type ProcessInstances : many ProcessInstance; @@ -79,9 +82,27 @@ service ImportProcess_Attributes_And_OutputsService { processInstanceId : String not null ) returns ProcessOutputs; - function getInstancesByBusinessKey( - businessKey : String not null, - status : many String + function getInstances( + id : String, + businessKey : String, + status : many String, + definitionId : String, + definitionVersion : String, + startedAt : Timestamp, + startedFrom : Timestamp, + startedUpTo : Timestamp, + completedAt : Timestamp, + completedFrom : Timestamp, + completedUpTo : Timestamp, + startedBy : String, + subject : String, + containsText : String, + rootInstanceId : String, + parentInstanceId : String, + top : Integer, + skip : Integer, + orderBy : String, + inlinecount : String ) returns ProcessInstances; action suspend( diff --git a/tests/bookshop/srv/external/eu12.cdsmunich.capprocesspluginhybridtest.importProcess_Complex_Inputs.cds b/tests/bookshop/srv/external/eu12.cdsmunich.capprocesspluginhybridtest.importProcess_Complex_Inputs.cds index 93483826..022b5d13 100644 --- a/tests/bookshop/srv/external/eu12.cdsmunich.capprocesspluginhybridtest.importProcess_Complex_Inputs.cds +++ b/tests/bookshop/srv/external/eu12.cdsmunich.capprocesspluginhybridtest.importProcess_Complex_Inputs.cds @@ -1,4 +1,4 @@ -/* checksum : b0ced28bb4d1bef714f6714bff14642e */ +/* checksum : 679467542ca0d729a5f5f51f59061cbd */ namespace eu12.cdsmunich.capprocesspluginhybridtest; /** DO NOT EDIT. THIS IS A GENERATED SERVICE THAT WILL BE OVERRIDDEN ON NEXT IMPORT. */ @@ -49,12 +49,15 @@ service ImportProcess_Complex_InputsService { type ProcessAttributes : many ProcessAttribute; type ProcessInstance { + id : String; definitionId : String; definitionVersion : String; - id : String; status : String; - startedAt : String; + startedAt : Timestamp; + completedAt : Timestamp; startedBy : String; + subject : String; + businessKey : String; }; type ProcessInstances : many ProcessInstance; @@ -71,9 +74,27 @@ service ImportProcess_Complex_InputsService { processInstanceId : String not null ) returns ProcessOutputs; - function getInstancesByBusinessKey( - businessKey : String not null, - status : many String + function getInstances( + id : String, + businessKey : String, + status : many String, + definitionId : String, + definitionVersion : String, + startedAt : Timestamp, + startedFrom : Timestamp, + startedUpTo : Timestamp, + completedAt : Timestamp, + completedFrom : Timestamp, + completedUpTo : Timestamp, + startedBy : String, + subject : String, + containsText : String, + rootInstanceId : String, + parentInstanceId : String, + top : Integer, + skip : Integer, + orderBy : String, + inlinecount : String ) returns ProcessInstances; action suspend( diff --git a/tests/bookshop/srv/external/eu12.cdsmunich.capprocesspluginhybridtest.importProcess_Simple_Inputs.cds b/tests/bookshop/srv/external/eu12.cdsmunich.capprocesspluginhybridtest.importProcess_Simple_Inputs.cds index d26fccaf..af469a44 100644 --- a/tests/bookshop/srv/external/eu12.cdsmunich.capprocesspluginhybridtest.importProcess_Simple_Inputs.cds +++ b/tests/bookshop/srv/external/eu12.cdsmunich.capprocesspluginhybridtest.importProcess_Simple_Inputs.cds @@ -1,4 +1,4 @@ -/* checksum : 7e054e53e107a7f5c8375eb51a454e7f */ +/* checksum : 2a382c5f1ed621b369d90ef1ccdcc4d6 */ namespace eu12.cdsmunich.capprocesspluginhybridtest; /** DO NOT EDIT. THIS IS A GENERATED SERVICE THAT WILL BE OVERRIDDEN ON NEXT IMPORT. */ @@ -26,12 +26,15 @@ service ImportProcess_Simple_InputsService { type ProcessAttributes : many ProcessAttribute; type ProcessInstance { + id : String; definitionId : String; definitionVersion : String; - id : String; status : String; - startedAt : String; + startedAt : Timestamp; + completedAt : Timestamp; startedBy : String; + subject : String; + businessKey : String; }; type ProcessInstances : many ProcessInstance; @@ -48,9 +51,27 @@ service ImportProcess_Simple_InputsService { processInstanceId : String not null ) returns ProcessOutputs; - function getInstancesByBusinessKey( - businessKey : String not null, - status : many String + function getInstances( + id : String, + businessKey : String, + status : many String, + definitionId : String, + definitionVersion : String, + startedAt : Timestamp, + startedFrom : Timestamp, + startedUpTo : Timestamp, + completedAt : Timestamp, + completedFrom : Timestamp, + completedUpTo : Timestamp, + startedBy : String, + subject : String, + containsText : String, + rootInstanceId : String, + parentInstanceId : String, + top : Integer, + skip : Integer, + orderBy : String, + inlinecount : String ) returns ProcessInstances; action suspend( diff --git a/tests/bookshop/srv/external/eu12.cdsmunich.capprocesspluginhybridtest.programmatic_Lifecycle_Process.cds b/tests/bookshop/srv/external/eu12.cdsmunich.capprocesspluginhybridtest.programmatic_Lifecycle_Process.cds index 177eecb0..4b53bbb9 100644 --- a/tests/bookshop/srv/external/eu12.cdsmunich.capprocesspluginhybridtest.programmatic_Lifecycle_Process.cds +++ b/tests/bookshop/srv/external/eu12.cdsmunich.capprocesspluginhybridtest.programmatic_Lifecycle_Process.cds @@ -1,4 +1,4 @@ -/* checksum : 721729e92c5456fb13b5e792ac205215 */ +/* checksum : 24f98826fee0c33da91132659edd551b */ namespace eu12.cdsmunich.capprocesspluginhybridtest; /** DO NOT EDIT. THIS IS A GENERATED SERVICE THAT WILL BE OVERRIDDEN ON NEXT IMPORT. */ @@ -23,12 +23,15 @@ service Programmatic_Lifecycle_ProcessService { type ProcessAttributes : many ProcessAttribute; type ProcessInstance { + id : String; definitionId : String; definitionVersion : String; - id : String; status : String; - startedAt : String; + startedAt : Timestamp; + completedAt : Timestamp; startedBy : String; + subject : String; + businessKey : String; }; type ProcessInstances : many ProcessInstance; @@ -45,9 +48,27 @@ service Programmatic_Lifecycle_ProcessService { processInstanceId : String not null ) returns ProcessOutputs; - function getInstancesByBusinessKey( - businessKey : String not null, - status : many String + function getInstances( + id : String, + businessKey : String, + status : many String, + definitionId : String, + definitionVersion : String, + startedAt : Timestamp, + startedFrom : Timestamp, + startedUpTo : Timestamp, + completedAt : Timestamp, + completedFrom : Timestamp, + completedUpTo : Timestamp, + startedBy : String, + subject : String, + containsText : String, + rootInstanceId : String, + parentInstanceId : String, + top : Integer, + skip : Integer, + orderBy : String, + inlinecount : String ) returns ProcessInstances; action suspend( diff --git a/tests/bookshop/srv/external/eu12.cdsmunich.capprocesspluginhybridtest.programmatic_Output_Process.cds b/tests/bookshop/srv/external/eu12.cdsmunich.capprocesspluginhybridtest.programmatic_Output_Process.cds index 8a937cd8..57d15d94 100644 --- a/tests/bookshop/srv/external/eu12.cdsmunich.capprocesspluginhybridtest.programmatic_Output_Process.cds +++ b/tests/bookshop/srv/external/eu12.cdsmunich.capprocesspluginhybridtest.programmatic_Output_Process.cds @@ -1,4 +1,4 @@ -/* checksum : 5d4deaa24aac52f7afcf274a034ff450 */ +/* checksum : 672c9171d8b5797ae70a8e84f1307371 */ namespace eu12.cdsmunich.capprocesspluginhybridtest; /** DO NOT EDIT. THIS IS A GENERATED SERVICE THAT WILL BE OVERRIDDEN ON NEXT IMPORT. */ @@ -30,12 +30,15 @@ service Programmatic_Output_ProcessService { type ProcessAttributes : many ProcessAttribute; type ProcessInstance { + id : String; definitionId : String; definitionVersion : String; - id : String; status : String; - startedAt : String; + startedAt : Timestamp; + completedAt : Timestamp; startedBy : String; + subject : String; + businessKey : String; }; type ProcessInstances : many ProcessInstance; @@ -52,9 +55,27 @@ service Programmatic_Output_ProcessService { processInstanceId : String not null ) returns ProcessOutputs; - function getInstancesByBusinessKey( - businessKey : String not null, - status : many String + function getInstances( + id : String, + businessKey : String, + status : many String, + definitionId : String, + definitionVersion : String, + startedAt : Timestamp, + startedFrom : Timestamp, + startedUpTo : Timestamp, + completedAt : Timestamp, + completedFrom : Timestamp, + completedUpTo : Timestamp, + startedBy : String, + subject : String, + containsText : String, + rootInstanceId : String, + parentInstanceId : String, + top : Integer, + skip : Integer, + orderBy : String, + inlinecount : String ) returns ProcessInstances; action suspend( diff --git a/tests/bookshop/srv/workflows/eu12.cdsmunich.capprocesspluginhybridtest.importProcess_Attributes_And_Outputs.json b/tests/bookshop/srv/workflows/eu12.cdsmunich.capprocesspluginhybridtest.importProcess_Attributes_And_Outputs.json index 72264a98..339d47f3 100644 --- a/tests/bookshop/srv/workflows/eu12.cdsmunich.capprocesspluginhybridtest.importProcess_Attributes_And_Outputs.json +++ b/tests/bookshop/srv/workflows/eu12.cdsmunich.capprocesspluginhybridtest.importProcess_Attributes_And_Outputs.json @@ -1,9 +1,10 @@ { "uid": "5edc02ff-ce9a-4aaf-94bb-440b0c981a20", "name": "ImportProcess_Attributes_And_Outputs", - "identifier": "importProcess_Attributes_And_Outputs", + "description": "", "type": "bpi.process", - "projectId": "eu12.cdsmunich.capprocesspluginhybridtest", + "createdAt": "2026-03-18T10:23:31.648909Z", + "updatedAt": "2026-03-18T12:48:22.484258Z", "header": { "inputs": { "title": "inputs", @@ -12,28 +13,23 @@ "definitions": { "date": { "type": "string", - "format": "date", - "title": "date" + "format": "date" }, "dateTime": { "type": "string", - "format": "date-time", - "title": "dateTime" + "format": "date-time" }, "password": { "type": "string", - "password": true, - "title": "password" + "password": true }, "time": { "type": "string", - "format": "time", - "title": "time" + "format": "time" }, "documentFolder": { "type": "string", - "format": "document-folder", - "title": "documentFolder" + "format": "document-folder" } }, "properties": { @@ -54,8 +50,7 @@ "refName": "ImportProcess_Complex_DataType" }, "title": "Complexe", - "description": "", - "refName": "ImportProcess_Complex_DataType" + "description": "" }, "optionalcomplexe": { "$ref": "$.11cbfe86-2e73-4198-868a-d7a2115d82c1", @@ -77,28 +72,23 @@ "definitions": { "date": { "type": "string", - "format": "date", - "title": "date" + "format": "date" }, "dateTime": { "type": "string", - "format": "date-time", - "title": "dateTime" + "format": "date-time" }, "password": { "type": "string", - "password": true, - "title": "password" + "password": true }, "time": { "type": "string", - "format": "time", - "title": "time" + "format": "time" }, "documentFolder": { "type": "string", - "format": "document-folder", - "title": "documentFolder" + "format": "document-folder" } }, "properties": { @@ -125,8 +115,7 @@ "refName": "ImportProcess_Complex_DataType" }, "title": "Complexe", - "description": "", - "refName": "ImportProcess_Complex_DataType" + "description": "" } }, "required": [ @@ -135,6 +124,8 @@ ] }, "processAttributes": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "processAttributes", "type": "object", "properties": {}, "required": [] @@ -146,12 +137,17 @@ "type": "both" } ], + "identifier": "importProcess_Attributes_And_Outputs", + "valid": true, + "projectId": "eu12.cdsmunich.capprocesspluginhybridtest", "dataTypes": [ { "uid": "11cbfe86-2e73-4198-868a-d7a2115d82c1", "name": "ImportProcess_Complex_DataType", - "identifier": "ImportProcess_Complex_DataType", + "description": "", "type": "datatype", + "createdAt": "2026-03-18T10:28:54.794097Z", + "updatedAt": "2026-03-18T10:31:37.975015Z", "header": { "$schema": "http://json-schema.org/draft-07/schema#", "type": "object", @@ -164,13 +160,11 @@ "properties": { "SubString1": { "type": "string", - "uid": "567555b3-ffee-41d4-a650-e9006ca9c5f6", - "title": "SubString1" + "uid": "567555b3-ffee-41d4-a650-e9006ca9c5f6" }, "Substring2": { "type": "string", - "uid": "794440a0-a06e-4fa6-a703-1f8fe4bc9d94", - "title": "Substring2" + "uid": "794440a0-a06e-4fa6-a703-1f8fe4bc9d94" } }, "required": [ @@ -178,8 +172,7 @@ "Substring2" ] }, - "type": "array", - "title": "StringList" + "type": "array" }, "StringType": { "type": "object", @@ -194,27 +187,21 @@ "uid": "f3dd8f45-fcd2-4fe2-9d2f-868e51f52b64", "properties": { "SubSubSubDate": { - "type": "string", - "format": "date", - "title": "SubSubSubDate" + "uid": "361a2e90-c32d-421d-8077-6a412436f031", + "$ref": "#/definitions/date" }, "SubSubSubPassword": { - "type": "string", - "password": true, - "title": "SubSubSubPassword" + "uid": "9901ea3d-4e0f-4421-981e-22fbc0d35cc6", + "$ref": "#/definitions/password" }, "SubSubSubAny": { - "uid": "99fd32c0-7ce9-4c22-bb85-890160827365", - "title": "SubSubSubAny" + "uid": "99fd32c0-7ce9-4c22-bb85-890160827365" } - }, - "title": "SubSubStringType" + } } - }, - "title": "SubStringType" + } } - }, - "title": "StringType" + } } }, "required": [ @@ -223,18 +210,17 @@ "definitions": { "password": { "type": "string", - "password": true, - "title": "password" + "password": true }, "date": { "type": "string", - "format": "date", - "title": "date" + "format": "date" } }, - "version": 1, - "description": "" - } + "version": 1 + }, + "identifier": "importProcess_Complex_DataType", + "valid": true } ] } \ No newline at end of file diff --git a/tests/bookshop/srv/workflows/eu12.cdsmunich.capprocesspluginhybridtest.importProcess_Complex_Inputs.json b/tests/bookshop/srv/workflows/eu12.cdsmunich.capprocesspluginhybridtest.importProcess_Complex_Inputs.json index 1d011fae..155ebcc8 100644 --- a/tests/bookshop/srv/workflows/eu12.cdsmunich.capprocesspluginhybridtest.importProcess_Complex_Inputs.json +++ b/tests/bookshop/srv/workflows/eu12.cdsmunich.capprocesspluginhybridtest.importProcess_Complex_Inputs.json @@ -1,9 +1,10 @@ { "uid": "edb7b7fd-8a1c-4c51-a19f-b357aece8428", "name": "ImportProcess_Complex_Inputs", - "identifier": "importProcess_Complex_Inputs", + "description": "", "type": "bpi.process", - "projectId": "eu12.cdsmunich.capprocesspluginhybridtest", + "createdAt": "2026-03-18T10:23:06.672697Z", + "updatedAt": "2026-03-19T11:43:58.132360Z", "header": { "inputs": { "title": "inputs", @@ -12,28 +13,23 @@ "definitions": { "date": { "type": "string", - "format": "date", - "title": "date" + "format": "date" }, "dateTime": { "type": "string", - "format": "date-time", - "title": "dateTime" + "format": "date-time" }, "password": { "type": "string", - "password": true, - "title": "password" + "password": true }, "time": { "type": "string", - "format": "time", - "title": "time" + "format": "time" }, "documentFolder": { "type": "string", - "format": "document-folder", - "title": "documentFolder" + "format": "document-folder" } }, "properties": { @@ -70,6 +66,8 @@ "properties": {} }, "processAttributes": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "processAttributes", "type": "object", "properties": {}, "required": [] @@ -81,12 +79,17 @@ "type": "both" } ], + "identifier": "importProcess_Complex_Inputs", + "valid": true, + "projectId": "eu12.cdsmunich.capprocesspluginhybridtest", "dataTypes": [ { "uid": "11cbfe86-2e73-4198-868a-d7a2115d82c1", "name": "ImportProcess_Complex_DataType", - "identifier": "ImportProcess_Complex_DataType", + "description": "", "type": "datatype", + "createdAt": "2026-03-18T10:28:54.794097Z", + "updatedAt": "2026-03-18T10:31:37.975015Z", "header": { "$schema": "http://json-schema.org/draft-07/schema#", "type": "object", @@ -99,13 +102,11 @@ "properties": { "SubString1": { "type": "string", - "uid": "567555b3-ffee-41d4-a650-e9006ca9c5f6", - "title": "SubString1" + "uid": "567555b3-ffee-41d4-a650-e9006ca9c5f6" }, "Substring2": { "type": "string", - "uid": "794440a0-a06e-4fa6-a703-1f8fe4bc9d94", - "title": "Substring2" + "uid": "794440a0-a06e-4fa6-a703-1f8fe4bc9d94" } }, "required": [ @@ -113,8 +114,7 @@ "Substring2" ] }, - "type": "array", - "title": "StringList" + "type": "array" }, "StringType": { "type": "object", @@ -129,27 +129,21 @@ "uid": "f3dd8f45-fcd2-4fe2-9d2f-868e51f52b64", "properties": { "SubSubSubDate": { - "type": "string", - "format": "date", - "title": "SubSubSubDate" + "uid": "361a2e90-c32d-421d-8077-6a412436f031", + "$ref": "#/definitions/date" }, "SubSubSubPassword": { - "type": "string", - "password": true, - "title": "SubSubSubPassword" + "uid": "9901ea3d-4e0f-4421-981e-22fbc0d35cc6", + "$ref": "#/definitions/password" }, "SubSubSubAny": { - "uid": "99fd32c0-7ce9-4c22-bb85-890160827365", - "title": "SubSubSubAny" + "uid": "99fd32c0-7ce9-4c22-bb85-890160827365" } - }, - "title": "SubSubStringType" + } } - }, - "title": "SubStringType" + } } - }, - "title": "StringType" + } } }, "required": [ @@ -158,18 +152,17 @@ "definitions": { "password": { "type": "string", - "password": true, - "title": "password" + "password": true }, "date": { "type": "string", - "format": "date", - "title": "date" + "format": "date" } }, - "version": 1, - "description": "" - } + "version": 1 + }, + "identifier": "importProcess_Complex_DataType", + "valid": true } ] } \ No newline at end of file diff --git a/tests/bookshop/srv/workflows/eu12.cdsmunich.capprocesspluginhybridtest.importProcess_Simple_Inputs.json b/tests/bookshop/srv/workflows/eu12.cdsmunich.capprocesspluginhybridtest.importProcess_Simple_Inputs.json index 13169e36..19d87987 100644 --- a/tests/bookshop/srv/workflows/eu12.cdsmunich.capprocesspluginhybridtest.importProcess_Simple_Inputs.json +++ b/tests/bookshop/srv/workflows/eu12.cdsmunich.capprocesspluginhybridtest.importProcess_Simple_Inputs.json @@ -1,9 +1,10 @@ { "uid": "0fc541c5-5266-458e-801a-9fe7fbfc32e5", "name": "ImportProcess_Simple_Inputs", - "identifier": "importProcess_Simple_Inputs", + "description": "", "type": "bpi.process", - "projectId": "eu12.cdsmunich.capprocesspluginhybridtest", + "createdAt": "2026-03-18T10:22:42.550371Z", + "updatedAt": "2026-03-18T10:25:09.164955Z", "header": { "inputs": { "title": "inputs", @@ -12,28 +13,23 @@ "definitions": { "date": { "type": "string", - "format": "date", - "title": "date" + "format": "date" }, "dateTime": { "type": "string", - "format": "date-time", - "title": "dateTime" + "format": "date-time" }, "password": { "type": "string", - "password": true, - "title": "password" + "password": true }, "time": { "type": "string", - "format": "time", - "title": "time" + "format": "time" }, "documentFolder": { "type": "string", - "format": "document-folder", - "title": "documentFolder" + "format": "document-folder" } }, "properties": { @@ -53,20 +49,17 @@ "description": "" }, "date": { - "type": "string", - "format": "date", + "$ref": "#/definitions/date", "title": "Date", "description": "" }, "datetime": { - "type": "string", - "format": "date-time", + "$ref": "#/definitions/dateTime", "title": "DateTime", "description": "" }, "documentfolder": { - "type": "string", - "format": "document-folder", + "$ref": "#/definitions/documentFolder", "title": "DocumentFolder", "description": "" } @@ -87,9 +80,14 @@ "properties": {} }, "processAttributes": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "processAttributes", "type": "object", "properties": {}, "required": [] } - } + }, + "identifier": "importProcess_Simple_Inputs", + "valid": true, + "projectId": "eu12.cdsmunich.capprocesspluginhybridtest" } \ No newline at end of file diff --git a/tests/hybrid/importedCDS/eu12.cdsmunich.capprocesspluginhybridtest.importProcess_Attributes_And_Outputs.cds b/tests/hybrid/importedCDS/eu12.cdsmunich.capprocesspluginhybridtest.importProcess_Attributes_And_Outputs.cds index c11851f6..3fca6d2c 100644 --- a/tests/hybrid/importedCDS/eu12.cdsmunich.capprocesspluginhybridtest.importProcess_Attributes_And_Outputs.cds +++ b/tests/hybrid/importedCDS/eu12.cdsmunich.capprocesspluginhybridtest.importProcess_Attributes_And_Outputs.cds @@ -1,4 +1,4 @@ -/* checksum : b2be28c9da2617d511526b2f68e5e6b0 */ +/* checksum : 99dc8a7a72e25f64ab1b6bdec8fbafa8 */ namespace eu12.cdsmunich.capprocesspluginhybridtest; /** DO NOT EDIT. THIS IS A GENERATED SERVICE THAT WILL BE OVERRIDDEN ON NEXT IMPORT. */ @@ -57,12 +57,15 @@ service ImportProcess_Attributes_And_OutputsService { type ProcessAttributes : many ProcessAttribute; type ProcessInstance { + id : String; definitionId : String; definitionVersion : String; - id : String; status : String; - startedAt : String; + startedAt : Timestamp; + completedAt : Timestamp; startedBy : String; + subject : String; + businessKey : String; }; type ProcessInstances : many ProcessInstance; @@ -79,9 +82,27 @@ service ImportProcess_Attributes_And_OutputsService { processInstanceId : String not null ) returns ProcessOutputs; - function getInstancesByBusinessKey( - businessKey : String not null, - status : many String + function getInstances( + id : String, + businessKey : String, + status : many String, + definitionId : String, + definitionVersion : String, + startedAt : Timestamp, + startedFrom : Timestamp, + startedUpTo : Timestamp, + completedAt : Timestamp, + completedFrom : Timestamp, + completedUpTo : Timestamp, + startedBy : String, + subject : String, + containsText : String, + rootInstanceId : String, + parentInstanceId : String, + top : Integer, + skip : Integer, + orderBy : String, + inlinecount : String ) returns ProcessInstances; action suspend( diff --git a/tests/hybrid/importedCDS/eu12.cdsmunich.capprocesspluginhybridtest.importProcess_Complex_Inputs.cds b/tests/hybrid/importedCDS/eu12.cdsmunich.capprocesspluginhybridtest.importProcess_Complex_Inputs.cds index 93483826..022b5d13 100644 --- a/tests/hybrid/importedCDS/eu12.cdsmunich.capprocesspluginhybridtest.importProcess_Complex_Inputs.cds +++ b/tests/hybrid/importedCDS/eu12.cdsmunich.capprocesspluginhybridtest.importProcess_Complex_Inputs.cds @@ -1,4 +1,4 @@ -/* checksum : b0ced28bb4d1bef714f6714bff14642e */ +/* checksum : 679467542ca0d729a5f5f51f59061cbd */ namespace eu12.cdsmunich.capprocesspluginhybridtest; /** DO NOT EDIT. THIS IS A GENERATED SERVICE THAT WILL BE OVERRIDDEN ON NEXT IMPORT. */ @@ -49,12 +49,15 @@ service ImportProcess_Complex_InputsService { type ProcessAttributes : many ProcessAttribute; type ProcessInstance { + id : String; definitionId : String; definitionVersion : String; - id : String; status : String; - startedAt : String; + startedAt : Timestamp; + completedAt : Timestamp; startedBy : String; + subject : String; + businessKey : String; }; type ProcessInstances : many ProcessInstance; @@ -71,9 +74,27 @@ service ImportProcess_Complex_InputsService { processInstanceId : String not null ) returns ProcessOutputs; - function getInstancesByBusinessKey( - businessKey : String not null, - status : many String + function getInstances( + id : String, + businessKey : String, + status : many String, + definitionId : String, + definitionVersion : String, + startedAt : Timestamp, + startedFrom : Timestamp, + startedUpTo : Timestamp, + completedAt : Timestamp, + completedFrom : Timestamp, + completedUpTo : Timestamp, + startedBy : String, + subject : String, + containsText : String, + rootInstanceId : String, + parentInstanceId : String, + top : Integer, + skip : Integer, + orderBy : String, + inlinecount : String ) returns ProcessInstances; action suspend( diff --git a/tests/hybrid/importedCDS/eu12.cdsmunich.capprocesspluginhybridtest.importProcess_Simple_Inputs.cds b/tests/hybrid/importedCDS/eu12.cdsmunich.capprocesspluginhybridtest.importProcess_Simple_Inputs.cds index d26fccaf..af469a44 100644 --- a/tests/hybrid/importedCDS/eu12.cdsmunich.capprocesspluginhybridtest.importProcess_Simple_Inputs.cds +++ b/tests/hybrid/importedCDS/eu12.cdsmunich.capprocesspluginhybridtest.importProcess_Simple_Inputs.cds @@ -1,4 +1,4 @@ -/* checksum : 7e054e53e107a7f5c8375eb51a454e7f */ +/* checksum : 2a382c5f1ed621b369d90ef1ccdcc4d6 */ namespace eu12.cdsmunich.capprocesspluginhybridtest; /** DO NOT EDIT. THIS IS A GENERATED SERVICE THAT WILL BE OVERRIDDEN ON NEXT IMPORT. */ @@ -26,12 +26,15 @@ service ImportProcess_Simple_InputsService { type ProcessAttributes : many ProcessAttribute; type ProcessInstance { + id : String; definitionId : String; definitionVersion : String; - id : String; status : String; - startedAt : String; + startedAt : Timestamp; + completedAt : Timestamp; startedBy : String; + subject : String; + businessKey : String; }; type ProcessInstances : many ProcessInstance; @@ -48,9 +51,27 @@ service ImportProcess_Simple_InputsService { processInstanceId : String not null ) returns ProcessOutputs; - function getInstancesByBusinessKey( - businessKey : String not null, - status : many String + function getInstances( + id : String, + businessKey : String, + status : many String, + definitionId : String, + definitionVersion : String, + startedAt : Timestamp, + startedFrom : Timestamp, + startedUpTo : Timestamp, + completedAt : Timestamp, + completedFrom : Timestamp, + completedUpTo : Timestamp, + startedBy : String, + subject : String, + containsText : String, + rootInstanceId : String, + parentInstanceId : String, + top : Integer, + skip : Integer, + orderBy : String, + inlinecount : String ) returns ProcessInstances; action suspend( diff --git a/tests/sample/status-management/srv/external/eu12.cdsmunich.sampleapplicationproject.authorVerificationProcess.cds b/tests/sample/status-management/srv/external/eu12.cdsmunich.sampleapplicationproject.authorVerificationProcess.cds index dec633f0..3cb2d2cd 100644 --- a/tests/sample/status-management/srv/external/eu12.cdsmunich.sampleapplicationproject.authorVerificationProcess.cds +++ b/tests/sample/status-management/srv/external/eu12.cdsmunich.sampleapplicationproject.authorVerificationProcess.cds @@ -49,9 +49,15 @@ service AuthorVerificationProcessService { processInstanceId : String not null ) returns ProcessOutputs; - function getInstancesByBusinessKey( - businessKey : String not null, - status : many String + function getInstances( + businessKey : String, + status : many String, + definitionId : String, + definitionVersion : String, + startedAt : String, + completedAt : String, + startedBy : String, + subject : String ) returns ProcessInstances; action suspend( diff --git a/tests/sample/status-management/srv/external/eu12.cdsmunich.sampleapplicationproject.bookApprovalProcess.cds b/tests/sample/status-management/srv/external/eu12.cdsmunich.sampleapplicationproject.bookApprovalProcess.cds index 1c78ad8a..dbd0e25f 100644 --- a/tests/sample/status-management/srv/external/eu12.cdsmunich.sampleapplicationproject.bookApprovalProcess.cds +++ b/tests/sample/status-management/srv/external/eu12.cdsmunich.sampleapplicationproject.bookApprovalProcess.cds @@ -55,9 +55,15 @@ service BookApprovalProcessService { processInstanceId : String not null ) returns ProcessOutputs; - function getInstancesByBusinessKey( - businessKey : String not null, - status : many String + function getInstances( + businessKey : String, + status : many String, + definitionId : String, + definitionVersion : String, + startedAt : String, + completedAt : String, + startedBy : String, + subject : String ) returns ProcessInstances; action suspend( From de3e4a169aa5633b3b5c74b338be9863fffcb460 Mon Sep 17 00:00:00 2001 From: Yannis Moser Date: Mon, 11 May 2026 14:05:21 +0200 Subject: [PATCH 02/13] Changed the services acording to the new getInstace inputs --- lib/api/index.ts | 1 + lib/api/workflow-client.ts | 28 ++++++++++++++++++++++++++++ srv/BTPProcessService.cds | 24 +++++++++++++++++++++--- srv/BTPProcessService.ts | 26 ++++++-------------------- srv/localProcessService.ts | 28 +++++++--------------------- 5 files changed, 63 insertions(+), 44 deletions(-) diff --git a/lib/api/index.ts b/lib/api/index.ts index c44043eb..4c9c2cef 100644 --- a/lib/api/index.ts +++ b/lib/api/index.ts @@ -18,6 +18,7 @@ export { IWorkflowInstanceClient, WorkflowInstance, WorkflowStatus, + GetInstancesParams, StartWorkflowResult, UpdateStatusResult, createWorkflowInstanceClient, diff --git a/lib/api/workflow-client.ts b/lib/api/workflow-client.ts index 50d442ad..c5b4638e 100644 --- a/lib/api/workflow-client.ts +++ b/lib/api/workflow-client.ts @@ -19,6 +19,34 @@ export interface WorkflowInstance { businessKey?: string; status: WorkflowStatus; definitionId?: string; + definitionVersion?: string; + startedAt?: string; + completedAt?: string; + startedBy?: string; + subject?: string; +} + +export interface GetInstancesParams { + id?: string | null; + businessKey?: string | null; + status?: WorkflowStatus[]; + definitionId?: string | null; + definitionVersion?: string | null; + startedAt?: string | null; + startedFrom?: string | null; + startedUpTo?: string | null; + completedAt?: string | null; + completedFrom?: string | null; + completedUpTo?: string | null; + startedBy?: string | null; + subject?: string | null; + containsText?: string | null; + rootInstanceId?: string | null; + parentInstanceId?: string | null; + orderBy?: string | null; + top?: number | null; + skip?: number | null; + inlinecount?: string | null; } export interface StartWorkflowResult { diff --git a/srv/BTPProcessService.cds b/srv/BTPProcessService.cds index 6e066e46..a1306368 100644 --- a/srv/BTPProcessService.cds +++ b/srv/BTPProcessService.cds @@ -35,8 +35,26 @@ service ProcessService { @mandatory processInstanceId : String(256) )returns AnyType; - function getInstancesByBusinessKey( - @mandatory businessKey : String(256), - status : many String(256) + function getInstances( + id : String(256), + businessKey : String(256), + status : many String(256), + definitionId : String(256), + definitionVersion : String(256), + startedAt : Timestamp, + startedFrom : Timestamp, + startedUpTo : Timestamp, + completedAt : Timestamp, + completedFrom : Timestamp, + completedUpTo : Timestamp, + startedBy : String(256), + subject : String(256), + containsText : String(256), + rootInstanceId : String(256), + parentInstanceId : String(256), + top : Integer, + skip : Integer, + orderBy : String(256), + inlinecount : String(256) )returns InstancesReturn; } diff --git a/srv/BTPProcessService.ts b/srv/BTPProcessService.ts index e3f05af1..86e2dcf2 100644 --- a/srv/BTPProcessService.ts +++ b/srv/BTPProcessService.ts @@ -1,6 +1,6 @@ import cds from '@sap/cds'; import { getServiceCredentials, CachingTokenProvider, createXsuaaTokenProvider } from '../lib/auth'; -import { IWorkflowInstanceClient, createWorkflowInstanceClient, WorkflowStatus } from '../lib/api'; +import { IWorkflowInstanceClient, createWorkflowInstanceClient, WorkflowStatus, GetInstancesParams } from '../lib/api'; import { PROCESS_LOGGER_PREFIX, PROCESS_SERVICE } from '../lib'; const LOG = cds.log(PROCESS_LOGGER_PREFIX); @@ -87,27 +87,13 @@ class ProcessService extends cds.ApplicationService { ); }); - this.on('getInstancesByBusinessKey', async (request: cds.Request) => { - const { businessKey } = request.data; - let { status } = request.data; - LOG.info('Getting instances for', businessKey); - - if (!businessKey) { - return request.reject({ status: 400, message: 'Missing required parameter: businessKey' }); - } - - if (!status) { - status = [ - WorkflowStatus.RUNNING, - WorkflowStatus.SUSPENDED, - WorkflowStatus.COMPLETED, - WorkflowStatus.ERRONEOUS, - ]; - } + this.on('getInstances', async (request: cds.Request) => { + const params = request.data as GetInstancesParams; + LOG.info('Getting instances'); const instances = await this.workflowInstanceClient.getWorkflowsByBusinessKey( - businessKey, - status, + params.businessKey ?? '', + params.status ?? [], ); return instances; }); diff --git a/srv/localProcessService.ts b/srv/localProcessService.ts index 7148dabe..cd66965d 100644 --- a/srv/localProcessService.ts +++ b/srv/localProcessService.ts @@ -1,6 +1,6 @@ import cds from '@sap/cds'; import { localWorkflowStore } from '../lib/api/local-workflow-store'; -import { WorkflowStatus } from '../lib/api/workflow-client'; +import { WorkflowStatus, GetInstancesParams } from '../lib/api/workflow-client'; import { PROCESS_LOGGER_PREFIX } from '../lib'; const LOG = cds.log(PROCESS_LOGGER_PREFIX); @@ -127,33 +127,19 @@ class ProcessService extends cds.ApplicationService { return; }); - this.on('getInstancesByBusinessKey', async (req: cds.Request) => { - const { businessKey } = req.data; - let { status } = req.data; - LOG.info('Getting instances for', businessKey); + this.on('getInstances', async (req: cds.Request) => { + const params = req.data as GetInstancesParams; + LOG.info('Getting instances'); LOG.debug( `==============================================================\n` + - `Get instances by businessKey: ${businessKey}\n` + + `Get instances\n` + `==============================================================`, ); - if (!businessKey) { - return req.reject({ status: 400, message: 'Missing required parameter: businessKey' }); - } - - if (!status) { - status = [ - WorkflowStatus.RUNNING, - WorkflowStatus.SUSPENDED, - WorkflowStatus.COMPLETED, - WorkflowStatus.ERRONEOUS, - ]; - } - - const instances = localWorkflowStore.getInstancesByBusinessKey(businessKey, status); + const instances = localWorkflowStore.getInstancesByBusinessKey(params.businessKey ?? '', params.status); - LOG.debug(`Found ${instances.length} workflow instance(s) for businessKey: ${businessKey}`); + LOG.debug(`Found ${instances.length} workflow instance(s)`); return instances; }); From b85847424bf72878cf12339c00e214f7167a01bb Mon Sep 17 00:00:00 2001 From: Yannis Moser Date: Mon, 11 May 2026 14:08:09 +0200 Subject: [PATCH 03/13] changed processService acording to new getInstace function --- lib/handlers/processService.ts | 27 +++++++++++---------------- 1 file changed, 11 insertions(+), 16 deletions(-) diff --git a/lib/handlers/processService.ts b/lib/handlers/processService.ts index 597659e4..eb508357 100644 --- a/lib/handlers/processService.ts +++ b/lib/handlers/processService.ts @@ -1,7 +1,7 @@ import cds from '@sap/cds'; import { PROCESS_LOGGER_PREFIX, PROCESS_PREFIX, PROCESS_SERVICE } from '../constants'; import { emitProcessEvent, ProcessLifecyclePayload, ProcessStartPayload } from './utils'; -import { WorkflowStatus } from '../api'; +import { WorkflowStatus, GetInstancesParams } from '../api'; const LOG = cds.log(PROCESS_LOGGER_PREFIX); @@ -22,7 +22,7 @@ export function registerProcessServiceHandlers(service: cds.Service): void { registerSuspendHandler(service, definitionId); registerResumeHandler(service, definitionId); registerCancelHandler(service, definitionId); - registerGetInstancesByBusinessKeyHandler(service, definitionId); + registerGetInstancesHandler(service, definitionId); registerGetAttributesHandler(service, definitionId); registerGetOutputsHandler(service, definitionId); } @@ -107,21 +107,19 @@ function registerCancelHandler(service: cds.Service, definitionId: string): void }); } -function registerGetInstancesByBusinessKeyHandler( +function registerGetInstancesHandler( service: cds.Service, definitionId: string, ): void { - service.on('getInstancesByBusinessKey', async (req) => { - LOG.debug(`Getting instances by businessKey for process: ${definitionId}`); + service.on('getInstances', async (req) => { + LOG.debug(`Getting instances for process: ${definitionId}`); - const { businessKey, status } = req.data; - if (!businessKey) { - return req.reject({ status: 400, message: 'Missing required parameter: businessKey' }); - } - if (status) { + const params = req.data as GetInstancesParams; + + if (params.status) { const validStatuses = Object.values(WorkflowStatus); - const statuses = Array.isArray(status) ? status : [status]; - const invalidStatuses = statuses.filter((s) => !validStatuses.includes(s)); + const statuses = Array.isArray(params.status) ? params.status : [params.status]; + const invalidStatuses = statuses.filter((s) => !validStatuses.includes(s as WorkflowStatus)); if (invalidStatuses.length > 0) { return req.reject({ status: 400, @@ -131,10 +129,7 @@ function registerGetInstancesByBusinessKeyHandler( } const processService = await cds.connect.to(PROCESS_SERVICE); - const result = await processService.send('getInstancesByBusinessKey', { - businessKey, - status, - }); + const result = await processService.send('getInstances', params); return result; }); From d9e1b89888fb2c73684e86e0d2118a35c6c64b63 Mon Sep 17 00:00:00 2001 From: Yannis Moser Date: Mon, 11 May 2026 14:33:15 +0200 Subject: [PATCH 04/13] changed the api requests --- lib/api/index.ts | 5 +++ lib/api/local-workflow-store.ts | 58 +++++++++++++++++++++++++++++++++ lib/api/workflow-client.ts | 55 +++++++++++++++++++++++++++++++ srv/BTPProcessService.ts | 5 +-- srv/localProcessService.ts | 2 +- 5 files changed, 120 insertions(+), 5 deletions(-) diff --git a/lib/api/index.ts b/lib/api/index.ts index 4c9c2cef..908d7a80 100644 --- a/lib/api/index.ts +++ b/lib/api/index.ts @@ -21,11 +21,16 @@ export { GetInstancesParams, StartWorkflowResult, UpdateStatusResult, + INSTANCES_PARAMS_SKIP_KEYS, + INSTANCES_PARAM_KEY_MAP, createWorkflowInstanceClient, startWorkflow, getWorkflowsByBusinessKey, + getInstances, updateWorkflowStatus, updateMultipleWorkflowStatus, + getAttributes, + getOutputs, } from './workflow-client'; // Local Workflow Store - for local development diff --git a/lib/api/local-workflow-store.ts b/lib/api/local-workflow-store.ts index 22e95ff8..f8143124 100644 --- a/lib/api/local-workflow-store.ts +++ b/lib/api/local-workflow-store.ts @@ -1,6 +1,8 @@ import { WorkflowStatus, WorkflowInstance, + GetInstancesParams, + INSTANCES_PARAMS_SKIP_KEYS, StartWorkflowResult, UpdateStatusResult, } from './workflow-client'; @@ -75,6 +77,62 @@ export class LocalWorkflowStore { return filtered; } + getInstances(params: GetInstancesParams): LocalWorkflowInstance[] { + const specialKeys = new Set([ + ...INSTANCES_PARAMS_SKIP_KEYS, + 'startedFrom', 'startedUpTo', 'completedFrom', 'completedUpTo', + 'containsText', 'rootInstanceId', 'parentInstanceId', + 'skip', 'top', 'orderBy', 'inlinecount', + ]); + + let filtered = [...this.instances]; + + for (const [key, value] of Object.entries(params)) { + if (value == null || specialKeys.has(key)) continue; + filtered = filtered.filter((i) => i[key as keyof LocalWorkflowInstance] === value); + } + + if (params.status && params.status.length > 0) { + filtered = filtered.filter((i) => params.status!.includes(i.status)); + } + + if (params.startedFrom != null) { + const from = new Date(params.startedFrom); + filtered = filtered.filter((i) => i.startedAt != null && new Date(i.startedAt) >= from); + } + if (params.startedUpTo != null) { + const upTo = new Date(params.startedUpTo); + filtered = filtered.filter((i) => i.startedAt != null && new Date(i.startedAt) <= upTo); + } + if (params.completedFrom != null) { + const from = new Date(params.completedFrom); + filtered = filtered.filter((i) => i.completedAt != null && new Date(i.completedAt) >= from); + } + if (params.completedUpTo != null) { + const upTo = new Date(params.completedUpTo); + filtered = filtered.filter((i) => i.completedAt != null && new Date(i.completedAt) <= upTo); + } + + if (params.containsText != null) { + const text = params.containsText.toLowerCase(); + filtered = filtered.filter( + (i) => + i.id.toLowerCase().includes(text) || + i.subject?.toLowerCase().includes(text) || + i.businessKey?.toLowerCase().includes(text), + ); + } + + if (params.rootInstanceId != null) filtered = filtered.filter((i) => i.id === params.rootInstanceId); + if (params.parentInstanceId != null) filtered = filtered.filter((i) => i.id === params.parentInstanceId); + + const skip = params.skip ?? 0; + const top = params.top ?? filtered.length; + filtered = filtered.slice(skip, skip + top); + + return filtered; + } + getInstance(instanceId: string): LocalWorkflowInstance | undefined { return this.instances.find((i) => i.id === instanceId); } diff --git a/lib/api/workflow-client.ts b/lib/api/workflow-client.ts index c5b4638e..9a835a79 100644 --- a/lib/api/workflow-client.ts +++ b/lib/api/workflow-client.ts @@ -4,6 +4,17 @@ import { PROCESS_LOGGER_PREFIX } from '../constants'; const LOG = cds.log(PROCESS_LOGGER_PREFIX); const BASE_PATH = '/public/workflow/rest'; +// Keys in GetInstancesParams that need special handling and are not direct API query params +export const INSTANCES_PARAMS_SKIP_KEYS = new Set(['status']); + +// Remap camelCase param keys to the API's expected query param names +export const INSTANCES_PARAM_KEY_MAP: Partial> = { + orderBy: '$orderby', + top: '$top', + skip: '$skip', + inlinecount: '$inlinecount', +}; + // ============ Types & Enums ============ export enum WorkflowStatus { @@ -69,6 +80,8 @@ export interface IWorkflowInstanceClient { status: WorkflowStatus[], ): Promise; + getInstances(params: GetInstancesParams): Promise; + updateWorkflowStatus( instanceId: string, status: WorkflowStatus, @@ -148,6 +161,43 @@ export async function getWorkflowsByBusinessKey( return await res.json(); } +export async function getInstances( + serviceUrl: string, + jwt: string, + params: GetInstancesParams, +): Promise { + const queryParts: string[] = []; + + for (const [key, value] of Object.entries(params)) { + if (value == null || INSTANCES_PARAMS_SKIP_KEYS.has(key as keyof GetInstancesParams)) continue; + const apiKey = INSTANCES_PARAM_KEY_MAP[key as keyof GetInstancesParams] ?? key; + queryParts.push(`${apiKey}=${encodeURIComponent(String(value))}`); + } + + for (const s of params.status ?? []) { + queryParts.push(`status=${s}`); + } + + const queryUrl = `${serviceUrl}${BASE_PATH}/v1/workflow-instances?${queryParts.join('&')}`; + LOG.debug('Invoking url: ' + queryUrl); + + const res = await fetch(queryUrl, { + method: 'GET', + headers: { + Authorization: `Bearer ${jwt}`, + 'Content-Type': 'application/json', + }, + }); + + if (!res.ok) { + const body = await res.text(); + const errorMessage = `Failed to retrieve workflow instances: ${body || res.statusText || 'Unknown error'}`; + throw cds.error(res.status, errorMessage); + } + + return await res.json(); +} + export async function updateWorkflowStatus( serviceUrl: string, jwt: string, @@ -284,6 +334,11 @@ export function createWorkflowInstanceClient( return getWorkflowsByBusinessKey(serviceUrl, jwt, businessKey, status); }, + getInstances: async (params) => { + const jwt = await getToken(); + return getInstances(serviceUrl, jwt, params); + }, + updateWorkflowStatus: async (instanceId, status, cascade) => { const jwt = await getToken(); return updateWorkflowStatus(serviceUrl, jwt, instanceId, status, cascade); diff --git a/srv/BTPProcessService.ts b/srv/BTPProcessService.ts index 86e2dcf2..f1ff8d5b 100644 --- a/srv/BTPProcessService.ts +++ b/srv/BTPProcessService.ts @@ -91,10 +91,7 @@ class ProcessService extends cds.ApplicationService { const params = request.data as GetInstancesParams; LOG.info('Getting instances'); - const instances = await this.workflowInstanceClient.getWorkflowsByBusinessKey( - params.businessKey ?? '', - params.status ?? [], - ); + const instances = await this.workflowInstanceClient.getInstances(params); return instances; }); diff --git a/srv/localProcessService.ts b/srv/localProcessService.ts index cd66965d..1f5f93c5 100644 --- a/srv/localProcessService.ts +++ b/srv/localProcessService.ts @@ -137,7 +137,7 @@ class ProcessService extends cds.ApplicationService { `==============================================================`, ); - const instances = localWorkflowStore.getInstancesByBusinessKey(params.businessKey ?? '', params.status); + const instances = localWorkflowStore.getInstances(params); LOG.debug(`Found ${instances.length} workflow instance(s)`); return instances; From 25efdcfaad4df9cac7bfacff0b60ca0ab352b414 Mon Sep 17 00:00:00 2001 From: Yannis Moser Date: Mon, 11 May 2026 15:22:59 +0200 Subject: [PATCH 05/13] fixed existing tests --- tests/bookshop/srv/annotation-hybrid-service.cds | 4 ++-- tests/bookshop/srv/annotation-hybrid-service.ts | 4 ++-- tests/bookshop/srv/programmatic-service.cds | 8 ++++---- tests/bookshop/srv/programmatic-service.ts | 12 ++++++------ tests/hybrid/annotationApproach.test.ts | 2 +- tests/hybrid/programmaticApproach.test.ts | 2 +- .../sample/status-management/srv/authors-service.js | 6 +++--- tests/sample/status-management/srv/books-service.js | 4 ++-- 8 files changed, 21 insertions(+), 21 deletions(-) diff --git a/tests/bookshop/srv/annotation-hybrid-service.cds b/tests/bookshop/srv/annotation-hybrid-service.cds index 861fbbc9..4b16b2e1 100644 --- a/tests/bookshop/srv/annotation-hybrid-service.cds +++ b/tests/bookshop/srv/annotation-hybrid-service.cds @@ -70,7 +70,7 @@ service AnnotationHybridService { year : Integer; } - action getInstancesByBusinessKey(ID: String, - status: many String) returns many ProcessInstance; + action getInstances(ID: String, + status: many String) returns many ProcessInstance; } diff --git a/tests/bookshop/srv/annotation-hybrid-service.ts b/tests/bookshop/srv/annotation-hybrid-service.ts index bb92c04a..f67d6b11 100644 --- a/tests/bookshop/srv/annotation-hybrid-service.ts +++ b/tests/bookshop/srv/annotation-hybrid-service.ts @@ -5,9 +5,9 @@ class AnnotationHybridService extends cds.ApplicationService { async init() { const annotationLifecycleProcess = await cds.connect.to(Annotation_Lifecycle_ProcessService); - this.on('getInstancesByBusinessKey', async (req: cds.Request) => { + this.on('getInstances', async (req: cds.Request) => { const { ID, status } = req.data; - const instances = await annotationLifecycleProcess.getInstancesByBusinessKey({ + const instances = await annotationLifecycleProcess.getInstances({ businessKey: ID, status: status, }); diff --git a/tests/bookshop/srv/programmatic-service.cds b/tests/bookshop/srv/programmatic-service.cds index 90685fe4..30b8d579 100644 --- a/tests/bookshop/srv/programmatic-service.cds +++ b/tests/bookshop/srv/programmatic-service.cds @@ -29,8 +29,8 @@ service ProgrammaticService { action getOutputs(instanceId: String) returns ProcessOutputs; - action getInstancesByBusinessKey(ID: UUID, - status: many String) returns many ProcessInstance; + action getInstances(ID: UUID, + status: many String) returns many ProcessInstance; action startForGetOutputs(ID: UUID, mandatory_datetime: Timestamp, @@ -46,8 +46,8 @@ service ProgrammaticService { action genericCancel(businessKey: String, cascade: Boolean); action genericSuspend(businessKey: String, cascade: Boolean); action genericResume(businessKey: String, cascade: Boolean); - action genericGetInstancesByBusinessKey(businessKey: String, - status: many String) returns many ProcessInstance; + action genericGetInstances(businessKey: String, + status: many String) returns many ProcessInstance; action genericGetAttributes(processInstanceId: String) returns many ProcessAttribute; action genericGetOutputs(processInstanceId: String) returns ProcessOutputs; } diff --git a/tests/bookshop/srv/programmatic-service.ts b/tests/bookshop/srv/programmatic-service.ts index d038f650..37e1279d 100644 --- a/tests/bookshop/srv/programmatic-service.ts +++ b/tests/bookshop/srv/programmatic-service.ts @@ -33,9 +33,9 @@ class ProgrammaticService extends cds.ApplicationService { await programmaticLifecycleProcess.cancel({ businessKey: ID }); }); - this.on('getInstancesByBusinessKey', async (req: cds.Request) => { + this.on('getInstances', async (req: cds.Request) => { const { ID, status } = req.data; - const instances = await programmaticLifecycleProcess.getInstancesByBusinessKey({ + const instances = await programmaticLifecycleProcess.getInstances({ businessKey: ID, status: status, }); @@ -44,7 +44,7 @@ class ProgrammaticService extends cds.ApplicationService { this.on('getAttributes', async (req: cds.Request) => { const { ID } = req.data; - const processInstances = await programmaticLifecycleProcess.getInstancesByBusinessKey({ + const processInstances = await programmaticLifecycleProcess.getInstances({ businessKey: ID, }); const allAttributes = []; @@ -74,7 +74,7 @@ class ProgrammaticService extends cds.ApplicationService { this.on('getInstanceIDForGetOutputs', async (req: cds.Request) => { const { ID, status } = req.data; - const processInstances = await programmaticOutputProcess.getInstancesByBusinessKey({ + const processInstances = await programmaticOutputProcess.getInstances({ businessKey: ID, status: status, }); @@ -127,9 +127,9 @@ class ProgrammaticService extends cds.ApplicationService { await queuedProcessService.emit('resume', { businessKey, cascade: cascade ?? false }); }); - this.on('genericGetInstancesByBusinessKey', async (req: cds.Request) => { + this.on('genericGetInstances', async (req: cds.Request) => { const { businessKey, status } = req.data; - const result = await processService.send('getInstancesByBusinessKey', { + const result = await processService.send('getInstances', { businessKey, status, }); diff --git a/tests/hybrid/annotationApproach.test.ts b/tests/hybrid/annotationApproach.test.ts index 876f3981..6002030e 100644 --- a/tests/hybrid/annotationApproach.test.ts +++ b/tests/hybrid/annotationApproach.test.ts @@ -12,7 +12,7 @@ describe('Annotation Approach Hybrid Tests', () => { } async function getInstances(ID: string, status?: string[]): Promise { - const res = await POST('/odata/v4/annotation-hybrid/getInstancesByBusinessKey', { ID, status }); + const res = await POST('/odata/v4/annotation-hybrid/getInstances', { ID, status }); return res.data?.value ?? res.data ?? []; } diff --git a/tests/hybrid/programmaticApproach.test.ts b/tests/hybrid/programmaticApproach.test.ts index 0413de97..dc641f5c 100644 --- a/tests/hybrid/programmaticApproach.test.ts +++ b/tests/hybrid/programmaticApproach.test.ts @@ -20,7 +20,7 @@ describe('Programmatic Approach Hybrid Tests', () => { } async function getInstances(ID: string, status?: string[]): Promise { - const res = await POST('/odata/v4/programmatic/getInstancesByBusinessKey', { ID, status }); + const res = await POST('/odata/v4/programmatic/getInstances', { ID, status }); return res.data?.value ?? res.data ?? []; } diff --git a/tests/sample/status-management/srv/authors-service.js b/tests/sample/status-management/srv/authors-service.js index d765b946..0c93cab1 100644 --- a/tests/sample/status-management/srv/authors-service.js +++ b/tests/sample/status-management/srv/authors-service.js @@ -31,7 +31,7 @@ module.exports = class AuthorsService extends cds.ApplicationService { this.after('DELETE', Authors, async (author, req) => { if (!author.ID) return; - const instances = await authorProcess.getInstancesByBusinessKey(author.ID, ['RUNNING']); + const instances = await authorProcess.getInstances({ businessKey: author.ID, status: ['RUNNING'] }); if (instances.length > 0) { await authorProcess.cancel({ businessKey: author.ID, cascade: true }); } @@ -48,11 +48,11 @@ module.exports = class AuthorsService extends cds.ApplicationService { return; } - const instances = await authorProcess.getInstancesByBusinessKey(author.ID, [ + const instances = await authorProcess.getInstances({ businessKey: author.ID, status: [ 'RUNNING', 'COMPLETED', 'CANCELED', - ]); + ] }); if (instances[0]?.id && instances[0]?.status) { const { id, status } = instances[0]; diff --git a/tests/sample/status-management/srv/books-service.js b/tests/sample/status-management/srv/books-service.js index 4f6dd9f2..abcc49e7 100644 --- a/tests/sample/status-management/srv/books-service.js +++ b/tests/sample/status-management/srv/books-service.js @@ -25,11 +25,11 @@ module.exports = class BooksService extends cds.ApplicationService { return; } - const instances = await bookProcess.getInstancesByBusinessKey(bookID, [ + const instances = await bookProcess.getInstances({ businessKey: bookID, status: [ 'RUNNING', 'COMPLETED', 'CANCELED', - ]); + ] }); if (instances[0]?.id && instances[0]?.status) { const { id, status } = instances[0]; From 9259934ab9bbf75ee16542888d694aba20b46a82 Mon Sep 17 00:00:00 2001 From: Yannis Moser Date: Mon, 11 May 2026 15:43:37 +0200 Subject: [PATCH 06/13] added new tests --- lib/api/local-workflow-store.ts | 20 ++- lib/handlers/processService.ts | 5 +- srv/BTPProcessService.ts | 7 +- tests/bookshop/srv/programmatic-service.cds | 4 +- tests/bookshop/srv/programmatic-service.ts | 4 +- tests/hybrid/programmaticApproach.test.ts | 38 ++++++ tests/integration/getInstances.test.ts | 127 ++++++++++++++++++ .../status-management/srv/authors-service.js | 14 +- .../status-management/srv/books-service.js | 9 +- tests/unit/workflowClient.test.ts | 115 ++++++++++++++++ 10 files changed, 320 insertions(+), 23 deletions(-) create mode 100644 tests/integration/getInstances.test.ts create mode 100644 tests/unit/workflowClient.test.ts diff --git a/lib/api/local-workflow-store.ts b/lib/api/local-workflow-store.ts index f8143124..65ec8f02 100644 --- a/lib/api/local-workflow-store.ts +++ b/lib/api/local-workflow-store.ts @@ -80,9 +80,17 @@ export class LocalWorkflowStore { getInstances(params: GetInstancesParams): LocalWorkflowInstance[] { const specialKeys = new Set([ ...INSTANCES_PARAMS_SKIP_KEYS, - 'startedFrom', 'startedUpTo', 'completedFrom', 'completedUpTo', - 'containsText', 'rootInstanceId', 'parentInstanceId', - 'skip', 'top', 'orderBy', 'inlinecount', + 'startedFrom', + 'startedUpTo', + 'completedFrom', + 'completedUpTo', + 'containsText', + 'rootInstanceId', + 'parentInstanceId', + 'skip', + 'top', + 'orderBy', + 'inlinecount', ]); let filtered = [...this.instances]; @@ -123,8 +131,10 @@ export class LocalWorkflowStore { ); } - if (params.rootInstanceId != null) filtered = filtered.filter((i) => i.id === params.rootInstanceId); - if (params.parentInstanceId != null) filtered = filtered.filter((i) => i.id === params.parentInstanceId); + if (params.rootInstanceId != null) + filtered = filtered.filter((i) => i.id === params.rootInstanceId); + if (params.parentInstanceId != null) + filtered = filtered.filter((i) => i.id === params.parentInstanceId); const skip = params.skip ?? 0; const top = params.top ?? filtered.length; diff --git a/lib/handlers/processService.ts b/lib/handlers/processService.ts index eb508357..3b381321 100644 --- a/lib/handlers/processService.ts +++ b/lib/handlers/processService.ts @@ -107,10 +107,7 @@ function registerCancelHandler(service: cds.Service, definitionId: string): void }); } -function registerGetInstancesHandler( - service: cds.Service, - definitionId: string, -): void { +function registerGetInstancesHandler(service: cds.Service, definitionId: string): void { service.on('getInstances', async (req) => { LOG.debug(`Getting instances for process: ${definitionId}`); diff --git a/srv/BTPProcessService.ts b/srv/BTPProcessService.ts index f1ff8d5b..a6a51047 100644 --- a/srv/BTPProcessService.ts +++ b/srv/BTPProcessService.ts @@ -1,6 +1,11 @@ import cds from '@sap/cds'; import { getServiceCredentials, CachingTokenProvider, createXsuaaTokenProvider } from '../lib/auth'; -import { IWorkflowInstanceClient, createWorkflowInstanceClient, WorkflowStatus, GetInstancesParams } from '../lib/api'; +import { + IWorkflowInstanceClient, + createWorkflowInstanceClient, + WorkflowStatus, + GetInstancesParams, +} from '../lib/api'; import { PROCESS_LOGGER_PREFIX, PROCESS_SERVICE } from '../lib'; const LOG = cds.log(PROCESS_LOGGER_PREFIX); diff --git a/tests/bookshop/srv/programmatic-service.cds b/tests/bookshop/srv/programmatic-service.cds index 30b8d579..2f79351c 100644 --- a/tests/bookshop/srv/programmatic-service.cds +++ b/tests/bookshop/srv/programmatic-service.cds @@ -30,7 +30,9 @@ service ProgrammaticService { action getOutputs(instanceId: String) returns ProcessOutputs; action getInstances(ID: UUID, - status: many String) returns many ProcessInstance; + status: many String, + top: Integer, + skip: Integer) returns many ProcessInstance; action startForGetOutputs(ID: UUID, mandatory_datetime: Timestamp, diff --git a/tests/bookshop/srv/programmatic-service.ts b/tests/bookshop/srv/programmatic-service.ts index 37e1279d..bf727f65 100644 --- a/tests/bookshop/srv/programmatic-service.ts +++ b/tests/bookshop/srv/programmatic-service.ts @@ -34,10 +34,12 @@ class ProgrammaticService extends cds.ApplicationService { }); this.on('getInstances', async (req: cds.Request) => { - const { ID, status } = req.data; + const { ID, status, top, skip } = req.data; const instances = await programmaticLifecycleProcess.getInstances({ businessKey: ID, status: status, + top: top, + skip: skip, }); return instances; }); diff --git a/tests/hybrid/programmaticApproach.test.ts b/tests/hybrid/programmaticApproach.test.ts index dc641f5c..1ad2f205 100644 --- a/tests/hybrid/programmaticApproach.test.ts +++ b/tests/hybrid/programmaticApproach.test.ts @@ -364,4 +364,42 @@ describe('Programmatic Approach Hybrid Tests', () => { expect(outputs).toHaveProperty('optional_datetime'); }); }); + + describe('getInstances – query params against SBPA', () => { + it('should filter instances by definitionId', async () => { + const ID = generateID(); + await startProcess(ID); + await waitForInstances(ID, ['RUNNING']); + + const res = await POST('/odata/v4/programmatic/genericGetInstances', { + businessKey: ID, + definitionId: 'eu12.cdsmunich.capprocesspluginhybridtest.programmatic_Lifecycle_Process', + status: ['RUNNING'], + }); + const instances = res.data?.value ?? res.data ?? []; + + expect(instances.length).toBe(1); + expect(instances[0]).toHaveProperty( + 'definitionId', + 'eu12.cdsmunich.capprocesspluginhybridtest.programmatic_Lifecycle_Process', + ); + }); + + it('should respect top=1 and return only one instance', async () => { + const idA = generateID(); + const idB = generateID(); + await startProcess(idA); + await startProcess(idB); + await waitForInstances(idA, ['RUNNING']); + await waitForInstances(idB, ['RUNNING']); + + const res = await POST('/odata/v4/programmatic/genericGetInstances', { + status: ['RUNNING'], + top: 1, + }); + const instances = res.data?.value ?? res.data ?? []; + + expect(instances.length).toBe(1); + }); + }); }); diff --git a/tests/integration/getInstances.test.ts b/tests/integration/getInstances.test.ts new file mode 100644 index 00000000..c88a47ff --- /dev/null +++ b/tests/integration/getInstances.test.ts @@ -0,0 +1,127 @@ +/* eslint-disable @typescript-eslint/no-explicit-any */ +import cds from '@sap/cds'; +import * as path from 'path'; + +const app = path.join(__dirname, '../bookshop/'); +const { POST } = cds.test(app); + +const DEFINITION_ID = 'eu12.cdsmunich.capprocesspluginhybridtest.programmatic_Lifecycle_Process'; + +describe('getInstances Integration Tests', () => { + afterAll(async () => { + await (cds as any).flush(); + await new Promise((resolve) => setTimeout(resolve, 5000)); + }); + + function generateID(): string { + return cds.utils.uuid(); + } + + // Start a process directly via ProcessService (synchronous, bypasses outbox) + async function startProcess(businessKey: string) { + const processService = await cds.connect.to('ProcessService'); + await processService.send( + 'start', + { definitionId: DEFINITION_ID, context: { ID: businessKey } }, + { businessKey }, + ); + } + + // Query via programmatic getInstances action (businessKey mapped as ID + status + top/skip) + async function getInstances(params: Record): Promise { + const res = await POST('/odata/v4/programmatic/getInstances', params); + return res.data?.value ?? res.data ?? []; + } + + // Query via genericGetInstances which passes all params directly to ProcessService + async function genericGetInstances(params: Record): Promise { + const res = await POST('/odata/v4/programmatic/genericGetInstances', params); + return res.data?.value ?? res.data ?? []; + } + + describe('filter by businessKey', () => { + it('returns instances matching businessKey', async () => { + const ID = generateID(); + await startProcess(ID); + + const instances = await getInstances({ ID }); + expect(instances.length).toBe(1); + expect(instances[0]).toHaveProperty('status', 'RUNNING'); + }); + + it('returns empty array for unknown businessKey', async () => { + const instances = await getInstances({ ID: generateID() }); + expect(instances).toHaveLength(0); + }); + }); + + describe('filter by status', () => { + it('returns instance when status matches', async () => { + const ID = generateID(); + await startProcess(ID); + + const instances = await getInstances({ ID, status: ['RUNNING'] }); + expect(instances.length).toBe(1); + expect(instances[0]).toHaveProperty('status', 'RUNNING'); + }); + + it('returns empty array when status does not match', async () => { + const ID = generateID(); + await startProcess(ID); + + const instances = await getInstances({ ID, status: ['SUSPENDED'] }); + expect(instances).toHaveLength(0); + }); + + it('returns instance when one of multiple statuses matches', async () => { + const ID = generateID(); + await startProcess(ID); + + const instances = await getInstances({ ID, status: ['RUNNING', 'SUSPENDED'] }); + expect(instances.length).toBe(1); + }); + }); + + describe('pagination', () => { + it('respects top parameter', async () => { + const idA = generateID(); + const idB = generateID(); + await startProcess(idA); + await startProcess(idB); + + const all = await getInstances({ status: ['RUNNING'] }); + const paged = await getInstances({ status: ['RUNNING'], top: 1 }); + + expect(all.length).toBeGreaterThanOrEqual(2); + expect(paged.length).toBe(1); + }); + + it('respects skip parameter', async () => { + const idA = generateID(); + const idB = generateID(); + await startProcess(idA); + await startProcess(idB); + + const all = await getInstances({ status: ['RUNNING'] }); + const skipped = await getInstances({ status: ['RUNNING'], skip: 1 }); + + expect(skipped.length).toBe(all.length - 1); + }); + }); + + describe('generic getInstances with direct params', () => { + it('filters by businessKey and status', async () => { + const ID = generateID(); + await startProcess(ID); + + const instances = await genericGetInstances({ businessKey: ID, status: ['RUNNING'] }); + expect(instances.length).toBe(1); + expect(instances[0]).toHaveProperty('status', 'RUNNING'); + }); + + it('returns empty when no match', async () => { + const instances = await genericGetInstances({ businessKey: generateID() }); + expect(instances).toHaveLength(0); + }); + }); +}); diff --git a/tests/sample/status-management/srv/authors-service.js b/tests/sample/status-management/srv/authors-service.js index 0c93cab1..2adea6f8 100644 --- a/tests/sample/status-management/srv/authors-service.js +++ b/tests/sample/status-management/srv/authors-service.js @@ -31,7 +31,10 @@ module.exports = class AuthorsService extends cds.ApplicationService { this.after('DELETE', Authors, async (author, req) => { if (!author.ID) return; - const instances = await authorProcess.getInstances({ businessKey: author.ID, status: ['RUNNING'] }); + const instances = await authorProcess.getInstances({ + businessKey: author.ID, + status: ['RUNNING'], + }); if (instances.length > 0) { await authorProcess.cancel({ businessKey: author.ID, cascade: true }); } @@ -48,11 +51,10 @@ module.exports = class AuthorsService extends cds.ApplicationService { return; } - const instances = await authorProcess.getInstances({ businessKey: author.ID, status: [ - 'RUNNING', - 'COMPLETED', - 'CANCELED', - ] }); + const instances = await authorProcess.getInstances({ + businessKey: author.ID, + status: ['RUNNING', 'COMPLETED', 'CANCELED'], + }); if (instances[0]?.id && instances[0]?.status) { const { id, status } = instances[0]; diff --git a/tests/sample/status-management/srv/books-service.js b/tests/sample/status-management/srv/books-service.js index abcc49e7..93eb9675 100644 --- a/tests/sample/status-management/srv/books-service.js +++ b/tests/sample/status-management/srv/books-service.js @@ -25,11 +25,10 @@ module.exports = class BooksService extends cds.ApplicationService { return; } - const instances = await bookProcess.getInstances({ businessKey: bookID, status: [ - 'RUNNING', - 'COMPLETED', - 'CANCELED', - ] }); + const instances = await bookProcess.getInstances({ + businessKey: bookID, + status: ['RUNNING', 'COMPLETED', 'CANCELED'], + }); if (instances[0]?.id && instances[0]?.status) { const { id, status } = instances[0]; diff --git a/tests/unit/workflowClient.test.ts b/tests/unit/workflowClient.test.ts new file mode 100644 index 00000000..b15c32e1 --- /dev/null +++ b/tests/unit/workflowClient.test.ts @@ -0,0 +1,115 @@ +import { getInstances, WorkflowStatus } from '../../lib/api/workflow-client'; + +const SERVICE_URL = 'https://example.sbpa.com'; +const JWT = 'test-jwt'; +const BASE = `${SERVICE_URL}/public/workflow/rest/v1/workflow-instances`; + +function mockFetch(instances: unknown[] = []) { + global.fetch = jest.fn().mockResolvedValue({ + ok: true, + json: async () => instances, + } as Response); +} + +function capturedUrl(): string { + return (global.fetch as jest.Mock).mock.calls[0][0] as string; +} + +describe('getInstances – URL building', () => { + beforeEach(() => mockFetch()); + + it('builds URL with no params', async () => { + await getInstances(SERVICE_URL, JWT, {}); + expect(capturedUrl()).toBe(`${BASE}?`); + }); + + it('filters by businessKey', async () => { + await getInstances(SERVICE_URL, JWT, { businessKey: 'my-key' }); + expect(capturedUrl()).toContain('businessKey=my-key'); + }); + + it('filters by single status', async () => { + await getInstances(SERVICE_URL, JWT, { status: [WorkflowStatus.RUNNING] }); + expect(capturedUrl()).toContain('status=RUNNING'); + }); + + it('filters by multiple statuses', async () => { + await getInstances(SERVICE_URL, JWT, { + status: [WorkflowStatus.RUNNING, WorkflowStatus.SUSPENDED], + }); + const url = capturedUrl(); + expect(url).toContain('status=RUNNING'); + expect(url).toContain('status=SUSPENDED'); + }); + + it('remaps orderBy to $orderby', async () => { + await getInstances(SERVICE_URL, JWT, { orderBy: 'startedAt desc' }); + expect(capturedUrl()).toContain('$orderby=startedAt%20desc'); + }); + + it('remaps top to $top', async () => { + await getInstances(SERVICE_URL, JWT, { top: 10 }); + expect(capturedUrl()).toContain('$top=10'); + }); + + it('remaps skip to $skip', async () => { + await getInstances(SERVICE_URL, JWT, { skip: 5 }); + expect(capturedUrl()).toContain('$skip=5'); + }); + + it('remaps inlinecount to $inlinecount', async () => { + await getInstances(SERVICE_URL, JWT, { inlinecount: 'allpages' }); + expect(capturedUrl()).toContain('$inlinecount=allpages'); + }); + + it('skips null and undefined params', async () => { + await getInstances(SERVICE_URL, JWT, { businessKey: null, definitionId: null }); + expect(capturedUrl()).toBe(`${BASE}?`); + }); + + it('encodes special characters in param values', async () => { + await getInstances(SERVICE_URL, JWT, { subject: 'hello world & more' }); + expect(capturedUrl()).toContain('subject=hello%20world%20%26%20more'); + }); + + it('combines multiple direct params with status', async () => { + await getInstances(SERVICE_URL, JWT, { + businessKey: 'bk-1', + definitionId: 'def-1', + status: [WorkflowStatus.RUNNING], + top: 20, + }); + const url = capturedUrl(); + expect(url).toContain('businessKey=bk-1'); + expect(url).toContain('definitionId=def-1'); + expect(url).toContain('status=RUNNING'); + expect(url).toContain('$top=20'); + }); + + it('includes startedFrom and startedUpTo as direct params', async () => { + await getInstances(SERVICE_URL, JWT, { + startedFrom: '2024-01-01T00:00:00Z', + startedUpTo: '2024-12-31T23:59:59Z', + }); + const url = capturedUrl(); + expect(url).toContain('startedFrom='); + expect(url).toContain('startedUpTo='); + }); + + it('sends Bearer token in Authorization header', async () => { + await getInstances(SERVICE_URL, JWT, {}); + const headers = (global.fetch as jest.Mock).mock.calls[0][1].headers; + expect(headers.Authorization).toBe(`Bearer ${JWT}`); + }); + + it('throws on non-ok response', async () => { + global.fetch = jest.fn().mockResolvedValue({ + ok: false, + status: 403, + statusText: 'Forbidden', + text: async () => 'Not allowed', + } as unknown as Response); + + await expect(getInstances(SERVICE_URL, JWT, {})).rejects.toBeDefined(); + }); +}); From 951c148a572f8bd3d95d2c331c3a05eb172befdc Mon Sep 17 00:00:00 2001 From: Yannis Moser Date: Mon, 11 May 2026 15:51:12 +0200 Subject: [PATCH 07/13] added changelog and readme --- CHANGELOG.md | 10 ++++ README.md | 72 ++++++++++++++++++------ tests/sample/status-management/README.md | 4 +- 3 files changed, 66 insertions(+), 20 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index de45ef46..13acb949 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,16 @@ - The format is based on [Keep a Changelog](https://keepachangelog.com/). - This project adheres to [Semantic Versioning](https://semver.org/). +## Version 0.3.0 - 2026-05-11 + +### Added + +- `getInstances` function replacing `getInstancesByBusinessKey` — supports filtering by all SBPA workflow instance query parameters: `id`, `businessKey`, `status`, `definitionId`, `definitionVersion`, `startedAt`, `startedFrom`, `startedUpTo`, `completedAt`, `completedFrom`, `completedUpTo`, `startedBy`, `subject`, `containsText`, `rootInstanceId`, `parentInstanceId`, `orderBy`, `top`, `skip`, `inlinecount` + +### Changed + +- `getInstancesByBusinessKey` is replaced by `getInstances` in both the specific process services and the generic `ProcessService` + ## Version 0.2.1 - 2026-04-20 ### Fixed diff --git a/README.md b/README.md index 0ed362a6..062daeb1 100644 --- a/README.md +++ b/README.md @@ -30,8 +30,7 @@ CAP Plugin to interact with SAP Build Process Automation to manage processes. - [What Gets Generated](#what-gets-generated) - [Starting a Process](#starting-a-process) - [Suspending, Resuming, and Cancelling a Process](#suspending-resuming-and-cancelling-a-process) - - [Querying Process Instances](#querying-process-instances) - - [Limitations](#limitations) + - [Querying Process Instances](#querying-process-instances) - [Limitations](#limitations) - [Generic ProcessService](#generic-processservice) - [Service Definition](#service-definition) - [Usage](#usage) @@ -567,7 +566,7 @@ The import generates: - A CDS service definition in `./srv/external/` (annotated with `@bpm.process` and `@protocol: 'none'`) - Typed `ProcessInputs`, `ProcessOutputs`, `ProcessAttribute`, and `ProcessInstance` types based on the process definition - Typed actions: `start`, `suspend`, `resume`, `cancel` -- Typed functions: `getAttributes`, `getOutputs`, `getInstancesByBusinessKey` +- Typed functions: `getAttributes`, `getOutputs`, `getInstances` - A process definition JSON in `./srv/workflows/` After importing, run `cds-typer` to generate TypeScript types for the imported service. @@ -609,11 +608,20 @@ The `cascade` parameter is optional and defaults to `false`. When set to `true`, ```typescript // Get all instances matching a business key, optionally filtered by status -const instances = await processService.getInstancesByBusinessKey({ +const instances = await processService.getInstances({ businessKey: 'order-12345', status: ['RUNNING', 'SUSPENDED'], }); +// Filter by additional parameters +const instances = await processService.getInstances({ + definitionId: 'eu12.myorg.myproject.myProcess', + startedFrom: '2024-01-01T00:00:00Z', + orderBy: 'startedAt desc', + top: 10, + skip: 0, +}); + // Get attributes for a specific process instance const attributes = await processService.getAttributes({ processInstanceId: 'instance-uuid', @@ -625,8 +633,28 @@ const outputs = await processService.getOutputs({ }); ``` -Valid status values are: `RUNNING`, `SUSPENDED`, `CANCELLED`, `ERRONEOUS`, `COMPLETED`. -If no status filter is provided, all statuses except `CANCELLED` are returned. +All parameters are optional. Supported filter parameters: + +| Parameter | Type | Description | +| ------------------ | --------------- | -------------------------------------------------------- | +| `id` | `String` | Filter by workflow instance ID | +| `businessKey` | `String` | Filter by business key | +| `status` | `Array` | Filter by status (`RUNNING`, `SUSPENDED`, `CANCELED`, `ERRONEOUS`, `COMPLETED`) | +| `definitionId` | `String` | Filter by process definition ID | +| `definitionVersion`| `String` | Filter by process definition version | +| `startedFrom` | `Timestamp` | Filter instances started on or after this date | +| `startedUpTo` | `Timestamp` | Filter instances started on or before this date | +| `completedFrom` | `Timestamp` | Filter instances completed on or after this date | +| `completedUpTo` | `Timestamp` | Filter instances completed on or before this date | +| `startedBy` | `String` | Filter by the user who started the instance | +| `subject` | `String` | Filter by subject | +| `containsText` | `String` | Full-text search across instance fields | +| `rootInstanceId` | `String` | Filter by root instance ID | +| `parentInstanceId` | `String` | Filter by parent instance ID | +| `orderBy` | `String` | Sort order (e.g. `startedAt desc`, `businessKey asc`) | +| `top` | `Integer` | Maximum number of results to return | +| `skip` | `Integer` | Number of results to skip (for pagination) | +| `inlinecount` | `String` | Set to `allpages` to include total count in response | #### Limitations @@ -642,15 +670,15 @@ The generic `ProcessService` allows setting the business key to mimic the behavi The generic `ProcessService` defines the following events and functions: -| Operation | Type | Description | -| --------------------------- | -------- | ----------------------------------------------------------------- | -| `start` | event | Start a workflow instance with a `definitionId` and `context` | -| `cancel` | event | Cancel all running/suspended instances matching a `businessKey` | -| `suspend` | event | Suspend all running instances matching a `businessKey` | -| `resume` | event | Resume all suspended instances matching a `businessKey` | -| `getAttributes` | function | Retrieve attributes for a specific process instance | -| `getOutputs` | function | Retrieve outputs for a specific process instance | -| `getInstancesByBusinessKey` | function | Find process instances by business key and optional status filter | +| Operation | Type | Description | +| --------------- | -------- | ----------------------------------------------------------------- | +| `start` | event | Start a workflow instance with a `definitionId` and `context` | +| `cancel` | event | Cancel all running/suspended instances matching a `businessKey` | +| `suspend` | event | Suspend all running instances matching a `businessKey` | +| `resume` | event | Resume all suspended instances matching a `businessKey` | +| `getAttributes` | function | Retrieve attributes for a specific process instance | +| `getOutputs` | function | Retrieve outputs for a specific process instance | +| `getInstances` | function | Query process instances with flexible filter parameters | #### Usage @@ -688,12 +716,20 @@ await processService.emit('resume', { cascade: false, }); -// Query instances by business key -const instances = await processService.send('getInstancesByBusinessKey', { +// Query instances with flexible filters +const instances = await processService.send('getInstances', { businessKey: 'order-12345', status: ['RUNNING', 'SUSPENDED'], }); +// Query with additional params +const instances = await processService.send('getInstances', { + definitionId: 'eu12.myorg.myproject.myProcess', + startedFrom: '2024-01-01T00:00:00Z', + orderBy: 'startedAt desc', + top: 10, +}); + // Get attributes of a specific instance const attributes = await processService.send('getAttributes', { processInstanceId: 'instance-uuid', @@ -705,7 +741,7 @@ const outputs = await processService.send('getOutputs', { }); ``` -> **Note:** The generic ProcessService uses `emit` for lifecycle events (start, cancel, suspend, resume) which are processed asynchronously through the CDS outbox, and `send` for query functions (getAttributes, getOutputs, getInstancesByBusinessKey) which return data synchronously. +> **Note:** The generic ProcessService uses `emit` for lifecycle events (start, cancel, suspend, resume) which are processed asynchronously through the CDS outbox, and `send` for query functions (getAttributes, getOutputs, getInstances) which return data synchronously. > Make sure to check whether the outbox is correctly used. If not, refer to cds.queued to make sure it is used. ## Build-Time Validation diff --git a/tests/sample/status-management/README.md b/tests/sample/status-management/README.md index 57416fe0..f30a321e 100644 --- a/tests/sample/status-management/README.md +++ b/tests/sample/status-management/README.md @@ -182,7 +182,7 @@ this.after('CREATE', Authors, async (author, req) => { this.after('DELETE', Authors, async (author, req) => { if (!author.ID) return; - const instances = await authorProcess.getInstancesByBusinessKey(author.ID, ['RUNNING']); + const instances = await authorProcess.getInstances({ businessKey: author.ID, status: ['RUNNING'] }); if (instances.length > 0) { await authorProcess.cancel({ businessKey: author.ID, cascade: true }); } @@ -193,7 +193,7 @@ this.after('DELETE', Authors, async (author, req) => { Both services use the same pattern to display live process status in the UI. Virtual fields (`processStatus`, `isApproved`, `processCriticality` for Books; `verificationStatus`, `isVerified`, `verificationCriticality` for Authors) are declared in the CDS projections and populated in `after('READ')` handlers: -1. **Look up** the process instance via `getInstancesByBusinessKey(businessKey, statusFilters)` +1. **Look up** the process instance via `getInstances({ businessKey, status: statusFilters })` 2. **Based on status:** - `RUNNING` -- Fetch current step via `getAttributes(instanceId)` - `COMPLETED` -- Fetch final result via `getOutputs(instanceId)` From c0f1ebb983442a2bbfe38f578edfc0f538964039 Mon Sep 17 00:00:00 2001 From: Yannis Moser Date: Mon, 11 May 2026 15:58:51 +0200 Subject: [PATCH 08/13] PR-Review fixes --- lib/api/workflow-client.ts | 5 ++-- ...ationproject.authorVerificationProcess.cds | 23 +++++++++++++++---- ...applicationproject.bookApprovalProcess.cds | 23 +++++++++++++++---- tests/unit/workflowClient.test.ts | 11 +++++++-- 4 files changed, 50 insertions(+), 12 deletions(-) diff --git a/lib/api/workflow-client.ts b/lib/api/workflow-client.ts index 9a835a79..84df3f7a 100644 --- a/lib/api/workflow-client.ts +++ b/lib/api/workflow-client.ts @@ -175,10 +175,11 @@ export async function getInstances( } for (const s of params.status ?? []) { - queryParts.push(`status=${s}`); + queryParts.push(`status=${encodeURIComponent(s)}`); } - const queryUrl = `${serviceUrl}${BASE_PATH}/v1/workflow-instances?${queryParts.join('&')}`; + const queryString = queryParts.join('&'); + const queryUrl = `${serviceUrl}${BASE_PATH}/v1/workflow-instances${queryString ? '?' + queryString : ''}`; LOG.debug('Invoking url: ' + queryUrl); const res = await fetch(queryUrl, { diff --git a/tests/sample/status-management/srv/external/eu12.cdsmunich.sampleapplicationproject.authorVerificationProcess.cds b/tests/sample/status-management/srv/external/eu12.cdsmunich.sampleapplicationproject.authorVerificationProcess.cds index 3cb2d2cd..7435f4d0 100644 --- a/tests/sample/status-management/srv/external/eu12.cdsmunich.sampleapplicationproject.authorVerificationProcess.cds +++ b/tests/sample/status-management/srv/external/eu12.cdsmunich.sampleapplicationproject.authorVerificationProcess.cds @@ -31,8 +31,11 @@ service AuthorVerificationProcessService { definitionVersion : String; id : String; status : String; - startedAt : String; + startedAt : Timestamp; + completedAt : Timestamp; startedBy : String; + subject : String; + businessKey : String; }; type ProcessInstances : many ProcessInstance; @@ -50,14 +53,26 @@ service AuthorVerificationProcessService { ) returns ProcessOutputs; function getInstances( + id : String, businessKey : String, status : many String, definitionId : String, definitionVersion : String, - startedAt : String, - completedAt : String, + startedAt : Timestamp, + startedFrom : Timestamp, + startedUpTo : Timestamp, + completedAt : Timestamp, + completedFrom : Timestamp, + completedUpTo : Timestamp, startedBy : String, - subject : String + subject : String, + containsText : String, + rootInstanceId : String, + parentInstanceId : String, + top : Integer, + skip : Integer, + orderBy : String, + inlinecount : String ) returns ProcessInstances; action suspend( diff --git a/tests/sample/status-management/srv/external/eu12.cdsmunich.sampleapplicationproject.bookApprovalProcess.cds b/tests/sample/status-management/srv/external/eu12.cdsmunich.sampleapplicationproject.bookApprovalProcess.cds index dbd0e25f..3f0dd793 100644 --- a/tests/sample/status-management/srv/external/eu12.cdsmunich.sampleapplicationproject.bookApprovalProcess.cds +++ b/tests/sample/status-management/srv/external/eu12.cdsmunich.sampleapplicationproject.bookApprovalProcess.cds @@ -37,8 +37,11 @@ service BookApprovalProcessService { definitionVersion : String; id : String; status : String; - startedAt : String; + startedAt : Timestamp; + completedAt : Timestamp; startedBy : String; + subject : String; + businessKey : String; }; type ProcessInstances : many ProcessInstance; @@ -56,14 +59,26 @@ service BookApprovalProcessService { ) returns ProcessOutputs; function getInstances( + id : String, businessKey : String, status : many String, definitionId : String, definitionVersion : String, - startedAt : String, - completedAt : String, + startedAt : Timestamp, + startedFrom : Timestamp, + startedUpTo : Timestamp, + completedAt : Timestamp, + completedFrom : Timestamp, + completedUpTo : Timestamp, startedBy : String, - subject : String + subject : String, + containsText : String, + rootInstanceId : String, + parentInstanceId : String, + top : Integer, + skip : Integer, + orderBy : String, + inlinecount : String ) returns ProcessInstances; action suspend( diff --git a/tests/unit/workflowClient.test.ts b/tests/unit/workflowClient.test.ts index b15c32e1..834f6be6 100644 --- a/tests/unit/workflowClient.test.ts +++ b/tests/unit/workflowClient.test.ts @@ -20,7 +20,7 @@ describe('getInstances – URL building', () => { it('builds URL with no params', async () => { await getInstances(SERVICE_URL, JWT, {}); - expect(capturedUrl()).toBe(`${BASE}?`); + expect(capturedUrl()).toBe(BASE); }); it('filters by businessKey', async () => { @@ -28,6 +28,13 @@ describe('getInstances – URL building', () => { expect(capturedUrl()).toContain('businessKey=my-key'); }); + it('encodes status values', async () => { + await getInstances(SERVICE_URL, JWT, { status: [WorkflowStatus.RUNNING] }); + expect(capturedUrl()).toContain('status=RUNNING'); + // verify it's encoded (no raw spaces or special chars) + expect(capturedUrl()).not.toContain('status= '); + }); + it('filters by single status', async () => { await getInstances(SERVICE_URL, JWT, { status: [WorkflowStatus.RUNNING] }); expect(capturedUrl()).toContain('status=RUNNING'); @@ -64,7 +71,7 @@ describe('getInstances – URL building', () => { it('skips null and undefined params', async () => { await getInstances(SERVICE_URL, JWT, { businessKey: null, definitionId: null }); - expect(capturedUrl()).toBe(`${BASE}?`); + expect(capturedUrl()).toBe(BASE); }); it('encodes special characters in param values', async () => { From 3cd706a333b9361226e1782bbf6d9031ee88a504 Mon Sep 17 00:00:00 2001 From: Yannis Moser Date: Mon, 11 May 2026 15:59:41 +0200 Subject: [PATCH 09/13] fix lint --- README.md | 60 ++++++++++++------------ tests/sample/status-management/README.md | 5 +- 2 files changed, 34 insertions(+), 31 deletions(-) diff --git a/README.md b/README.md index 062daeb1..f16e9309 100644 --- a/README.md +++ b/README.md @@ -30,7 +30,7 @@ CAP Plugin to interact with SAP Build Process Automation to manage processes. - [What Gets Generated](#what-gets-generated) - [Starting a Process](#starting-a-process) - [Suspending, Resuming, and Cancelling a Process](#suspending-resuming-and-cancelling-a-process) - - [Querying Process Instances](#querying-process-instances) - [Limitations](#limitations) + - [Querying Process Instances](#querying-process-instances) - [Limitations](#limitations) - [Generic ProcessService](#generic-processservice) - [Service Definition](#service-definition) - [Usage](#usage) @@ -635,26 +635,26 @@ const outputs = await processService.getOutputs({ All parameters are optional. Supported filter parameters: -| Parameter | Type | Description | -| ------------------ | --------------- | -------------------------------------------------------- | -| `id` | `String` | Filter by workflow instance ID | -| `businessKey` | `String` | Filter by business key | -| `status` | `Array` | Filter by status (`RUNNING`, `SUSPENDED`, `CANCELED`, `ERRONEOUS`, `COMPLETED`) | -| `definitionId` | `String` | Filter by process definition ID | -| `definitionVersion`| `String` | Filter by process definition version | -| `startedFrom` | `Timestamp` | Filter instances started on or after this date | -| `startedUpTo` | `Timestamp` | Filter instances started on or before this date | -| `completedFrom` | `Timestamp` | Filter instances completed on or after this date | -| `completedUpTo` | `Timestamp` | Filter instances completed on or before this date | -| `startedBy` | `String` | Filter by the user who started the instance | -| `subject` | `String` | Filter by subject | -| `containsText` | `String` | Full-text search across instance fields | -| `rootInstanceId` | `String` | Filter by root instance ID | -| `parentInstanceId` | `String` | Filter by parent instance ID | -| `orderBy` | `String` | Sort order (e.g. `startedAt desc`, `businessKey asc`) | -| `top` | `Integer` | Maximum number of results to return | -| `skip` | `Integer` | Number of results to skip (for pagination) | -| `inlinecount` | `String` | Set to `allpages` to include total count in response | +| Parameter | Type | Description | +| ------------------- | --------------- | ------------------------------------------------------------------------------- | +| `id` | `String` | Filter by workflow instance ID | +| `businessKey` | `String` | Filter by business key | +| `status` | `Array` | Filter by status (`RUNNING`, `SUSPENDED`, `CANCELED`, `ERRONEOUS`, `COMPLETED`) | +| `definitionId` | `String` | Filter by process definition ID | +| `definitionVersion` | `String` | Filter by process definition version | +| `startedFrom` | `Timestamp` | Filter instances started on or after this date | +| `startedUpTo` | `Timestamp` | Filter instances started on or before this date | +| `completedFrom` | `Timestamp` | Filter instances completed on or after this date | +| `completedUpTo` | `Timestamp` | Filter instances completed on or before this date | +| `startedBy` | `String` | Filter by the user who started the instance | +| `subject` | `String` | Filter by subject | +| `containsText` | `String` | Full-text search across instance fields | +| `rootInstanceId` | `String` | Filter by root instance ID | +| `parentInstanceId` | `String` | Filter by parent instance ID | +| `orderBy` | `String` | Sort order (e.g. `startedAt desc`, `businessKey asc`) | +| `top` | `Integer` | Maximum number of results to return | +| `skip` | `Integer` | Number of results to skip (for pagination) | +| `inlinecount` | `String` | Set to `allpages` to include total count in response | #### Limitations @@ -670,15 +670,15 @@ The generic `ProcessService` allows setting the business key to mimic the behavi The generic `ProcessService` defines the following events and functions: -| Operation | Type | Description | -| --------------- | -------- | ----------------------------------------------------------------- | -| `start` | event | Start a workflow instance with a `definitionId` and `context` | -| `cancel` | event | Cancel all running/suspended instances matching a `businessKey` | -| `suspend` | event | Suspend all running instances matching a `businessKey` | -| `resume` | event | Resume all suspended instances matching a `businessKey` | -| `getAttributes` | function | Retrieve attributes for a specific process instance | -| `getOutputs` | function | Retrieve outputs for a specific process instance | -| `getInstances` | function | Query process instances with flexible filter parameters | +| Operation | Type | Description | +| --------------- | -------- | --------------------------------------------------------------- | +| `start` | event | Start a workflow instance with a `definitionId` and `context` | +| `cancel` | event | Cancel all running/suspended instances matching a `businessKey` | +| `suspend` | event | Suspend all running instances matching a `businessKey` | +| `resume` | event | Resume all suspended instances matching a `businessKey` | +| `getAttributes` | function | Retrieve attributes for a specific process instance | +| `getOutputs` | function | Retrieve outputs for a specific process instance | +| `getInstances` | function | Query process instances with flexible filter parameters | #### Usage diff --git a/tests/sample/status-management/README.md b/tests/sample/status-management/README.md index f30a321e..0810224b 100644 --- a/tests/sample/status-management/README.md +++ b/tests/sample/status-management/README.md @@ -182,7 +182,10 @@ this.after('CREATE', Authors, async (author, req) => { this.after('DELETE', Authors, async (author, req) => { if (!author.ID) return; - const instances = await authorProcess.getInstances({ businessKey: author.ID, status: ['RUNNING'] }); + const instances = await authorProcess.getInstances({ + businessKey: author.ID, + status: ['RUNNING'], + }); if (instances.length > 0) { await authorProcess.cancel({ businessKey: author.ID, cascade: true }); } From 5522a090c6b27b62c15ea73c2be7cd31018b81c1 Mon Sep 17 00:00:00 2001 From: Yannis Moser Date: Tue, 12 May 2026 13:23:46 +0200 Subject: [PATCH 10/13] fixed the input for the tests --- tests/bookshop/srv/programmatic-service.cds | 24 +++++++++++++++++++-- tests/bookshop/srv/programmatic-service.ts | 6 +----- 2 files changed, 23 insertions(+), 7 deletions(-) diff --git a/tests/bookshop/srv/programmatic-service.cds b/tests/bookshop/srv/programmatic-service.cds index 2f79351c..5b3b07ec 100644 --- a/tests/bookshop/srv/programmatic-service.cds +++ b/tests/bookshop/srv/programmatic-service.cds @@ -48,8 +48,28 @@ service ProgrammaticService { action genericCancel(businessKey: String, cascade: Boolean); action genericSuspend(businessKey: String, cascade: Boolean); action genericResume(businessKey: String, cascade: Boolean); - action genericGetInstances(businessKey: String, - status: many String) returns many ProcessInstance; + action genericGetInstances( + id: String, + businessKey: String, + status: many String, + definitionId: String, + definitionVersion: String, + startedAt: Timestamp, + startedFrom: Timestamp, + startedUpTo: Timestamp, + completedAt: Timestamp, + completedFrom: Timestamp, + completedUpTo: Timestamp, + startedBy: String, + subject: String, + containsText: String, + rootInstanceId: String, + parentInstanceId: String, + top: Integer, + skip: Integer, + orderBy: String, + inlinecount: String + ) returns many ProcessInstance; action genericGetAttributes(processInstanceId: String) returns many ProcessAttribute; action genericGetOutputs(processInstanceId: String) returns ProcessOutputs; } diff --git a/tests/bookshop/srv/programmatic-service.ts b/tests/bookshop/srv/programmatic-service.ts index bf727f65..f9450009 100644 --- a/tests/bookshop/srv/programmatic-service.ts +++ b/tests/bookshop/srv/programmatic-service.ts @@ -130,11 +130,7 @@ class ProgrammaticService extends cds.ApplicationService { }); this.on('genericGetInstances', async (req: cds.Request) => { - const { businessKey, status } = req.data; - const result = await processService.send('getInstances', { - businessKey, - status, - }); + const result = await processService.send('getInstances', req.data); return result; }); From 51b93fe35824177f39675aa6cf8da8c76be100fc Mon Sep 17 00:00:00 2001 From: Yannis Moser Date: Tue, 12 May 2026 13:58:05 +0200 Subject: [PATCH 11/13] renaming filtered to filteredInstances --- lib/api/local-workflow-store.ts | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/lib/api/local-workflow-store.ts b/lib/api/local-workflow-store.ts index 65ec8f02..60419f50 100644 --- a/lib/api/local-workflow-store.ts +++ b/lib/api/local-workflow-store.ts @@ -93,37 +93,37 @@ export class LocalWorkflowStore { 'inlinecount', ]); - let filtered = [...this.instances]; + let filteredInstances = [...this.instances]; for (const [key, value] of Object.entries(params)) { if (value == null || specialKeys.has(key)) continue; - filtered = filtered.filter((i) => i[key as keyof LocalWorkflowInstance] === value); + filteredInstances = filteredInstances.filter((i) => i[key as keyof LocalWorkflowInstance] === value); } if (params.status && params.status.length > 0) { - filtered = filtered.filter((i) => params.status!.includes(i.status)); + filteredInstances = filteredInstances.filter((i) => params.status!.includes(i.status)); } if (params.startedFrom != null) { const from = new Date(params.startedFrom); - filtered = filtered.filter((i) => i.startedAt != null && new Date(i.startedAt) >= from); + filteredInstances = filteredInstances.filter((i) => i.startedAt != null && new Date(i.startedAt) >= from); } if (params.startedUpTo != null) { const upTo = new Date(params.startedUpTo); - filtered = filtered.filter((i) => i.startedAt != null && new Date(i.startedAt) <= upTo); + filteredInstances = filteredInstances.filter((i) => i.startedAt != null && new Date(i.startedAt) <= upTo); } if (params.completedFrom != null) { const from = new Date(params.completedFrom); - filtered = filtered.filter((i) => i.completedAt != null && new Date(i.completedAt) >= from); + filteredInstances = filteredInstances.filter((i) => i.completedAt != null && new Date(i.completedAt) >= from); } if (params.completedUpTo != null) { const upTo = new Date(params.completedUpTo); - filtered = filtered.filter((i) => i.completedAt != null && new Date(i.completedAt) <= upTo); + filteredInstances = filteredInstances.filter((i) => i.completedAt != null && new Date(i.completedAt) <= upTo); } if (params.containsText != null) { const text = params.containsText.toLowerCase(); - filtered = filtered.filter( + filteredInstances = filteredInstances.filter( (i) => i.id.toLowerCase().includes(text) || i.subject?.toLowerCase().includes(text) || @@ -132,15 +132,15 @@ export class LocalWorkflowStore { } if (params.rootInstanceId != null) - filtered = filtered.filter((i) => i.id === params.rootInstanceId); + filteredInstances = filteredInstances.filter((i) => i.id === params.rootInstanceId); if (params.parentInstanceId != null) - filtered = filtered.filter((i) => i.id === params.parentInstanceId); + filteredInstances = filteredInstances.filter((i) => i.id === params.parentInstanceId); const skip = params.skip ?? 0; - const top = params.top ?? filtered.length; - filtered = filtered.slice(skip, skip + top); + const top = params.top ?? filteredInstances.length; + filteredInstances = filteredInstances.slice(skip, skip + top); - return filtered; + return filteredInstances; } getInstance(instanceId: string): LocalWorkflowInstance | undefined { From 3227070fcfd8e90384abd84b7bebe27941a9f7d5 Mon Sep 17 00:00:00 2001 From: Yannis Moser Date: Tue, 12 May 2026 14:03:02 +0200 Subject: [PATCH 12/13] added a test to verify behavior when no params are given to getInstances --- tests/hybrid/programmaticApproach.test.ts | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/tests/hybrid/programmaticApproach.test.ts b/tests/hybrid/programmaticApproach.test.ts index 1ad2f205..3292e7c3 100644 --- a/tests/hybrid/programmaticApproach.test.ts +++ b/tests/hybrid/programmaticApproach.test.ts @@ -401,5 +401,17 @@ describe('Programmatic Approach Hybrid Tests', () => { expect(instances.length).toBe(1); }); + + it('should return instances when called with no params', async () => { + const ID = generateID(); + await startProcess(ID); + await waitForInstances(ID, ['RUNNING']); + + const res = await POST('/odata/v4/programmatic/genericGetInstances', {}); + const instances = res.data?.value ?? res.data ?? []; + + expect(Array.isArray(instances)).toBe(true); + expect(instances.length).toBeGreaterThan(0); + }); }); }); From 5c94ee6ce094b75ab6f846007e72999d0ba1855a Mon Sep 17 00:00:00 2001 From: I569192 Date: Tue, 12 May 2026 15:07:15 +0200 Subject: [PATCH 13/13] =?UTF-8?q?n=C3=A4chstes=20mal=20machst=20das=20selb?= =?UTF-8?q?er=20;)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/api/local-workflow-store.ts | 20 +++++++++++++++----- 1 file changed, 15 insertions(+), 5 deletions(-) diff --git a/lib/api/local-workflow-store.ts b/lib/api/local-workflow-store.ts index 60419f50..abdbf43a 100644 --- a/lib/api/local-workflow-store.ts +++ b/lib/api/local-workflow-store.ts @@ -97,7 +97,9 @@ export class LocalWorkflowStore { for (const [key, value] of Object.entries(params)) { if (value == null || specialKeys.has(key)) continue; - filteredInstances = filteredInstances.filter((i) => i[key as keyof LocalWorkflowInstance] === value); + filteredInstances = filteredInstances.filter( + (i) => i[key as keyof LocalWorkflowInstance] === value, + ); } if (params.status && params.status.length > 0) { @@ -106,19 +108,27 @@ export class LocalWorkflowStore { if (params.startedFrom != null) { const from = new Date(params.startedFrom); - filteredInstances = filteredInstances.filter((i) => i.startedAt != null && new Date(i.startedAt) >= from); + filteredInstances = filteredInstances.filter( + (i) => i.startedAt != null && new Date(i.startedAt) >= from, + ); } if (params.startedUpTo != null) { const upTo = new Date(params.startedUpTo); - filteredInstances = filteredInstances.filter((i) => i.startedAt != null && new Date(i.startedAt) <= upTo); + filteredInstances = filteredInstances.filter( + (i) => i.startedAt != null && new Date(i.startedAt) <= upTo, + ); } if (params.completedFrom != null) { const from = new Date(params.completedFrom); - filteredInstances = filteredInstances.filter((i) => i.completedAt != null && new Date(i.completedAt) >= from); + filteredInstances = filteredInstances.filter( + (i) => i.completedAt != null && new Date(i.completedAt) >= from, + ); } if (params.completedUpTo != null) { const upTo = new Date(params.completedUpTo); - filteredInstances = filteredInstances.filter((i) => i.completedAt != null && new Date(i.completedAt) <= upTo); + filteredInstances = filteredInstances.filter( + (i) => i.completedAt != null && new Date(i.completedAt) <= upTo, + ); } if (params.containsText != null) {