FAQ

Questions and Answers

Process Logging (Script Task)

JavaScript Script Format

Declare and use a logger in the 'Script' field or use the standard println function.

var logger = java.util.logging.Logger;
var log = logger.getLogger('MY_JS_LOGGER');
log.info('Im logging INFO');

Groovy Script Format

import org.slf4j.*
 
Logger logger = LoggerFactory.getLogger('MyScriptLogger');
logger.info('Hello World');

Analyzing Process Execution Errors

Various errors may occur during process execution that are not apparent during the initial process review in monitoring.

To avoid situations where errors are hard to identify, set additional wait points.

Example with a Gateway Condition Error

empty

There is a process that, at first glance, seems to have no issues with execution. The process is waiting for a task to be completed.

empty

If we enter the task and try to process it, we'll see an error - a 500 response code for the task processing method. This means that after processing the task, an exception occurs during the process execution that cannot be handled. Since there are no additional wait points set in the process (the user task has the default one), the process, when an exception occurs, rolls back the transaction to the last wait point, which is the task.

In such cases, it is necessary to check the execution service log. This can be done through Kibana or by directly exporting logs from the service.

Log for this case

At the end of this log, you may encounter the following error (you can see the full error description and stack trace in the log file):

ERROR: condition expression returns non-Boolean: result has class java.lang.String and not java.lang.Boolean

This error indicates that the Conditional Expression returns a result of type "String," but it should be of type "Boolean" (true/false). Such an error can occur in places where conditional expressions are used—on branches/joins or on an element: intermediate conditional event.

In our process, this error could only occur in two places, namely, the two remaining gateways. By checking variables and conditions on each branch,

In this specific situation, an error occurred at the gateway after the "Get the list of vacation requests" step, specifically when checking the vacationRequestStatusId variable, which in this process version had the type "String" (as seen in the variable list in the screenshot above). You can download the process diagram and check why the error occurred.

Process Diagram

Process Execution Stuck at the Kafka Message Consumption Cube

In the business process, there is a step of subscribing to a Kafka topic. During execution, if there is a message in Kafka, the process may hang. This means that the correlation identifiers for the message in Kafka and the process are different. It is necessary to correctly configure the correlation mechanism, described below.

Error When Starting a Process Instance

Option 1

When starting a business process instance (calling the POST method), you may receive an HTTP 500 response code. You can parse the errorMessage parameter or check the MS logs. The specified parameter will contain a message like:

Internal exception, please see server log, message: Output topic «input-topic» not registered

This error indicates that the "input-topic" topic is not registered for sending (output) messages in the business process execution microservice. A similar error can occur for subscription topics.

Log for this case

Option 2

The situation arises when transforming an existing (copied) process, where the default start step is changed to a message start step.

Conditions that led to the error:

  • Upon the initial publication of the business process, the name and identifier of the process pool remain unchanged.
  • In subsequent version changes to the diagram and publication, change the name and identifier of the process pool.
ENGINE-13030 Cannot correlate a message with name 'qhldrq-vacationrequest-command-processstart' to a single process definition. 2 process definitions match the correlations keys: CorrelationSet [businessKey=null, processInstanceId=null, processDefinitionId=null, correlationKeys=null, localCorrelationKeys=null, tenantId=null, isTenantIdSet=false, isExecutionsOnly=false]

Error summary: Two published processes with the same start element subscribed to the same topic and waiting for messages in Kafka to start.

This error can be corrected, but it is best to eliminate the root cause that led to it.

To fix the problem, it will be sufficient not to start the first version of the process AND in the service's database, find the act_ru_event_subscr table, and delete the record with the old process identifier, which is no longer relevant. This way, the subscription of the starting process to the topic is removed, leaving only the current one.

The actions described above will fix the problem, but it may reoccur. To address this, it is necessary to identify the problematic version of the business process (input data: diagram identifier):

  1. In the "Process Designer" module's database, in the diagram_item table, find all records related to the process pool.
