BPMN Process
This tutorial will guide you through the steps of creating a Business Process
with Service Task
, User Task
and Choice Gateway
elements.
The result of the business process modeling would be a Time Entry Request process, that once started would trigger an approval process (with mail notifications, if configured) with the following steps:
Steps
Start Eclipse Dirigible
Info
You can find more information on how to do that by following:
- Getting Started section.
- Setup section.
Create Project
- Go to the
Projects
perspective and createNew Project
. - Enter
sample-bpm
for the name of the project. - The project will appear under the projects list.
Create JavaScript Process Task Handlers
JavaScript handlers should be provided for the Service Task
steps in the Business Process
. The following handlers will be executed during the Approve Time Entry Request
, Deny Time Entry Request
and Send Notification
tasks.
- Right click on the
sample-bpm
project and select New → Folder. - Enter
tasks
for the name of the folder. - Create
approve-request.js
,reject-request.js
andsend-notification.js
files.
- Right click on the
tasks
folder and select New → JavaScript CJS Service. - Enter
approve-request.js
for the name of the file. - Double-click to open the file.
-
Replace the content with the following:
const process = require("bpm/v4/process"); const mailClient = require("mail/v4/client"); const config = require("core/v4/configurations"); let execution = process.getExecutionContext(); let executionId = execution.getId(); let user = process.getVariable(executionId, "user"); console.log(`Time Entry Request Approved for User [${user}]`); if (isMailConfigured()) { let from = config.get("APP_SAMPLE_BPM_FROM_EMAIL"); let to = config.get("APP_SAMPLE_BPM_TO_EMAIL"); let subject = "Time Entry Request - Approved"; let content = `<h2>Status:</h2><h4>Time Entry Request for [${user}] - Approved</4>`; let subType = "html"; mailClient.send(from, to, subject, content, subType); } else { console.error("Missing mail configuration"); } function isMailConfigured() { return config.get("DIRIGIBLE_MAIL_USERNAME") != "" && config.get("DIRIGIBLE_MAIL_PASSWORD") != "" && config.get("DIRIGIBLE_MAIL_TRANSPORT_PROTOCOL") != "" && config.get("DIRIGIBLE_MAIL_SMTPS_HOST") != "" && config.get("DIRIGIBLE_MAIL_SMTPS_PORT") != "" && config.get("APP_SAMPLE_BPM_FROM_EMAIL") != "" && config.get("APP_SAMPLE_BPM_TO_EMAIL") != "" }
-
Save the changes.
- Right click on the
tasks
folder and select New → JavaScript CJS Service. - Enter
reject-request.js
for the name of the file. - Double-click to open the file.
-
Replace the content with the following:
const process = require("bpm/v4/process"); const mailClient = require("mail/v4/client"); const config = require("core/v4/configurations"); let execution = process.getExecutionContext(); let executionId = execution.getId(); let user = process.getVariable(executionId, "user"); console.error(`Time Entry Request Rejected for User [${user}]`); if (isMailConfigured()) { let from = config.get("APP_SAMPLE_BPM_FROM_EMAIL"); let to = config.get("APP_SAMPLE_BPM_TO_EMAIL"); let subject = "Time Entry Request - Rejected"; let content = `<h2>Status:</h2><h4>Time Entry Request for [${user}] - Rejected</h4>`; let subType = "html"; mailClient.send(from, to, subject, content, subType); } else { console.error("Missing mail configuration"); } function isMailConfigured() { return config.get("DIRIGIBLE_MAIL_USERNAME") != "" && config.get("DIRIGIBLE_MAIL_PASSWORD") != "" && config.get("DIRIGIBLE_MAIL_TRANSPORT_PROTOCOL") != "" && config.get("DIRIGIBLE_MAIL_SMTPS_HOST") != "" && config.get("DIRIGIBLE_MAIL_SMTPS_PORT") != "" && config.get("APP_SAMPLE_BPM_FROM_EMAIL") != "" && config.get("APP_SAMPLE_BPM_TO_EMAIL") != "" }
-
Save the changes.
- Right click on the
tasks
folder and select New → JavaScript CJS Service. - Enter
send-notification.js
for the name of the file. - Double-click to open the file.
-
Replace the content with the following:
const process = require("bpm/v4/process"); const base64 = require("utils/v4/base64"); const mailClient = require("mail/v4/client"); const config = require("core/v4/configurations"); let execution = process.getExecutionContext(); let executionId = execution.getId(); let data = { executionId: executionId, User: process.getVariable(executionId, "User"), Project: process.getVariable(executionId, "Project"), Start: process.getVariable(executionId, "Start"), End: process.getVariable(executionId, "End"), Hours: process.getVariable(executionId, "Hours") }; let urlEncodedData = base64.encode(JSON.stringify(data)); let url = `http://localhost:8080/services/v4/web/sample-bpm/process/?data=${urlEncodedData}`; console.log(`Approve Request URL: ${url}`); if (isMailConfigured()) { let from = config.get("APP_SAMPLE_BPM_FROM_EMAIL"); let to = config.get("APP_SAMPLE_BPM_TO_EMAIL"); let subject = "Time Entry Request - Pending"; let content = `<h2>Status:</h2><h4>Time Entry Request for [${data.User}] - Pending</h4>Click <a href="${url}" target="_blank">here</a> to process request.`; let subType = "html"; mailClient.send(from, to, subject, content, subType); } else { console.error("Missing mail configuration"); } function isMailConfigured() { return config.get("DIRIGIBLE_MAIL_USERNAME") != "" && config.get("DIRIGIBLE_MAIL_PASSWORD") != "" && config.get("DIRIGIBLE_MAIL_TRANSPORT_PROTOCOL") != "" && config.get("DIRIGIBLE_MAIL_SMTPS_HOST") != "" && config.get("DIRIGIBLE_MAIL_SMTPS_PORT") != "" && config.get("APP_SAMPLE_BPM_FROM_EMAIL") != "" && config.get("APP_SAMPLE_BPM_TO_EMAIL") != "" }
-
Save the changes.
Create Business Process Model
- Right click on the
sample-bpm
project and select New → Business Process Model. - Enter
time-entry-request.bpmn
for the name of the business process.
- Double-click the
time-entry-request.bpmn
file to open it with theFlowable Editor
. - Click on the Process identifier field and change the value to
time-entry-request
. -
Click on the Name field and change the value to
Time Entry Request
. -
Click on the MyServiceTasks to select the first step of the business process.
-
Click on the Name field and change the value to
Send Notification
. - Scroll down to the Class fields and click on it.
-
Change the handler filed to
sample-bpm/tasks/send-notification.js
.JavaScript Task Handler
The value of the handler field (e.g.
sample-bpm/tasks/send-notification.js
) points to the location of the javascript task handler created in the previous step. -
Delete the arrow comming out of the Send Notification step.
-
Expand the Activities group and drag and drop new User task to editor area.
-
Connect the Send Notification task and the newly created user task.
User Task
Once the business process is triggered, it would stop at the Process Time Entry Request user task and it will wait for process continuation after the user task is completed.
-
Select the user task.
- Click on the Name field and change the value to
Process Time Entry Request
. -
Create Choice gateway comming out of the Process Time Entry Request user task.
-
Expand the Activities group and drag and drop new Service task to editor area.
- Select the service task.
- Click on the Name field and change the value to
Approve Time Entry Request
. - Scroll down to the Class fields and click on it.
- Change the handler filed to
sample-bpm/tasks/approve-request.js
. - Expand the Activities group and drag and drop new Service task to editor area.
- Select the service task.
- Click on the Name field and change the value to
Reject Time Entry Request
. - Scroll down to the Class fields and click on it.
- Change the handler filed to
sample-bpm/tasks/reject-request.js
. - Connect the Choice gateway with the Approve Time Entry Request and Reject Time Entry Request steps.
- Select the connection between the Choice gateway and the Reject Time Entry Request step.
-
Click on the Default flow checkbox.
-
Select the connection between the Choice gateway and the Approve Time Entry Request step.
-
Click on the Flow condition field and change the value to
${isRequestApproved}
.Flow Condition
In the flow condition
isRequestApproved
is a process context variable, that would be set as part of the process continuation after the completion of the Process Time Entry Request user task. -
Connect the Approve Time Entry Request and Reject Time Entry Request steps with the end event.
-
Save the changes.
- Right click on the
time-entry-request.bpmn
file and select Open With → Code Editor. -
Replace the content with the following:
<?xml version='1.0' encoding='UTF-8'?> <definitions xmlns="http://www.omg.org/spec/BPMN/20100524/MODEL" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:flowable="http:// flowable.org/bpmn" xmlns:bpmndi="http://www.omg.org/spec/BPMN/20100524/DI" xmlns:omgdc="http://www.omg.org/spec/DD/20100524/DC" xmlns:omgdi="http://www.omg.org/spec/DD/20100524/DI" typeLanguage="http://www.w3.org/2001/XMLSchema" expressionLanguage="http://www.w3.org/1999/XPath" targetNamespace="http://www.flowable.org/processdef" exporter="Flowable Open Source Modeler" exporterVersion="6.7.2"> <process id="time-entry-request" name="Time Entry Request" isExecutable="true"> <startEvent id="sid-3334E861-7999-4B89-B8B0-11724BA17A3E"/> <serviceTask id="sid-ED1BBD7E-41C4-42D7-A933-2CD979372BE7" name="Send Notification" flowable:class="org.eclipse.dirigible.bpm.flowable.DirigibleCallDelegate"> <extensionElements> <flowable:field name="handler"> <flowable:string><![CDATA[sample-bpm/tasks/send-notification.js]]></flowable:string> </flowable:field> </extensionElements> </serviceTask> <sequenceFlow id="sid-797626AE-B2F6-4C00-ABEE-FB30ADC177E4" sourceRef="sid-3334E861-7999-4B89-B8B0-11724BA17A3E" targetRef="sid-ED1BBD7E-41C4-42D7-A933-2CD979372BE7"/> <endEvent id="sid-70B488C1-384A-4E19-8091-1B12D1AEC7FD"/> <userTask id="sid-1949B473-2C74-4A44-BBB5-EA6235D62426" name="Process Time Entry Request"/> <serviceTask id="sid-07258D72-C009-406E-82EE-512FC68F9339" name="Approve Time Entry Request" flowable:class="org.eclipse.dirigible.bpm.flowable.DirigibleCallDelegate"> <extensionElements> <flowable:field name="handler"> <flowable:string><![CDATA[sample-bpm/tasks/approve-request.js]]></flowable:string> </flowable:field> </extensionElements> </serviceTask> <exclusiveGateway id="sid-9D358738-173A-49C8-8B5C-DE28F95CF812" default="sid-354260C8-2700-4D26-ACB3-03990B1B83B6"/> <sequenceFlow id="sid-A3B49B75-2D22-4D46-A01D-89663F5D9398" sourceRef="sid-1949B473-2C74-4A44-BBB5-EA6235D62426" targetRef="sid-9D358738-173A-49C8-8B5C-DE28F95CF812"/> <sequenceFlow id="sid-645847E8-C959-48BD-816B-2E9CC4A2F08A" sourceRef="sid-ED1BBD7E-41C4-42D7-A933-2CD979372BE7" targetRef="sid-1949B473-2C74-4A44-BBB5-EA6235D62426"/> <serviceTask id="sid-EBA0B783-C844-4C0D-AFAD-18699F661DB4" name="Reject Time Entry Request" flowable:class="org.eclipse.dirigible.bpm.flowable.DirigibleCallDelegate"> <extensionElements> <flowable:field name="handler"> <flowable:string><![CDATA[sample-bpm/tasks/reject-request.js]]></flowable:string> </flowable:field> </extensionElements> </serviceTask> <sequenceFlow id="sid-23401766-A5CA-4A91-8187-EDDCCD2BC5D5" sourceRef="sid-EBA0B783-C844-4C0D-AFAD-18699F661DB4" targetRef="sid-70B488C1-384A-4E19-8091-1B12D1AEC7FD"/> <sequenceFlow id="sid-4CFA2E86-C3CD-4290-95B9-010BC9C0BEDC" sourceRef="sid-07258D72-C009-406E-82EE-512FC68F9339" targetRef="sid-70B488C1-384A-4E19-8091-1B12D1AEC7FD"/> <sequenceFlow id="sid-354260C8-2700-4D26-ACB3-03990B1B83B6" sourceRef="sid-9D358738-173A-49C8-8B5C-DE28F95CF812" targetRef="sid-EBA0B783-C844-4C0D-AFAD-18699F661DB4"/> <sequenceFlow id="sid-300248C3-876E-4B89-86F4-E978FA150CA5" sourceRef="sid-9D358738-173A-49C8-8B5C-DE28F95CF812" targetRef="sid-07258D72-C009-406E-82EE-512FC68F9339"> <conditionExpression xsi:type="tFormalExpression"><![CDATA[${isRequestApproved}]]></conditionExpression> </sequenceFlow> </process> <bpmndi:BPMNDiagram id="BPMNDiagram_time-entry-request"> <bpmndi:BPMNPlane bpmnElement="time-entry-request" id="BPMNPlane_time-entry-request"> <bpmndi:BPMNShape bpmnElement="sid-3334E861-7999-4B89-B8B0-11724BA17A3E" id="BPMNShape_sid-3334E861-7999-4B89-B8B0-11724BA17A3E"> <omgdc:Bounds height="30.0" width="30.0" x="103.0" y="78.0"/> </bpmndi:BPMNShape> <bpmndi:BPMNShape bpmnElement="sid-ED1BBD7E-41C4-42D7-A933-2CD979372BE7" id="BPMNShape_sid-ED1BBD7E-41C4-42D7-A933-2CD979372BE7"> <omgdc:Bounds height="80.0" width="100.0" x="180.0" y="52.0"/> </bpmndi:BPMNShape> <bpmndi:BPMNShape bpmnElement="sid-70B488C1-384A-4E19-8091-1B12D1AEC7FD" id="BPMNShape_sid-70B488C1-384A-4E19-8091-1B12D1AEC7FD"> <omgdc:Bounds height="28.00000000000003" width="28.0" x="683.3333061801073" y="233.33332406150006"/> </bpmndi:BPMNShape> <bpmndi:BPMNShape bpmnElement="sid-1949B473-2C74-4A44-BBB5-EA6235D62426" id="BPMNShape_sid-1949B473-2C74-4A44-BBB5-EA6235D62426"> <omgdc:Bounds height="80.0" width="100.0" x="315.0" y="52.0"/> </bpmndi:BPMNShape> <bpmndi:BPMNShape bpmnElement="sid-07258D72-C009-406E-82EE-512FC68F9339" id="BPMNShape_sid-07258D72-C009-406E-82EE-512FC68F9339"> <omgdc:Bounds height="80.0" width="100.0" x="450.0" y="144.99999999999997"/> </bpmndi:BPMNShape> <bpmndi:BPMNShape bpmnElement="sid-9D358738-173A-49C8-8B5C-DE28F95CF812" id="BPMNShape_sid-9D358738-173A-49C8-8B5C-DE28F95CF812"> <omgdc:Bounds height="40.0" width="40.0" x="345.0" y="165.0"/> </bpmndi:BPMNShape> <bpmndi:BPMNShape bpmnElement="sid-EBA0B783-C844-4C0D-AFAD-18699F661DB4" id="BPMNShape_sid-EBA0B783-C844-4C0D-AFAD-18699F661DB4"> <omgdc:Bounds height="80.0" width="100.0" x="450.0" y="240.0"/> </bpmndi:BPMNShape> <bpmndi:BPMNEdge bpmnElement="sid-797626AE-B2F6-4C00-ABEE-FB30ADC177E4" id="BPMNEdge_sid-797626AE-B2F6-4C00-ABEE-FB30ADC177E4" flowable:sourceDockerX="15.0" flowable:sourceDockerY="15.0" flowable:targetDockerX="50.0" flowable:targetDockerY="40.0"> <omgdi:waypoint x="132.9494165151691" y="92.86607665568077"/> <omgdi:waypoint x="179.99999999999878" y="92.44598214285713"/> </bpmndi:BPMNEdge> <bpmndi:BPMNEdge bpmnElement="sid-645847E8-C959-48BD-816B-2E9CC4A2F08A" id="BPMNEdge_sid-645847E8-C959-48BD-816B-2E9CC4A2F08A" flowable:sourceDockerX="50.0" flowable:sourceDockerY="40.0" flowable:targetDockerX="50.0" flowable:targetDockerY="40.0"> <omgdi:waypoint x="279.95000000000005" y="92.0"/> <omgdi:waypoint x="314.9999999999962" y="92.0"/> </bpmndi:BPMNEdge> <bpmndi:BPMNEdge bpmnElement="sid-23401766-A5CA-4A91-8187-EDDCCD2BC5D5" id="BPMNEdge_sid-23401766-A5CA-4A91-8187-EDDCCD2BC5D5" flowable:sourceDockerX="50.0" flowable:sourceDockerY="40.0" flowable:targetDockerX="14.0" flowable:targetDockerY="14.000000000000014"> <omgdi:waypoint x="549.9499999999998" y="271.7229694847648"/> <omgdi:waypoint x="683.5191504285505" y="249.61196067916165"/> </bpmndi:BPMNEdge> <bpmndi:BPMNEdge bpmnElement="sid-4CFA2E86-C3CD-4290-95B9-010BC9C0BEDC" id="BPMNEdge_sid-4CFA2E86-C3CD-4290-95B9-010BC9C0BEDC" flowable:sourceDockerX="50.0" flowable:sourceDockerY="40.0" flowable:targetDockerX="14.0" flowable:targetDockerY="14.000000000000014"> <omgdi:waypoint x="549.95" y="200.77812482414993"/> <omgdi:waypoint x="683.9707941300649" y="243.1152758099949"/> </bpmndi:BPMNEdge> <bpmndi:BPMNEdge bpmnElement="sid-354260C8-2700-4D26-ACB3-03990B1B83B6" id="BPMNEdge_sid-354260C8-2700-4D26-ACB3-03990B1B83B6" flowable:sourceDockerX="20.0" flowable:sourceDockerY="20.0" flowable:targetDockerX="50.0" flowable:targetDockerY="40.0"> <omgdi:waypoint x="365.0" y="204.93951104100947"/> <omgdi:waypoint x="365.0" y="280.0"/> <omgdi:waypoint x="449.99999999997203" y="280.0"/> </bpmndi:BPMNEdge> <bpmndi:BPMNEdge bpmnElement="sid-A3B49B75-2D22-4D46-A01D-89663F5D9398" id="BPMNEdge_sid-A3B49B75-2D22-4D46-A01D-89663F5D9398" flowable:sourceDockerX="50.0" flowable:sourceDockerY="40.0" flowable:targetDockerX="20.0" flowable:targetDockerY="20.0"> <omgdi:waypoint x="365.0" y="131.95"/> <omgdi:waypoint x="365.0" y="165.0"/> </bpmndi:BPMNEdge> <bpmndi:BPMNEdge bpmnElement="sid-300248C3-876E-4B89-86F4-E978FA150CA5" id="BPMNEdge_sid-300248C3-876E-4B89-86F4-E978FA150CA5" flowable:sourceDockerX="20.0" flowable:sourceDockerY="20.0" flowable:targetDockerX="50.0" flowable:targetDockerY="40.0"> <omgdi:waypoint x="384.94261658031087" y="185.0"/> <omgdi:waypoint x="450.0" y="184.99999999999997"/> </bpmndi:BPMNEdge> </bpmndi:BPMNPlane> </bpmndi:BPMNDiagram> </definitions>
-
Save the changes.
Business Process Synchronization
Usually when the *.bpmn
process is saved it would take between one and two minutes to be deployed and active. After that period of time the business process can be executed. The synchronization period by default is set to 50 seconds (0/50 * * * * ?
). Find out more about the Job Expression environment variables.
- Updating the
*.bpmn
file would result in new synchronization being triggered and the updated process flow would be available after minute or two. - Updating the JavaScript Task Handler won't require new synchronization and the new behaviour of the handlers will be available on the fly.
(Optional) Add candidate users or groups
In order to add users / groups who can potentially claim a user task and execute it.
- Double click on the bpmn process definition to open the flowable editor
- Select the Process Time Entry Request User task
- In the properties tab click on Assignments, the Assignment editor should open
- In the Assignment editor add ROLE_ADMINISTRATOR and ROLE_DEVELOPER in the Candidate groups section
- Click Save in the Assignment editor
- Right click on the time-entry-request.bpmn and click Publish
Create Process API
To trigger and continue the BPMN Process execution a server-side JavaScript API will be created.
- Right click on the
sample-bpm
project and select New → Folder. - Enter
api
for the name of the folder. - Create
process.js
file.
- Right click on the
api
folder and select New → JavaScript CJS Service. - Enter
process.js
for the name of the file. - Double-click to open the file.
-
Replace the content with the following:
const rs = require("http/v4/rs"); const process = require("bpm/v4/process"); const tasks = require("bpm/v4/tasks"); const user = require("security/v4/user"); rs.service() .post("", (ctx, request, response) => { let data = request.getJSON(); process.start('time-entry-request', { "User": "" + user.getName(), "Project": "" + data.Project, "Start": "" + data.Start, "End": "" + data.End, "Hours": "" + data.Hours }); response.setStatus(response.ACCEPTED); }) .resource("continue/:executionId") .post((ctx, request, response) => { let executionId = request.params.executionId; let tasksList = tasks.list(); let data = request.getJSON(); for (const task of tasksList) { if (task.executionId.toString() === executionId.toString()) { tasks.completeTask(task.id, { isRequestApproved: data.approved, user: data.user }); break; } } response.setStatus(response.ACCEPTED); }) .execute()
Create Submit Form
The submit form would call the server-side javascript api that was created in the previous step and will trigger the business process.
- Right click on the
sample-bpm
project and select New → Folder. - Enter
submit
for the name of the folder. - Create
index.html
andcontroller.js
files.
- Right click on the
submit
folder and select New → HTML5 Page. - Enter
index.html
for the name of the file. - Double-click to open the file.
-
Replace the content with the following:
<!DOCTYPE HTML> <html lang="en" xmlns="http://www.w3.org/1999/xhtml" ng-app="page" ng-controller="PageController"> <head> <meta charset="utf-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1"> <link rel="icon" href="data:;base64,iVBORw0KGgo=" dg-brand-icon /> <title dg-brand-title></title> <theme></theme> <script type="text/javascript" src="/services/v4/js/resources-core/services/loader.js?id=application-view-js"> </script> <link type="text/css" rel="stylesheet" href="/services/v4/js/resources-core/services/loader.js?id=application-view-css" /> <script type="text/javascript" src="controller.js"></script> </head> <body class="dg-vbox" dg-contextmenu="contextMenuContent"> <div> <fd-message-page glyph="sap-icon--time-entry-request"> <fd-message-page-title>Submit Time Entry Request</fd-message-page-title> <fd-message-page-subtitle> <fd-scrollbar class="dg-full-height"> <fd-fieldset class="fd-margin--md" ng-form="formFieldset"> <fd-form-group name="entityForm"> <fd-form-item horizontal="false"> <fd-form-label for="idProject" dg-required="true" dg-colon="true">Project </fd-form-label> <fd-combobox-input id="idProject" name="Project" state="{{ formErrors.Project ? 'error' : '' }}" ng-required="true" ng-change="isValid(formFieldset['Project'].$valid, 'Project')" ng-model="entity.Project" dropdown-items="optionsProject" dg-placeholder="Search Project ..."> </fd-combobox-input> </fd-form-item> <fd-form-item horizontal="false"> <fd-form-label for="idStart" dg-required="true" dg-colon="true">Start </fd-form-label> <fd-form-input-message-group dg-inactive="{{ formErrors.Start ? false : true }}"> <fd-input id="idStart" name="Start" state="{{ formErrors.Start ? 'error' : '' }}" ng-required="true" ng-change="isValid(formFieldset['Start'].$valid, 'Start')" ng-model="entity.Start" type="date"> </fd-input> <fd-form-message dg-type="error">Incorrect Input</fd-form-message> </fd-form-input-message-group> </fd-form-item> <fd-form-item horizontal="false"> <fd-form-label for="idEnd" dg-required="true" dg-colon="true">End</fd-form-label> <fd-form-input-message-group dg-inactive="{{ formErrors.End ? false : true }}"> <fd-input id="idEnd" name="End" state="{{ formErrors.End ? 'error' : '' }}" ng-required="true" ng-change="isValid(formFieldset['End'].$valid, 'End')" ng-model="entity.End" type="date"> </fd-input> <fd-form-message dg-type="error">Incorrect Input</fd-form-message> </fd-form-input-message-group> </fd-form-item> <fd-form-item horizontal="false"> <fd-form-label for="idHours" dg-required="true" dg-colon="true">Hours </fd-form-label> <fd-form-input-message-group dg-inactive="{{ formErrors.Hours ? false : true }}"> <fd-input id="idHours" name="Hours" state="{{ formErrors.Hours ? 'error' : '' }}" ng-required="true" ng-change="isValid(formFieldset['Hours'].$valid, 'Hours')" ng-model="entity.Hours" min="0" max="40" dg-input-rules="{ patterns: [''] }" type="number" placeholder="Enter Hours"> </fd-input> <fd-form-message dg-type="error">Incorrect Input</fd-form-message> </fd-form-input-message-group> </fd-form-item> </fd-form-group> </fd-fieldset> </fd-scrollbar> </fd-message-page-subtitle> <fd-message-page-actions> <fd-button class="fd-margin-end--tiny fd-dialog__decisive-button" compact="true" dg-type="emphasized" dg-label="Submit" ng-click="submit()" state="{{ !isFormValid ? 'disabled' : '' }}"> </fd-button> <fd-button class="fd-dialog__decisive-button" compact="true" dg-type="transparent" dg-label="Cancel" ng-click="resetForm()"></fd-button> </fd-message-page-actions> </fd-message-page> </div> </body> </html>
- Right click on the
api
folder and select New → File. - Enter
controller.js
for the name of the file. - Double-click to open the file.
-
Replace the content with the following:
angular.module('page', ["ideUI", "ideView"]) .controller('PageController', ['$scope', '$http', function ($scope, $http) { $scope.entity = {}; $scope.optionsProject = [{ text: "Project Alpha", value: "Project Alpha" }, { text: "Project Beta", value: "Project Beta" }, { text: "Project Evolution", value: "Project Evolution" }, { text: "Project Next", value: "Project Next" }]; $scope.isValid = function (isValid, property) { $scope.formErrors[property] = !isValid ? true : undefined; for (let next in $scope.formErrors) { if ($scope.formErrors[next] === true) { $scope.isFormValid = false; return; } } $scope.isFormValid = true; }; $scope.submit = function () { $http.post("/services/v4/js/sample-bpm/api/process.js", JSON.stringify($scope.entity)).then(function (response) { if (response.status != 202) { alert(`Unable to submit Time Entry Request: '${response.message}'`); $scope.resetForm(); return; } alert("Time Entry Request successfully submitted"); $scope.resetForm(); }); }; $scope.resetForm = function () { $scope.entity = {}; $scope.formErrors = { Project: true, Start: true, End: true, Hours: true, }; }; $scope.resetForm(); }]);
Create Process Form
The process form would call the server-side javascript api that was created before and will resume the business process execution.
- Right click on the
sample-bpm
project and select New → Folder. - Enter
process
for the name of the folder. - Create
index.html
andcontroller.js
files.
- Right click on the
process
folder and select New → HTML5 Page. - Enter
index.html
for the name of the file. - Double-click to open the file.
-
Replace the content with the following:
<!DOCTYPE HTML> <html lang="en" xmlns="http://www.w3.org/1999/xhtml" ng-app="page" ng-controller="PageController"> <head> <meta charset="utf-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1"> <link rel="icon" href="data:;base64,iVBORw0KGgo=" dg-brand-icon /> <title dg-brand-title></title> <theme></theme> <script type="text/javascript" src="/services/v4/js/resources-core/services/loader.js?id=application-view-js"> </script> <link type="text/css" rel="stylesheet" href="/services/v4/js/resources-core/services/loader.js?id=application-view-css" /> <script type="text/javascript" src="controller.js"></script> </head> <body class="dg-vbox" dg-contextmenu="contextMenuContent"> <div> <fd-message-page glyph="sap-icon--approvals"> <fd-message-page-title>Approve Time Entry Request</fd-message-page-title> <fd-message-page-subtitle> <fd-scrollbar class="dg-full-height"> <fd-fieldset class="fd-margin--md" ng-form="formFieldset"> <fd-form-group name="entityForm"> <fd-form-item horizontal="false"> <fd-form-label for="idHours" dg-colon="true">Hours </fd-form-label> <fd-form-input-message-group> <fd-input id="idProject" name="Project" ng-model="entity.Project" type="input" ng-readonly="true"> </fd-input> <fd-form-message dg-type="error">Incorrect Input</fd-form-message> </fd-form-input-message-group> </fd-form-item> <fd-form-item horizontal="false"> <fd-form-label for="idStart" dg-colon="true">Start </fd-form-label> <fd-form-input-message-group> <fd-input id="idStart" name="Start" ng-model="entity.Start" type="date" ng-readonly="true"> </fd-input> <fd-form-message dg-type="error">Incorrect Input</fd-form-message> </fd-form-input-message-group> </fd-form-item> <fd-form-item horizontal="false"> <fd-form-label for="idEnd" dg-colon="true">End</fd-form-label> <fd-form-input-message-group> <fd-input id="idEnd" name="End" ng-model="entity.End" type="date" ng-readonly="true"> </fd-input> <fd-form-message dg-type="error">Incorrect Input</fd-form-message> </fd-form-input-message-group> </fd-form-item> <fd-form-item horizontal="false"> <fd-form-label for="idHours" dg-colon="true">Hours </fd-form-label> <fd-form-input-message-group> <fd-input id="idHours" name="Hours" ng-model="entity.Hours" type="number" ng-readonly="true"> </fd-input> <fd-form-message dg-type="error">Incorrect Input</fd-form-message> </fd-form-input-message-group> </fd-form-item> </fd-form-group> </fd-fieldset> </fd-scrollbar> </fd-message-page-subtitle> <fd-message-page-actions> <fd-button class="fd-margin-end--tiny fd-dialog__decisive-button" compact="true" dg-type="emphasized" dg-label="Approve" ng-click="approve()"> </fd-button> <fd-button class="fd-dialog__decisive-button" compact="true" dg-type="negative" dg-label="Reject" ng-click="reject()"></fd-button> </fd-message-page-actions> </fd-message-page> </div> </body> </html>
- Right click on the
process
folder and select New → File. - Enter
controller.js
for the name of the file. - Double-click to open the file.
-
Replace the content with the following:
angular.module('page', ["ideUI", "ideView"]) .controller('PageController', ['$scope', '$http', '$location', function ($scope, $http, $location) { let data = JSON.parse(atob(window.location.search.split("=")[1])); $scope.executionId = data.executionId; $scope.user = data.User; $scope.entity = { Project: data.Project, Start: new Date(data.Start), End: new Date(data.End), Hours: parseInt(data.Hours) }; $scope.approve = function () { $http.post("/services/v4/js/sample-bpm/api/process.js/continue/" + $scope.executionId, JSON.stringify( { user: $scope.user, approved: true } )).then(function (response) { if (response.status != 202) { alert(`Unable to approve Time Entry Request: '${response.message}'`); return; } $scope.entity = {}; alert("Time Entry Request Approved"); }); }; $scope.reject = function () { $http.post("/services/v4/js/sample-bpm/api/process.js/continue/" + $scope.executionId, JSON.stringify( { user: $scope.user, approved: false } )).then(function (response) { if (response.status != 202) { alert(`Unable to reject Time Entry Request: '${response.message}'`); return; } $scope.entity = {}; alert("Time Entry Request Rejected"); }); }; }]);
(Optional) Create custom approval form
You can use custom form to approve / reject the user task in the process. - Right-click on the sample-bpm project and select New → Form Definition. - Enter approval.form for the name of the custom form - Right-click on the form and choose Open With -> Code Editor - Replace the content of the file with the following:
{
"feeds": [],
"scripts": [],
"code": "let url = new URL(window.location);\nlet params = new URLSearchParams(url.search);\nlet taskId = params.get(\"taskId\");\nconsole.log(taskId);\n\n$scope.approve = function () {\n $http.post(\"/services/bpm/bpm-processes/tasks/\" + taskId, JSON.stringify(\n {\n action: \"COMPLETE\",\n data: {\n user: $scope.user,\n decision: $scope.model.decisionText,\n isRequestApproved: true\n }\n }\n )).then(function (response) {\n if (response.status != 200) {\n alert(`Unable to approve Time Entry Request: '${response.message}'`);\n return;\n }\n $scope.entity = {};\n alert(\"Time Entry Request Approved\");\n });\n};\n\n$scope.reject = function () {\n $http.post(\"/services/bpm/bpm-processes/tasks/\" + taskId, JSON.stringify(\n {\n action: \"COMPLETE\",\n data: {\n user: $scope.user,\n decision: $scope.model.decisionText,\n isRequestApproved: true\n }\n }\n )).then(function (response) {\n if (response.status != 200) {\n alert(`Unable to reject Time Entry Request: '${response.message}'`);\n return;\n }\n $scope.entity = {};\n alert(\"Time Entry Request Rejected\");\n });\n};",
"form": [
{
"controlId": "input-textarea",
"groupId": "fb-controls",
"id": "i8eb25310-5c60-da90-3e71-41d946687248",
"label": "Reason",
"horizontal": false,
"isCompact": false,
"placeholder": "Decision reason",
"type": "text",
"model": "decisionText",
"required": true,
"minLength": 0,
"maxLength": -1,
"validationRegex": "",
"errorState": "Incorrect input"
},
{
"controlId": "container-hbox",
"groupId": "fb-containers",
"children": [
{
"controlId": "button",
"groupId": "fb-controls",
"label": "Approve",
"type": "positive",
"sizeToText": false,
"isSubmit": true,
"isCompact": false,
"callback": "approve()"
},
{
"controlId": "button",
"groupId": "fb-controls",
"label": "Reject",
"type": "negative",
"sizeToText": false,
"isSubmit": true,
"isCompact": false,
"callback": "reject()"
}
]
}
]
}
(Optional) Email Configuration
In order to receive email notifications about the process steps a mail configuration should be provided.
The following environment variables are needed:
DIRIGIBLE_MAIL_USERNAME=<YOUR_MAIL_USERNAME>
DIRIGIBLE_MAIL_PASSWORD=<YOUR_MAIL_PASSWORD>
DIRIGIBLE_MAIL_TRANSPORT_PROTOCOL=smtps
DIRIGIBLE_MAIL_SMTPS_HOST=<YOUR_MAIL_HOST>
DIRIGIBLE_MAIL_SMTPS_PORT=465
APP_SAMPLE_BPM_FROM_EMAIL=<SENDER_EMAIL>
APP_SAMPLE_BPM_TO_EMAIL=<RECEIVER_EMAIL>
Connecting Eclipse Dirigible with SendGrid SMTP Relay
To use a gmail account for the mail configuration follow the steps in the Connecting Eclipse Dirigible with SendGrid SMTP Relay blog.
DIRIGIBLE_MAIL_USERNAME=apikey
DIRIGIBLE_MAIL_PASSWORD=<YOUR_API_KEY_HERE>
DIRIGIBLE_MAIL_TRANSPORT_PROTOCOL=smtps
DIRIGIBLE_MAIL_SMTPS_HOST=smtp.sendgrid.net
DIRIGIBLE_MAIL_SMTPS_PORT=465
APP_SAMPLE_BPM_FROM_EMAIL=<SENDER_EMAIL>
APP_SAMPLE_BPM_TO_EMAIL=<RECEIVER_EMAIL>
Demo
[Using custom approval form] 1. Navigate to http://localhost:8080/services/v4/web/sample-bpm/submit/ to open the Submit form. 2. Enter the required data and press the Submit button. 3. Navigate to the Processes Workspace 4. Select your active process instance in the Process Instances view 5. Click on the User Tasks view 6. Click on the active user task, the Claim button should become active 7. Click Claim (Assuming you are logged in as admin the task should move from Candidate tasks to Assigned tasks) 8. In the Assigned task section click on the Open Form button 9. The custom approval form should be opened in a new browser window 10. Enter 'Decision reason' and click Approve to resume the process execution 11. If successful you can check that the process is completed and moved to the "Historic Process Instances" view
[Using approval url from the console]
1. Navigate to http://localhost:8080/services/v4/web/sample-bpm/submit/ to open the Submit form.
1. Enter the required data and press the Submit button.
1. If email configuration was provided an email notification will be send to the email address set by the APP_SAMPLE_BPM_TO_EMAIL=<RECEIVER_EMAIL>
environment variable.
1. If email configuration wasn't provided then in the Console
view the following message can be found:
```
Approve Request URL: http://localhost:8080/services/v4/web/sample-bpm/process/?data=eyJleGVjdXRpb25JZCI6IjE4Ni...
```
- Open the URL from the
Console
view or open it from the email notification. - The Process form would be prefilled with the data that was entered in the Submit form.
- Press the Approve or Reject button to resume the process execution.
- One more email notification would be sent and message in the
Console
would be logged as part of the last step of the Business Process.
BPM Sample GitHub Repository
Go to https://github.com/dirigiblelabs/sample-bpm to find the complete sample. The repository can be cloned in the Git
perspective and after few minutes the BPM Sample would be active.