Differences
This shows you the differences between two versions of the page.
Both sides previous revision Previous revision Next revision | Previous revision Next revision Both sides next revision | ||
devel:documentation:workflows:dev:workflow [2018/11/14 13:12] kucerar Priklad jak pouzit selectBox a taskHistory |
devel:documentation:workflows:dev:workflow [2020/06/25 14:52] svandav [Sending notifications] |
||
---|---|---|---|
Line 1: | Line 1: | ||
+ | ====== Workflow ====== | ||
+ | |||
+ | {{tag> workflow}} | ||
+ | |||
+ | We are using Activiti BPM Platform workflow engine. It is currently very widespread workflow engine, which is used for its ease of use and performance. It's main purpose is control and execution of process written by BPMN 2.0 language in Java. | ||
+ | |||
+ | Its advantage lies in integration with Spring framework and it is easy to use in ours devstack. Activiti Platform sets up Rest Api and like that it will be using most of it's functionalities. By running Rest and Activiti Platform on a stand-alone application server, we can communicate even with a non-Java environment. Disadvantage of this solution is lossage of direct Java integration. It is possible to call Spring beans directly from workflow process (by [[http:// | ||
+ | |||
+ | |||
+ | <note tip> | ||
+ | * **/ | ||
+ | * **/ | ||
+ | * **/ | ||
+ | </ | ||
+ | |||
+ | =====Design of process===== | ||
+ | Design of workflow process is realized with XML in format BPM 2.0. It is very helpful to use Activiti Designer. It is [[http:// | ||
+ | |||
+ | **Sample of Activiti process in IDE Eclipse plugin:** | ||
+ | |||
+ | {{ : | ||
+ | |||
+ | =====Example of usage Expression Language===== | ||
+ | |||
+ | In our example of EL (lower), there is an option to assembly description of user's task. There is element " | ||
+ | |||
+ | Because of this procedure, we need in process attributes identifiers. With identifiers and EL expressions, | ||
+ | |||
+ | < | ||
+ | < | ||
+ | activiti: | ||
+ | < | ||
+ | ${defaultIdmRoleService.get(roleIdentifier).name} vedoucím pro uživatele | ||
+ | ${defaultIdmIdentityService.get(identityIdentifier).getUsername()}. | ||
+ | </ | ||
+ | </ | ||
+ | </ | ||
+ | |||
+ | =====User task===== | ||
+ | Workflow activity **UserTask** can assign a task to a specific user by attribute **assignee**. The task can have just one assignee. If we want to assign more users, who can solve the task, we use attribute **candidateUsers**. | ||
+ | |||
+ | In this step there are no differences between assignee and candidateUsers. If a user is assignee or is in a list of **candidateUsers**, | ||
+ | |||
+ | =====Service task===== | ||
+ | Service task is activity run automatically by the system. Code, which will be executed, can be defined: | ||
+ | * In Java class, which path is defined in attribute **class**. | ||
+ | * In Java class implementing JavaDelegate. | ||
+ | * **By Experssion Language**. | ||
+ | |||
+ | In our example of EL (lower) is definition of service activity, which run expression **# | ||
+ | |||
+ | This expression calls method **addRole** for adding a new role. The new role will be passed to **newIdmIdentityRole** process, which contains new link DTO between role and user. | ||
+ | Of service **defaultIdmIdentityService** method **addRole** is called from Spring context. | ||
+ | |||
+ | |||
+ | < | ||
+ | < | ||
+ | | ||
+ | | ||
+ | | ||
+ | </ | ||
+ | </ | ||
+ | |||
+ | =====Script task===== | ||
+ | Script task activity allows run scripts defined in the workflow. Workflow supports JavaScript and Groovy. We will be using Groovy for it's performance and specific implementation version (in pom.xml). JavaScript engine is part of Java and implementation version will depend on the version on which version whole application will run. | ||
+ | |||
+ | The script is used, when EL cannot make expression we want. For example of iteration or creation new instance of Object and setting attributes. In our example (lower) we are creating new instance of link object between role and user and object is filled by identificators. This instance is then put in process variables with key **newIdmIdentityRole** execution.setVariable(" | ||
+ | |||
+ | < | ||
+ | < | ||
+ | | ||
+ | | ||
+ | | ||
+ | | ||
+ | < | ||
+ | | ||
+ | ir.setIdentity(identityIdentifier); | ||
+ | ir.setRole(roleIdentifier); | ||
+ | ir.setValidFrom(validFrom); | ||
+ | ir.setValidTill(validTill); | ||
+ | | ||
+ | </ | ||
+ | </ | ||
+ | </ | ||
+ | =====Email Task===== | ||
+ | Email task activity sends emails. Our emailer (EmailService) is injected into workflow engine, so it is possible disable sending notification (e.g. for debuging). Email can be send to specific identity by putting in username instead of email address. | ||
+ | |||
+ | < | ||
+ | < | ||
+ | < | ||
+ | < | ||
+ | < | ||
+ | </ | ||
+ | < | ||
+ | < | ||
+ | </ | ||
+ | < | ||
+ | < | ||
+ | </ | ||
+ | < | ||
+ | < | ||
+ | </ | ||
+ | < | ||
+ | < | ||
+ | </ | ||
+ | </ | ||
+ | </ | ||
+ | </ | ||
+ | |||
+ | =====Workflow localization===== | ||
+ | {{tag> localization}} | ||
+ | |||
+ | We support localization of workflow process. The main principle is, the process definition (XML) does not contain translations. Translations contain frontend part of our application in the standard localization files. Workflow definition only contains information on how to construct the localization key and variables needs for translation. | ||
+ | |||
+ | At this point, it is possible to translate these parts of the workflow process: | ||
+ | |||
+ | * **Workflow process name** | ||
+ | * **Task name** | ||
+ | * **Task description** | ||
+ | |||
+ | ==== How translate the name of a workflow process ==== | ||
+ | |||
+ | For example, we will be using basic process for change the user permission ' | ||
+ | This process contains activity with name ' | ||
+ | |||
+ | Without translation looks code this: | ||
+ | |||
+ | <code java> | ||
+ | | ||
+ | </ | ||
+ | |||
+ | For get the name of the applicant it's calls the identity service and user's full name is returned. We want to use this result in our translation. For this, we have to wrap (uses double braces) name of the applicant. | ||
+ | |||
+ | With wrapped variable looks code this: | ||
+ | |||
+ | <code java> | ||
+ | | ||
+ | </ | ||
+ | |||
+ | <note tip>You can use more variables in one translation. Name of the variables are order number in the source text.</ | ||
+ | |||
+ | Now we have to create translation in the frontend. In the localization file (usually in the / | ||
+ | |||
+ | <code javascript> | ||
+ | " | ||
+ | " | ||
+ | " | ||
+ | } | ||
+ | } | ||
+ | </ | ||
+ | |||
+ | Where ' | ||
+ | |||
+ | ==== Translation with the context ==== | ||
+ | |||
+ | Sometimes there is a need for multiple variants of translations to one key. To do this, you can use a variable defining context. The context defines the end part of the localization key (in our example is context ' | ||
+ | |||
+ | <code java> | ||
+ | | ||
+ | </ | ||
+ | |||
+ | <code javascript> | ||
+ | " | ||
+ | " | ||
+ | " | ||
+ | " | ||
+ | } | ||
+ | } | ||
+ | </ | ||
+ | ==== How translate the name and description of a workflow task ==== | ||
+ | |||
+ | For example, we will be using again basic process for change the user permission ' | ||
+ | This process contains activity with the name ' | ||
+ | |||
+ | We creates translation in the frontend for this task. In the localization file (usually in the / | ||
+ | |||
+ | <code javascript> | ||
+ | " | ||
+ | " | ||
+ | " | ||
+ | " | ||
+ | " | ||
+ | " | ||
+ | } | ||
+ | } | ||
+ | } | ||
+ | } | ||
+ | </ | ||
+ | |||
+ | Where ' | ||
+ | |||
+ | Usually we want translate documentation for process task. In our example contains documentation of the ' | ||
+ | |||
+ | <code javascript> | ||
+ | | ||
+ | </ | ||
+ | |||
+ | It means, documentation contains the name of a process with wrapped variable contains the name of the applicant. | ||
+ | We will uses this variable in translation on the frontend. In the localization file (usually in the / | ||
+ | |||
+ | <code javascript> | ||
+ | " | ||
+ | " | ||
+ | " | ||
+ | " | ||
+ | " | ||
+ | " | ||
+ | " | ||
+ | } | ||
+ | } | ||
+ | } | ||
+ | } | ||
+ | </ | ||
+ | |||
+ | Translation of description contains variable with the name of applicant again. | ||
+ | |||
+ | <note tip>If none transaltion for task in the specific workflow is found, then we try find translation without workflow ID (for example ' | ||
+ | |||
+ | =====Passage of workflow - solver decision===== | ||
+ | The main principle of the workflow process is a creation of user task. An assigned user must decide how this task will be solved. The development of process is dependent on assign user's decision. We can explain further in an example of process assign role. A user will make a request to assign himself a specific role. A task is created and will be assigned to superior of the requester. Superior will decide if he agrees to assign a role to the requester or not. This **decision** will be **assessed**. In case of rejection, task will be terminated, otherwise, process will continue with next round of decisions. | ||
+ | |||
+ | It is important to properly define process, so solver will clearly know, of which possibilities he has to choose. And this has to be determined in each user's task. After termination of user's task, evaluation of decision have to be implemented. | ||
+ | |||
+ | ====Definition of decisions==== | ||
+ | Definition of decisions in user tasks have to be more complex than text represented of code enumeration " | ||
+ | |||
+ | In workflow we have to know how to define decision as complex type, which contains all attributes for generation these decision buttons. | ||
+ | |||
+ | Decision attributes: | ||
+ | * **id** - Decision' | ||
+ | * **label** - Description of decision for user (" | ||
+ | * **level** - It sets design of button (" | ||
+ | * **tooltip** - Description in detail ("By approving you will agree with terms and conditions" | ||
+ | * **showWarning** - Defines if warning message will be shown on end of terminating task (true/ | ||
+ | * **warningMessage** - Defines contend of showWarning message ("Do you really want to approve request?" | ||
+ | * **permissions** - Defines required permissions, | ||
+ | * **skipValidation** - If you not used this attribute the behavioral will same as if you set it to false. When you set it to true then the validation of form(userTask) will be ignored. | ||
+ | |||
+ | ===From properties=== | ||
+ | We created our decision system, because Aktiviti does not have decision system. On the other hand, Aktiviti allows define **form properties**. This system allows set for each user's activity, which process attributes will be shown. | ||
+ | |||
+ | In our example (lower) there are two **properties** defined in user task. The first property has id " | ||
+ | |||
+ | < | ||
+ | < | ||
+ | < | ||
+ | ${defaultIdmIdentityService.get(identityIdentifier).getUsername()}. | ||
+ | </ | ||
+ | < | ||
+ | < | ||
+ | id=" | ||
+ | name=" | ||
+ | type=" | ||
+ | writable=" | ||
+ | required=" | ||
+ | </ | ||
+ | < | ||
+ | id=" | ||
+ | name=" | ||
+ | type=" | ||
+ | readable=" | ||
+ | writable=" | ||
+ | required=" | ||
+ | </ | ||
+ | </ | ||
+ | </ | ||
+ | |||
+ | ===Decision=== | ||
+ | In previous article we described how **form properties** works. We do not want return complex definition of decison as string, so we need new data typ. Our data type is registered by Spring configuration in Aktiviti engine as **DecisionFormType** with key **decision**. | ||
+ | |||
+ | Decision values are defined by workflow definition as JSON objects and these values are written at creation of task (**approve**, | ||
+ | |||
+ | < | ||
+ | < | ||
+ | < | ||
+ | < | ||
+ | " | ||
+ | </ | ||
+ | </ | ||
+ | </ | ||
+ | < | ||
+ | < | ||
+ | < | ||
+ | " | ||
+ | </ | ||
+ | </ | ||
+ | </ | ||
+ | < | ||
+ | < | ||
+ | < | ||
+ | vedoucímu?", | ||
+ | </ | ||
+ | </ | ||
+ | </ | ||
+ | </ | ||
+ | |||
+ | |||
+ | Access to these attributes is realized by form properties (type = decision). In our example (lower) there is user's task, which has form properties **approve** and **disapprove**. In this activity user will have two decision options (buttons). | ||
+ | |||
+ | < | ||
+ | < | ||
+ | < | ||
+ | < | ||
+ | < | ||
+ | </ | ||
+ | </ | ||
+ | </ | ||
+ | |||
+ | |||
+ | ====Evaluation of decision==== | ||
+ | |||
+ | We already described how to define decision for user's activity, Now we will evaluate decisions in process. | ||
+ | |||
+ | Before closing user's task, Aktiviti engine is called with form properties values and decision' | ||
+ | |||
+ | In next step component **exclusiveGateway** (XOR) is used. This component evaluate conditions on each outgoing path, if evaluation of path is **true**, process will continue on this path. If all outgoing paths are evaluated as **false**, exception is thrown. | ||
+ | |||
+ | In our example (lower) is defined two outgoing paths - **sequenceFlow**. The first path has condition **${decision.equals(" | ||
+ | |||
+ | |||
+ | < | ||
+ | < | ||
+ | < | ||
+ | < | ||
+ | </ | ||
+ | < | ||
+ | < | ||
+ | </ | ||
+ | </ | ||
+ | |||
+ | =====Dynamic detail of task===== | ||
+ | We described **Form properties** as process attributes, which will be shown in user's task and we defined **decisions** as buttons for controlling the flow (paths) in process. Form properties can be used for dynamic design for each task. | ||
+ | |||
+ | Principle is similiar to **decision**. In process is definition, which components will be used, and on the frontend side is by that definition generated detail by using default frontend components. Aktiviti engine by default provide a few components (**string**, | ||
+ | |||
+ | But these components are insufficient, | ||
+ | |||
+ | * **textArea** - It will be shown as one-line field (as additional options, key " | ||
+ | * **textField** - It will be shown as multiline editor (as additional options, key " | ||
+ | * **date** | ||
+ | * **checkbox** - Shows as checkbox. | ||
+ | * **selectBox** - Show as selectbox. It is using EnumSelectBox component. | ||
+ | * **taskHistory** - It can show previous tasks from WF in table. | ||
+ | |||
+ | Each component has additional settings **From property**: | ||
+ | * **id** - Unique component identifier in single process. If attribute **variables** is not filled, then id even define process attribute. | ||
+ | * **name** - Name, which will be used as description of component (label). It is possible to use final text or localization key. | ||
+ | * **type** - Type of component. See above (textArea, textField, etc.). | ||
+ | * **variable** - Defines process attribute, which can be used in component (value can be read, but also result can be written). | ||
+ | * **expression** - Expression (EL) can be defined here. Expression will be executed at start of the task and result will be used as value of component. | ||
+ | * **readable** - Defines, if component will be shown (default value is **true**). | ||
+ | * **writable** - Defines, if component could be edited (default value is **true**). | ||
+ | * **required** - Defines, if value will be mandatory (default value is **false**). | ||
+ | |||
+ | <note tip> | ||
+ | |||
+ | ===Example of user's task selectBox: | ||
+ | {{: | ||
+ | |||
+ | Data which are displayed in selectBox can be set via Map< | ||
+ | You can set options directly in WF without service on BE. Just use field Default in Form property configuration {" | ||
+ | |||
+ | ===Example of user's task taskHistory: | ||
+ | {{: | ||
+ | |||
+ | Data which are displayed in table can be set via List< | ||
+ | |||
+ | |||
+ | |||
+ | In our user's task example (lower) there are generated frontend result and definition in XML. | ||
+ | |||
+ | ===Example of user's task with dynamic detail:=== | ||
+ | {{ : | ||
+ | ===Example of user's task definition with dynamic detail:=== | ||
+ | |||
+ | < | ||
+ | < | ||
+ | < | ||
+ | < | ||
+ | < | ||
+ | < | ||
+ | < | ||
+ | < | ||
+ | < | ||
+ | </ | ||
+ | < | ||
+ | < | ||
+ | < | ||
+ | </ | ||
+ | < | ||
+ | < | ||
+ | </ | ||
+ | </ | ||
+ | </ | ||
+ | =====Custom task detail===== | ||
+ | |||
+ | Dynamic definition of components provide us with quick and easy way to add another item to task. But even this system has a few disadvantages: | ||
+ | |||
+ | * Components cannot interact with each other. | ||
+ | * Cannot modified placement of each component on task detail (except for order). | ||
+ | * Cannot show complex components like tables with specific properties (show detail, specific filtr, etc.). | ||
+ | |||
+ | But if some of these feature is necessary on task detail, yet it can be made by specially created detail, which can be modified as you wish. it means, in frontend will be created new component (page), which will be used in specific task. | ||
+ | |||
+ | Usage of this specially created detail is setted in user's task detail with parameter **Form key**. If this parameter is filled, **DynamicTaskDetail** will not be used, but component with same name as value of Form key parameter will be used. | ||
+ | |||
+ | < | ||
+ | < | ||
+ | </ | ||
+ | |||
+ | |||
+ | <note tip> | ||
+ | |||
+ | < | ||
+ | module.exports = { | ||
+ | ' | ||
+ | ' | ||
+ | ' | ||
+ | ' | ||
+ | { | ||
+ | ' | ||
+ | ' | ||
+ | } | ||
+ | ] | ||
+ | }; | ||
+ | </ | ||
+ | |||
+ | |||
+ | =====Localization===== | ||
+ | |||
+ | There are three ways to name buttons in decision: | ||
+ | * **Custom text** - If we do not want to create localization we can put in custom text. We could put " | ||
+ | * **Localization key** - If we want use specific localization translation, | ||
+ | * **Automatic assembly of localization key** - If we do not fill value **label**, this value will be filled automatically in form **decision.approve.label**, | ||
+ | |||
+ | < | ||
+ | |||
+ | <note tip>In similar way works localization for items in dynamic generated detail task. Automatic key uses prefix **formData**. Supported items are **name**, **tooltip**, | ||
+ | |||
+ | **Localization example** | ||
+ | < | ||
+ | " | ||
+ | " | ||
+ | " | ||
+ | " | ||
+ | " | ||
+ | }, | ||
+ | " | ||
+ | " | ||
+ | " | ||
+ | " | ||
+ | }, | ||
+ | " | ||
+ | " | ||
+ | " | ||
+ | " | ||
+ | }, | ||
+ | " | ||
+ | " | ||
+ | " | ||
+ | " | ||
+ | }, | ||
+ | " | ||
+ | " | ||
+ | " | ||
+ | " | ||
+ | } | ||
+ | }, | ||
+ | " | ||
+ | " | ||
+ | " | ||
+ | " | ||
+ | " | ||
+ | }, | ||
+ | " | ||
+ | " | ||
+ | " | ||
+ | " | ||
+ | }, | ||
+ | " | ||
+ | " | ||
+ | " | ||
+ | " | ||
+ | } | ||
+ | } | ||
+ | </ | ||
+ | |||
+ | =====Sending notifications===== | ||
+ | There is global configuration in application properties, which can turned on or off sending notifications from UserTask. | ||
+ | < | ||
+ | # Global property that allow disable or enable sending notification from WF | ||
+ | idm.sec.core.wf.notification.send=false | ||
+ | </ | ||
+ | |||
+ | Sending notification can be turnd off in each UserTask by setting Form property **sendNotification**. Example of this setting: | ||
+ | <code xml> | ||
+ | < | ||
+ | id=" | ||
+ | type=" | ||
+ | expression=" | ||
+ | writable=" | ||
+ | </ | ||
+ | </ | ||
+ | |||
+ | Type of variable have to be **configuration**. It is FormType, which is not propagated to frontend. ID or name have to be **sendNotification**. It does not matter even in case of **idm.sec.core.wf.notification.send** is setted to false, because form property **sendNotification** has **higher priority**. | ||
+ | |||
+ | If Form property **sendNotification** is not explicitly filled, notification will be sent. | ||
+ | |||
+ | New listener **TaskSendNotificationEventListener** was created for purposes of sending notifications. This listener reacts on these events: TASK\_ASSIGNED, | ||
+ | |||