select *
from qbpmdesigner.q_bpm_diagram_item qbdi
where diagram_version_id in (
    select id
    from qbpmdesigner.q_bpm_diagram_version qbdv
    where diagram_id = <Diagram Record Identifier>
)
and type = 'process'
  1. Using the system_name column, identify the system name of the outdated process pool.
  2. If there are multiple records for the outdated process pool, remember all values from the diagram_version_id column – this is the "bad" process version.
  3. Delete all existing connections in all tables of the "Process Designer" module database, including the act_* tables (Camunda system tables).

After completing these actions, the error will be considered resolved.

Message Correlation

Processes may involve asynchronous interaction through Kafka. Process instance(s) can respond to incoming messages using the correlation key - correlationId. Published messages in Kafka are matched with the process instance - message correlation.

A message is not sent directly to an instance. Message correlation is based on subscriptions that contain message name and correlation key.

There are two possible implementation approaches (the first one works by default).

  1. Automatic All settings for the microservice remain unchanged, and the correlation key is generated automatically and placed in the correlationId parameter.

    The key is generated automatically when starting a process instance. In the "Send Message" cube, when sending a message, the key is automatically placed in both the body and the header.
    Sending a message to Kafka is done for the consumer system, and if the main process instance subscribes to the response message, the consumer system in the response message must return the correlation key unchanged.
    When there is a suitable message in Kafka, the process instance compares its generation key with the one that came from the message (checking the message's ownership to the instance).

    🚫

    If the message lacks the correlation key or does not match the process instance's correlation key, the instance will hang on the message receiving cube. If successful processing cannot be guaranteed, it is advisable to set a boundary timer event (timeout).

  2. Manual

    The correlation key can be generated/set manually during process design. To do this, in the settings of the qbpetqbpmplayer service, in the ${QBPM_CORRELATION_KEYS:dqMessageGuid} parameter, specify the field name for the correlation key.

    When sending a message to Kafka, the specified parameter with the correlation key will be placed in both the body and the header of the message.

Reserved Platform Events

Message Header NameEvent DescriptionMessage BodyInitiator -> Receiver
dq-q-bpm-task-eventEvent on user task change (creation, update, closure) - passing data about the performerHuman task → Player
taskActionEventTopicTask action event, tasks send information to the player.Human task → Player
taskCreateEventTopicEvent on task creationPlayer → Human task
taskCancelEventTopicEvent on task cancellation in case of process termination (kill)Human task → Player
dq-q-bpm-decision-definitionEvent with the description of a specific rule on publicationPlayer → Designer
dq-q-bpm-decision-requirements-definitionEvent with the description of a DMN diagram (decision tree) on publicationPlayer → Designer
dq-q-bpm-decision-evaluationEvent about executed business rulePlayer → Designer
dq-q-bpm-diagram-deploy-requestEvent about publication from the designer to the playerDesigner → Player
dq-q-bpm-diagram-deploy-responseEvent about the fact of publication from the player to the designerPlayer → Designer
dq-q-bpm-diagram-process-definitionEvent from the player to the designer, during which process pools in the diagram are highlighted and linked to the diagramPlayer → Designer
dq-q-bpm-diagram-versionEvent from the designer to Kafka on manual saving (floppy disk button in the editor) of the diagramDesigner → All
dq-q-bpm-process-instance-protocolProtocol on process instance execution. On start and completion.Player → Cockpit
dq-q-bpm-process-activity-protocolProtocol on process step instance execution. On start and completion.Player → Cockpit
dsOnAfterProcessDefinitionCreateEvent on process appearance (player sends to cockpit on publication)Player → Cockpit
dsOnProcessActivityEventEvent on each node of the process in the diagram from the playerPlayer → Cockpit
dsOnProcessInstanceEventWhen a process instance appears and completes. Process start status = ACTIVE, and process completion status = COMPLETED.Player → Cockpit
dq-q-bpm-incidentEvent on a failed process that is suspended for user interventionPlayer → Cockpit

Data Type Conversion in JavaScript Business Process Scripts

The business process context stores data in variables with Java object types. To process objects with JavaScript functions, it is necessary to convert Java objects obtained from the process context into JavaScript objects. For this conversion in script nodes of the process, use the methods of the ObjectMapper object.

writeValueAsString(variable <object>) // converting an object to a string
readValue(someVariableAsStringOutput, java.util.LinkedHashMap.class) // converting a string variable to a Java object of the specified type (in this case, LinkedHashMap)

To work with JavaScript objects in a business process script node, it is necessary to first convert a string variable to a JavaScript object:

JSON.parse(someVariableAsString)

To convert a JavaScript object to a string, you need to use the method:

JSON.stringify(someVariableAsObject)

The sequence of necessary transformations in the process script to obtain a Java object from the process context, process the data as a JavaScript object, and pass it back to the process context as a Java object:

// Getting a variable from the process context
var someVariable = execution.getVariable("someVariable");
 
// Converting the obtained object to a JSON string
var someVariableAsString = objectMapper.writeValueAsString(someVariable);
 
// Converting the JSON string to a JavaScript object
var someVariableAsObject = JSON.parse(someVariableAsString);
 
//
// Executing script logic with the JavaScript object
//
 
// Converting the JavaScript object to a string
var someVariableAsStringOutput = JSON.stringify(someVariableAsObject);
 
// Converting the string to a Java object (java.util.LinkedHashMap)
var someVariableAsMapOutput = objectMapper.readValue(someVariableAsStringOutput, java.util.LinkedHashMap.class);
 
// Passing the variable to the process context
execution.setVariable("someVariable", someVariableAsMapOutput);

Creating a Widget and Using it in a Custom Task Template

To create a widget, you need to follow the instructions (opens in a new tab).

Interaction Modes between the Widget and the Main Task Processing Form

  1. Task processing actions are controlled on the main form.

    This option is recommended for simple custom task processing forms that provide information to users for viewing only. The widget itself only provides visual representation. No control actions or additional logic need to be implemented in the application component.

    To display information on the form received from the process in the custom task, you need to add the data(@Input() data: Task = {};) property to the widget code. In this case, all task data and parameters passed from the bpmn process will be available in the data object.

  2. Controlling actions on the widget side

    This option is intended for implementing more complex interaction with the user during custom task processing. The widget receives a list of actions from the main form and independently manages the layout, logic for the availability of these actions, and other application logic executed when the user selects a specific action.

    To achieve this, the widget needs to add:

    • the actions property;
    • the taskActionTriggered, widgetInfo, and cancel events;
    • in ngOnInit (the moment the widget is initialized), you need to pass the event to widgetInfo to notify the main form of the current widget's operating mode:
      ngOnInit
      @Input() actions: MenuItem[];
       
      @Output() widgetInfo = new EventEmitter<TaskProcessingWidgetInfo>();
      @Output() taskActionTriggered = new EventEmitter<TaskProcessingResultAction>();
      @Output() cancel = new EventEmitter<string>();
       
      ngOnInit(): void {
          this.widgetInfo.emit({
          usesActionButtons: true // true - control buttons are implemented on the widget side, false - control buttons should be provided by the main task processing form
          });
      }

    The widget can initiate an event signaling the completion of task processing by the user and transfer control to the main form, which will mark the user task as "Completed". To do this, it is necessary to propagate the event to taskActionTriggered.

    onActionButtonClicked(): void {
        this.taskActionTriggered.emit({
            id: 'DONE', // The id field from the selected actions item. Anything not included in actions is ignored, as such an action cannot be processed in the bpmn process.
            comment: 'Some comment' // User comment or additional information (optional field)
        });
    }

    The widget can also trigger an event to cancel the processing of the user task (in this case, the processing form will be closed without performing any actions on the task). To do this, you need to pass the event to "cancel."

    onCancel(): void {
        this.cancel.emit();
    }

    To ensure the interaction of the task processing widget with the common task processing form, the widget must comply with the contract provided by the TaskProcessingWidgetComponent interface. To do this, it is necessary to connect the library npm i @diasoft/q-bpm-human-task-widget or add "@diasoft/q-bpm-human-task-widget": "0.0.9" to the package.json file.

    export interface TaskProcessingWidgetComponent {
        actions: MenuItem[]; // Set or list of actions available for task processing
        data: Task; // Data of the processed task
        taskActionTriggered: EventEmitter<TaskProcessingResultAction>; // Event caught by the common task processing form. This event should be triggered when processing the task based on user action.
    }

Example of implementing a widget

Example of task processing widget code
import { ChangeDetectorRef, Component, EventEmitter, Input, OnInit, Output } from '@angular/core';
import { FormBuilder, FormGroup } from '@angular/forms';
import { MenuItem } from 'primeng/api';
import { TaskProcessingAction } from './task-processing-action.model';
import {
    Task,
    TaskProcessingResultAction,
    TaskProcessingWidgetComponent,
    TaskProcessingWidgetInfo
} from '@diasoft/q-bpm-human-task-widget';  // Library import
 
@Component({
    selector: 'app-task-processing-widget',
    templateUrl: './task-processing-default-widget.component.html',
    styleUrls: ['./task-processing-default-widget.component.scss']
})
export class TaskProcessingDefaultWidgetComponent implements TaskProcessingWidgetComponent, OnInit {
    private readonly defaultActions: TaskProcessingAction[] = [
        { label: 'Process', id: 'NEXT' }
    ];
 
    // implement the component
 
    private actionsInner: TaskProcessingAction[] = this.defaultActions;
 
    @Input() set actions(actions: any) {
        this.actionsInner = actions || [];
        this.actionButtonItems = this.getActionButtonItems();
 
        if (actions.length > 0) {
            this.taskProcessingAction = this.mapActionToSplitButtonItem(actions[0]);
        }
    }; // Get task actions
 
    @Input() data: Task = {}; // Get other task information for widget logic
 
    @Output() taskActionTriggered = new EventEmitter<TaskProcessingResultAction>(); // Send event
    @Output() cancel = new EventEmitter<string>();
    @Output() widgetInfo = new EventEmitter<TaskProcessingWidgetInfo>();
 
    actionButtonItems: MenuItem[] = this.getActionButtonItems();
    taskProcessingAction: MenuItem = this.mapActionToSplitButtonItem(this.defaultActions[0]);
 
    form: FormGroup = this.fb.group({
        comment: ['']
    });
 
    showActionsButtons = true;
 
    constructor(private fb: FormBuilder, private cdr: ChangeDetectorRef) {
    }
 
    ngOnInit(): void {
        this.widgetInfo.emit({
            usesActionButtons: this.showActionsButtons
        });
    }
 
    getActionButtonItems(): MenuItem[] {
        return this.actionsInner.map(action => this.mapActionToSplitButtonItem(action));
    }
 
    mapActionToSplitButtonItem(action: TaskProcessingAction): MenuItem {
        return ({
            label: action.label,
            id: action.id,
            command: () => this.onActionSelected(action.id)
        });
    }
 
    onCancel(): void {
        this.cancel.emit();
    }
 
    onActionSelected(actionId: string): void {
        const action = this.actionsInner.find(act => act.id === actionId);
 
        if (action) {
            this.taskProcessingAction = this.mapActionToSplitButtonItem(action);
        }
    }
 
    onActionButtonClicked(): void {
        if (this.taskProcessingAction?.id) {
            this.taskActionTriggered.emit({
                id: this.taskProcessingAction.id,
                comment: this.form.controls.comment.value
            });
        }
    }
}
Interface of the data object
export interface Task {
    id?: any;
    priority?: number; // task priority
    templateSysName?: string; // template system name
    templateName?: string; // template name
    objectType?: string;
    version?: number; // version
    maxProcessTime?: number; // maximum task processing date
    maxExecutionTime?: number; // maximum task execution time
    taskForm?: string;
    assignAlgorithm?: string; // assignment algorithm
    state?: string; // state
    stateName?: string;
    createDate?: Date;
    endDate?: Date;
    assignDate?: Date; // assignment date
    assignGroupType?: string;
    assignGroupName?: string;
    assignId?: number;
    assignName?: string;
    assigneeId?: number;
    details?: TaskDetails; // task parameters from BPM process context
    activityName?: string;
    activityId?: string;
    processInstanceId?: string;
    processDefinitionId?: string;
    processDefinitionKey?: string;
    processDefinitionName?: string;
    processDefinitionVersion?: string;
    comment?: string; // task comment
    serviceName?: string; // name of the task initiator service
}

Using the widget in the custom task template

Now it remains to insert the ready-made widget into the custom task template, which will be displayed on the custom task processing form.

In the "Task Processing Widget" field, you need to specify the widget component address in the format <ui-service name>:<component name>:<widget name>. empty