Skip to content
Snippets Groups Projects
Commit e4e627ff authored by S.Listl's avatar S.Listl
Browse files

WorkflowTask optimization

parent 89e6029b
No related branches found
No related tags found
No related merge requests found
Showing
with 286 additions and 27 deletions
<?xml version="1.1" encoding="UTF-8" standalone="no"?>
<databaseChangeLog xmlns="http://www.liquibase.org/xml/ns/dbchangelog"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-3.6.xsd">
<changeSet author="s.listl" id="989b7d7d-ba1e-40b8-99b0-e4a1c13e9375">
<insert tableName="AB_KEYWORD_ENTRY">
<column name="AB_KEYWORD_ENTRYID" value="78367cf3-111a-4852-9a4f-a505ad2953cd"/>
<column name="KEYID" value="TRIGGEREVENTINSERT"/>
<column name="TITLE" value="Creation"/>
<column name="CONTAINER" value="WorkflowSignalTrigger"/>
<column name="SORTING" valueNumeric="1"/>
<column name="ISACTIVE" valueNumeric="1"/>
<column name="ISESSENTIAL" valueNumeric="1"/>
</insert>
<insert tableName="AB_KEYWORD_ENTRY">
<column name="AB_KEYWORD_ENTRYID" value="94cc11ec-a384-439f-b5c7-70d7168959a5"/>
<column name="KEYID" value="TRIGGEREVENTUPDATE"/>
<column name="TITLE" value="Update"/>
<column name="CONTAINER" value="WorkflowSignalTrigger"/>
<column name="SORTING" valueNumeric="2"/>
<column name="ISACTIVE" valueNumeric="1"/>
<column name="ISESSENTIAL" valueNumeric="1"/>
</insert>
<insert tableName="AB_KEYWORD_ENTRY">
<column name="AB_KEYWORD_ENTRYID" value="d8b78606-30bf-4633-9aaf-4da691ef6d8f"/>
<column name="KEYID" value="TRIGGEREVENTDELETE"/>
<column name="TITLE" value="Deletion"/>
<column name="CONTAINER" value="WorkflowSignalTrigger"/>
<column name="SORTING" valueNumeric="3"/>
<column name="ISACTIVE" valueNumeric="1"/>
<column name="ISESSENTIAL" valueNumeric="1"/>
</insert>
</changeSet>
</databaseChangeLog>
......@@ -2,4 +2,5 @@
<databaseChangeLog xmlns="http://www.liquibase.org/xml/ns/dbchangelog"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-3.6.xsd">
<include file="WorkflowSignal/create_WorkflowSignal.xml" relativeToChangelogFile="true"/>
<include file="WorkflowSignal/insert_WorkflowSignalTrigger_keyword.xml" relativeToChangelogFile="true"/>
</databaseChangeLog>
......@@ -7,4 +7,4 @@ if (vars.exists("$context.workflowQueue") && vars.get("$context.workflowQueue"))
WorkflowStarter.inserted(vars.get("$context.workflowQueue"));
vars.set("$context.workflowQueue", null);
}
WorkflowSignalSender.doInsertedOrUpdated();
\ No newline at end of file
WorkflowSignalSender.throwQueuedEvents();
\ No newline at end of file
......@@ -6,4 +6,4 @@ if (vars.exists("$context.workflowQueue") && vars.get("$context.workflowQueue"))
WorkflowStarter.inserted(vars.get("$context.workflowQueue"));
vars.set("$context.workflowQueue", null);
}
WorkflowSignalSender.doInsertedOrUpdated();
WorkflowSignalSender.throwQueuedEvents();
......@@ -6,4 +6,4 @@ if (vars.exists("$context.workflowQueue") && vars.get("$context.workflowQueue"))
WorkflowStarter.inserted(vars.get("$context.workflowQueue"));
vars.set("$context.workflowQueue", null);
}
WorkflowSignalSender.doInsertedOrUpdated();
\ No newline at end of file
WorkflowSignalSender.throwQueuedEvents();
\ No newline at end of file
......@@ -2,7 +2,7 @@
<entity xmlns="http://www.adito.de/2018/ao/Model" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" VERSION="1.3.13" xsi:schemaLocation="http://www.adito.de/2018/ao/Model adito://models/xsd/entity/1.3.13">
<name>WorkflowSignal_entity</name>
<majorModelMode>DISTRIBUTED</majorModelMode>
<title>Workflow signal</title>
<title>Signal</title>
<titlePlural>Signals</titlePlural>
<recordContainer>db</recordContainer>
<entityFields>
......
import("KeywordRegistry_basic");
import("system.result");
result.string($KeywordRegistry.workflowTrigger());
\ No newline at end of file
result.string($KeywordRegistry.workflowSignalTrigger());
\ No newline at end of file
......@@ -2,5 +2,5 @@ import("system.result");
import("Keyword_lib");
import("KeywordRegistry_basic");
var sql = KeywordUtils.getResolvedTitleSqlPart($KeywordRegistry.workflowTrigger(), "WORKFLOWSIGNAL.TRIGGER_EVENT");
var sql = KeywordUtils.getResolvedTitleSqlPart($KeywordRegistry.workflowSignalTrigger(), "WORKFLOWSIGNAL.TRIGGER_EVENT");
result.string(sql);
\ No newline at end of file
......@@ -35,6 +35,7 @@
</entityField>
<entityField>
<name>PROCESSDEFINITION_ID</name>
<displayValueProcess>%aditoprj%/entity/WorkflowTask_entity/entityfields/processdefinition_id/displayValueProcess.js</displayValueProcess>
</entityField>
<entityField>
<name>PROCESSINSTANCE_ID</name>
......@@ -115,6 +116,7 @@
<title>Claim task</title>
<onActionProcess>%aditoprj%/entity/WorkflowTask_entity/entityfields/claimtask/onActionProcess.js</onActionProcess>
<iconId>VAADIN:CLIPBOARD_USER</iconId>
<stateProcess>%aditoprj%/entity/WorkflowTask_entity/entityfields/claimtask/stateProcess.js</stateProcess>
</entityActionField>
<entityField>
<name>ISACTIVE</name>
......
import("system.neon");
import("Employee_lib");
import("system.vars");
import("system.workflow");
workflow.claimTask(vars.get("$field.UID"), EmployeeUtils.getCurrentUserId());
\ No newline at end of file
if (vars.get("$field.ASSIGNEE") != EmployeeUtils.getCurrentUserId())
{
workflow.claimTask(vars.get("$field.UID"), EmployeeUtils.getCurrentUserId());
neon.refreshAll();
}
\ No newline at end of file
import("system.neon");
import("Employee_lib");
import("system.vars");
import("system.result");
result.string(vars.get("$field.ASSIGNEE") == EmployeeUtils.getCurrentUserId()
? neon.COMPONENTSTATE_DISABLED
: neon.COMPONENTSTATE_EDITABLE
);
\ No newline at end of file
import("Context_lib");
import("system.result");
import("system.neon");
import("system.vars");
if (vars.get("$sys.viewmode") == neon.FRAME_VIEWMODE_DATASET)
result.string(ContextUtils.loadContentTitle("WorkflowDefinition_entity", vars.get("$field.PROCESSDEFINITION_ID")));
......@@ -6080,6 +6080,12 @@
<entry>
<key>${CANCELLED}</key>
</entry>
<entry>
<key>Signals</key>
</entry>
<entry>
<key>${ORDER_CANCELLED}</key>
</entry>
</keyValueMap>
<font name="Dialog" style="0" size="11" />
<sqlModels>
......
......@@ -70,6 +70,10 @@
<key>Data imported. Address could not be read.</key>
<value>Daten wurden importiert. Adresse konnte nicht ausgelesen werden.</value>
</entry>
<entry>
<key>Claim task</key>
<value>Aufgabe annehmen</value>
</entry>
<entry>
<key>Data imported.</key>
<value>Daten wurden importiert.</value>
......@@ -7806,6 +7810,9 @@ Bitte Datumseingabe prüfen</value>
<entry>
<key>Weitere Kontakte</key>
</entry>
<entry>
<key>${CANCELLED}</key>
</entry>
</keyValueMap>
<font name="Dialog" style="0" size="11" />
</language>
......@@ -6137,6 +6137,12 @@
<key>${CONDITION}</key>
<value>Condition</value>
</entry>
<entry>
<key>Signals</key>
</entry>
<entry>
<key>${CANCELLED}</key>
</entry>
</keyValueMap>
<font name="Dialog" style="0" size="11" />
</language>
......@@ -238,4 +238,9 @@ $KeywordRegistry.workflowTrigger = function(){return "WorkflowTrigger";};
$KeywordRegistry.workflowTrigger$manual = function(){return "WORKFLOWTRIGGERMANUAL";};
$KeywordRegistry.workflowTrigger$create = function(){return "WORKFLOWTRIGGERINSERT";};
$KeywordRegistry.workflowTrigger$update = function(){return "WORKFLOWTRIGGERUPDATE";};
$KeywordRegistry.workflowTrigger$delete = function(){return "WORKFLOWTRIGGERDELETE";};
\ No newline at end of file
$KeywordRegistry.workflowTrigger$delete = function(){return "WORKFLOWTRIGGERDELETE";};
$KeywordRegistry.workflowSignalTrigger = function(){return "WorkflowSignalTrigger";};
$KeywordRegistry.workflowSignalTrigger$create = function(){return "TRIGGEREVENTINSERT";};
$KeywordRegistry.workflowSignalTrigger$update = function(){return "TRIGGEREVENTUPDATE";};
$KeywordRegistry.workflowSignalTrigger$delete = function(){return "TRIGGEREVENTDELETE";};
\ No newline at end of file
......@@ -88,7 +88,7 @@ WorkflowUtils.engineIsEnabled = function ()
}
/**
* obsolete
* obsolete, use signals
*/
function WorkflowStarter () {}
......@@ -134,76 +134,124 @@ WorkflowStarter._startProcessByAction = function (pAction, pVariables, pTargetId
});
}
/**
* provides functions for working with workflow signals
*/
function WorkflowSignalSender () {}
/**
* Constant for the event of inserting a dataset, wraps a keyword
*/
WorkflowSignalSender.EVENT_INSERT = function ()
{
return $KeywordRegistry.workflowTrigger$create();
return $KeywordRegistry.workflowSignalTrigger$create();
}
/**
* Constant for the event of updating a dataset, wraps a keyword
*/
WorkflowSignalSender.EVENT_UPDATE = function ()
{
return $KeywordRegistry.workflowTrigger$update();
return $KeywordRegistry.workflowSignalTrigger$update();
}
/**
* Constant for the event of deleting a dataset, wraps a keyword
*/
WorkflowSignalSender.EVENT_DELETE = function ()
{
return $KeywordRegistry.workflowTrigger$delete();
return $KeywordRegistry.workflowSignalTrigger$delete();
}
/**
* Adds an entry to a context-variable that tells a insert happened. The variable is then checked by WorkflowSignalSender.doInsertedOrUpdated,
* where the actual logic is executed. The reason for this approach is that 'inserted' signals should be thrown after the insert finished.
*
* @param {String} pTargetId uid of the inserted dataset
* @param {String} pTargetContext context of the inserted dataset
*/
WorkflowSignalSender.markInserted = function (pTargetId, pTargetContext)
{
//TODO: is a signal like 'before insert' required?
WorkflowSignalSender._addToQueue(WorkflowSignalSender.EVENT_INSERT(), pTargetId, pTargetContext);
}
/**
* Adds an entry to a context-variable that tells a update happened. The variable is then checked by WorkflowSignalSender.doInsertedOrUpdated,
* where the actual logic is executed. The reason for this approach is that 'updated' signals should be thrown after the update finished.
*
* @param {String} pTargetId uid of the updated dataset
* @param {String} pTargetContext context of the updated dataset
*/
WorkflowSignalSender.markUpdated = function (pTargetId, pTargetContext)
{
WorkflowSignalSender._addToQueue(WorkflowSignalSender.EVENT_UPDATE(), pTargetId, pTargetContext);
}
/**
* constant for the context variable for queueing events
*/
WorkflowSignalSender._QUEUE_VARNAME = function ()
{
return "$context.workflowSignalQueue";
}
WorkflowSignalSender._addToQueue = function (pAction, pTargetId, pTargetContext)
/**
* adds events to the queue-context-variable to be evaluated later
*/
WorkflowSignalSender._addToQueue = function (pEvent, pTargetId, pTargetContext)
{
var contextVar = WorkflowSignalSender._QUEUE_VARNAME();
var queue = [];
if (vars.exists(contextVar) && vars.get(contextVar))
queue = vars.get(contextVar);
queue.push([pAction, pTargetId, pTargetContext]);
queue.push([pEvent, pTargetId, pTargetContext]);
vars.set(contextVar, queue);
}
WorkflowSignalSender.doInsertedOrUpdated = function ()
/**
* Evaluates the queued events in the context-variable and throws the events. That can result in workflow signals being thrown.
* This queueing can be used if something happens in a context, but the signal should not be thrown immediately (e. g. in the onInsert process
* of the recordcontainer, when the insert isn't finished).
*/
WorkflowSignalSender.throwQueuedEvents = function ()
{
var contextVar = WorkflowSignalSender._QUEUE_VARNAME();
if (vars.exists(contextVar) && vars.get(contextVar))
{
vars.get(contextVar).forEach(function (action)
{
WorkflowSignalSender.actionPerformed.apply(null, action);
WorkflowSignalSender.eventHappened.apply(null, action);
});
vars.set(contextVar, null);
}
}
/**
* @param {String} pTargetId uid of the deleted dataset
* @param {String} pTargetContext context of the deleted dataset
*/
WorkflowSignalSender.deleted = function (pTargetId, pTargetContext)
{
WorkflowSignalSender.actionPerformed(WorkflowSignalSender.EVENT_DELETE(), pTargetId, pTargetContext);
}
WorkflowSignalSender.actionPerformed = function (pAction, pTargetId, pTargetContext)
/**
* Checks the signal configuration for the given event and then throws signals that were configured to the event.
*
* @param {String} pEvent event
* @param {String} pTargetId uid of the dataset
* @param {String} pTargetContext context of the dataset
*/
WorkflowSignalSender.eventHappened = function (pEvent, pTargetId, pTargetContext)
{
if (!pTargetId)
pTargetId = vars.get("$sys.uid");
if (!pTargetContext)
pTargetContext = ContextUtils.getCurrentContextId();
var signals = WorkflowSignalSender.getSignalConfig(pTargetContext, pAction);
var signals = WorkflowSignalSender.getSignalConfig(pTargetContext, pEvent);
signals.forEach(function (signal)
{
if (_checkCondition(signal.entity, pTargetId, signal.condition))
......@@ -224,24 +272,37 @@ WorkflowSignalSender.actionPerformed = function (pAction, pTargetId, pTargetCont
}
}
WorkflowSignalSender.getSignalConfig = function (pContext, pAction)
/**
* loads signal definitions from the database
*
* @param {String} [pContext] contexts of the signal definitions
* @param {String} [pEvent] event that is defined for the signals
* @return {Object[]} Array of objects, with these properties:<br/>
* <ul>
* <li>name: name of the signal</li>
* <li>event: event that triggers the signal</li>
* <li>entity: entity where the event should happen</li>
* <li>condition: a condition that is checked before the signal is sent, null if there's no condition</li>
* </ul>
*/
WorkflowSignalSender.getSignalConfig = function (pContext, pEvent)
{
var entity = ContextUtils.getEntity(pContext);
var signals = newSelect("SIGNAL_NAME, OBJECT_CONDITION")
var signals = newSelect("SIGNAL_NAME, TRIGGER_EVENT, OBJECT_CONDITION, OBJECT_TYPE")
.from("WORKFLOWSIGNAL")
.where("WORKFLOWSIGNAL.OBJECT_TYPE", pContext)
.and("WORKFLOWSIGNAL.TRIGGER_EVENT", pAction)
.whereIfSet("WORKFLOWSIGNAL.OBJECT_TYPE", pContext)
.andIfSet("WORKFLOWSIGNAL.TRIGGER_EVENT", pEvent)
.table();
return signals.map(function (signal)
{
var condition = signal[1]
? JSON.parse(signal[1]).filter
var condition = signal[2]
? JSON.parse(signal[2]).filter
: null;
return {
name : signal[0],
entity : entity,
event : signal[1],
entity : ContextUtils.getEntity(signal[3]),
condition : condition
};
});
......
import("system.project");
import("system.process");
function restget (pRequest)
{
let request = JSON.parse(pRequest);
let serviceTasks = project.getDataModels(project.DATAMODEL_KIND_PROCESS).filter(function (row)
{
return /.+_workflowService$/.test(row[0]);
});
request.response.body = JSON.stringify({});
return JSON.stringify(request);
}
<?xml version="1.0" encoding="UTF-8"?>
<process xmlns="http://www.adito.de/2018/ao/Model" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" VERSION="1.2.1" xsi:schemaLocation="http://www.adito.de/2018/ao/Model adito://models/xsd/process/1.2.1">
<name>workflowServiceTasks_rest</name>
<majorModelMode>DISTRIBUTED</majorModelMode>
<process>%aditoprj%/process/workflowServiceTasks_rest/process.js</process>
<style>REST</style>
<variants>
<element>EXECUTABLE</element>
</variants>
</process>
import("system.vars");
import("system.logging");
import("system.tools");
function restget (pRequest)
{
//maximum row count of the resultset
const MAX_COUNT = 100;
//minimum required length of the search pattern
const MIN_SEARCHLENGTH = 0;
let request = JSON.parse(pRequest);
let searchValue = request.header.filter || request.header.Filter;
request.response.body = JSON.stringify({users : _getUsers(searchValue)});
return JSON.stringify(request);
function _getUsers (pSearch)
{
if (pSearch === undefined || pSearch.trim() < MIN_SEARCHLENGTH)
return [];
//separate words by spaces
let patterns = pSearch.toUpperCase().split(/\s/).filter(function (pat) {return pat;});
let allUsers = tools.getUsersByAttribute(tools.ISACTIVE, ["true"], tools.PROFILE_DEFAULT);
let resultUsers = [];
for (let i = 0, l = allUsers.length; i < l; i++)
{
let user = allUsers[i];
let id = user[tools.NAME];
let firstName = user[tools.PARAMS][tools.FIRSTNAME];
let lastName = user[tools.PARAMS][tools.LASTNAME];
let email = user[tools.EMAIL];
let rating = _getRating(patterns, [firstName.toUpperCase(), lastName.toUpperCase()]);
if (rating !== 0)
{
resultUsers.push({
id : id,
firstName : firstName,
lastName : lastName,
email : email,
fullName : (firstName + " " + lastName).trim(),
tenantId : null,
groups : [],
privileges : [],
searchRating : rating
});
}
}
//sort by result quality
resultUsers.sort(function (a, b)
{
return b.searchRating - a.searchRating;
});
return resultUsers.slice(0, MAX_COUNT);
}
/**
* checks if the pattern is contained in the name
*
* @return {Number} higher number -> better result, 0 if no match
*/
function _getRating (pPatterns, pRow)
{
let rating = 0;
return pPatterns.every(function (pattern)
{
let patternRating = pRow.reduce(function (patRating, value)
{
let index = value.indexOf(pattern);
if (index !== -1)
patRating++;
if (index === 0)
patRating += 2;
return patRating;
}, 0);
rating += patternRating;
//if one word wasn't found, the loop will stop and rating is 0
return patternRating !== 0;
}) ? rating : 0;
}
}
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment