Technical Articles
SAP Cloud SDK for JavaScript – Getting started with Workflow API Client
Introduction
SAP Workflow Service provides a set of APIs which allow you to list and manage workflow instances, definitions, and user tasks across recipients. If you consume those APIs directly, you need to write code for handling workflow service URL and authentication on your own.
SAP Cloud SDK for JavaScript provides predefined API Client for SAP Workflow Service in the Cloud Foundry Environment. By using the API Client, you don’t have to write boilerplate code which is mentioned above, so you can quickly start developing your business logic.
In this blog, I will show you how to create a service which consumes Workflow Service API Client and test that service.
The source code is available at GitHub.
Prerequisites
Steps
- Create a workflow service instance
- Create a destination pointing to workflow service rest URL
- Create a service that consumes workflow APIs
- Test the service
1. Create a workflow service instance
1.1. Create a service instance
First, let’s create a workflow service instance. The point here is to assign the instance the necessary scopes to execute a certain workflow API. You can find necessary scopes in the API documentation at API Business Hub.
{
"authorities": [
"WORKFLOW_INSTANCE_START",
"WORKFLOW_INSTANCE_GET_CONTEXT",
"WORKFLOW_INSTANCE_GET",
"TASK_GET",
"TASK_COMPLETE",
"TASK_UPDATE"
]
}
cf create-service workflow lite wf_cloud_sdk -c authorities.json
If you need more scopes at a later point of time, you can update the service instance with the following command.
cf update-service wf_cloud_sdk -c authorities.json
1.2. Create a service key
Execute the following command to create a service key.
cf create-service-key wf_cloud_sdk key1
Get the information of the service key by the following command. You’ll need this information in the next step.
cf service-key wf_cloud_sdk key1
{
"content_endpoint": "https://api.workflow-sap.cfapps.eu10.hana.ondemand.com/workflow-deploy/rest/internal/v1",
"endpoints": {
"workflow_odata_url": "https://api.workflow-sap.cfapps.eu10.hana.ondemand.com/workflow-service/odata",
"workflow_rest_url": "https://api.workflow-sap.cfapps.eu10.hana.ondemand.com/workflow-service/rest"
},
"html5-apps-repo": {
"app_host_id": "1365363a-6e04-4f43-876a-67b81f32306e,1a5b93af-f1af-4acf-aee0-8c6cc8d3f315,8964e911-e35d-4cfd-972e-08e681a2df0f,9ea7410f-80ea-4b19-bbf0-4fca238ef098"
},
"portal_content_provider": {
"instance_id": "b87e14b7-ea72-4866-80b7-fe284e75e83a"
},
"saasregistryappname": "workflow",
"sap.cloud.service": "com.sap.bpm.workflow",
"uaa": {
"apiurl": "https://api.authentication.eu10.hana.ondemand.com",
"clientid": "xxxxxxxxxx",
"clientsecret": "xxxxxxxxxx",
"credential-type": "binding-secret",
"identityzone": "b736177ctrial",
"identityzoneid": "cf4bec0f-ec63-4d37-8efd-ded0e8f33c58",
"sburl": "https://internal-xsuaa.authentication.eu10.hana.ondemand.com",
"subaccountid": "cf4bec0f-ec63-4d37-8efd-ded0e8f33c58",
"tenantid": "cf4bec0f-ec63-4d37-8efd-ded0e8f33c58",
"tenantmode": "dedicated",
"uaadomain": "authentication.eu10.hana.ondemand.com",
"url": "https://b736177ctrial.authentication.eu10.hana.ondemand.com",
"verificationkey": "-----BEGIN PUBLIC KEY-----MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAxfwBi8D8tOJ61KJ8PJel0ML3oUt+A7k7r7F9gpUAAMon5wqS018oOWErn3gU8VwLCrtDiz9ow8aiia5gxvKXSyMIulMUK/H5QaLMPDFmlPG8hwFR8c3KbxwbzvY4gase7B4Gi2YS6eii+7sNejCbz8tFzpOJJbxia6VX7JCuhs463vFA1svPFDE7Ewa6A5mdpP/uRpW1FQC4zL2npN9dH3T/Osp6b30EKPzL2WcsNes8stxcC8xkMGHDZSBCiAUMhCseD0eei3cwCOMzZj92agyUQiIBr1qgXjIOz8W3xlimkEmRTW8xNlTp7jO972plK1nlVYNnvfA2ObE7cOSqYwIDAQAB-----END PUBLIC KEY-----",
"xsappname": "clone-52964022-f257-4e19-a621-463a969e3e70!b54386|workflow!b10150",
"zoneid": "cf4bec0f-ec63-4d37-8efd-ded0e8f33c58"
}
}
2. Create a destination pointing to workflow service rest URL
Go to your BTP Cockpit and create a destination as described here.
- Name: Workflow-Api
- Type: HTTP
- URL: The value of
workflow_rest_url
- Proxy Type: Internet
- Authentication: OAuth2ClientCredentials
- Client ID: The value of
clientid
- Client Secret: The value of
clientsecret
- Token Service URL: The value of
url
appended by/oauth/token?grant_type=client_credentials
3. Create a service that consumes workflow APIs
Create a Cloud SDK project and implement methods to call workflow APIs.
The service will provide the following endpoints.
- Start a new workflow instance
- Get workflow instances
- Get a workflow instance by id
- Get workflow instance context
- Get user tasks for a specified workflow instance
- Update a user task
3.1. Create a Cloud SDK project
Initialize your project by the following command.
sap-cloud-sdk init wf_cloud_sdk
Next, create a new module for handling requests related to workflow. Go to the root of the project and use the following Nest CLI for generating necessary artifacts.
nest generate module workflow
nest generate controller workflow
nest generate service workflow
As a result, you’ll get the project structure as below.
3.2. Implement the service to call workflow APIs
First you need to add @sap/cloud-sdk-workflow-service-cf
to dependency of your project.
npm install @sap/cloud-sdk-workflow-service-cf
Open src/workflow/workflow.service.ts
and add the following code. These methods will be called from the controller which we’ll implement in the next step. See only a minimal lines of code is required for executing each API.
import { Injectable } from '@nestjs/common';
import {
WorkflowInstance,
WorkflowInstancesApi,
UserTaskInstancesApi,
TaskInstance,
} from '@sap/cloud-sdk-workflow-service-cf';
@Injectable()
export class WorkflowService {
private destination = { destinationName: 'Workflow-Api' };
startWorkflow(definitionId: string, body: any): Promise<WorkflowInstance> {
return WorkflowInstancesApi.startInstance({
definitionId: definitionId,
context: body,
}).execute(this.destination);
}
getWorkflowInstances(definitionId: string): Promise<WorkflowInstance[]> {
return WorkflowInstancesApi.queryInstances({
definitionId: definitionId ? definitionId : '',
$top: 10,
$orderby: 'startedAt desc',
}).execute(this.destination);
}
getWorkflowInstance(workflowInstanceID: string): Promise<WorkflowInstance> {
return WorkflowInstancesApi.getInstance(workflowInstanceID).execute(
this.destination,
);
}
getWorkflowContext(workflowInstanceID: string): Promise<any> {
return WorkflowInstancesApi.getInstanceContext(workflowInstanceID).execute(
this.destination,
);
}
getUserTasks(workflowInstanceID: string): Promise<TaskInstance[]> {
return UserTaskInstancesApi.queryInstances({
workflowInstanceId: workflowInstanceID,
}).execute(this.destination);
}
updateUserTask(taskInstanceID: string, body: any): Promise<void> {
return UserTaskInstancesApi.updateInstance(taskInstanceID, {
context: body,
status: 'COMPLETED',
}).execute(this.destination);
}
}
Open src/workflow/workflow.controller.ts
and add the following code.
import {
Body,
Controller,
Get,
HttpCode,
Param,
Patch,
Post,
Query,
} from '@nestjs/common';
import { WorkflowService } from './workflow.service';
import {
TaskInstance,
WorkflowInstance,
} from '@sap/cloud-sdk-workflow-service-cf';
@Controller('workflow')
export class WorkflowController {
constructor(private readonly workflowService: WorkflowService) {}
@Post('/instance/:definitionId')
@HttpCode(201)
startWorkflow(
@Body() body,
@Param('definitionId') definitionId,
): Promise<WorkflowInstance> {
return this.workflowService.startWorkflow(definitionId, body);
}
@Get('/instance')
getWorkflowInstances(
@Query('definitionId') definitionId,
): Promise<WorkflowInstance[]> {
return this.workflowService.getWorkflowInstances(definitionId);
}
@Get('/instance/:workflowInstanceID')
getWorkflowInstance(
@Param('workflowInstanceID') workflowInstanceID,
): Promise<WorkflowInstance> {
return this.workflowService.getWorkflowInstance(workflowInstanceID);
}
@Get('/instance/:workflowInstanceID/context')
getWorkflowContext(
@Param('workflowInstanceID') workflowInstanceID,
): Promise<any> {
return this.workflowService.getWorkflowContext(workflowInstanceID);
}
@Get('/instance/:workflowInstanceID/usertask')
getUserTasks(
@Param('workflowInstanceID') workflowInstanceID,
): Promise<TaskInstance> {
return this.workflowService.getUserTasks(workflowInstanceID);
}
@Patch('/usertask/:taskInstanceID')
@HttpCode(204)
updateUserTask(
@Body() body,
@Param('taskInstanceID') taskInstanceID,
): Promise<void> {
return this.workflowService.updateUserTask(taskInstanceID, body);
}
}
3.3. Deploy the service to the Cloud Foundry
Since this service needs to access BTP destination, you need to bind destination and xsuaa service instances to this service.
First, create a destination service instance.
cf create-service destination lite wf_cloud_sdk-destination
Second, create xs-security.json
file with the following content.
{
"xsappname": "wf_cloud_sdk",
"tenant-mode": "dedicated"
}
Then, create a xsuaa service instance.
cf create-service xsuaa application wf_cloud_sdk-xsuaa -c xs-security.json
Add the newly created services to the services section of manifest.yml
.
applications:
- name: wf_cloud_sdk
path: deployment/
buildpacks:
- nodejs_buildpack
memory: 256M
command: npm run start:prod
random-route: true
services:
- wf_cloud_sdk-destination
- wf_cloud_sdk-xsuaa
Finally, execute the following command to deploy the service to the Cloud Foundry.
npm run deploy
4. Test the service
If you don’t have a workflow definition yet, you can create one by following this tutorial.
The following section assumes you have a workflow definition "onboard"
, but any workflow definition can be used.
4.1. Get workflow instances
Execute the following request. If you don’t specify ?definitionId=onboard
, up to 10 workflow instances will be retrieved in descending order by creation date.
/workflow/instance?definitionId=onboard
4.2. Get an instance by id
Pick an instance id from the response above. (example: d16bbe10-3363-11eb-8e66-eeee0a979f81)
Execute the following request.
/workflow/instance/<INSTANCE_ID>
4.3. Get workflow instance context
Execute the following request.
/workflow/instance/<INSTANCE_ID>/context
4.4. Start a new workflow instance
To execute POST requests I use Postman (you can use any API client).
Execute the following request with the body down below.
/workflow/instance/onboard
{
"managerId": "john.edrich@sapdemo.com",
"buddyId": "kevin.hart@saptest.com",
"userId": "cgrant1",
"empData": {
"firstName": "Carla",
"lastName": "Grant",
"city": "San Mateo",
"country": "United States",
"hireDate": "2020-07-11",
"jobTitle": "General Manager, Industries"
}
}
You’ll get the result looking like the image below. Copy the instance id in the response for the next text.
4.5. Get user tasks for a specified workflow instance
Execute the following request. Use the instance id you’ve got in the previous step.
/workflow/instance/<INSTANCE_ID>/usertask
Copy the id in the response for the next text.
4.6. Update a user task
Here again I use Postman for sending a PATCH request.
Execute the following request with the body down below.
/workflow/usertask/<USER_TASK_ID>
{
"approve": true
}
Now, get the same user task again, and you’ll see the status of the task has been changed from “READY” to “COMPLETED”.
/workflow/instance/<INSTANCE_ID>/usertask
Conclusion
In this blog you’ve learned how to create a service using Workflow Service API Client and test that service. With SAP Cloud SDK you can execute workflow APIs with only minimal lines of code, which makes development easier.
References
- SAP Cloud SDK document
- SAP API Business Hub: you can find all the available APIs here
It's really useful! I read it all and it's really good information. Thanks for this wonderful article!
Good job, thx for sharing your journey and your way to use the SDK to make things easier!