mirror of
https://github.com/status-im/spiff-arena.git
synced 2025-02-20 21:48:07 +00:00
Merge remote-tracking branch 'origin/main' into feature/create_containers
This commit is contained in:
commit
ec0c6f4555
3
.gitignore
vendored
3
.gitignore
vendored
@ -1,2 +1,3 @@
|
||||
pyrightconfig.json
|
||||
.idea/
|
||||
.idea/
|
||||
t
|
||||
|
@ -177,7 +177,7 @@ class BPMNDictionarySerializer(DictionarySerializer):
|
||||
return s_state
|
||||
|
||||
def deserialize_business_rule_task(self, wf_spec, s_state):
|
||||
dt = DecisionTable(None,None)
|
||||
dt = DecisionTable(None, None, None)
|
||||
dt.deserialize(s_state['dmn'])
|
||||
dmn_engine = DMNEngine(dt)
|
||||
spec = BusinessRuleTask(wf_spec, s_state['name'], dmn_engine)
|
||||
@ -224,7 +224,7 @@ class BPMNDictionarySerializer(DictionarySerializer):
|
||||
if s_state.get('expanded',None):
|
||||
cls.expanded = self.deserialize_arg(s_state['expanded'])
|
||||
if isinstance(cls,BusinessRuleTask):
|
||||
dt = DecisionTable(None,None)
|
||||
dt = DecisionTable(None,None,None)
|
||||
dt.deserialize(s_state['dmn'])
|
||||
dmn_engine = DMNEngine(dt)
|
||||
cls.dmnEngine=dmn_engine
|
||||
|
@ -1,6 +1,7 @@
|
||||
import logging
|
||||
import re
|
||||
|
||||
from ..specs.model import HitPolicy
|
||||
from ...util import levenshtein
|
||||
from ...workflow import WorkflowException
|
||||
|
||||
@ -16,9 +17,32 @@ class DMNEngine:
|
||||
self.decision_table = decision_table
|
||||
|
||||
def decide(self, task):
|
||||
rules = []
|
||||
for rule in self.decision_table.rules:
|
||||
if self.__check_rule(rule, task):
|
||||
return rule
|
||||
rules.append(rule)
|
||||
if self.decision_table.hit_policy == HitPolicy.UNIQUE.value:
|
||||
return rules
|
||||
return rules
|
||||
|
||||
def result(self, task):
|
||||
"""Returns the results of running this decision table against
|
||||
a given task."""
|
||||
result = {}
|
||||
matched_rules = self.decide(task)
|
||||
if len(matched_rules) == 1:
|
||||
result = matched_rules[0].output_as_dict(task)
|
||||
elif len(matched_rules) > 1: # This must be a multi-output
|
||||
# each output will be an array of values, all outputs will
|
||||
# be placed in a dict, which we will then merge.
|
||||
for rule in matched_rules:
|
||||
rule_output = rule.output_as_dict(task)
|
||||
for key in rule_output.keys():
|
||||
if not key in result:
|
||||
result[key] = []
|
||||
result[key].append(rule_output[key])
|
||||
return result
|
||||
|
||||
|
||||
def __check_rule(self, rule, task):
|
||||
for input_entry in rule.inputEntries:
|
||||
|
@ -93,9 +93,10 @@ class DMNParser(NodeParser):
|
||||
|
||||
def _parse_decision_tables(self, decision, decisionElement):
|
||||
for decision_table_element in decisionElement.findall('{*}decisionTable'):
|
||||
name = decision_table_element.attrib.get('name', '')
|
||||
hitPolicy = decision_table_element.attrib.get('hitPolicy', 'UNIQUE').upper()
|
||||
decision_table = DecisionTable(decision_table_element.attrib['id'],
|
||||
decision_table_element.attrib.get(
|
||||
'name', ''))
|
||||
name, hitPolicy)
|
||||
decision.decisionTables.append(decision_table)
|
||||
|
||||
# parse inputs
|
||||
|
@ -1,7 +1,7 @@
|
||||
from ...bpmn.serializer.bpmn_converters import BpmnTaskSpecConverter
|
||||
|
||||
from ..specs.BusinessRuleTask import BusinessRuleTask
|
||||
from ..specs.model import DecisionTable, Rule
|
||||
from ..specs.model import DecisionTable, Rule, HitPolicy
|
||||
from ..specs.model import Input, InputEntry, Output, OutputEntry
|
||||
from ..engine.DMNEngine import DMNEngine
|
||||
|
||||
@ -57,7 +57,8 @@ class BusinessRuleTaskConverter(BpmnTaskSpecConverter):
|
||||
return self.task_spec_from_dict(dct)
|
||||
|
||||
def decision_table_from_dict(self, dct):
|
||||
table = DecisionTable(dct['id'], dct['name'])
|
||||
hit_policy = dct.get('hit_policy', HitPolicy.UNIQUE.value)
|
||||
table = DecisionTable(dct['id'], dct['name'], hit_policy)
|
||||
table.inputs = [ Input(**val) for val in dct['inputs'] ]
|
||||
table.outputs = [ Output(**val) for val in dct['outputs'] ]
|
||||
table.rules = [ self.rule_from_dict(rule, table.inputs, table.outputs)
|
||||
|
@ -18,7 +18,6 @@ class BusinessRuleTask(Simple, BpmnSpecMixin):
|
||||
super().__init__(wf_spec, name, **kwargs)
|
||||
|
||||
self.dmnEngine = dmnEngine
|
||||
self.res = None
|
||||
self.resDict = None
|
||||
|
||||
@property
|
||||
@ -27,10 +26,8 @@ class BusinessRuleTask(Simple, BpmnSpecMixin):
|
||||
|
||||
def _on_complete_hook(self, my_task):
|
||||
try:
|
||||
self.res = self.dmnEngine.decide(my_task)
|
||||
if self.res is not None: # it is conceivable that no rules fire.
|
||||
self.resDict = self.res.output_as_dict(my_task)
|
||||
my_task.data = DeepMerge.merge(my_task.data,self.resDict)
|
||||
my_task.data = DeepMerge.merge(my_task.data,
|
||||
self.dmnEngine.result(my_task))
|
||||
super(BusinessRuleTask, self)._on_complete_hook(my_task)
|
||||
except Exception as e:
|
||||
raise WorkflowTaskExecException(my_task, str(e))
|
||||
|
@ -1,8 +1,24 @@
|
||||
from collections import OrderedDict
|
||||
from enum import Enum
|
||||
|
||||
from ...util.deep_merge import DeepMerge
|
||||
|
||||
|
||||
class HitPolicy(Enum):
|
||||
UNIQUE = "UNIQUE"
|
||||
COLLECT = "COLLECT"
|
||||
# ANY = "ANY"
|
||||
# PRIORITY = "PRIORITY"
|
||||
# FIRST = "FIRST"
|
||||
# OUTPUT_ORDER = "OUTPUT ORDER"
|
||||
# RULE_ORDER = "RULE ORDER"
|
||||
|
||||
# class Aggregation(Enum):
|
||||
# SUM = "SUM"
|
||||
# COUNT = "COUNT"
|
||||
# MIN = "MIN"
|
||||
# MAX = "MAX"
|
||||
|
||||
class Decision:
|
||||
def __init__(self, id, name):
|
||||
self.id = id
|
||||
@ -11,9 +27,10 @@ class Decision:
|
||||
self.decisionTables = []
|
||||
|
||||
class DecisionTable:
|
||||
def __init__(self, id, name):
|
||||
def __init__(self, id, name, hit_policy):
|
||||
self.id = id
|
||||
self.name = name
|
||||
self.hit_policy = hit_policy
|
||||
|
||||
self.inputs = []
|
||||
self.outputs = []
|
||||
@ -23,6 +40,7 @@ class DecisionTable:
|
||||
out = {}
|
||||
out['id'] = self.id
|
||||
out['name'] = self.name
|
||||
out['hit_policy'] = self.hit_policy
|
||||
out['inputs'] = [x.serialize() for x in self.inputs]
|
||||
out['outputs'] = [x.serialize() for x in self.outputs]
|
||||
out['rules'] = [x.serialize() for x in self.rules]
|
||||
@ -31,6 +49,10 @@ class DecisionTable:
|
||||
def deserialize(self,indict):
|
||||
self.id = indict['id']
|
||||
self.name = indict['name']
|
||||
if 'hit_policy' in indict:
|
||||
self.hit_policy = indict['hit_policy']
|
||||
else:
|
||||
self.hit_policy = HitPolicy.UNIQUE.value
|
||||
self.inputs = [Input(**x) for x in indict['inputs']]
|
||||
list(map(lambda x, y: x.deserialize(y), self.inputs, indict['inputs']))
|
||||
self.outputs = [Output(**x) for x in indict['outputs']]
|
||||
|
@ -41,11 +41,17 @@ class DecisionRunner:
|
||||
'Exactly one decision table should exist! (%s)' \
|
||||
% (len(decision.decisionTables))
|
||||
|
||||
self.dmnEngine = DMNEngine(decision.decisionTables[0])
|
||||
self.decision_table = decision.decisionTables[0]
|
||||
self.dmnEngine = DMNEngine(self.decision_table)
|
||||
|
||||
def decide(self, context):
|
||||
|
||||
"""Makes the rather ugly assumption that there is only one
|
||||
rule match for a decision - which was previously the case"""
|
||||
if not isinstance(context, dict):
|
||||
context = {'input': context}
|
||||
task = Task(self.script_engine, context)
|
||||
return self.dmnEngine.decide(task)
|
||||
return self.dmnEngine.decide(task)[0]
|
||||
|
||||
def result(self, context):
|
||||
task = Task(self.script_engine, context)
|
||||
return self.dmnEngine.result(task)
|
||||
|
37
SpiffWorkflow/tests/SpiffWorkflow/dmn/HitPolicyTest.py
Normal file
37
SpiffWorkflow/tests/SpiffWorkflow/dmn/HitPolicyTest.py
Normal file
@ -0,0 +1,37 @@
|
||||
import os
|
||||
import unittest
|
||||
|
||||
from SpiffWorkflow.dmn.engine.DMNEngine import DMNEngine
|
||||
from SpiffWorkflow.dmn.parser.BpmnDmnParser import BpmnDmnParser
|
||||
from tests.SpiffWorkflow.bpmn.BpmnWorkflowTestCase import BpmnWorkflowTestCase
|
||||
from tests.SpiffWorkflow.dmn.DecisionRunner import DecisionRunner
|
||||
from tests.SpiffWorkflow.dmn.python_engine.PythonDecisionRunner import \
|
||||
PythonDecisionRunner
|
||||
|
||||
|
||||
class HitPolicyTest(BpmnWorkflowTestCase):
|
||||
PARSER_CLASS = BpmnDmnParser
|
||||
|
||||
def testHitPolicyUnique(self):
|
||||
file_name = os.path.join(os.path.dirname(__file__), 'data', 'unique_hit.dmn')
|
||||
runner = PythonDecisionRunner(file_name)
|
||||
decision_table = runner.decision_table
|
||||
self.assertEqual('UNIQUE', decision_table.hit_policy)
|
||||
res = runner.result({'name': 'Larry'})
|
||||
self.assertEqual(1, res['result'])
|
||||
|
||||
def testHitPolicyCollect(self):
|
||||
file_name = os.path.join(os.path.dirname(__file__), 'data', 'collect_hit.dmn')
|
||||
runner = PythonDecisionRunner(file_name)
|
||||
decision_table = runner.decision_table
|
||||
self.assertEqual('COLLECT', decision_table.hit_policy)
|
||||
res = runner.result({'type': 'stooge'})
|
||||
self.assertEqual(4, len(res['name']))
|
||||
|
||||
|
||||
def suite():
|
||||
return unittest.TestLoader().loadTestsFromTestCase(HitPolicyTest)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.TextTestRunner(verbosity=2).run(suite())
|
78
SpiffWorkflow/tests/SpiffWorkflow/dmn/data/collect_hit.dmn
Normal file
78
SpiffWorkflow/tests/SpiffWorkflow/dmn/data/collect_hit.dmn
Normal file
@ -0,0 +1,78 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<definitions xmlns="https://www.omg.org/spec/DMN/20191111/MODEL/" xmlns:dmndi="https://www.omg.org/spec/DMN/20191111/DMNDI/" xmlns:dc="http://www.omg.org/spec/DMN/20180521/DC/" id="Definitions_06veek1" name="DRD" namespace="http://camunda.org/schema/1.0/dmn" exporter="Camunda Modeler" exporterVersion="5.0.0">
|
||||
<decision id="Decision_ExclusiveAMCheck" name="UniqueHit">
|
||||
<decisionTable id="decisionTable_1" hitPolicy="COLLECT">
|
||||
<input id="InputClause_1ley07z" label="Input "type"">
|
||||
<inputExpression id="LiteralExpression_0yhfcz3" typeRef="string" expressionLanguage="python">
|
||||
<text>type</text>
|
||||
</inputExpression>
|
||||
</input>
|
||||
<output id="output_1" label="Output "result"" name="result" typeRef="integer" />
|
||||
<output id="OutputClause_1yrcvgl" label="Output "name"" name="name" typeRef="string" />
|
||||
<rule id="DecisionRule_07162mr">
|
||||
<description></description>
|
||||
<inputEntry id="UnaryTests_079zee3">
|
||||
<text>"stooge"</text>
|
||||
</inputEntry>
|
||||
<outputEntry id="LiteralExpression_16l50ps">
|
||||
<text>1</text>
|
||||
</outputEntry>
|
||||
<outputEntry id="LiteralExpression_1lgpxjl">
|
||||
<text>"Larry"</text>
|
||||
</outputEntry>
|
||||
</rule>
|
||||
<rule id="DecisionRule_0ifa4wu">
|
||||
<description></description>
|
||||
<inputEntry id="UnaryTests_1fj8ed0">
|
||||
<text>"stooge"</text>
|
||||
</inputEntry>
|
||||
<outputEntry id="LiteralExpression_0td8sa6">
|
||||
<text>2</text>
|
||||
</outputEntry>
|
||||
<outputEntry id="LiteralExpression_13y7kcx">
|
||||
<text>"Mo"</text>
|
||||
</outputEntry>
|
||||
</rule>
|
||||
<rule id="DecisionRule_0x9z2jm">
|
||||
<inputEntry id="UnaryTests_1nsph49">
|
||||
<text>"stooge"</text>
|
||||
</inputEntry>
|
||||
<outputEntry id="LiteralExpression_04v8i8e">
|
||||
<text>3</text>
|
||||
</outputEntry>
|
||||
<outputEntry id="LiteralExpression_1f70eo7">
|
||||
<text>"Curly"</text>
|
||||
</outputEntry>
|
||||
</rule>
|
||||
<rule id="DecisionRule_16ur1y7">
|
||||
<inputEntry id="UnaryTests_0etcimx">
|
||||
<text>"stooge"</text>
|
||||
</inputEntry>
|
||||
<outputEntry id="LiteralExpression_0yc7vs6">
|
||||
<text>4</text>
|
||||
</outputEntry>
|
||||
<outputEntry id="LiteralExpression_1k2kclw">
|
||||
<text>"Shemp"</text>
|
||||
</outputEntry>
|
||||
</rule>
|
||||
<rule id="DecisionRule_0fzx40q">
|
||||
<inputEntry id="UnaryTests_1gzu54o">
|
||||
<text>"farmer"</text>
|
||||
</inputEntry>
|
||||
<outputEntry id="LiteralExpression_11h0epz">
|
||||
<text>5</text>
|
||||
</outputEntry>
|
||||
<outputEntry id="LiteralExpression_031adn2">
|
||||
<text>"Elmer Fudd"</text>
|
||||
</outputEntry>
|
||||
</rule>
|
||||
</decisionTable>
|
||||
</decision>
|
||||
<dmndi:DMNDI>
|
||||
<dmndi:DMNDiagram id="DMNDiagram_1vu8ul5">
|
||||
<dmndi:DMNShape id="DMNShape_0sl8kie" dmnElementRef="Decision_ExclusiveAMCheck">
|
||||
<dc:Bounds height="80" width="180" x="150" y="150" />
|
||||
</dmndi:DMNShape>
|
||||
</dmndi:DMNDiagram>
|
||||
</dmndi:DMNDI>
|
||||
</definitions>
|
54
SpiffWorkflow/tests/SpiffWorkflow/dmn/data/unique_hit.dmn
Normal file
54
SpiffWorkflow/tests/SpiffWorkflow/dmn/data/unique_hit.dmn
Normal file
@ -0,0 +1,54 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<definitions xmlns="https://www.omg.org/spec/DMN/20191111/MODEL/" xmlns:biodi="http://bpmn.io/schema/dmn/biodi/2.0" xmlns:dmndi="https://www.omg.org/spec/DMN/20191111/DMNDI/" xmlns:dc="http://www.omg.org/spec/DMN/20180521/DC/" id="Definitions_06veek1" name="DRD" namespace="http://camunda.org/schema/1.0/dmn" exporter="Camunda Modeler" exporterVersion="5.0.0">
|
||||
<decision id="Decision_ExclusiveAMCheck" name="UniqueHit">
|
||||
<decisionTable id="decisionTable_1">
|
||||
<input id="input_1" label="Input "name"" biodi:width="192">
|
||||
<inputExpression id="inputExpression_1" typeRef="string" expressionLanguage="python">
|
||||
<text>name</text>
|
||||
</inputExpression>
|
||||
</input>
|
||||
<output id="output_1" label="Output "result"" name="result" typeRef="integer" />
|
||||
<rule id="DecisionRule_07162mr">
|
||||
<description></description>
|
||||
<inputEntry id="UnaryTests_1jqxc3u">
|
||||
<text>"Larry"</text>
|
||||
</inputEntry>
|
||||
<outputEntry id="LiteralExpression_16l50ps">
|
||||
<text>1</text>
|
||||
</outputEntry>
|
||||
</rule>
|
||||
<rule id="DecisionRule_0ifa4wu">
|
||||
<description></description>
|
||||
<inputEntry id="UnaryTests_0szbwxc">
|
||||
<text>"Mo"</text>
|
||||
</inputEntry>
|
||||
<outputEntry id="LiteralExpression_0td8sa6">
|
||||
<text>2</text>
|
||||
</outputEntry>
|
||||
</rule>
|
||||
<rule id="DecisionRule_0x9z2jm">
|
||||
<inputEntry id="UnaryTests_1ul57lx">
|
||||
<text>"Curly"</text>
|
||||
</inputEntry>
|
||||
<outputEntry id="LiteralExpression_04v8i8e">
|
||||
<text>3</text>
|
||||
</outputEntry>
|
||||
</rule>
|
||||
<rule id="DecisionRule_16ur1y7">
|
||||
<inputEntry id="UnaryTests_09q92u9">
|
||||
<text>"Shemp"</text>
|
||||
</inputEntry>
|
||||
<outputEntry id="LiteralExpression_0yc7vs6">
|
||||
<text>4</text>
|
||||
</outputEntry>
|
||||
</rule>
|
||||
</decisionTable>
|
||||
</decision>
|
||||
<dmndi:DMNDI>
|
||||
<dmndi:DMNDiagram id="DMNDiagram_1vu8ul5">
|
||||
<dmndi:DMNShape id="DMNShape_0sl8kie" dmnElementRef="Decision_ExclusiveAMCheck">
|
||||
<dc:Bounds height="80" width="180" x="150" y="150" />
|
||||
</dmndi:DMNShape>
|
||||
</dmndi:DMNDiagram>
|
||||
</dmndi:DMNDI>
|
||||
</definitions>
|
@ -20,6 +20,12 @@ function get_python_dirs() {
|
||||
(git ls-tree -r HEAD --name-only | grep -E '\.py$' | awk -F '/' '{print $1}' | sort | uniq | grep -v '\.' | grep -Ev '^(bin|migrations)$') || echo ''
|
||||
}
|
||||
|
||||
function run_fix_docstrings() {
|
||||
if command -v fix_python_docstrings >/dev/null ; then
|
||||
fix_python_docstrings $(get_top_level_directories_containing_python_files)
|
||||
fi
|
||||
}
|
||||
|
||||
function run_autoflake() {
|
||||
if ! command -v autoflake8 >/dev/null ; then
|
||||
pip install autoflake8
|
||||
@ -51,6 +57,7 @@ done
|
||||
|
||||
for python_project in "${python_projects[@]}" ; do
|
||||
pushd "$python_project"
|
||||
run_fix_docstrings || run_fix_docstrings
|
||||
run_autoflake || run_autoflake
|
||||
popd
|
||||
done
|
||||
|
@ -20,7 +20,10 @@ export function findDataObjects(parent) {
|
||||
return [];
|
||||
}
|
||||
for (const element of process.flowElements) {
|
||||
if (element.$type === 'bpmn:DataObject') {
|
||||
if (
|
||||
element.$type === 'bpmn:DataObject' &&
|
||||
dataObjects.indexOf(element) < 0
|
||||
) {
|
||||
dataObjects.push(element);
|
||||
}
|
||||
}
|
||||
@ -48,7 +51,7 @@ export function findDataReferenceShapes(processShape, id) {
|
||||
}
|
||||
|
||||
export function idToHumanReadableName(id) {
|
||||
const words = id.match(/[A-Za-z][a-z]*/g) || [];
|
||||
const words = id.match(/[A-Za-z][a-z]*/g) || [id];
|
||||
return words.map(capitalize).join(' ');
|
||||
|
||||
function capitalize(word) {
|
||||
|
@ -1,10 +1,13 @@
|
||||
import CommandInterceptor from 'diagram-js/lib/command/CommandInterceptor';
|
||||
import {getDi, is} from 'bpmn-js/lib/util/ModelUtil';
|
||||
import {findDataObjects, findDataReferenceShapes, idToHumanReadableName} from './DataObjectHelpers';
|
||||
var HIGH_PRIORITY = 1500;
|
||||
import { getDi, is } from 'bpmn-js/lib/util/ModelUtil';
|
||||
import { remove as collectionRemove } from 'diagram-js/lib/util/Collections';
|
||||
import {
|
||||
remove as collectionRemove,
|
||||
} from 'diagram-js/lib/util/Collections';
|
||||
findDataObjects,
|
||||
findDataReferenceShapes,
|
||||
idToHumanReadableName,
|
||||
} from './DataObjectHelpers';
|
||||
|
||||
const HIGH_PRIORITY = 1500;
|
||||
/**
|
||||
* This Command Interceptor functions like the BpmnUpdator in BPMN.js - It hooks into events
|
||||
* from Diagram.js and updates the underlying BPMN model accordingly.
|
||||
@ -17,7 +20,7 @@ import {
|
||||
* 4) Don't allow someone to move a DataObjectReference from one process to another process.
|
||||
*/
|
||||
export default class DataObjectInterceptor extends CommandInterceptor {
|
||||
constructor(eventBus, bpmnFactory, bpmnUpdater) {
|
||||
constructor(eventBus, bpmnFactory, commandStack) {
|
||||
super(eventBus);
|
||||
|
||||
/**
|
||||
@ -26,8 +29,9 @@ export default class DataObjectInterceptor extends CommandInterceptor {
|
||||
* attempt to crete a dataObject immediately. We can't create the dataObject until
|
||||
* we know where it is placed - as we want to reuse data objects of the parent when
|
||||
* possible */
|
||||
this.preExecute([ 'shape.create' ], HIGH_PRIORITY, function(event) {
|
||||
const context = event.context, shape = context.shape;
|
||||
this.preExecute(['shape.create'], HIGH_PRIORITY, function (event) {
|
||||
const { context } = event;
|
||||
const { shape } = context;
|
||||
if (is(shape, 'bpmn:DataObjectReference') && shape.type !== 'label') {
|
||||
event.stopPropagation();
|
||||
}
|
||||
@ -36,11 +40,12 @@ export default class DataObjectInterceptor extends CommandInterceptor {
|
||||
/**
|
||||
* Don't just create a new data object, use the first existing one if it already exists
|
||||
*/
|
||||
this.executed([ 'shape.create' ], HIGH_PRIORITY, function(event) {
|
||||
const context = event.context, shape = context.shape;
|
||||
this.executed(['shape.create'], HIGH_PRIORITY, function (event) {
|
||||
const { context } = event;
|
||||
const { shape } = context;
|
||||
if (is(shape, 'bpmn:DataObjectReference') && shape.type !== 'label') {
|
||||
let process = shape.parent.businessObject;
|
||||
let existingDataObjects = findDataObjects(process);
|
||||
const process = shape.parent.businessObject;
|
||||
const existingDataObjects = findDataObjects(process);
|
||||
let dataObject;
|
||||
if (existingDataObjects.length > 0) {
|
||||
dataObject = existingDataObjects[0];
|
||||
@ -48,20 +53,36 @@ export default class DataObjectInterceptor extends CommandInterceptor {
|
||||
dataObject = bpmnFactory.create('bpmn:DataObject');
|
||||
}
|
||||
|
||||
// Update the name of the reference to match the data object's id.
|
||||
shape.businessObject.name = idToHumanReadableName(dataObject.id);
|
||||
|
||||
// set the reference to the DataObject
|
||||
shape.businessObject.dataObjectRef = dataObject;
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* In order for the label to display correctly, we need to update it in POST step.
|
||||
*/
|
||||
this.postExecuted(['shape.create'], HIGH_PRIORITY, function (event) {
|
||||
const { context } = event;
|
||||
const { shape } = context;
|
||||
// set the reference to the DataObject
|
||||
// Update the name of the reference to match the data object's id.
|
||||
if (is(shape, 'bpmn:DataObjectReference') && shape.type !== 'label') {
|
||||
commandStack.execute('element.updateProperties', {
|
||||
element: shape,
|
||||
moddleElement: shape.businessObject,
|
||||
properties: {
|
||||
name: idToHumanReadableName(shape.businessObject.dataObjectRef.id),
|
||||
},
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* Don't remove the associated DataObject, unless all references to that data object
|
||||
* Difficult to do given placement of this logic in the BPMN Updater, so we have
|
||||
* to manually handle the removal.
|
||||
*/
|
||||
this.executed([ 'shape.delete' ], HIGH_PRIORITY, function(event) {
|
||||
this.executed(['shape.delete'], HIGH_PRIORITY, function (event) {
|
||||
const { context } = event;
|
||||
const { shape, oldParent } = context;
|
||||
if (is(shape, 'bpmn:DataObjectReference') && shape.type !== 'label') {
|
||||
@ -97,4 +118,4 @@ export default class DataObjectInterceptor extends CommandInterceptor {
|
||||
}
|
||||
}
|
||||
|
||||
DataObjectInterceptor.$inject = [ 'eventBus', 'bpmnFactory', 'bpmnUpdater' ];
|
||||
DataObjectInterceptor.$inject = ['eventBus', 'bpmnFactory', 'commandStack'];
|
||||
|
@ -78,17 +78,10 @@ function removeFactory(props) {
|
||||
flowElements: without(process.get('flowElements'), dataObject),
|
||||
},
|
||||
});
|
||||
// Also update the label of all the references
|
||||
// When a data object is removed, remove all references as well.
|
||||
const references = findDataReferenceShapes(element, dataObject.id);
|
||||
for (const ref of references) {
|
||||
commandStack.execute('element.updateProperties', {
|
||||
element: ref,
|
||||
moddleElement: ref.businessObject,
|
||||
properties: {
|
||||
name: '???',
|
||||
},
|
||||
changed: [ref], // everything is already marked as changed, don't recalculate.
|
||||
});
|
||||
commandStack.execute('shape.delete', { shape: ref });
|
||||
}
|
||||
};
|
||||
}
|
||||
|
@ -1,6 +1,6 @@
|
||||
import {useService } from 'bpmn-js-properties-panel';
|
||||
import { SelectEntry } from '@bpmn-io/properties-panel';
|
||||
import {idToHumanReadableName} from '../DataObjectHelpers';
|
||||
import {findDataObjects, idToHumanReadableName} from '../DataObjectHelpers';
|
||||
|
||||
/**
|
||||
* Finds the value of the given type within the extensionElements
|
||||
@ -54,13 +54,12 @@ export function DataObjectSelect(props) {
|
||||
const getOptions = value => {
|
||||
const businessObject = element.businessObject;
|
||||
const parent = businessObject.$parent;
|
||||
let options = []
|
||||
for (const element of parent.flowElements) {
|
||||
if (element.$type === 'bpmn:DataObject') {
|
||||
options.push({label: element.id, value: element.id})
|
||||
}
|
||||
}
|
||||
return options
|
||||
let dataObjects = findDataObjects(parent);
|
||||
let options = [];
|
||||
dataObjects.forEach(dataObj => {
|
||||
options.push({label: dataObj.id, value: dataObj.id})
|
||||
});
|
||||
return options;
|
||||
}
|
||||
|
||||
return <SelectEntry
|
||||
|
@ -74,7 +74,7 @@ def main():
|
||||
print(f"ticket_identifier: {ticket_identifier}")
|
||||
print(f"priority: {priority}")
|
||||
|
||||
process_instance = ProcessInstanceService.create_process_instance(
|
||||
process_instance = ProcessInstanceService.create_process_instance_from_process_model_identifier(
|
||||
process_model_identifier_ticket,
|
||||
user,
|
||||
process_group_identifier="sartography-admin",
|
||||
|
@ -68,7 +68,7 @@ def main():
|
||||
print(f"priority: {priority}")
|
||||
# if there is no month, who cares about it.
|
||||
if month:
|
||||
process_instance = ProcessInstanceService.create_process_instance(
|
||||
process_instance = ProcessInstanceService.create_process_instance_from_process_model_identifier(
|
||||
process_model_identifier=process_model_identifier_ticket,
|
||||
user=user,
|
||||
process_group_identifier="sartography-admin",
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -27,7 +27,7 @@ docker run \
|
||||
-e KEYCLOAK_LOGLEVEL=ALL \
|
||||
-e ROOT_LOGLEVEL=ALL \
|
||||
-e KEYCLOAK_ADMIN=admin \
|
||||
-e KEYCLOAK_ADMIN_PASSWORD=admin quay.io/keycloak/keycloak:19.0.3 start-dev \
|
||||
-e KEYCLOAK_ADMIN_PASSWORD=admin quay.io/keycloak/keycloak:20.0.1 start-dev \
|
||||
-Dkeycloak.profile.feature.token_exchange=enabled \
|
||||
-Dkeycloak.profile.feature.admin_fine_grained_authz=enabled
|
||||
|
||||
|
@ -96,7 +96,7 @@ def setup_process_instances_for_reports(
|
||||
# )
|
||||
process_instances = []
|
||||
for data in [kay(), ray(), jay()]:
|
||||
process_instance = ProcessInstanceService.create_process_instance(
|
||||
process_instance = ProcessInstanceService.create_process_instance_from_process_model_identifier(
|
||||
# process_group_identifier=process_group_id,
|
||||
process_model_identifier=process_model_identifier,
|
||||
user=user,
|
||||
|
@ -1,4 +1,4 @@
|
||||
FROM quay.io/keycloak/keycloak:19.0.3 as builder
|
||||
FROM quay.io/keycloak/keycloak:20.0.1 as builder
|
||||
|
||||
ENV KEYCLOAK_LOGLEVEL="ALL"
|
||||
ENV ROOT_LOGLEVEL="ALL"
|
||||
|
@ -1,8 +1,8 @@
|
||||
"""empty message
|
||||
|
||||
Revision ID: 70223f5c7b98
|
||||
Revision ID: ff1c1628337c
|
||||
Revises:
|
||||
Create Date: 2022-11-20 19:54:45.061376
|
||||
Create Date: 2022-11-28 15:08:52.014254
|
||||
|
||||
"""
|
||||
from alembic import op
|
||||
@ -10,7 +10,7 @@ import sqlalchemy as sa
|
||||
|
||||
|
||||
# revision identifiers, used by Alembic.
|
||||
revision = '70223f5c7b98'
|
||||
revision = 'ff1c1628337c'
|
||||
down_revision = None
|
||||
branch_labels = None
|
||||
depends_on = None
|
||||
@ -97,14 +97,12 @@ def upgrade():
|
||||
sa.Column('id', sa.Integer(), nullable=False),
|
||||
sa.Column('message_model_id', sa.Integer(), nullable=False),
|
||||
sa.Column('process_model_identifier', sa.String(length=50), nullable=False),
|
||||
sa.Column('process_group_identifier', sa.String(length=50), nullable=False),
|
||||
sa.Column('updated_at_in_seconds', sa.Integer(), nullable=True),
|
||||
sa.Column('created_at_in_seconds', sa.Integer(), nullable=True),
|
||||
sa.ForeignKeyConstraint(['message_model_id'], ['message_model.id'], ),
|
||||
sa.PrimaryKeyConstraint('id'),
|
||||
sa.UniqueConstraint('message_model_id')
|
||||
)
|
||||
op.create_index(op.f('ix_message_triggerable_process_model_process_group_identifier'), 'message_triggerable_process_model', ['process_group_identifier'], unique=False)
|
||||
op.create_index(op.f('ix_message_triggerable_process_model_process_model_identifier'), 'message_triggerable_process_model', ['process_model_identifier'], unique=False)
|
||||
op.create_table('principal',
|
||||
sa.Column('id', sa.Integer(), nullable=False),
|
||||
@ -120,7 +118,7 @@ def upgrade():
|
||||
op.create_table('process_instance',
|
||||
sa.Column('id', sa.Integer(), nullable=False),
|
||||
sa.Column('process_model_identifier', sa.String(length=255), nullable=False),
|
||||
sa.Column('process_group_identifier', sa.String(length=50), nullable=False),
|
||||
sa.Column('process_model_display_name', sa.String(length=255), nullable=False),
|
||||
sa.Column('process_initiator_id', sa.Integer(), nullable=False),
|
||||
sa.Column('bpmn_json', sa.JSON(), nullable=True),
|
||||
sa.Column('start_in_seconds', sa.Integer(), nullable=True),
|
||||
@ -134,7 +132,7 @@ def upgrade():
|
||||
sa.ForeignKeyConstraint(['process_initiator_id'], ['user.id'], ),
|
||||
sa.PrimaryKeyConstraint('id')
|
||||
)
|
||||
op.create_index(op.f('ix_process_instance_process_group_identifier'), 'process_instance', ['process_group_identifier'], unique=False)
|
||||
op.create_index(op.f('ix_process_instance_process_model_display_name'), 'process_instance', ['process_model_display_name'], unique=False)
|
||||
op.create_index(op.f('ix_process_instance_process_model_identifier'), 'process_instance', ['process_model_identifier'], unique=False)
|
||||
op.create_table('process_instance_report',
|
||||
sa.Column('id', sa.Integer(), nullable=False),
|
||||
@ -168,17 +166,6 @@ def upgrade():
|
||||
sa.PrimaryKeyConstraint('id'),
|
||||
sa.UniqueConstraint('key')
|
||||
)
|
||||
op.create_table('spiff_step_details',
|
||||
sa.Column('id', sa.Integer(), nullable=False),
|
||||
sa.Column('process_instance_id', sa.Integer(), nullable=False),
|
||||
sa.Column('spiff_step', sa.Integer(), nullable=False),
|
||||
sa.Column('task_json', sa.JSON(), nullable=False),
|
||||
sa.Column('timestamp', sa.DECIMAL(precision=17, scale=6), nullable=False),
|
||||
sa.Column('completed_by_user_id', sa.Integer(), nullable=True),
|
||||
sa.Column('lane_assignment_id', sa.Integer(), nullable=True),
|
||||
sa.ForeignKeyConstraint(['lane_assignment_id'], ['group.id'], ),
|
||||
sa.PrimaryKeyConstraint('id')
|
||||
)
|
||||
op.create_table('user_group_assignment',
|
||||
sa.Column('id', sa.Integer(), nullable=False),
|
||||
sa.Column('user_id', sa.Integer(), nullable=False),
|
||||
@ -251,6 +238,29 @@ def upgrade():
|
||||
sa.PrimaryKeyConstraint('id'),
|
||||
sa.UniqueConstraint('principal_id', 'permission_target_id', 'permission', name='permission_assignment_uniq')
|
||||
)
|
||||
op.create_table('process_instance_metadata',
|
||||
sa.Column('id', sa.Integer(), nullable=False),
|
||||
sa.Column('process_instance_id', sa.Integer(), nullable=False),
|
||||
sa.Column('key', sa.String(length=255), nullable=False),
|
||||
sa.Column('value', sa.String(length=255), nullable=False),
|
||||
sa.Column('updated_at_in_seconds', sa.Integer(), nullable=False),
|
||||
sa.Column('created_at_in_seconds', sa.Integer(), nullable=False),
|
||||
sa.ForeignKeyConstraint(['process_instance_id'], ['process_instance.id'], ),
|
||||
sa.PrimaryKeyConstraint('id'),
|
||||
sa.UniqueConstraint('process_instance_id', 'key', name='process_instance_metadata_unique')
|
||||
)
|
||||
op.create_table('spiff_step_details',
|
||||
sa.Column('id', sa.Integer(), nullable=False),
|
||||
sa.Column('process_instance_id', sa.Integer(), nullable=False),
|
||||
sa.Column('spiff_step', sa.Integer(), nullable=False),
|
||||
sa.Column('task_json', sa.JSON(), nullable=False),
|
||||
sa.Column('timestamp', sa.DECIMAL(precision=17, scale=6), nullable=False),
|
||||
sa.Column('completed_by_user_id', sa.Integer(), nullable=True),
|
||||
sa.Column('lane_assignment_id', sa.Integer(), nullable=True),
|
||||
sa.ForeignKeyConstraint(['lane_assignment_id'], ['group.id'], ),
|
||||
sa.ForeignKeyConstraint(['process_instance_id'], ['process_instance.id'], ),
|
||||
sa.PrimaryKeyConstraint('id')
|
||||
)
|
||||
op.create_table('active_task_user',
|
||||
sa.Column('id', sa.Integer(), nullable=False),
|
||||
sa.Column('active_task_id', sa.Integer(), nullable=False),
|
||||
@ -284,6 +294,8 @@ def downgrade():
|
||||
op.drop_index(op.f('ix_active_task_user_user_id'), table_name='active_task_user')
|
||||
op.drop_index(op.f('ix_active_task_user_active_task_id'), table_name='active_task_user')
|
||||
op.drop_table('active_task_user')
|
||||
op.drop_table('spiff_step_details')
|
||||
op.drop_table('process_instance_metadata')
|
||||
op.drop_table('permission_assignment')
|
||||
op.drop_table('message_instance')
|
||||
op.drop_index(op.f('ix_message_correlation_value'), table_name='message_correlation')
|
||||
@ -293,18 +305,16 @@ def downgrade():
|
||||
op.drop_table('message_correlation')
|
||||
op.drop_table('active_task')
|
||||
op.drop_table('user_group_assignment')
|
||||
op.drop_table('spiff_step_details')
|
||||
op.drop_table('secret')
|
||||
op.drop_table('refresh_token')
|
||||
op.drop_index(op.f('ix_process_instance_report_identifier'), table_name='process_instance_report')
|
||||
op.drop_index(op.f('ix_process_instance_report_created_by_id'), table_name='process_instance_report')
|
||||
op.drop_table('process_instance_report')
|
||||
op.drop_index(op.f('ix_process_instance_process_model_identifier'), table_name='process_instance')
|
||||
op.drop_index(op.f('ix_process_instance_process_group_identifier'), table_name='process_instance')
|
||||
op.drop_index(op.f('ix_process_instance_process_model_display_name'), table_name='process_instance')
|
||||
op.drop_table('process_instance')
|
||||
op.drop_table('principal')
|
||||
op.drop_index(op.f('ix_message_triggerable_process_model_process_model_identifier'), table_name='message_triggerable_process_model')
|
||||
op.drop_index(op.f('ix_message_triggerable_process_model_process_group_identifier'), table_name='message_triggerable_process_model')
|
||||
op.drop_table('message_triggerable_process_model')
|
||||
op.drop_index(op.f('ix_message_correlation_property_identifier'), table_name='message_correlation_property')
|
||||
op.drop_table('message_correlation_property')
|
@ -160,7 +160,7 @@ paths:
|
||||
schema:
|
||||
type: integer
|
||||
get:
|
||||
operationId: spiffworkflow_backend.routes.process_api_blueprint.process_groups_list
|
||||
operationId: spiffworkflow_backend.routes.process_api_blueprint.process_group_list
|
||||
summary: get list
|
||||
tags:
|
||||
- Process Groups
|
||||
@ -278,7 +278,13 @@ paths:
|
||||
required: false
|
||||
description: Get all sub process models recursively if true
|
||||
schema:
|
||||
type: string
|
||||
type: boolean
|
||||
- name: filter_runnable_by_user
|
||||
in: query
|
||||
required: false
|
||||
description: Get only the process models that the user can run
|
||||
schema:
|
||||
type: boolean
|
||||
- name: page
|
||||
in: query
|
||||
required: false
|
||||
@ -508,6 +514,24 @@ paths:
|
||||
description: For filtering - not_started, user_input_required, waiting, complete, error, or suspended
|
||||
schema:
|
||||
type: string
|
||||
- name: initiated_by_me
|
||||
in: query
|
||||
required: false
|
||||
description: For filtering - show instances initiated by me
|
||||
schema:
|
||||
type: boolean
|
||||
- name: with_tasks_completed_by_me
|
||||
in: query
|
||||
required: false
|
||||
description: For filtering - show instances with tasks completed by me
|
||||
schema:
|
||||
type: boolean
|
||||
- name: with_tasks_completed_by_my_group
|
||||
in: query
|
||||
required: false
|
||||
description: For filtering - show instances with tasks completed by my group
|
||||
schema:
|
||||
type: boolean
|
||||
- name: user_filter
|
||||
in: query
|
||||
required: false
|
||||
@ -686,7 +710,7 @@ paths:
|
||||
schema:
|
||||
$ref: "#/components/schemas/Workflow"
|
||||
|
||||
/process-instances/{process_instance_id}/run:
|
||||
/process-instances/{modified_process_model_identifier}/{process_instance_id}/run:
|
||||
parameters:
|
||||
- name: process_instance_id
|
||||
in: path
|
||||
@ -700,7 +724,6 @@ paths:
|
||||
description: Defaults to true, can be set to false if you are just looking at the workflow not completeing it.
|
||||
schema:
|
||||
type: boolean
|
||||
# process_instance_run
|
||||
post:
|
||||
operationId: spiffworkflow_backend.routes.process_api_blueprint.process_instance_run
|
||||
summary: Run a process instance
|
||||
@ -1642,10 +1665,6 @@ components:
|
||||
type: integer
|
||||
x-nullable: true
|
||||
example: 12
|
||||
study_id:
|
||||
type: integer
|
||||
x-nullable: true
|
||||
example: 42
|
||||
user_id:
|
||||
type: string
|
||||
x-nullable: true
|
||||
@ -1770,8 +1789,6 @@ components:
|
||||
type: integer
|
||||
num_tasks_incomplete:
|
||||
type: integer
|
||||
study_id:
|
||||
type: integer
|
||||
|
||||
example:
|
||||
id: 291234
|
||||
@ -1906,9 +1923,6 @@ components:
|
||||
workflow_id:
|
||||
example: 42
|
||||
type: integer
|
||||
study_id:
|
||||
example: 187
|
||||
type: integer
|
||||
user_uid:
|
||||
example: "dhf8r"
|
||||
type: string
|
||||
|
@ -4,26 +4,22 @@ groups:
|
||||
admin:
|
||||
users:
|
||||
[
|
||||
admin,
|
||||
jakub,
|
||||
kb,
|
||||
alex,
|
||||
dan,
|
||||
mike,
|
||||
jason,
|
||||
j,
|
||||
amir,
|
||||
jarrad,
|
||||
elizabeth,
|
||||
jon,
|
||||
harmeet,
|
||||
sasha,
|
||||
manuchehr,
|
||||
natalia,
|
||||
]
|
||||
|
||||
Finance Team:
|
||||
users: [finance_user1, jason]
|
||||
|
||||
Project Lead:
|
||||
users:
|
||||
[
|
||||
jakub,
|
||||
@ -31,18 +27,42 @@ groups:
|
||||
dan,
|
||||
mike,
|
||||
jason,
|
||||
j,
|
||||
amir,
|
||||
jarrad,
|
||||
elizabeth,
|
||||
jon,
|
||||
natalia,
|
||||
sasha,
|
||||
fin,
|
||||
fin1,
|
||||
]
|
||||
|
||||
demo:
|
||||
users:
|
||||
[
|
||||
core,
|
||||
fin,
|
||||
fin1,
|
||||
harmeet,
|
||||
sasha,
|
||||
manuchehr,
|
||||
lead,
|
||||
lead1
|
||||
]
|
||||
|
||||
core-contributor:
|
||||
users:
|
||||
[
|
||||
core,
|
||||
harmeet,
|
||||
]
|
||||
|
||||
permissions:
|
||||
admin:
|
||||
groups: [admin]
|
||||
users: []
|
||||
allowed_permissions: [create, read, update, delete, list, instantiate]
|
||||
allowed_permissions: [create, read, update, delete]
|
||||
uri: /*
|
||||
|
||||
tasks-crud:
|
||||
@ -51,45 +71,116 @@ permissions:
|
||||
allowed_permissions: [create, read, update, delete]
|
||||
uri: /v1.0/tasks/*
|
||||
|
||||
process-model-read-all:
|
||||
groups: [everybody]
|
||||
users: []
|
||||
allowed_permissions: [read]
|
||||
uri: /v1.0/process-models/*
|
||||
|
||||
process-group-read-all:
|
||||
# read all for everybody
|
||||
read-all-process-groups:
|
||||
groups: [everybody]
|
||||
users: []
|
||||
allowed_permissions: [read]
|
||||
uri: /v1.0/process-groups/*
|
||||
|
||||
process-instance-list:
|
||||
read-all-process-models:
|
||||
groups: [everybody]
|
||||
users: []
|
||||
allowed_permissions: [read]
|
||||
uri: /v1.0/process-instances
|
||||
uri: /v1.0/process-models/*
|
||||
read-all-process-instance:
|
||||
groups: [everybody]
|
||||
users: []
|
||||
allowed_permissions: [read]
|
||||
uri: /v1.0/process-instances/*
|
||||
read-process-instance-reports:
|
||||
groups: [everybody]
|
||||
users: []
|
||||
allowed_permissions: [read]
|
||||
uri: /v1.0/process-instances/reports/*
|
||||
processes-read:
|
||||
groups: [everybody]
|
||||
users: []
|
||||
allowed_permissions: [read]
|
||||
uri: /v1.0/processes
|
||||
|
||||
# TODO: all uris should really have the same structure
|
||||
finance-admin-group:
|
||||
|
||||
manage-procurement-admin:
|
||||
groups: ["Project Lead"]
|
||||
users: []
|
||||
allowed_permissions: [create, read, update, delete]
|
||||
uri: /v1.0/process-groups/manage-procurement:*
|
||||
manage-procurement-admin-slash:
|
||||
groups: ["Project Lead"]
|
||||
users: []
|
||||
allowed_permissions: [create, read, update, delete]
|
||||
uri: /v1.0/process-groups/manage-procurement/*
|
||||
manage-procurement-admin-models:
|
||||
groups: ["Project Lead"]
|
||||
users: []
|
||||
allowed_permissions: [create, read, update, delete]
|
||||
uri: /v1.0/process-models/manage-procurement:*
|
||||
manage-procurement-admin-models-slash:
|
||||
groups: ["Project Lead"]
|
||||
users: []
|
||||
allowed_permissions: [create, read, update, delete]
|
||||
uri: /v1.0/process-models/manage-procurement/*
|
||||
manage-procurement-admin-instances:
|
||||
groups: ["Project Lead"]
|
||||
users: []
|
||||
allowed_permissions: [create, read, update, delete]
|
||||
uri: /v1.0/process-instances/manage-procurement:*
|
||||
manage-procurement-admin-instances-slash:
|
||||
groups: ["Project Lead"]
|
||||
users: []
|
||||
allowed_permissions: [create, read, update, delete]
|
||||
uri: /v1.0/process-instances/manage-procurement/*
|
||||
|
||||
finance-admin:
|
||||
groups: ["Finance Team"]
|
||||
users: []
|
||||
allowed_permissions: [create, read, update, delete]
|
||||
uri: /v1.0/process-groups/finance/*
|
||||
uri: /v1.0/process-groups/manage-procurement:procurement:*
|
||||
|
||||
finance-admin-model:
|
||||
groups: ["Finance Team"]
|
||||
manage-revenue-streams-instantiate:
|
||||
groups: ["core-contributor", "demo"]
|
||||
users: []
|
||||
allowed_permissions: [create, read, update, delete]
|
||||
uri: /v1.0/process-models/finance/*
|
||||
allowed_permissions: [create]
|
||||
uri: /v1.0/process-models/manage-revenue-streams:product-revenue-streams:customer-contracts-trade-terms/*
|
||||
manage-revenue-streams-instances:
|
||||
groups: ["core-contributor", "demo"]
|
||||
users: []
|
||||
allowed_permissions: [create, read]
|
||||
uri: /v1.0/process-instances/manage-revenue-streams:product-revenue-streams:customer-contracts-trade-terms/*
|
||||
|
||||
read-all:
|
||||
groups: [admin, "Project Lead"]
|
||||
manage-procurement-invoice-instantiate:
|
||||
groups: ["core-contributor", "demo"]
|
||||
users: []
|
||||
allowed_permissions: [read]
|
||||
uri: /*
|
||||
allowed_permissions: [create]
|
||||
uri: /v1.0/process-models/manage-procurement:procurement:core-contributor-invoice-management:*
|
||||
manage-procurement-invoice-instances:
|
||||
groups: ["core-contributor", "demo"]
|
||||
users: []
|
||||
allowed_permissions: [create, read]
|
||||
uri: /v1.0/process-instances/manage-procurement:procurement:core-contributor-invoice-management:*
|
||||
|
||||
invoice-approval-tasks-read:
|
||||
groups: ["Finance Team"]
|
||||
manage-procurement-instantiate:
|
||||
groups: ["core-contributor", "demo"]
|
||||
users: []
|
||||
allowed_permissions: [read]
|
||||
uri: /v1.0/process-instances/category_number_one:lanes/*
|
||||
allowed_permissions: [create]
|
||||
uri: /v1.0/process-models/manage-procurement:vendor-lifecycle-management:*
|
||||
manage-procurement-instances:
|
||||
groups: ["core-contributor", "demo"]
|
||||
users: []
|
||||
allowed_permissions: [create, read]
|
||||
uri: /v1.0/process-instances/manage-procurement:vendor-lifecycle-management:*
|
||||
|
||||
core1-admin-models-instantiate:
|
||||
groups: ["core-contributor"]
|
||||
users: []
|
||||
allowed_permissions: [create]
|
||||
uri: /v1.0/process-models/misc:category_number_one:process-model-with-form/process-instances
|
||||
core1-admin-instances:
|
||||
groups: ["core-contributor"]
|
||||
users: []
|
||||
allowed_permissions: [create, read]
|
||||
uri: /v1.0/process-instances/misc:category_number_one:process-model-with-form:*
|
||||
core1-admin-instances-slash:
|
||||
groups: ["core-contributor"]
|
||||
users: []
|
||||
allowed_permissions: [create, read]
|
||||
uri: /v1.0/process-instances/misc:category_number_one:process-model-with-form/*
|
||||
|
@ -4,20 +4,19 @@ groups:
|
||||
admin:
|
||||
users:
|
||||
[
|
||||
admin,
|
||||
jakub,
|
||||
kb,
|
||||
alex,
|
||||
dan,
|
||||
mike,
|
||||
jason,
|
||||
j,
|
||||
amir,
|
||||
jarrad,
|
||||
elizabeth,
|
||||
jon,
|
||||
natalia,
|
||||
harmeet,
|
||||
sasha,
|
||||
manuchehr,
|
||||
]
|
||||
|
||||
Finance Team:
|
||||
@ -28,60 +27,144 @@ groups:
|
||||
dan,
|
||||
mike,
|
||||
jason,
|
||||
j,
|
||||
amir,
|
||||
jarrad,
|
||||
elizabeth,
|
||||
jon,
|
||||
natalia,
|
||||
sasha,
|
||||
fin,
|
||||
fin1,
|
||||
]
|
||||
|
||||
Project Lead:
|
||||
demo:
|
||||
users:
|
||||
[
|
||||
jakub,
|
||||
alex,
|
||||
dan,
|
||||
mike,
|
||||
jason,
|
||||
jarrad,
|
||||
elizabeth,
|
||||
jon,
|
||||
natalia,
|
||||
core,
|
||||
fin,
|
||||
fin1,
|
||||
harmeet,
|
||||
sasha,
|
||||
manuchehr,
|
||||
lead,
|
||||
lead1
|
||||
]
|
||||
|
||||
hr:
|
||||
users: [manuchehr]
|
||||
core-contributor:
|
||||
users:
|
||||
[
|
||||
core,
|
||||
harmeet,
|
||||
]
|
||||
|
||||
permissions:
|
||||
admin:
|
||||
groups: [admin]
|
||||
users: []
|
||||
allowed_permissions: [create, read, update, delete]
|
||||
uri: /*
|
||||
|
||||
tasks-crud:
|
||||
groups: [everybody]
|
||||
users: []
|
||||
allowed_permissions: [create, read, update, delete]
|
||||
uri: /v1.0/tasks/*
|
||||
|
||||
admin:
|
||||
groups: [admin]
|
||||
# read all for everybody
|
||||
read-all-process-groups:
|
||||
groups: [everybody]
|
||||
users: []
|
||||
allowed_permissions: [create, read, update, delete, list, instantiate]
|
||||
uri: /*
|
||||
allowed_permissions: [read]
|
||||
uri: /v1.0/process-groups/*
|
||||
read-all-process-models:
|
||||
groups: [everybody]
|
||||
users: []
|
||||
allowed_permissions: [read]
|
||||
uri: /v1.0/process-models/*
|
||||
read-all-process-instance:
|
||||
groups: [everybody]
|
||||
users: []
|
||||
allowed_permissions: [read]
|
||||
uri: /v1.0/process-instances/*
|
||||
read-process-instance-reports:
|
||||
groups: [everybody]
|
||||
users: []
|
||||
allowed_permissions: [read]
|
||||
uri: /v1.0/process-instances/reports/*
|
||||
processes-read:
|
||||
groups: [everybody]
|
||||
users: []
|
||||
allowed_permissions: [read]
|
||||
uri: /v1.0/processes
|
||||
|
||||
# TODO: all uris should really have the same structure
|
||||
finance-admin-group:
|
||||
groups: ["Finance Team"]
|
||||
|
||||
manage-procurement-admin:
|
||||
groups: ["Project Lead"]
|
||||
users: []
|
||||
allowed_permissions: [create, read, update, delete]
|
||||
uri: /v1.0/process-groups/finance/*
|
||||
uri: /v1.0/process-groups/manage-procurement:*
|
||||
manage-procurement-admin-slash:
|
||||
groups: ["Project Lead"]
|
||||
users: []
|
||||
allowed_permissions: [create, read, update, delete]
|
||||
uri: /v1.0/process-groups/manage-procurement/*
|
||||
manage-procurement-admin-models:
|
||||
groups: ["Project Lead"]
|
||||
users: []
|
||||
allowed_permissions: [create, read, update, delete]
|
||||
uri: /v1.0/process-models/manage-procurement:*
|
||||
manage-procurement-admin-models-slash:
|
||||
groups: ["Project Lead"]
|
||||
users: []
|
||||
allowed_permissions: [create, read, update, delete]
|
||||
uri: /v1.0/process-models/manage-procurement/*
|
||||
manage-procurement-admin-instances:
|
||||
groups: ["Project Lead"]
|
||||
users: []
|
||||
allowed_permissions: [create, read, update, delete]
|
||||
uri: /v1.0/process-instances/manage-procurement:*
|
||||
manage-procurement-admin-instances-slash:
|
||||
groups: ["Project Lead"]
|
||||
users: []
|
||||
allowed_permissions: [create, read, update, delete]
|
||||
uri: /v1.0/process-instances/manage-procurement/*
|
||||
|
||||
finance-admin:
|
||||
groups: ["Finance Team"]
|
||||
users: []
|
||||
allowed_permissions: [create, read, update, delete]
|
||||
uri: /v1.0/process-groups/finance/*
|
||||
uri: /v1.0/process-groups/manage-procurement:procurement:*
|
||||
|
||||
read-all:
|
||||
groups: ["Finance Team", "Project Lead", hr, admin]
|
||||
manage-revenue-streams-instantiate:
|
||||
groups: ["core-contributor", "demo"]
|
||||
users: []
|
||||
allowed_permissions: [read]
|
||||
uri: /*
|
||||
allowed_permissions: [create]
|
||||
uri: /v1.0/process-models/manage-revenue-streams:product-revenue-streams:customer-contracts-trade-terms/*
|
||||
manage-revenue-streams-instances:
|
||||
groups: ["core-contributor", "demo"]
|
||||
users: []
|
||||
allowed_permissions: [create, read]
|
||||
uri: /v1.0/process-instances/manage-revenue-streams:product-revenue-streams:customer-contracts-trade-terms/*
|
||||
|
||||
manage-procurement-invoice-instantiate:
|
||||
groups: ["core-contributor", "demo"]
|
||||
users: []
|
||||
allowed_permissions: [create]
|
||||
uri: /v1.0/process-models/manage-procurement:procurement:core-contributor-invoice-management:*
|
||||
manage-procurement-invoice-instances:
|
||||
groups: ["core-contributor", "demo"]
|
||||
users: []
|
||||
allowed_permissions: [create, read]
|
||||
uri: /v1.0/process-instances/manage-procurement:procurement:core-contributor-invoice-management:*
|
||||
|
||||
manage-procurement-instantiate:
|
||||
groups: ["core-contributor", "demo"]
|
||||
users: []
|
||||
allowed_permissions: [create]
|
||||
uri: /v1.0/process-models/manage-procurement:vendor-lifecycle-management:*
|
||||
manage-procurement-instances:
|
||||
groups: ["core-contributor", "demo"]
|
||||
users: []
|
||||
allowed_permissions: [create, read]
|
||||
uri: /v1.0/process-instances/manage-procurement:vendor-lifecycle-management:*
|
||||
|
@ -51,5 +51,8 @@ from spiffworkflow_backend.models.spiff_step_details import (
|
||||
) # noqa: F401
|
||||
from spiffworkflow_backend.models.user import UserModel # noqa: F401
|
||||
from spiffworkflow_backend.models.group import GroupModel # noqa: F401
|
||||
from spiffworkflow_backend.models.process_instance_metadata import (
|
||||
ProcessInstanceMetadataModel,
|
||||
) # noqa: F401
|
||||
|
||||
add_listeners()
|
||||
|
@ -16,8 +16,6 @@ class MessageTriggerableProcessModel(SpiffworkflowBaseDBModel):
|
||||
ForeignKey(MessageModel.id), nullable=False, unique=True
|
||||
)
|
||||
process_model_identifier: str = db.Column(db.String(50), nullable=False, index=True)
|
||||
# fixme: Maybe we don't need this anymore?
|
||||
process_group_identifier: str = db.Column(db.String(50), nullable=False, index=True)
|
||||
|
||||
updated_at_in_seconds: int = db.Column(db.Integer)
|
||||
created_at_in_seconds: int = db.Column(db.Integer)
|
||||
|
@ -29,6 +29,7 @@ class ProcessGroup:
|
||||
default_factory=list[ProcessModelInfo]
|
||||
)
|
||||
process_groups: list[ProcessGroup] = field(default_factory=list["ProcessGroup"])
|
||||
parent_groups: list[dict] | None = None
|
||||
|
||||
def __post_init__(self) -> None:
|
||||
"""__post_init__."""
|
||||
|
@ -1,7 +1,6 @@
|
||||
"""Process_instance."""
|
||||
from __future__ import annotations
|
||||
|
||||
from dataclasses import dataclass
|
||||
from typing import Any
|
||||
from typing import cast
|
||||
|
||||
@ -18,12 +17,15 @@ from sqlalchemy.orm import relationship
|
||||
from sqlalchemy.orm import validates
|
||||
|
||||
from spiffworkflow_backend.helpers.spiff_enum import SpiffEnum
|
||||
from spiffworkflow_backend.models.process_model import ProcessModelInfo
|
||||
from spiffworkflow_backend.models.task import Task
|
||||
from spiffworkflow_backend.models.task import TaskSchema
|
||||
from spiffworkflow_backend.models.user import UserModel
|
||||
|
||||
|
||||
class ProcessInstanceNotFoundError(Exception):
|
||||
"""ProcessInstanceNotFoundError."""
|
||||
|
||||
|
||||
class NavigationItemSchema(Schema):
|
||||
"""NavigationItemSchema."""
|
||||
|
||||
@ -74,7 +76,9 @@ class ProcessInstanceModel(SpiffworkflowBaseDBModel):
|
||||
process_model_identifier: str = db.Column(
|
||||
db.String(255), nullable=False, index=True
|
||||
)
|
||||
process_group_identifier: str = db.Column(db.String(50), nullable=False, index=True)
|
||||
process_model_display_name: str = db.Column(
|
||||
db.String(255), nullable=False, index=True
|
||||
)
|
||||
process_initiator_id: int = db.Column(ForeignKey(UserModel.id), nullable=False)
|
||||
process_initiator = relationship("UserModel")
|
||||
|
||||
@ -103,7 +107,7 @@ class ProcessInstanceModel(SpiffworkflowBaseDBModel):
|
||||
return {
|
||||
"id": self.id,
|
||||
"process_model_identifier": self.process_model_identifier,
|
||||
"process_group_identifier": self.process_group_identifier,
|
||||
"process_model_display_name": self.process_model_display_name,
|
||||
"status": self.status,
|
||||
"start_in_seconds": self.start_in_seconds,
|
||||
"end_in_seconds": self.end_in_seconds,
|
||||
@ -112,6 +116,7 @@ class ProcessInstanceModel(SpiffworkflowBaseDBModel):
|
||||
"bpmn_version_control_identifier": self.bpmn_version_control_identifier,
|
||||
"bpmn_version_control_type": self.bpmn_version_control_type,
|
||||
"spiff_step": self.spiff_step,
|
||||
"username": self.process_initiator.username,
|
||||
}
|
||||
|
||||
@property
|
||||
@ -140,7 +145,7 @@ class ProcessInstanceModelSchema(Schema):
|
||||
fields = [
|
||||
"id",
|
||||
"process_model_identifier",
|
||||
"process_group_identifier",
|
||||
"process_model_display_name",
|
||||
"process_initiator_id",
|
||||
"start_in_seconds",
|
||||
"end_in_seconds",
|
||||
@ -166,23 +171,18 @@ class ProcessInstanceApi:
|
||||
status: ProcessInstanceStatus,
|
||||
next_task: Task | None,
|
||||
process_model_identifier: str,
|
||||
process_group_identifier: str,
|
||||
process_model_display_name: str,
|
||||
completed_tasks: int,
|
||||
updated_at_in_seconds: int,
|
||||
is_review: bool,
|
||||
title: str,
|
||||
) -> None:
|
||||
"""__init__."""
|
||||
self.id = id
|
||||
self.status = status
|
||||
self.next_task = next_task # The next task that requires user input.
|
||||
# self.navigation = navigation fixme: would be a hotness.
|
||||
self.process_model_identifier = process_model_identifier
|
||||
self.process_group_identifier = process_group_identifier
|
||||
self.process_model_display_name = process_model_display_name
|
||||
self.completed_tasks = completed_tasks
|
||||
self.updated_at_in_seconds = updated_at_in_seconds
|
||||
self.title = title
|
||||
self.is_review = is_review
|
||||
|
||||
|
||||
class ProcessInstanceApiSchema(Schema):
|
||||
@ -196,24 +196,15 @@ class ProcessInstanceApiSchema(Schema):
|
||||
"id",
|
||||
"status",
|
||||
"next_task",
|
||||
"navigation",
|
||||
"process_model_identifier",
|
||||
"process_group_identifier",
|
||||
"process_model_display_name",
|
||||
"completed_tasks",
|
||||
"updated_at_in_seconds",
|
||||
"is_review",
|
||||
"title",
|
||||
"study_id",
|
||||
"state",
|
||||
]
|
||||
unknown = INCLUDE
|
||||
|
||||
status = EnumField(ProcessInstanceStatus)
|
||||
next_task = marshmallow.fields.Nested(TaskSchema, dump_only=True, required=False)
|
||||
navigation = marshmallow.fields.List(
|
||||
marshmallow.fields.Nested(NavigationItemSchema, dump_only=True)
|
||||
)
|
||||
state = marshmallow.fields.String(allow_none=True)
|
||||
|
||||
@marshmallow.post_load
|
||||
def make_process_instance(
|
||||
@ -224,73 +215,11 @@ class ProcessInstanceApiSchema(Schema):
|
||||
"id",
|
||||
"status",
|
||||
"next_task",
|
||||
"navigation",
|
||||
"process_model_identifier",
|
||||
"process_group_identifier",
|
||||
"process_model_display_name",
|
||||
"completed_tasks",
|
||||
"updated_at_in_seconds",
|
||||
"is_review",
|
||||
"title",
|
||||
"study_id",
|
||||
"state",
|
||||
]
|
||||
filtered_fields = {key: data[key] for key in keys}
|
||||
filtered_fields["next_task"] = TaskSchema().make_task(data["next_task"])
|
||||
return ProcessInstanceApi(**filtered_fields)
|
||||
|
||||
|
||||
@dataclass
|
||||
class ProcessInstanceMetadata:
|
||||
"""ProcessInstanceMetadata."""
|
||||
|
||||
id: int
|
||||
display_name: str | None = None
|
||||
description: str | None = None
|
||||
spec_version: str | None = None
|
||||
state: str | None = None
|
||||
status: str | None = None
|
||||
completed_tasks: int | None = None
|
||||
is_review: bool | None = None
|
||||
state_message: str | None = None
|
||||
process_model_identifier: str | None = None
|
||||
process_group_id: str | None = None
|
||||
|
||||
@classmethod
|
||||
def from_process_instance(
|
||||
cls, process_instance: ProcessInstanceModel, process_model: ProcessModelInfo
|
||||
) -> ProcessInstanceMetadata:
|
||||
"""From_process_instance."""
|
||||
instance = cls(
|
||||
id=process_instance.id,
|
||||
display_name=process_model.display_name,
|
||||
description=process_model.description,
|
||||
process_group_id=process_model.process_group,
|
||||
state_message=process_instance.state_message,
|
||||
status=process_instance.status,
|
||||
completed_tasks=process_instance.completed_tasks,
|
||||
is_review=process_model.is_review,
|
||||
process_model_identifier=process_instance.process_model_identifier,
|
||||
)
|
||||
return instance
|
||||
|
||||
|
||||
class ProcessInstanceMetadataSchema(Schema):
|
||||
"""ProcessInstanceMetadataSchema."""
|
||||
|
||||
status = EnumField(ProcessInstanceStatus)
|
||||
|
||||
class Meta:
|
||||
"""Meta."""
|
||||
|
||||
model = ProcessInstanceMetadata
|
||||
additional = [
|
||||
"id",
|
||||
"display_name",
|
||||
"description",
|
||||
"state",
|
||||
"completed_tasks",
|
||||
"process_group_id",
|
||||
"is_review",
|
||||
"state_message",
|
||||
]
|
||||
unknown = INCLUDE
|
||||
|
@ -0,0 +1,30 @@
|
||||
"""Spiff_step_details."""
|
||||
from dataclasses import dataclass
|
||||
|
||||
from flask_bpmn.models.db import db
|
||||
from flask_bpmn.models.db import SpiffworkflowBaseDBModel
|
||||
from sqlalchemy import ForeignKey
|
||||
|
||||
from spiffworkflow_backend.models.process_instance import ProcessInstanceModel
|
||||
|
||||
|
||||
@dataclass
|
||||
class ProcessInstanceMetadataModel(SpiffworkflowBaseDBModel):
|
||||
"""ProcessInstanceMetadataModel."""
|
||||
|
||||
__tablename__ = "process_instance_metadata"
|
||||
__table_args__ = (
|
||||
db.UniqueConstraint(
|
||||
"process_instance_id", "key", name="process_instance_metadata_unique"
|
||||
),
|
||||
)
|
||||
|
||||
id: int = db.Column(db.Integer, primary_key=True)
|
||||
process_instance_id: int = db.Column(
|
||||
ForeignKey(ProcessInstanceModel.id), nullable=False # type: ignore
|
||||
)
|
||||
key: str = db.Column(db.String(255), nullable=False)
|
||||
value: str = db.Column(db.String(255), nullable=False)
|
||||
|
||||
updated_at_in_seconds: int = db.Column(db.Integer, nullable=False)
|
||||
created_at_in_seconds: int = db.Column(db.Integer, nullable=False)
|
@ -75,7 +75,7 @@ class ProcessInstanceReportModel(SpiffworkflowBaseDBModel):
|
||||
def add_fixtures(cls) -> None:
|
||||
"""Add_fixtures."""
|
||||
try:
|
||||
# process_model = ProcessModelService().get_process_model(
|
||||
# process_model = ProcessModelService.get_process_model(
|
||||
# process_model_id="sartography-admin/ticket"
|
||||
# )
|
||||
user = UserModel.query.first()
|
||||
@ -205,7 +205,7 @@ class ProcessInstanceReportModel(SpiffworkflowBaseDBModel):
|
||||
) -> ProcessInstanceReportModel:
|
||||
"""Create_with_attributes."""
|
||||
# <<<<<<< HEAD
|
||||
# process_model = ProcessModelService().get_process_model(
|
||||
# process_model = ProcessModelService.get_process_model(
|
||||
# process_model_id=f"{process_model_identifier}"
|
||||
# )
|
||||
# process_instance_report = cls(
|
||||
|
@ -34,10 +34,10 @@ class ProcessModelInfo:
|
||||
primary_file_name: str | None = None
|
||||
primary_process_id: str | None = None
|
||||
display_order: int | None = 0
|
||||
is_review: bool = False
|
||||
files: list[File] | None = field(default_factory=list[File])
|
||||
fault_or_suspend_on_exception: str = NotificationType.fault.value
|
||||
exception_notification_addresses: list[str] = field(default_factory=list)
|
||||
parent_groups: list[dict] | None = None
|
||||
|
||||
def __post_init__(self) -> None:
|
||||
"""__post_init__."""
|
||||
@ -71,7 +71,6 @@ class ProcessModelInfoSchema(Schema):
|
||||
display_order = marshmallow.fields.Integer(allow_none=True)
|
||||
primary_file_name = marshmallow.fields.String(allow_none=True)
|
||||
primary_process_id = marshmallow.fields.String(allow_none=True)
|
||||
is_review = marshmallow.fields.Boolean(allow_none=True)
|
||||
files = marshmallow.fields.List(marshmallow.fields.Nested("FileSchema"))
|
||||
fault_or_suspend_on_exception = marshmallow.fields.String()
|
||||
exception_notification_addresses = marshmallow.fields.List(
|
||||
|
@ -8,6 +8,7 @@ from sqlalchemy import ForeignKey
|
||||
from sqlalchemy.orm import deferred
|
||||
|
||||
from spiffworkflow_backend.models.group import GroupModel
|
||||
from spiffworkflow_backend.models.process_instance import ProcessInstanceModel
|
||||
|
||||
|
||||
@dataclass
|
||||
@ -16,7 +17,9 @@ class SpiffStepDetailsModel(SpiffworkflowBaseDBModel):
|
||||
|
||||
__tablename__ = "spiff_step_details"
|
||||
id: int = db.Column(db.Integer, primary_key=True)
|
||||
process_instance_id: int = db.Column(db.Integer, nullable=False)
|
||||
process_instance_id: int = db.Column(
|
||||
ForeignKey(ProcessInstanceModel.id), nullable=False # type: ignore
|
||||
)
|
||||
spiff_step: int = db.Column(db.Integer, nullable=False)
|
||||
task_json: str = deferred(db.Column(db.JSON, nullable=False)) # type: ignore
|
||||
timestamp: float = db.Column(db.DECIMAL(17, 6), nullable=False)
|
||||
|
@ -27,28 +27,28 @@ ALLOWED_BPMN_EXTENSIONS = {"bpmn", "dmn"}
|
||||
|
||||
|
||||
@admin_blueprint.route("/process-groups", methods=["GET"])
|
||||
def process_groups_list() -> str:
|
||||
"""Process_groups_list."""
|
||||
process_groups = ProcessModelService().get_process_groups()
|
||||
return render_template("process_groups_list.html", process_groups=process_groups)
|
||||
def process_group_list() -> str:
|
||||
"""Process_group_list."""
|
||||
process_groups = ProcessModelService.get_process_groups()
|
||||
return render_template("process_group_list.html", process_groups=process_groups)
|
||||
|
||||
|
||||
@admin_blueprint.route("/process-groups/<process_group_id>", methods=["GET"])
|
||||
def process_group_show(process_group_id: str) -> str:
|
||||
"""Show_process_group."""
|
||||
process_group = ProcessModelService().get_process_group(process_group_id)
|
||||
process_group = ProcessModelService.get_process_group(process_group_id)
|
||||
return render_template("process_group_show.html", process_group=process_group)
|
||||
|
||||
|
||||
@admin_blueprint.route("/process-models/<process_model_id>", methods=["GET"])
|
||||
def process_model_show(process_model_id: str) -> Union[str, Response]:
|
||||
"""Show_process_model."""
|
||||
process_model = ProcessModelService().get_process_model(process_model_id)
|
||||
process_model = ProcessModelService.get_process_model(process_model_id)
|
||||
files = SpecFileService.get_files(process_model, extension_filter="bpmn")
|
||||
current_file_name = process_model.primary_file_name
|
||||
if current_file_name is None:
|
||||
flash("No primary_file_name", "error")
|
||||
return redirect(url_for("admin.process_groups_list"))
|
||||
return redirect(url_for("admin.process_group_list"))
|
||||
bpmn_xml = SpecFileService.get_data(process_model, current_file_name)
|
||||
return render_template(
|
||||
"process_model_show.html",
|
||||
@ -64,7 +64,7 @@ def process_model_show(process_model_id: str) -> Union[str, Response]:
|
||||
)
|
||||
def process_model_show_file(process_model_id: str, file_name: str) -> str:
|
||||
"""Process_model_show_file."""
|
||||
process_model = ProcessModelService().get_process_model(process_model_id)
|
||||
process_model = ProcessModelService.get_process_model(process_model_id)
|
||||
bpmn_xml = SpecFileService.get_data(process_model, file_name)
|
||||
files = SpecFileService.get_files(process_model, extension_filter="bpmn")
|
||||
return render_template(
|
||||
@ -81,8 +81,7 @@ def process_model_show_file(process_model_id: str, file_name: str) -> str:
|
||||
)
|
||||
def process_model_upload_file(process_model_id: str) -> Response:
|
||||
"""Process_model_upload_file."""
|
||||
process_model_service = ProcessModelService()
|
||||
process_model = process_model_service.get_process_model(process_model_id)
|
||||
process_model = ProcessModelService.get_process_model(process_model_id)
|
||||
|
||||
if "file" not in request.files:
|
||||
flash("No file part", "error")
|
||||
@ -97,7 +96,7 @@ def process_model_upload_file(process_model_id: str) -> Response:
|
||||
SpecFileService.add_file(
|
||||
process_model, request_file.filename, request_file.stream.read()
|
||||
)
|
||||
process_model_service.save_process_model(process_model)
|
||||
ProcessModelService.save_process_model(process_model)
|
||||
|
||||
return redirect(
|
||||
url_for("admin.process_model_show", process_model_id=process_model.id)
|
||||
@ -109,7 +108,7 @@ def process_model_upload_file(process_model_id: str) -> Response:
|
||||
)
|
||||
def process_model_edit(process_model_id: str, file_name: str) -> str:
|
||||
"""Edit_bpmn."""
|
||||
process_model = ProcessModelService().get_process_model(process_model_id)
|
||||
process_model = ProcessModelService.get_process_model(process_model_id)
|
||||
bpmn_xml = SpecFileService.get_data(process_model, file_name)
|
||||
|
||||
return render_template(
|
||||
@ -125,11 +124,11 @@ def process_model_edit(process_model_id: str, file_name: str) -> str:
|
||||
)
|
||||
def process_model_save(process_model_id: str, file_name: str) -> Union[str, Response]:
|
||||
"""Process_model_save."""
|
||||
process_model = ProcessModelService().get_process_model(process_model_id)
|
||||
process_model = ProcessModelService.get_process_model(process_model_id)
|
||||
SpecFileService.update_file(process_model, file_name, request.get_data())
|
||||
if process_model.primary_file_name is None:
|
||||
flash("No primary_file_name", "error")
|
||||
return redirect(url_for("admin.process_groups_list"))
|
||||
return redirect(url_for("admin.process_group_list"))
|
||||
bpmn_xml = SpecFileService.get_data(process_model, process_model.primary_file_name)
|
||||
return render_template(
|
||||
"process_model_edit.html",
|
||||
@ -143,19 +142,21 @@ def process_model_save(process_model_id: str, file_name: str) -> Union[str, Resp
|
||||
def process_model_run(process_model_id: str) -> Union[str, Response]:
|
||||
"""Process_model_run."""
|
||||
user = UserService.create_user("internal", "Mr. Test", username="Mr. Test")
|
||||
process_instance = ProcessInstanceService.create_process_instance(
|
||||
process_model_id, user
|
||||
process_instance = (
|
||||
ProcessInstanceService.create_process_instance_from_process_model_identifier(
|
||||
process_model_id, user
|
||||
)
|
||||
)
|
||||
processor = ProcessInstanceProcessor(process_instance)
|
||||
processor.do_engine_steps()
|
||||
result = processor.get_data()
|
||||
|
||||
process_model = ProcessModelService().get_process_model(process_model_id)
|
||||
process_model = ProcessModelService.get_process_model(process_model_id)
|
||||
files = SpecFileService.get_files(process_model, extension_filter="bpmn")
|
||||
current_file_name = process_model.primary_file_name
|
||||
if current_file_name is None:
|
||||
flash("No primary_file_name", "error")
|
||||
return redirect(url_for("admin.process_groups_list"))
|
||||
return redirect(url_for("admin.process_group_list"))
|
||||
bpmn_xml = SpecFileService.get_data(process_model, current_file_name)
|
||||
|
||||
return render_template(
|
||||
|
@ -3,7 +3,7 @@
|
||||
{% block content %}
|
||||
<button
|
||||
type="button"
|
||||
onclick="window.location.href='{{ url_for( 'admin.process_groups_list') }}';"
|
||||
onclick="window.location.href='{{ url_for( 'admin.process_group_list') }}';"
|
||||
>
|
||||
Back
|
||||
</button>
|
||||
|
@ -30,6 +30,7 @@ from SpiffWorkflow.task import TaskState
|
||||
from sqlalchemy import and_
|
||||
from sqlalchemy import asc
|
||||
from sqlalchemy import desc
|
||||
from sqlalchemy.orm import joinedload
|
||||
|
||||
from spiffworkflow_backend.exceptions.process_entity_not_found_error import (
|
||||
ProcessEntityNotFoundError,
|
||||
@ -63,6 +64,7 @@ from spiffworkflow_backend.models.spec_reference import SpecReferenceSchema
|
||||
from spiffworkflow_backend.models.spiff_logging import SpiffLoggingModel
|
||||
from spiffworkflow_backend.models.spiff_step_details import SpiffStepDetailsModel
|
||||
from spiffworkflow_backend.models.user import UserModel
|
||||
from spiffworkflow_backend.models.user_group_assignment import UserGroupAssignmentModel
|
||||
from spiffworkflow_backend.routes.user import verify_token
|
||||
from spiffworkflow_backend.services.authorization_service import AuthorizationService
|
||||
from spiffworkflow_backend.services.error_handling_service import ErrorHandlingService
|
||||
@ -157,9 +159,8 @@ def un_modify_modified_process_model_id(modified_process_model_id: str) -> str:
|
||||
|
||||
def process_group_add(body: dict) -> flask.wrappers.Response:
|
||||
"""Add_process_group."""
|
||||
process_model_service = ProcessModelService()
|
||||
process_group = ProcessGroup(**body)
|
||||
process_model_service.add_process_group(process_group)
|
||||
ProcessModelService.add_process_group(process_group)
|
||||
return make_response(jsonify(process_group), 201)
|
||||
|
||||
|
||||
@ -183,20 +184,20 @@ def process_group_update(
|
||||
|
||||
process_group_id = un_modify_modified_process_model_id(modified_process_group_id)
|
||||
process_group = ProcessGroup(id=process_group_id, **body_filtered)
|
||||
ProcessModelService().update_process_group(process_group)
|
||||
ProcessModelService.update_process_group(process_group)
|
||||
return make_response(jsonify(process_group), 200)
|
||||
|
||||
|
||||
def process_groups_list(
|
||||
def process_group_list(
|
||||
process_group_identifier: Optional[str] = None, page: int = 1, per_page: int = 100
|
||||
) -> flask.wrappers.Response:
|
||||
"""Process_groups_list."""
|
||||
"""Process_group_list."""
|
||||
if process_group_identifier is not None:
|
||||
process_groups = ProcessModelService().get_process_groups(
|
||||
process_groups = ProcessModelService.get_process_groups(
|
||||
process_group_identifier
|
||||
)
|
||||
else:
|
||||
process_groups = ProcessModelService().get_process_groups()
|
||||
process_groups = ProcessModelService.get_process_groups()
|
||||
batch = ProcessModelService().get_batch(
|
||||
items=process_groups, page=page, per_page=per_page
|
||||
)
|
||||
@ -222,7 +223,7 @@ def process_group_show(
|
||||
"""Process_group_show."""
|
||||
process_group_id = un_modify_modified_process_model_id(modified_process_group_id)
|
||||
try:
|
||||
process_group = ProcessModelService().get_process_group(process_group_id)
|
||||
process_group = ProcessModelService.get_process_group(process_group_id)
|
||||
except ProcessEntityNotFoundError as exception:
|
||||
raise (
|
||||
ApiError(
|
||||
@ -231,13 +232,17 @@ def process_group_show(
|
||||
status_code=400,
|
||||
)
|
||||
) from exception
|
||||
|
||||
process_group.parent_groups = ProcessModelService.get_parent_group_array(
|
||||
process_group.id
|
||||
)
|
||||
return make_response(jsonify(process_group), 200)
|
||||
|
||||
|
||||
def process_group_move(
|
||||
modified_process_group_identifier: str, new_location: str
|
||||
) -> flask.wrappers.Response:
|
||||
"""process_group_move."""
|
||||
"""Process_group_move."""
|
||||
original_process_group_id = un_modify_modified_process_model_id(
|
||||
modified_process_group_identifier
|
||||
)
|
||||
@ -268,8 +273,7 @@ def process_model_create(
|
||||
unmodified_process_group_id = un_modify_modified_process_model_id(
|
||||
modified_process_group_id
|
||||
)
|
||||
process_model_service = ProcessModelService()
|
||||
process_group = process_model_service.get_process_group(unmodified_process_group_id)
|
||||
process_group = ProcessModelService.get_process_group(unmodified_process_group_id)
|
||||
if process_group is None:
|
||||
raise ApiError(
|
||||
error_code="process_model_could_not_be_created",
|
||||
@ -277,7 +281,7 @@ def process_model_create(
|
||||
status_code=400,
|
||||
)
|
||||
|
||||
process_model_service.add_process_model(process_model_info)
|
||||
ProcessModelService.add_process_model(process_model_info)
|
||||
return Response(
|
||||
json.dumps(ProcessModelInfoSchema().dump(process_model_info)),
|
||||
status=201,
|
||||
@ -314,7 +318,7 @@ def process_model_update(
|
||||
|
||||
# process_model_identifier = f"{process_group_id}/{process_model_id}"
|
||||
process_model = get_process_model(process_model_identifier)
|
||||
ProcessModelService().update_process_model(process_model, body_filtered)
|
||||
ProcessModelService.update_process_model(process_model, body_filtered)
|
||||
return ProcessModelInfoSchema().dump(process_model)
|
||||
|
||||
|
||||
@ -329,14 +333,17 @@ def process_model_show(modified_process_model_identifier: str) -> Any:
|
||||
process_model.files = files
|
||||
for file in process_model.files:
|
||||
file.references = SpecFileService.get_references_for_file(file, process_model)
|
||||
process_model_json = ProcessModelInfoSchema().dump(process_model)
|
||||
return process_model_json
|
||||
|
||||
process_model.parent_groups = ProcessModelService.get_parent_group_array(
|
||||
process_model.id
|
||||
)
|
||||
return make_response(jsonify(process_model), 200)
|
||||
|
||||
|
||||
def process_model_move(
|
||||
modified_process_model_identifier: str, new_location: str
|
||||
) -> flask.wrappers.Response:
|
||||
"""process_model_move."""
|
||||
"""Process_model_move."""
|
||||
original_process_model_id = un_modify_modified_process_model_id(
|
||||
modified_process_model_identifier
|
||||
)
|
||||
@ -349,12 +356,15 @@ def process_model_move(
|
||||
def process_model_list(
|
||||
process_group_identifier: Optional[str] = None,
|
||||
recursive: Optional[bool] = False,
|
||||
filter_runnable_by_user: Optional[bool] = False,
|
||||
page: int = 1,
|
||||
per_page: int = 100,
|
||||
) -> flask.wrappers.Response:
|
||||
"""Process model list!"""
|
||||
process_models = ProcessModelService().get_process_models(
|
||||
process_group_id=process_group_identifier, recursive=recursive
|
||||
process_models = ProcessModelService.get_process_models(
|
||||
process_group_id=process_group_identifier,
|
||||
recursive=recursive,
|
||||
filter_runnable_by_user=filter_runnable_by_user,
|
||||
)
|
||||
batch = ProcessModelService().get_batch(
|
||||
process_models, page=page, per_page=per_page
|
||||
@ -483,8 +493,10 @@ def process_instance_create(modified_process_model_id: str) -> flask.wrappers.Re
|
||||
process_model_identifier = un_modify_modified_process_model_id(
|
||||
modified_process_model_id
|
||||
)
|
||||
process_instance = ProcessInstanceService.create_process_instance(
|
||||
process_model_identifier, g.user
|
||||
process_instance = (
|
||||
ProcessInstanceService.create_process_instance_from_process_model_identifier(
|
||||
process_model_identifier, g.user
|
||||
)
|
||||
)
|
||||
return Response(
|
||||
json.dumps(ProcessInstanceModelSchema().dump(process_instance)),
|
||||
@ -494,6 +506,7 @@ def process_instance_create(modified_process_model_id: str) -> flask.wrappers.Re
|
||||
|
||||
|
||||
def process_instance_run(
|
||||
modified_process_model_identifier: str,
|
||||
process_instance_id: int,
|
||||
do_engine_steps: bool = True,
|
||||
) -> flask.wrappers.Response:
|
||||
@ -758,6 +771,9 @@ def process_instance_list(
|
||||
end_from: Optional[int] = None,
|
||||
end_to: Optional[int] = None,
|
||||
process_status: Optional[str] = None,
|
||||
initiated_by_me: Optional[bool] = None,
|
||||
with_tasks_completed_by_me: Optional[bool] = None,
|
||||
with_tasks_completed_by_my_group: Optional[bool] = None,
|
||||
user_filter: Optional[bool] = False,
|
||||
report_identifier: Optional[str] = None,
|
||||
) -> flask.wrappers.Response:
|
||||
@ -774,6 +790,9 @@ def process_instance_list(
|
||||
end_from,
|
||||
end_to,
|
||||
process_status.split(",") if process_status else None,
|
||||
initiated_by_me,
|
||||
with_tasks_completed_by_me,
|
||||
with_tasks_completed_by_my_group,
|
||||
)
|
||||
else:
|
||||
report_filter = (
|
||||
@ -785,11 +804,19 @@ def process_instance_list(
|
||||
end_from,
|
||||
end_to,
|
||||
process_status,
|
||||
initiated_by_me,
|
||||
with_tasks_completed_by_me,
|
||||
with_tasks_completed_by_my_group,
|
||||
)
|
||||
)
|
||||
|
||||
# process_model_identifier = un_modify_modified_process_model_id(modified_process_model_identifier)
|
||||
process_instance_query = ProcessInstanceModel.query
|
||||
# Always join that hot user table for good performance at serialization time.
|
||||
process_instance_query = process_instance_query.options(
|
||||
joinedload(ProcessInstanceModel.process_initiator)
|
||||
)
|
||||
|
||||
if report_filter.process_model_identifier is not None:
|
||||
process_model = get_process_model(
|
||||
f"{report_filter.process_model_identifier}",
|
||||
@ -833,9 +860,81 @@ def process_instance_list(
|
||||
ProcessInstanceModel.status.in_(report_filter.process_status) # type: ignore
|
||||
)
|
||||
|
||||
process_instances = process_instance_query.order_by(
|
||||
ProcessInstanceModel.start_in_seconds.desc(), ProcessInstanceModel.id.desc() # type: ignore
|
||||
).paginate(page=page, per_page=per_page, error_out=False)
|
||||
if report_filter.initiated_by_me is True:
|
||||
process_instance_query = process_instance_query.filter(
|
||||
ProcessInstanceModel.status.in_(["complete", "error", "terminated"]) # type: ignore
|
||||
)
|
||||
process_instance_query = process_instance_query.filter_by(
|
||||
process_initiator=g.user
|
||||
)
|
||||
|
||||
# TODO: not sure if this is exactly what is wanted
|
||||
if report_filter.with_tasks_completed_by_me is True:
|
||||
process_instance_query = process_instance_query.filter(
|
||||
ProcessInstanceModel.status.in_(["complete", "error", "terminated"]) # type: ignore
|
||||
)
|
||||
# process_instance_query = process_instance_query.join(UserModel, UserModel.id == ProcessInstanceModel.process_initiator_id)
|
||||
# process_instance_query = process_instance_query.add_columns(UserModel.username)
|
||||
# search for process_instance.UserModel.username in this file for more details about why adding columns is annoying.
|
||||
|
||||
process_instance_query = process_instance_query.filter(
|
||||
ProcessInstanceModel.process_initiator_id != g.user.id
|
||||
)
|
||||
process_instance_query = process_instance_query.join(
|
||||
SpiffStepDetailsModel,
|
||||
ProcessInstanceModel.id == SpiffStepDetailsModel.process_instance_id,
|
||||
)
|
||||
process_instance_query = process_instance_query.join(
|
||||
SpiffLoggingModel,
|
||||
ProcessInstanceModel.id == SpiffLoggingModel.process_instance_id,
|
||||
)
|
||||
process_instance_query = process_instance_query.filter(
|
||||
SpiffLoggingModel.message.contains("COMPLETED") # type: ignore
|
||||
)
|
||||
process_instance_query = process_instance_query.filter(
|
||||
SpiffLoggingModel.spiff_step == SpiffStepDetailsModel.spiff_step
|
||||
)
|
||||
process_instance_query = process_instance_query.filter(
|
||||
SpiffStepDetailsModel.completed_by_user_id == g.user.id
|
||||
)
|
||||
|
||||
if report_filter.with_tasks_completed_by_my_group is True:
|
||||
process_instance_query = process_instance_query.filter(
|
||||
ProcessInstanceModel.status.in_(["complete", "error", "terminated"]) # type: ignore
|
||||
)
|
||||
process_instance_query = process_instance_query.join(
|
||||
SpiffStepDetailsModel,
|
||||
ProcessInstanceModel.id == SpiffStepDetailsModel.process_instance_id,
|
||||
)
|
||||
process_instance_query = process_instance_query.join(
|
||||
SpiffLoggingModel,
|
||||
ProcessInstanceModel.id == SpiffLoggingModel.process_instance_id,
|
||||
)
|
||||
process_instance_query = process_instance_query.filter(
|
||||
SpiffLoggingModel.message.contains("COMPLETED") # type: ignore
|
||||
)
|
||||
process_instance_query = process_instance_query.filter(
|
||||
SpiffLoggingModel.spiff_step == SpiffStepDetailsModel.spiff_step
|
||||
)
|
||||
process_instance_query = process_instance_query.join(
|
||||
GroupModel,
|
||||
GroupModel.id == SpiffStepDetailsModel.lane_assignment_id,
|
||||
)
|
||||
process_instance_query = process_instance_query.join(
|
||||
UserGroupAssignmentModel,
|
||||
UserGroupAssignmentModel.group_id == GroupModel.id,
|
||||
)
|
||||
process_instance_query = process_instance_query.filter(
|
||||
UserGroupAssignmentModel.user_id == g.user.id
|
||||
)
|
||||
|
||||
process_instances = (
|
||||
process_instance_query.group_by(ProcessInstanceModel.id)
|
||||
.order_by(
|
||||
ProcessInstanceModel.start_in_seconds.desc(), ProcessInstanceModel.id.desc() # type: ignore
|
||||
)
|
||||
.paginate(page=page, per_page=per_page, error_out=False)
|
||||
)
|
||||
|
||||
results = list(
|
||||
map(
|
||||
@ -1001,7 +1100,7 @@ def authentication_callback(
|
||||
f"{service}/{auth_method}", response, g.user.id, create_if_not_exists=True
|
||||
)
|
||||
return redirect(
|
||||
f"{current_app.config['SPIFFWORKFLOW_FRONTEND_URL']}/admin/authentications"
|
||||
f"{current_app.config['SPIFFWORKFLOW_FRONTEND_URL']}/admin/configuration"
|
||||
)
|
||||
|
||||
|
||||
@ -1056,7 +1155,7 @@ def task_list_my_tasks(page: int = 1, per_page: int = 100) -> flask.wrappers.Res
|
||||
# just need this add_columns to add the process_model_identifier. Then add everything back that was removed.
|
||||
.add_columns(
|
||||
ProcessInstanceModel.process_model_identifier,
|
||||
ProcessInstanceModel.process_group_identifier,
|
||||
ProcessInstanceModel.process_model_display_name,
|
||||
ProcessInstanceModel.status,
|
||||
ActiveTaskModel.task_name,
|
||||
ActiveTaskModel.task_title,
|
||||
@ -1197,8 +1296,10 @@ def process_instance_task_list(
|
||||
)
|
||||
.first()
|
||||
)
|
||||
if step_detail is not None:
|
||||
process_instance.bpmn_json = json.dumps(step_detail.task_json)
|
||||
if step_detail is not None and process_instance.bpmn_json is not None:
|
||||
bpmn_json = json.loads(process_instance.bpmn_json)
|
||||
bpmn_json["tasks"] = step_detail.task_json
|
||||
process_instance.bpmn_json = json.dumps(bpmn_json)
|
||||
|
||||
processor = ProcessInstanceProcessor(process_instance)
|
||||
|
||||
@ -1318,7 +1419,7 @@ def task_submit(
|
||||
task_id, process_instance, processor=processor
|
||||
)
|
||||
AuthorizationService.assert_user_can_complete_spiff_task(
|
||||
processor, spiff_task, principal.user
|
||||
process_instance.id, spiff_task, principal.user
|
||||
)
|
||||
|
||||
if spiff_task.state != TaskState.READY:
|
||||
@ -1503,7 +1604,7 @@ def get_process_model(process_model_id: str) -> ProcessModelInfo:
|
||||
"""Get_process_model."""
|
||||
process_model = None
|
||||
try:
|
||||
process_model = ProcessModelService().get_process_model(process_model_id)
|
||||
process_model = ProcessModelService.get_process_model(process_model_id)
|
||||
except ProcessEntityNotFoundError as exception:
|
||||
raise (
|
||||
ApiError(
|
||||
|
@ -0,0 +1,42 @@
|
||||
"""Get_env."""
|
||||
from typing import Any
|
||||
|
||||
from flask_bpmn.models.db import db
|
||||
|
||||
from spiffworkflow_backend.models.process_instance_metadata import (
|
||||
ProcessInstanceMetadataModel,
|
||||
)
|
||||
from spiffworkflow_backend.models.script_attributes_context import (
|
||||
ScriptAttributesContext,
|
||||
)
|
||||
from spiffworkflow_backend.scripts.script import Script
|
||||
|
||||
|
||||
class SaveProcessInstanceMetadata(Script):
|
||||
"""SaveProcessInstanceMetadata."""
|
||||
|
||||
def get_description(self) -> str:
|
||||
"""Get_description."""
|
||||
return """Save a given dict as process instance metadata (useful for creating reports)."""
|
||||
|
||||
def run(
|
||||
self,
|
||||
script_attributes_context: ScriptAttributesContext,
|
||||
*args: Any,
|
||||
**kwargs: Any,
|
||||
) -> Any:
|
||||
"""Run."""
|
||||
metadata_dict = args[0]
|
||||
for key, value in metadata_dict.items():
|
||||
pim = ProcessInstanceMetadataModel.query.filter_by(
|
||||
process_instance_id=script_attributes_context.process_instance_id,
|
||||
key=key,
|
||||
).first()
|
||||
if pim is None:
|
||||
pim = ProcessInstanceMetadataModel(
|
||||
process_instance_id=script_attributes_context.process_instance_id,
|
||||
key=key,
|
||||
)
|
||||
pim.value = value
|
||||
db.session.add(pim)
|
||||
db.session.commit()
|
@ -30,7 +30,7 @@ def load_acceptance_test_fixtures() -> list[ProcessInstanceModel]:
|
||||
process_instances = []
|
||||
for i in range(len(statuses)):
|
||||
|
||||
process_instance = ProcessInstanceService.create_process_instance(
|
||||
process_instance = ProcessInstanceService.create_process_instance_from_process_model_identifier(
|
||||
test_process_model_id, user
|
||||
)
|
||||
process_instance.status = statuses[i]
|
||||
|
@ -24,9 +24,6 @@ from spiffworkflow_backend.models.user import UserModel
|
||||
from spiffworkflow_backend.models.user import UserNotFoundError
|
||||
from spiffworkflow_backend.models.user_group_assignment import UserGroupAssignmentModel
|
||||
from spiffworkflow_backend.services.group_service import GroupService
|
||||
from spiffworkflow_backend.services.process_instance_processor import (
|
||||
ProcessInstanceProcessor,
|
||||
)
|
||||
from spiffworkflow_backend.services.user_service import UserService
|
||||
|
||||
|
||||
@ -393,25 +390,25 @@ class AuthorizationService:
|
||||
|
||||
@staticmethod
|
||||
def assert_user_can_complete_spiff_task(
|
||||
processor: ProcessInstanceProcessor,
|
||||
process_instance_id: int,
|
||||
spiff_task: SpiffTask,
|
||||
user: UserModel,
|
||||
) -> bool:
|
||||
"""Assert_user_can_complete_spiff_task."""
|
||||
active_task = ActiveTaskModel.query.filter_by(
|
||||
task_name=spiff_task.task_spec.name,
|
||||
process_instance_id=processor.process_instance_model.id,
|
||||
process_instance_id=process_instance_id,
|
||||
).first()
|
||||
if active_task is None:
|
||||
raise ActiveTaskNotFoundError(
|
||||
f"Could find an active task with task name '{spiff_task.task_spec.name}'"
|
||||
f" for process instance '{processor.process_instance_model.id}'"
|
||||
f" for process instance '{process_instance_id}'"
|
||||
)
|
||||
|
||||
if user not in active_task.potential_owners:
|
||||
raise UserDoesNotHaveAccessToTaskError(
|
||||
f"User {user.username} does not have access to update task'{spiff_task.task_spec.name}'"
|
||||
f" for process instance '{processor.process_instance_model.id}'"
|
||||
f" for process instance '{process_instance_id}'"
|
||||
)
|
||||
return True
|
||||
|
||||
|
@ -26,7 +26,7 @@ class DataSetupService:
|
||||
|
||||
current_app.logger.debug("DataSetupService.save_all_process_models() start")
|
||||
failing_process_models = []
|
||||
process_models = ProcessModelService().get_process_models()
|
||||
process_models = ProcessModelService.get_process_models(recursive=True)
|
||||
SpecFileService.clear_caches()
|
||||
for process_model in process_models:
|
||||
current_app.logger.debug(f"Process Model: {process_model.display_name}")
|
||||
|
@ -34,7 +34,7 @@ class ErrorHandlingService:
|
||||
self, _processor: ProcessInstanceProcessor, _error: Union[ApiError, Exception]
|
||||
) -> None:
|
||||
"""On unhandled exceptions, set instance.status based on model.fault_or_suspend_on_exception."""
|
||||
process_model = ProcessModelService().get_process_model(
|
||||
process_model = ProcessModelService.get_process_model(
|
||||
_processor.process_model_identifier
|
||||
)
|
||||
if process_model.fault_or_suspend_on_exception == "suspend":
|
||||
|
@ -117,7 +117,7 @@ class MessageService:
|
||||
user: UserModel,
|
||||
) -> ProcessInstanceModel:
|
||||
"""Process_message_triggerable_process_model."""
|
||||
process_instance_receive = ProcessInstanceService.create_process_instance(
|
||||
process_instance_receive = ProcessInstanceService.create_process_instance_from_process_model_identifier(
|
||||
message_triggerable_process_model.process_model_identifier,
|
||||
user,
|
||||
)
|
||||
|
@ -349,7 +349,9 @@ class ProcessInstanceProcessor:
|
||||
check_sub_specs(test_spec, 5)
|
||||
|
||||
self.process_model_identifier = process_instance_model.process_model_identifier
|
||||
# self.process_group_identifier = process_instance_model.process_group_identifier
|
||||
self.process_model_display_name = (
|
||||
process_instance_model.process_model_display_name
|
||||
)
|
||||
|
||||
try:
|
||||
self.bpmn_process_instance = self.__get_bpmn_process_instance(
|
||||
@ -374,7 +376,7 @@ class ProcessInstanceProcessor:
|
||||
cls, process_model_identifier: str
|
||||
) -> Tuple[BpmnProcessSpec, IdToBpmnProcessSpecMapping]:
|
||||
"""Get_process_model_and_subprocesses."""
|
||||
process_model_info = ProcessModelService().get_process_model(
|
||||
process_model_info = ProcessModelService.get_process_model(
|
||||
process_model_identifier
|
||||
)
|
||||
if process_model_info is None:
|
||||
@ -540,13 +542,8 @@ class ProcessInstanceProcessor:
|
||||
"""SaveSpiffStepDetails."""
|
||||
bpmn_json = self.serialize()
|
||||
wf_json = json.loads(bpmn_json)
|
||||
task_json = "{}"
|
||||
if "tasks" in wf_json:
|
||||
task_json = json.dumps(wf_json["tasks"])
|
||||
task_json = wf_json["tasks"]
|
||||
|
||||
# TODO want to just save the tasks, something wasn't immediately working
|
||||
# so after the flow works with the full wf_json revisit this
|
||||
task_json = wf_json
|
||||
return {
|
||||
"process_instance_id": self.process_instance_model.id,
|
||||
"spiff_step": self.process_instance_model.spiff_step or 1,
|
||||
@ -593,16 +590,12 @@ class ProcessInstanceProcessor:
|
||||
if self.bpmn_process_instance.is_completed():
|
||||
self.process_instance_model.end_in_seconds = round(time.time())
|
||||
|
||||
active_tasks = ActiveTaskModel.query.filter_by(
|
||||
process_instance_id=self.process_instance_model.id
|
||||
).all()
|
||||
if len(active_tasks) > 0:
|
||||
for at in active_tasks:
|
||||
db.session.delete(at)
|
||||
|
||||
db.session.add(self.process_instance_model)
|
||||
db.session.commit()
|
||||
|
||||
active_tasks = ActiveTaskModel.query.filter_by(
|
||||
process_instance_id=self.process_instance_model.id
|
||||
).all()
|
||||
ready_or_waiting_tasks = self.get_all_ready_or_waiting_tasks()
|
||||
for ready_or_waiting_task in ready_or_waiting_tasks:
|
||||
# filter out non-usertasks
|
||||
@ -629,27 +622,41 @@ class ProcessInstanceProcessor:
|
||||
if process_model_info is not None:
|
||||
process_model_display_name = process_model_info.display_name
|
||||
|
||||
active_task = ActiveTaskModel(
|
||||
process_instance_id=self.process_instance_model.id,
|
||||
process_model_display_name=process_model_display_name,
|
||||
form_file_name=form_file_name,
|
||||
ui_form_file_name=ui_form_file_name,
|
||||
task_id=str(ready_or_waiting_task.id),
|
||||
task_name=ready_or_waiting_task.task_spec.name,
|
||||
task_title=ready_or_waiting_task.task_spec.description,
|
||||
task_type=ready_or_waiting_task.task_spec.__class__.__name__,
|
||||
task_status=ready_or_waiting_task.get_state_name(),
|
||||
lane_assignment_id=potential_owner_hash["lane_assignment_id"],
|
||||
)
|
||||
db.session.add(active_task)
|
||||
db.session.commit()
|
||||
active_task = None
|
||||
for at in active_tasks:
|
||||
if at.task_id == str(ready_or_waiting_task.id):
|
||||
active_task = at
|
||||
active_tasks.remove(at)
|
||||
|
||||
for potential_owner_id in potential_owner_hash["potential_owner_ids"]:
|
||||
active_task_user = ActiveTaskUserModel(
|
||||
user_id=potential_owner_id, active_task_id=active_task.id
|
||||
if active_task is None:
|
||||
active_task = ActiveTaskModel(
|
||||
process_instance_id=self.process_instance_model.id,
|
||||
process_model_display_name=process_model_display_name,
|
||||
form_file_name=form_file_name,
|
||||
ui_form_file_name=ui_form_file_name,
|
||||
task_id=str(ready_or_waiting_task.id),
|
||||
task_name=ready_or_waiting_task.task_spec.name,
|
||||
task_title=ready_or_waiting_task.task_spec.description,
|
||||
task_type=ready_or_waiting_task.task_spec.__class__.__name__,
|
||||
task_status=ready_or_waiting_task.get_state_name(),
|
||||
lane_assignment_id=potential_owner_hash["lane_assignment_id"],
|
||||
)
|
||||
db.session.add(active_task_user)
|
||||
db.session.commit()
|
||||
db.session.add(active_task)
|
||||
db.session.commit()
|
||||
|
||||
for potential_owner_id in potential_owner_hash[
|
||||
"potential_owner_ids"
|
||||
]:
|
||||
active_task_user = ActiveTaskUserModel(
|
||||
user_id=potential_owner_id, active_task_id=active_task.id
|
||||
)
|
||||
db.session.add(active_task_user)
|
||||
db.session.commit()
|
||||
|
||||
if len(active_tasks) > 0:
|
||||
for at in active_tasks:
|
||||
db.session.delete(at)
|
||||
db.session.commit()
|
||||
|
||||
@staticmethod
|
||||
def get_parser() -> MyCustomParser:
|
||||
@ -662,7 +669,7 @@ class ProcessInstanceProcessor:
|
||||
bpmn_process_identifier: str,
|
||||
) -> Optional[str]:
|
||||
"""Backfill_missing_spec_reference_records."""
|
||||
process_models = ProcessModelService().get_process_models(recursive=True)
|
||||
process_models = ProcessModelService.get_process_models(recursive=True)
|
||||
for process_model in process_models:
|
||||
try:
|
||||
refs = SpecFileService.reference_map(
|
||||
|
@ -18,6 +18,9 @@ class ProcessInstanceReportFilter:
|
||||
end_from: Optional[int] = None
|
||||
end_to: Optional[int] = None
|
||||
process_status: Optional[list[str]] = None
|
||||
initiated_by_me: Optional[bool] = None
|
||||
with_tasks_completed_by_me: Optional[bool] = None
|
||||
with_tasks_completed_by_my_group: Optional[bool] = None
|
||||
|
||||
def to_dict(self) -> dict[str, str]:
|
||||
"""To_dict."""
|
||||
@ -35,6 +38,16 @@ class ProcessInstanceReportFilter:
|
||||
d["end_to"] = str(self.end_to)
|
||||
if self.process_status is not None:
|
||||
d["process_status"] = ",".join(self.process_status)
|
||||
if self.initiated_by_me is not None:
|
||||
d["initiated_by_me"] = str(self.initiated_by_me).lower()
|
||||
if self.with_tasks_completed_by_me is not None:
|
||||
d["with_tasks_completed_by_me"] = str(
|
||||
self.with_tasks_completed_by_me
|
||||
).lower()
|
||||
if self.with_tasks_completed_by_my_group is not None:
|
||||
d["with_tasks_completed_by_my_group"] = str(
|
||||
self.with_tasks_completed_by_my_group
|
||||
).lower()
|
||||
|
||||
return d
|
||||
|
||||
@ -63,48 +76,61 @@ class ProcessInstanceReportService:
|
||||
"columns": [
|
||||
{"Header": "id", "accessor": "id"},
|
||||
{
|
||||
"Header": "process_model_identifier",
|
||||
"accessor": "process_model_identifier",
|
||||
"Header": "process_model_display_name",
|
||||
"accessor": "process_model_display_name",
|
||||
},
|
||||
{"Header": "start_in_seconds", "accessor": "start_in_seconds"},
|
||||
{"Header": "end_in_seconds", "accessor": "end_in_seconds"},
|
||||
{"Header": "username", "accessor": "username"},
|
||||
{"Header": "status", "accessor": "status"},
|
||||
],
|
||||
},
|
||||
"system_report_instances_initiated_by_me": {
|
||||
"columns": [
|
||||
{"Header": "id", "accessor": "id"},
|
||||
{
|
||||
"Header": "process_model_identifier",
|
||||
"accessor": "process_model_identifier",
|
||||
"Header": "process_model_display_name",
|
||||
"accessor": "process_model_display_name",
|
||||
},
|
||||
{"Header": "start_in_seconds", "accessor": "start_in_seconds"},
|
||||
{"Header": "id", "accessor": "id"},
|
||||
{"Header": "end_in_seconds", "accessor": "end_in_seconds"},
|
||||
{"Header": "status", "accessor": "status"},
|
||||
],
|
||||
"filter_by": [{"field_name": "initiated_by_me", "field_value": True}],
|
||||
},
|
||||
"system_report_instances_with_tasks_completed_by_me": {
|
||||
"columns": [
|
||||
{"Header": "start_in_seconds", "accessor": "start_in_seconds"},
|
||||
{"Header": "end_in_seconds", "accessor": "end_in_seconds"},
|
||||
{"Header": "status", "accessor": "status"},
|
||||
{"Header": "id", "accessor": "id"},
|
||||
{
|
||||
"Header": "process_model_identifier",
|
||||
"accessor": "process_model_identifier",
|
||||
"Header": "process_model_display_name",
|
||||
"accessor": "process_model_display_name",
|
||||
},
|
||||
{"Header": "start_in_seconds", "accessor": "start_in_seconds"},
|
||||
{"Header": "end_in_seconds", "accessor": "end_in_seconds"},
|
||||
{"Header": "username", "accessor": "username"},
|
||||
{"Header": "status", "accessor": "status"},
|
||||
],
|
||||
"filter_by": [
|
||||
{"field_name": "with_tasks_completed_by_me", "field_value": True}
|
||||
],
|
||||
},
|
||||
"system_report_instances_with_tasks_completed_by_my_groups": {
|
||||
"columns": [
|
||||
{"Header": "id", "accessor": "id"},
|
||||
{
|
||||
"Header": "process_model_identifier",
|
||||
"accessor": "process_model_identifier",
|
||||
"Header": "process_model_display_name",
|
||||
"accessor": "process_model_display_name",
|
||||
},
|
||||
{"Header": "start_in_seconds", "accessor": "start_in_seconds"},
|
||||
{"Header": "end_in_seconds", "accessor": "end_in_seconds"},
|
||||
{"Header": "username", "accessor": "username"},
|
||||
{"Header": "status", "accessor": "status"},
|
||||
{"Header": "id", "accessor": "id"},
|
||||
],
|
||||
"filter_by": [
|
||||
{
|
||||
"field_name": "with_tasks_completed_by_my_group",
|
||||
"field_value": True,
|
||||
}
|
||||
],
|
||||
},
|
||||
}
|
||||
@ -112,7 +138,7 @@ class ProcessInstanceReportService:
|
||||
process_instance_report = ProcessInstanceReportModel(
|
||||
identifier=report_identifier,
|
||||
created_by_id=user.id,
|
||||
report_metadata=temp_system_metadata_map[report_identifier],
|
||||
report_metadata=temp_system_metadata_map[report_identifier], # type: ignore
|
||||
)
|
||||
|
||||
return process_instance_report # type: ignore
|
||||
@ -138,6 +164,10 @@ class ProcessInstanceReportService:
|
||||
"""Filter_from_metadata."""
|
||||
filters = cls.filter_by_to_dict(process_instance_report)
|
||||
|
||||
def bool_value(key: str) -> Optional[bool]:
|
||||
"""Bool_value."""
|
||||
return bool(filters[key]) if key in filters else None
|
||||
|
||||
def int_value(key: str) -> Optional[int]:
|
||||
"""Int_value."""
|
||||
return int(filters[key]) if key in filters else None
|
||||
@ -152,6 +182,11 @@ class ProcessInstanceReportService:
|
||||
end_from = int_value("end_from")
|
||||
end_to = int_value("end_to")
|
||||
process_status = list_value("process_status")
|
||||
initiated_by_me = bool_value("initiated_by_me")
|
||||
with_tasks_completed_by_me = bool_value("with_tasks_completed_by_me")
|
||||
with_tasks_completed_by_my_group = bool_value(
|
||||
"with_tasks_completed_by_my_group"
|
||||
)
|
||||
|
||||
report_filter = ProcessInstanceReportFilter(
|
||||
process_model_identifier,
|
||||
@ -160,6 +195,9 @@ class ProcessInstanceReportService:
|
||||
end_from,
|
||||
end_to,
|
||||
process_status,
|
||||
initiated_by_me,
|
||||
with_tasks_completed_by_me,
|
||||
with_tasks_completed_by_my_group,
|
||||
)
|
||||
|
||||
return report_filter
|
||||
@ -174,6 +212,9 @@ class ProcessInstanceReportService:
|
||||
end_from: Optional[int] = None,
|
||||
end_to: Optional[int] = None,
|
||||
process_status: Optional[str] = None,
|
||||
initiated_by_me: Optional[bool] = None,
|
||||
with_tasks_completed_by_me: Optional[bool] = None,
|
||||
with_tasks_completed_by_my_group: Optional[bool] = None,
|
||||
) -> ProcessInstanceReportFilter:
|
||||
"""Filter_from_metadata_with_overrides."""
|
||||
report_filter = cls.filter_from_metadata(process_instance_report)
|
||||
@ -190,5 +231,13 @@ class ProcessInstanceReportService:
|
||||
report_filter.end_to = end_to
|
||||
if process_status is not None:
|
||||
report_filter.process_status = process_status.split(",")
|
||||
if initiated_by_me is not None:
|
||||
report_filter.initiated_by_me = initiated_by_me
|
||||
if with_tasks_completed_by_me is not None:
|
||||
report_filter.with_tasks_completed_by_me = with_tasks_completed_by_me
|
||||
if with_tasks_completed_by_my_group is not None:
|
||||
report_filter.with_tasks_completed_by_my_group = (
|
||||
with_tasks_completed_by_my_group
|
||||
)
|
||||
|
||||
return report_filter
|
||||
|
@ -12,6 +12,7 @@ from spiffworkflow_backend.models.active_task import ActiveTaskModel
|
||||
from spiffworkflow_backend.models.process_instance import ProcessInstanceApi
|
||||
from spiffworkflow_backend.models.process_instance import ProcessInstanceModel
|
||||
from spiffworkflow_backend.models.process_instance import ProcessInstanceStatus
|
||||
from spiffworkflow_backend.models.process_model import ProcessModelInfo
|
||||
from spiffworkflow_backend.models.task import MultiInstanceType
|
||||
from spiffworkflow_backend.models.task import Task
|
||||
from spiffworkflow_backend.models.user import UserModel
|
||||
@ -28,9 +29,10 @@ class ProcessInstanceService:
|
||||
|
||||
TASK_STATE_LOCKED = "locked"
|
||||
|
||||
@staticmethod
|
||||
@classmethod
|
||||
def create_process_instance(
|
||||
process_model_identifier: str,
|
||||
cls,
|
||||
process_model: ProcessModelInfo,
|
||||
user: UserModel,
|
||||
) -> ProcessInstanceModel:
|
||||
"""Get_process_instance_from_spec."""
|
||||
@ -38,8 +40,8 @@ class ProcessInstanceService:
|
||||
process_instance_model = ProcessInstanceModel(
|
||||
status=ProcessInstanceStatus.not_started.value,
|
||||
process_initiator=user,
|
||||
process_model_identifier=process_model_identifier,
|
||||
process_group_identifier="",
|
||||
process_model_identifier=process_model.id,
|
||||
process_model_display_name=process_model.display_name,
|
||||
start_in_seconds=round(time.time()),
|
||||
bpmn_version_control_type="git",
|
||||
bpmn_version_control_identifier=current_git_revision,
|
||||
@ -48,6 +50,16 @@ class ProcessInstanceService:
|
||||
db.session.commit()
|
||||
return process_instance_model
|
||||
|
||||
@classmethod
|
||||
def create_process_instance_from_process_model_identifier(
|
||||
cls,
|
||||
process_model_identifier: str,
|
||||
user: UserModel,
|
||||
) -> ProcessInstanceModel:
|
||||
"""Create_process_instance_from_process_model_identifier."""
|
||||
process_model = ProcessModelService.get_process_model(process_model_identifier)
|
||||
return cls.create_process_instance(process_model, user)
|
||||
|
||||
@staticmethod
|
||||
def do_waiting() -> None:
|
||||
"""Do_waiting."""
|
||||
@ -88,20 +100,15 @@ class ProcessInstanceService:
|
||||
process_model = process_model_service.get_process_model(
|
||||
processor.process_model_identifier
|
||||
)
|
||||
is_review_value = process_model.is_review if process_model else False
|
||||
title_value = process_model.display_name if process_model else ""
|
||||
process_model.display_name if process_model else ""
|
||||
process_instance_api = ProcessInstanceApi(
|
||||
id=processor.get_process_instance_id(),
|
||||
status=processor.get_status(),
|
||||
next_task=None,
|
||||
# navigation=navigation,
|
||||
process_model_identifier=processor.process_model_identifier,
|
||||
process_group_identifier="",
|
||||
# total_tasks=len(navigation),
|
||||
process_model_display_name=processor.process_model_display_name,
|
||||
completed_tasks=processor.process_instance_model.completed_tasks,
|
||||
updated_at_in_seconds=processor.process_instance_model.updated_at_in_seconds,
|
||||
is_review=is_review_value,
|
||||
title=title_value,
|
||||
)
|
||||
|
||||
next_task_trying_again = next_task
|
||||
@ -197,7 +204,7 @@ class ProcessInstanceService:
|
||||
a multi-instance task.
|
||||
"""
|
||||
AuthorizationService.assert_user_can_complete_spiff_task(
|
||||
processor, spiff_task, user
|
||||
processor.process_instance_model.id, spiff_task, user
|
||||
)
|
||||
|
||||
dot_dct = ProcessInstanceService.create_dot_dict(data)
|
||||
@ -320,12 +327,13 @@ class ProcessInstanceService:
|
||||
def serialize_flat_with_task_data(
|
||||
process_instance: ProcessInstanceModel,
|
||||
) -> dict[str, Any]:
|
||||
"""NOTE: This is crazy slow. Put the latest task data in the database."""
|
||||
"""Serialize_flat_with_task_data."""
|
||||
results = {}
|
||||
try:
|
||||
processor = ProcessInstanceProcessor(process_instance)
|
||||
process_instance.data = processor.get_current_data()
|
||||
results = process_instance.serialized_flat
|
||||
except ApiError:
|
||||
results = process_instance.serialized
|
||||
# results = {}
|
||||
# try:
|
||||
# processor = ProcessInstanceProcessor(process_instance)
|
||||
# process_instance.data = processor.get_current_data()
|
||||
# results = process_instance.serialized_flat
|
||||
# except ApiError:
|
||||
results = process_instance.serialized
|
||||
return results
|
||||
|
@ -18,7 +18,9 @@ from spiffworkflow_backend.models.process_group import ProcessGroupSchema
|
||||
from spiffworkflow_backend.models.process_instance import ProcessInstanceModel
|
||||
from spiffworkflow_backend.models.process_model import ProcessModelInfo
|
||||
from spiffworkflow_backend.models.process_model import ProcessModelInfoSchema
|
||||
from spiffworkflow_backend.services.authorization_service import AuthorizationService
|
||||
from spiffworkflow_backend.services.file_system_service import FileSystemService
|
||||
from spiffworkflow_backend.services.user_service import UserService
|
||||
|
||||
T = TypeVar("T")
|
||||
|
||||
@ -35,20 +37,54 @@ class ProcessModelService(FileSystemService):
|
||||
GROUP_SCHEMA = ProcessGroupSchema()
|
||||
PROCESS_MODEL_SCHEMA = ProcessModelInfoSchema()
|
||||
|
||||
def is_group(self, path: str) -> bool:
|
||||
@classmethod
|
||||
def is_group(cls, path: str) -> bool:
|
||||
"""Is_group."""
|
||||
group_json_path = os.path.join(path, self.PROCESS_GROUP_JSON_FILE)
|
||||
group_json_path = os.path.join(path, cls.PROCESS_GROUP_JSON_FILE)
|
||||
if os.path.exists(group_json_path):
|
||||
return True
|
||||
return False
|
||||
|
||||
def is_model(self, path: str) -> bool:
|
||||
@classmethod
|
||||
def is_group_identifier(cls, process_group_identifier: str) -> bool:
|
||||
"""Is_group_identifier."""
|
||||
if os.path.exists(FileSystemService.root_path()):
|
||||
process_group_path = os.path.abspath(
|
||||
os.path.join(
|
||||
FileSystemService.root_path(),
|
||||
FileSystemService.id_string_to_relative_path(
|
||||
process_group_identifier
|
||||
),
|
||||
)
|
||||
)
|
||||
return cls.is_group(process_group_path)
|
||||
|
||||
return False
|
||||
|
||||
@classmethod
|
||||
def is_model(cls, path: str) -> bool:
|
||||
"""Is_model."""
|
||||
model_json_path = os.path.join(path, self.PROCESS_MODEL_JSON_FILE)
|
||||
model_json_path = os.path.join(path, cls.PROCESS_MODEL_JSON_FILE)
|
||||
if os.path.exists(model_json_path):
|
||||
return True
|
||||
return False
|
||||
|
||||
@classmethod
|
||||
def is_model_identifier(cls, process_model_identifier: str) -> bool:
|
||||
"""Is_model_identifier."""
|
||||
if os.path.exists(FileSystemService.root_path()):
|
||||
process_model_path = os.path.abspath(
|
||||
os.path.join(
|
||||
FileSystemService.root_path(),
|
||||
FileSystemService.id_string_to_relative_path(
|
||||
process_model_identifier
|
||||
),
|
||||
)
|
||||
)
|
||||
return cls.is_model(process_model_path)
|
||||
|
||||
return False
|
||||
|
||||
@staticmethod
|
||||
def write_json_file(
|
||||
file_path: str, json_data: dict, indent: int = 4, sort_keys: bool = True
|
||||
@ -68,37 +104,38 @@ class ProcessModelService(FileSystemService):
|
||||
end = start + per_page
|
||||
return items[start:end]
|
||||
|
||||
def add_process_model(self, process_model: ProcessModelInfo) -> None:
|
||||
@classmethod
|
||||
def add_process_model(cls, process_model: ProcessModelInfo) -> None:
|
||||
"""Add_spec."""
|
||||
display_order = self.next_display_order(process_model)
|
||||
process_model.display_order = display_order
|
||||
self.save_process_model(process_model)
|
||||
cls.save_process_model(process_model)
|
||||
|
||||
@classmethod
|
||||
def update_process_model(
|
||||
self, process_model: ProcessModelInfo, attributes_to_update: dict
|
||||
cls, process_model: ProcessModelInfo, attributes_to_update: dict
|
||||
) -> None:
|
||||
"""Update_spec."""
|
||||
for atu_key, atu_value in attributes_to_update.items():
|
||||
if hasattr(process_model, atu_key):
|
||||
setattr(process_model, atu_key, atu_value)
|
||||
self.save_process_model(process_model)
|
||||
cls.save_process_model(process_model)
|
||||
|
||||
def save_process_model(self, process_model: ProcessModelInfo) -> None:
|
||||
@classmethod
|
||||
def save_process_model(cls, process_model: ProcessModelInfo) -> None:
|
||||
"""Save_process_model."""
|
||||
process_model_path = os.path.abspath(
|
||||
os.path.join(FileSystemService.root_path(), process_model.id)
|
||||
)
|
||||
os.makedirs(process_model_path, exist_ok=True)
|
||||
json_path = os.path.abspath(
|
||||
os.path.join(process_model_path, self.PROCESS_MODEL_JSON_FILE)
|
||||
os.path.join(process_model_path, cls.PROCESS_MODEL_JSON_FILE)
|
||||
)
|
||||
process_model_id = process_model.id
|
||||
# we don't save id in the json file
|
||||
# this allows us to move models around on the filesystem
|
||||
# the id is determined by its location on the filesystem
|
||||
delattr(process_model, "id")
|
||||
json_data = self.PROCESS_MODEL_SCHEMA.dump(process_model)
|
||||
self.write_json_file(json_path, json_data)
|
||||
json_data = cls.PROCESS_MODEL_SCHEMA.dump(process_model)
|
||||
cls.write_json_file(json_path, json_data)
|
||||
process_model.id = process_model_id
|
||||
|
||||
def process_model_delete(self, process_model_id: str) -> None:
|
||||
@ -119,7 +156,7 @@ class ProcessModelService(FileSystemService):
|
||||
def process_model_move(
|
||||
self, original_process_model_id: str, new_location: str
|
||||
) -> ProcessModelInfo:
|
||||
"""process_model_move."""
|
||||
"""Process_model_move."""
|
||||
original_model_path = os.path.abspath(
|
||||
os.path.join(FileSystemService.root_path(), original_process_model_id)
|
||||
)
|
||||
@ -138,11 +175,11 @@ class ProcessModelService(FileSystemService):
|
||||
) -> ProcessModelInfo:
|
||||
"""Get_process_model_from_relative_path."""
|
||||
process_group_identifier, _ = os.path.split(relative_path)
|
||||
process_group = cls().get_process_group(process_group_identifier)
|
||||
path = os.path.join(FileSystemService.root_path(), relative_path)
|
||||
return cls().__scan_process_model(path, process_group=process_group)
|
||||
return cls.__scan_process_model(path)
|
||||
|
||||
def get_process_model(self, process_model_id: str) -> ProcessModelInfo:
|
||||
@classmethod
|
||||
def get_process_model(cls, process_model_id: str) -> ProcessModelInfo:
|
||||
"""Get a process model from a model and group id.
|
||||
|
||||
process_model_id is the full path to the model--including groups.
|
||||
@ -153,33 +190,16 @@ class ProcessModelService(FileSystemService):
|
||||
model_path = os.path.abspath(
|
||||
os.path.join(FileSystemService.root_path(), process_model_id)
|
||||
)
|
||||
if self.is_model(model_path):
|
||||
process_model = self.get_process_model_from_relative_path(process_model_id)
|
||||
return process_model
|
||||
|
||||
# group_path, model_id = os.path.split(process_model_id)
|
||||
# if group_path is not None:
|
||||
# process_group = self.get_process_group(group_path)
|
||||
# if process_group is not None:
|
||||
# for process_model in process_group.process_models:
|
||||
# if process_model_id == process_model.id:
|
||||
# return process_model
|
||||
# with os.scandir(FileSystemService.root_path()) as process_group_dirs:
|
||||
# for item in process_group_dirs:
|
||||
# process_group_dir = item
|
||||
# if item.is_dir():
|
||||
# with os.scandir(item.path) as spec_dirs:
|
||||
# for sd in spec_dirs:
|
||||
# if sd.name == process_model_id:
|
||||
# # Now we have the process_group directory, and spec directory
|
||||
# process_group = self.__scan_process_group(
|
||||
# process_group_dir
|
||||
# )
|
||||
# return self.__scan_process_model(sd.path, sd.name, process_group)
|
||||
if cls.is_model(model_path):
|
||||
return cls.get_process_model_from_relative_path(process_model_id)
|
||||
raise ProcessEntityNotFoundError("process_model_not_found")
|
||||
|
||||
@classmethod
|
||||
def get_process_models(
|
||||
self, process_group_id: Optional[str] = None, recursive: Optional[bool] = False
|
||||
cls,
|
||||
process_group_id: Optional[str] = None,
|
||||
recursive: Optional[bool] = False,
|
||||
filter_runnable_by_user: Optional[bool] = False,
|
||||
) -> List[ProcessModelInfo]:
|
||||
"""Get process models."""
|
||||
process_models = []
|
||||
@ -196,22 +216,56 @@ class ProcessModelService(FileSystemService):
|
||||
process_model_relative_path = os.path.relpath(
|
||||
file, start=FileSystemService.root_path()
|
||||
)
|
||||
process_model = self.get_process_model_from_relative_path(
|
||||
process_model = cls.get_process_model_from_relative_path(
|
||||
os.path.dirname(process_model_relative_path)
|
||||
)
|
||||
process_models.append(process_model)
|
||||
process_models.sort()
|
||||
|
||||
if filter_runnable_by_user:
|
||||
user = UserService.current_user()
|
||||
new_process_model_list = []
|
||||
for process_model in process_models:
|
||||
uri = f"/v1.0/process-models/{process_model.id.replace('/', ':')}/process-instances"
|
||||
result = AuthorizationService.user_has_permission(
|
||||
user=user, permission="create", target_uri=uri
|
||||
)
|
||||
if result:
|
||||
new_process_model_list.append(process_model)
|
||||
return new_process_model_list
|
||||
|
||||
return process_models
|
||||
|
||||
@classmethod
|
||||
def get_parent_group_array(cls, process_identifier: str) -> list[dict]:
|
||||
"""Get_parent_group_array."""
|
||||
full_group_id_path = None
|
||||
parent_group_array = []
|
||||
for process_group_id_segment in process_identifier.split("/")[0:-1]:
|
||||
if full_group_id_path is None:
|
||||
full_group_id_path = process_group_id_segment
|
||||
else:
|
||||
full_group_id_path = f"{full_group_id_path}/{process_group_id_segment}" # type: ignore
|
||||
parent_group = ProcessModelService.get_process_group(full_group_id_path)
|
||||
if parent_group:
|
||||
parent_group_array.append(
|
||||
{"id": parent_group.id, "display_name": parent_group.display_name}
|
||||
)
|
||||
return parent_group_array
|
||||
|
||||
@classmethod
|
||||
def get_process_groups(
|
||||
self, process_group_id: Optional[str] = None
|
||||
cls, process_group_id: Optional[str] = None
|
||||
) -> list[ProcessGroup]:
|
||||
"""Returns the process_groups as a list in display order."""
|
||||
process_groups = self.__scan_process_groups(process_group_id)
|
||||
"""Returns the process_groups."""
|
||||
process_groups = cls.__scan_process_groups(process_group_id)
|
||||
process_groups.sort()
|
||||
return process_groups
|
||||
|
||||
def get_process_group(self, process_group_id: str) -> ProcessGroup:
|
||||
@classmethod
|
||||
def get_process_group(
|
||||
cls, process_group_id: str, find_direct_nested_items: bool = True
|
||||
) -> ProcessGroup:
|
||||
"""Look for a given process_group, and return it."""
|
||||
if os.path.exists(FileSystemService.root_path()):
|
||||
process_group_path = os.path.abspath(
|
||||
@ -220,48 +274,38 @@ class ProcessModelService(FileSystemService):
|
||||
FileSystemService.id_string_to_relative_path(process_group_id),
|
||||
)
|
||||
)
|
||||
if self.is_group(process_group_path):
|
||||
return self.__scan_process_group(process_group_path)
|
||||
# nested_groups = []
|
||||
# process_group_dir = os.scandir(process_group_path)
|
||||
# for item in process_group_dir:
|
||||
# if self.is_group(item.path):
|
||||
# nested_group = self.get_process_group(os.path.join(process_group_path, item.path))
|
||||
# nested_groups.append(nested_group)
|
||||
# elif self.is_model(item.path):
|
||||
# print("get_process_group: ")
|
||||
# return self.__scan_process_group(process_group_path)
|
||||
# with os.scandir(FileSystemService.root_path()) as directory_items:
|
||||
# for item in directory_items:
|
||||
# if item.is_dir() and item.name == process_group_id:
|
||||
# return self.__scan_process_group(item)
|
||||
if cls.is_group(process_group_path):
|
||||
return cls.find_or_create_process_group(
|
||||
process_group_path,
|
||||
find_direct_nested_items=find_direct_nested_items,
|
||||
)
|
||||
|
||||
raise ProcessEntityNotFoundError(
|
||||
"process_group_not_found", f"Process Group Id: {process_group_id}"
|
||||
)
|
||||
|
||||
def add_process_group(self, process_group: ProcessGroup) -> ProcessGroup:
|
||||
@classmethod
|
||||
def add_process_group(cls, process_group: ProcessGroup) -> ProcessGroup:
|
||||
"""Add_process_group."""
|
||||
display_order = len(self.get_process_groups())
|
||||
process_group.display_order = display_order
|
||||
return self.update_process_group(process_group)
|
||||
return cls.update_process_group(process_group)
|
||||
|
||||
def update_process_group(self, process_group: ProcessGroup) -> ProcessGroup:
|
||||
@classmethod
|
||||
def update_process_group(cls, process_group: ProcessGroup) -> ProcessGroup:
|
||||
"""Update_process_group."""
|
||||
cat_path = self.process_group_path(process_group.id)
|
||||
cat_path = cls.process_group_path(process_group.id)
|
||||
os.makedirs(cat_path, exist_ok=True)
|
||||
json_path = os.path.join(cat_path, self.PROCESS_GROUP_JSON_FILE)
|
||||
json_path = os.path.join(cat_path, cls.PROCESS_GROUP_JSON_FILE)
|
||||
serialized_process_group = process_group.serialized
|
||||
# we don't store `id` in the json files
|
||||
# this allows us to move groups around on the filesystem
|
||||
del serialized_process_group["id"]
|
||||
self.write_json_file(json_path, serialized_process_group)
|
||||
cls.write_json_file(json_path, serialized_process_group)
|
||||
return process_group
|
||||
|
||||
def process_group_move(
|
||||
self, original_process_group_id: str, new_location: str
|
||||
) -> ProcessGroup:
|
||||
"""process_group_move."""
|
||||
"""Process_group_move."""
|
||||
original_group_path = self.process_group_path(original_process_group_id)
|
||||
original_root, original_group_id = os.path.split(original_group_path)
|
||||
new_root = f"{FileSystemService.root_path()}/{new_location}"
|
||||
@ -278,7 +322,7 @@ class ProcessModelService(FileSystemService):
|
||||
for _root, dirs, _files in os.walk(group_path):
|
||||
for dir in dirs:
|
||||
model_dir = os.path.join(group_path, dir)
|
||||
if ProcessModelService().is_model(model_dir):
|
||||
if ProcessModelService.is_model(model_dir):
|
||||
process_model = self.get_process_model(model_dir)
|
||||
all_nested_models.append(process_model)
|
||||
return all_nested_models
|
||||
@ -314,8 +358,9 @@ class ProcessModelService(FileSystemService):
|
||||
index += 1
|
||||
return process_groups
|
||||
|
||||
@classmethod
|
||||
def __scan_process_groups(
|
||||
self, process_group_id: Optional[str] = None
|
||||
cls, process_group_id: Optional[str] = None
|
||||
) -> list[ProcessGroup]:
|
||||
"""__scan_process_groups."""
|
||||
if not os.path.exists(FileSystemService.root_path()):
|
||||
@ -329,14 +374,17 @@ class ProcessModelService(FileSystemService):
|
||||
process_groups = []
|
||||
for item in directory_items:
|
||||
# if item.is_dir() and not item.name[0] == ".":
|
||||
if item.is_dir() and self.is_group(item): # type: ignore
|
||||
scanned_process_group = self.__scan_process_group(item.path)
|
||||
if item.is_dir() and cls.is_group(item): # type: ignore
|
||||
scanned_process_group = cls.find_or_create_process_group(item.path)
|
||||
process_groups.append(scanned_process_group)
|
||||
return process_groups
|
||||
|
||||
def __scan_process_group(self, dir_path: str) -> ProcessGroup:
|
||||
@classmethod
|
||||
def find_or_create_process_group(
|
||||
cls, dir_path: str, find_direct_nested_items: bool = True
|
||||
) -> ProcessGroup:
|
||||
"""Reads the process_group.json file, and any nested directories."""
|
||||
cat_path = os.path.join(dir_path, self.PROCESS_GROUP_JSON_FILE)
|
||||
cat_path = os.path.join(dir_path, cls.PROCESS_GROUP_JSON_FILE)
|
||||
if os.path.exists(cat_path):
|
||||
with open(cat_path) as cat_json:
|
||||
data = json.load(cat_json)
|
||||
@ -357,40 +405,41 @@ class ProcessModelService(FileSystemService):
|
||||
display_order=10000,
|
||||
admin=False,
|
||||
)
|
||||
self.write_json_file(cat_path, self.GROUP_SCHEMA.dump(process_group))
|
||||
cls.write_json_file(cat_path, cls.GROUP_SCHEMA.dump(process_group))
|
||||
# we don't store `id` in the json files, so we add it in here
|
||||
process_group.id = process_group_id
|
||||
with os.scandir(dir_path) as nested_items:
|
||||
process_group.process_models = []
|
||||
process_group.process_groups = []
|
||||
for nested_item in nested_items:
|
||||
if nested_item.is_dir():
|
||||
# TODO: check whether this is a group or model
|
||||
if self.is_group(nested_item.path):
|
||||
# This is a nested group
|
||||
process_group.process_groups.append(
|
||||
self.__scan_process_group(nested_item.path)
|
||||
)
|
||||
elif self.is_model(nested_item.path):
|
||||
process_group.process_models.append(
|
||||
self.__scan_process_model(
|
||||
nested_item.path,
|
||||
nested_item.name,
|
||||
process_group=process_group,
|
||||
|
||||
if find_direct_nested_items:
|
||||
with os.scandir(dir_path) as nested_items:
|
||||
process_group.process_models = []
|
||||
process_group.process_groups = []
|
||||
for nested_item in nested_items:
|
||||
if nested_item.is_dir():
|
||||
# TODO: check whether this is a group or model
|
||||
if cls.is_group(nested_item.path):
|
||||
# This is a nested group
|
||||
process_group.process_groups.append(
|
||||
cls.find_or_create_process_group(nested_item.path)
|
||||
)
|
||||
)
|
||||
process_group.process_models.sort()
|
||||
# process_group.process_groups.sort()
|
||||
elif ProcessModelService.is_model(nested_item.path):
|
||||
process_group.process_models.append(
|
||||
cls.__scan_process_model(
|
||||
nested_item.path,
|
||||
nested_item.name,
|
||||
)
|
||||
)
|
||||
process_group.process_models.sort()
|
||||
# process_group.process_groups.sort()
|
||||
return process_group
|
||||
|
||||
@classmethod
|
||||
def __scan_process_model(
|
||||
self,
|
||||
cls,
|
||||
path: str,
|
||||
name: Optional[str] = None,
|
||||
process_group: Optional[ProcessGroup] = None,
|
||||
) -> ProcessModelInfo:
|
||||
"""__scan_process_model."""
|
||||
json_file_path = os.path.join(path, self.PROCESS_MODEL_JSON_FILE)
|
||||
json_file_path = os.path.join(path, cls.PROCESS_MODEL_JSON_FILE)
|
||||
|
||||
if os.path.exists(json_file_path):
|
||||
with open(json_file_path) as wf_json:
|
||||
@ -418,13 +467,10 @@ class ProcessModelService(FileSystemService):
|
||||
display_name=name,
|
||||
description="",
|
||||
display_order=0,
|
||||
is_review=False,
|
||||
)
|
||||
self.write_json_file(
|
||||
json_file_path, self.PROCESS_MODEL_SCHEMA.dump(process_model_info)
|
||||
cls.write_json_file(
|
||||
json_file_path, cls.PROCESS_MODEL_SCHEMA.dump(process_model_info)
|
||||
)
|
||||
# we don't store `id` in the json files, so we add it in here
|
||||
process_model_info.id = name
|
||||
if process_group:
|
||||
process_model_info.process_group = process_group.id
|
||||
return process_model_info
|
||||
|
@ -65,7 +65,7 @@ class SecretService:
|
||||
def update_secret(
|
||||
key: str,
|
||||
value: str,
|
||||
user_id: int,
|
||||
user_id: Optional[int] = None,
|
||||
create_if_not_exists: Optional[bool] = False,
|
||||
) -> None:
|
||||
"""Does this pass pre commit?"""
|
||||
@ -79,6 +79,12 @@ class SecretService:
|
||||
db.session.rollback()
|
||||
raise e
|
||||
elif create_if_not_exists:
|
||||
if user_id is None:
|
||||
raise ApiError(
|
||||
error_code="update_secret_error_no_user_id",
|
||||
message=f"Cannot update secret with key: {key}. Missing user id.",
|
||||
status_code=404,
|
||||
)
|
||||
SecretService.add_secret(key=key, value=value, user_id=user_id)
|
||||
else:
|
||||
raise ApiError(
|
||||
|
@ -8,6 +8,7 @@ from flask import g
|
||||
|
||||
from spiffworkflow_backend.services.file_system_service import FileSystemService
|
||||
from spiffworkflow_backend.services.secret_service import SecretService
|
||||
from spiffworkflow_backend.services.user_service import UserService
|
||||
|
||||
|
||||
class ConnectorProxyError(Exception):
|
||||
@ -65,7 +66,8 @@ class ServiceTaskDelegate:
|
||||
|
||||
secret_key = parsed_response["auth"]
|
||||
refreshed_token_set = json.dumps(parsed_response["refreshed_token_set"])
|
||||
SecretService().update_secret(secret_key, refreshed_token_set, g.user.id)
|
||||
user_id = g.user.id if UserService.has_user() else None
|
||||
SecretService().update_secret(secret_key, refreshed_token_set, user_id)
|
||||
|
||||
return json.dumps(parsed_response["api_response"])
|
||||
|
||||
|
@ -171,12 +171,11 @@ class SpecFileService(FileSystemService):
|
||||
ref.is_primary = True
|
||||
|
||||
if ref.is_primary:
|
||||
ProcessModelService().update_process_model(
|
||||
ProcessModelService.update_process_model(
|
||||
process_model_info,
|
||||
{
|
||||
"primary_process_id": ref.identifier,
|
||||
"primary_file_name": file_name,
|
||||
"is_review": ref.has_lanes,
|
||||
},
|
||||
)
|
||||
SpecFileService.update_caches(ref)
|
||||
@ -322,7 +321,6 @@ class SpecFileService(FileSystemService):
|
||||
message_triggerable_process_model = MessageTriggerableProcessModel(
|
||||
message_model_id=message_model.id,
|
||||
process_model_identifier=ref.process_model_id,
|
||||
process_group_identifier="process_group_identifier",
|
||||
)
|
||||
db.session.add(message_triggerable_process_model)
|
||||
db.session.commit()
|
||||
@ -330,8 +328,6 @@ class SpecFileService(FileSystemService):
|
||||
if (
|
||||
message_triggerable_process_model.process_model_identifier
|
||||
!= ref.process_model_id
|
||||
# or message_triggerable_process_model.process_group_identifier
|
||||
# != process_model_info.process_group_id
|
||||
):
|
||||
raise ValidationException(
|
||||
f"Message model is already used to start process model {ref.process_model_id}"
|
||||
|
@ -0,0 +1,52 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<bpmn:definitions xmlns:bpmn="http://www.omg.org/spec/BPMN/20100524/MODEL" xmlns:bpmndi="http://www.omg.org/spec/BPMN/20100524/DI" xmlns:dc="http://www.omg.org/spec/DD/20100524/DC" xmlns:di="http://www.omg.org/spec/DD/20100524/DI" id="Definitions_96f6665" targetNamespace="http://bpmn.io/schema/bpmn" exporter="Camunda Modeler" exporterVersion="3.0.0-dev">
|
||||
<bpmn:process id="test_save_process_instance_metadata" isExecutable="true">
|
||||
<bpmn:startEvent id="Event_0r6oru6">
|
||||
<bpmn:outgoing>Flow_1j4jzft</bpmn:outgoing>
|
||||
</bpmn:startEvent>
|
||||
<bpmn:sequenceFlow id="Flow_1j4jzft" sourceRef="Event_0r6oru6" targetRef="save_key1" />
|
||||
<bpmn:endEvent id="Event_1s123jg">
|
||||
<bpmn:incoming>Flow_01xr2ac</bpmn:incoming>
|
||||
</bpmn:endEvent>
|
||||
<bpmn:scriptTask id="save_key1">
|
||||
<bpmn:incoming>Flow_1j4jzft</bpmn:incoming>
|
||||
<bpmn:outgoing>Flow_10xyk22</bpmn:outgoing>
|
||||
<bpmn:script>save_process_instance_metadata({"key1": "value1"})</bpmn:script>
|
||||
</bpmn:scriptTask>
|
||||
<bpmn:sequenceFlow id="Flow_10xyk22" sourceRef="save_key1" targetRef="save_key2" />
|
||||
<bpmn:scriptTask id="save_key2">
|
||||
<bpmn:incoming>Flow_10xyk22</bpmn:incoming>
|
||||
<bpmn:outgoing>Flow_01xr2ac</bpmn:outgoing>
|
||||
<bpmn:script>save_process_instance_metadata({"key2": "value2", "key3": "value3"})</bpmn:script>
|
||||
</bpmn:scriptTask>
|
||||
<bpmn:sequenceFlow id="Flow_01xr2ac" sourceRef="save_key2" targetRef="Event_1s123jg" />
|
||||
</bpmn:process>
|
||||
<bpmndi:BPMNDiagram id="BPMNDiagram_1">
|
||||
<bpmndi:BPMNPlane id="BPMNPlane_1" bpmnElement="test_save_process_instance_metadata">
|
||||
<bpmndi:BPMNShape id="Event_0r6oru6_di" bpmnElement="Event_0r6oru6">
|
||||
<dc:Bounds x="162" y="162" width="36" height="36" />
|
||||
</bpmndi:BPMNShape>
|
||||
<bpmndi:BPMNShape id="Activity_0zfzev2_di" bpmnElement="save_key1">
|
||||
<dc:Bounds x="250" y="140" width="100" height="80" />
|
||||
</bpmndi:BPMNShape>
|
||||
<bpmndi:BPMNShape id="Activity_0d1q8x4_di" bpmnElement="save_key2">
|
||||
<dc:Bounds x="410" y="140" width="100" height="80" />
|
||||
</bpmndi:BPMNShape>
|
||||
<bpmndi:BPMNShape id="Event_1s123jg_di" bpmnElement="Event_1s123jg">
|
||||
<dc:Bounds x="582" y="162" width="36" height="36" />
|
||||
</bpmndi:BPMNShape>
|
||||
<bpmndi:BPMNEdge id="Flow_1j4jzft_di" bpmnElement="Flow_1j4jzft">
|
||||
<di:waypoint x="198" y="180" />
|
||||
<di:waypoint x="250" y="180" />
|
||||
</bpmndi:BPMNEdge>
|
||||
<bpmndi:BPMNEdge id="Flow_10xyk22_di" bpmnElement="Flow_10xyk22">
|
||||
<di:waypoint x="350" y="180" />
|
||||
<di:waypoint x="410" y="180" />
|
||||
</bpmndi:BPMNEdge>
|
||||
<bpmndi:BPMNEdge id="Flow_01xr2ac_di" bpmnElement="Flow_01xr2ac">
|
||||
<di:waypoint x="510" y="180" />
|
||||
<di:waypoint x="582" y="180" />
|
||||
</bpmndi:BPMNEdge>
|
||||
</bpmndi:BPMNPlane>
|
||||
</bpmndi:BPMNDiagram>
|
||||
</bpmn:definitions>
|
@ -140,7 +140,7 @@ class BaseTest:
|
||||
process_group_path = os.path.abspath(
|
||||
os.path.join(FileSystemService.root_path(), process_group_id)
|
||||
)
|
||||
if ProcessModelService().is_group(process_group_path):
|
||||
if ProcessModelService.is_group(process_group_path):
|
||||
|
||||
if exception_notification_addresses is None:
|
||||
exception_notification_addresses = []
|
||||
@ -149,7 +149,6 @@ class BaseTest:
|
||||
id=process_model_id,
|
||||
display_name=process_model_display_name,
|
||||
description=process_model_description,
|
||||
is_review=False,
|
||||
primary_process_id=primary_process_id,
|
||||
primary_file_name=primary_file_name,
|
||||
fault_or_suspend_on_exception=fault_or_suspend_on_exception,
|
||||
@ -253,6 +252,17 @@ class BaseTest:
|
||||
|
||||
There must be an existing process model to instantiate.
|
||||
"""
|
||||
if not ProcessModelService.is_model_identifier(test_process_model_id):
|
||||
dirname = os.path.dirname(test_process_model_id)
|
||||
if not ProcessModelService.is_group_identifier(dirname):
|
||||
process_group = ProcessGroup(id=dirname, display_name=dirname)
|
||||
ProcessModelService.add_process_group(process_group)
|
||||
basename = os.path.basename(test_process_model_id)
|
||||
load_test_spec(
|
||||
process_model_id=test_process_model_id,
|
||||
process_model_source_directory=basename,
|
||||
bpmn_file_name=basename,
|
||||
)
|
||||
modified_process_model_id = test_process_model_id.replace("/", ":")
|
||||
response = client.post(
|
||||
f"/v1.0/process-models/{modified_process_model_id}/process-instances",
|
||||
@ -284,7 +294,7 @@ class BaseTest:
|
||||
status=status,
|
||||
process_initiator=user,
|
||||
process_model_identifier=process_model.id,
|
||||
process_group_identifier="",
|
||||
process_model_display_name=process_model.display_name,
|
||||
updated_at_in_seconds=round(time.time()),
|
||||
start_in_seconds=current_time - (3600 * 1),
|
||||
end_in_seconds=current_time - (3600 * 1 - 20),
|
||||
@ -347,3 +357,16 @@ class BaseTest:
|
||||
target_uri=target_uri,
|
||||
)
|
||||
assert has_permission is expected_result
|
||||
|
||||
def modify_process_identifier_for_path_param(self, identifier: str) -> str:
|
||||
"""Identifier."""
|
||||
if "\\" in identifier:
|
||||
raise Exception(f"Found backslash in identifier: {identifier}")
|
||||
|
||||
return identifier.replace("/", ":")
|
||||
|
||||
def un_modify_modified_process_identifier_for_path_param(
|
||||
self, modified_identifier: str
|
||||
) -> str:
|
||||
"""Un_modify_modified_process_model_id."""
|
||||
return modified_identifier.replace(":", "/")
|
||||
|
@ -36,10 +36,8 @@ class ExampleDataLoader:
|
||||
display_name=display_name,
|
||||
description=description,
|
||||
display_order=display_order,
|
||||
is_review=False,
|
||||
)
|
||||
workflow_spec_service = ProcessModelService()
|
||||
workflow_spec_service.add_process_model(spec)
|
||||
ProcessModelService.add_process_model(spec)
|
||||
|
||||
bpmn_file_name_with_extension = bpmn_file_name
|
||||
if not bpmn_file_name_with_extension:
|
||||
@ -88,7 +86,7 @@ class ExampleDataLoader:
|
||||
)
|
||||
spec.primary_process_id = references[0].identifier
|
||||
spec.primary_file_name = filename
|
||||
ProcessModelService().save_process_model(spec)
|
||||
ProcessModelService.save_process_model(spec)
|
||||
finally:
|
||||
if file:
|
||||
file.close()
|
||||
|
@ -51,7 +51,7 @@ class TestLoggingService(BaseTest):
|
||||
assert response.json is not None
|
||||
process_instance_id = response.json["id"]
|
||||
response = client.post(
|
||||
f"/v1.0/process-instances/{process_instance_id}/run",
|
||||
f"/v1.0/process-instances/{self.modify_process_identifier_for_path_param(process_model_identifier)}/{process_instance_id}/run",
|
||||
headers=headers,
|
||||
)
|
||||
assert response.status_code == 200
|
||||
|
@ -46,7 +46,7 @@ class TestNestedGroups(BaseTest):
|
||||
process_instance_id = response.json["id"]
|
||||
|
||||
client.post(
|
||||
f"/v1.0/process-instances/{process_instance_id}/run",
|
||||
f"/v1.0/process-instances/{self.modify_process_identifier_for_path_param(process_model_identifier)}/{process_instance_id}/run",
|
||||
headers=self.logged_in_headers(with_super_admin_user),
|
||||
)
|
||||
process_instance = ProcessInstanceService().get_process_instance(
|
||||
|
@ -133,12 +133,12 @@ class TestProcessApi(BaseTest):
|
||||
process_model_description=model_description,
|
||||
user=with_super_admin_user,
|
||||
)
|
||||
process_model = ProcessModelService().get_process_model(
|
||||
process_model = ProcessModelService.get_process_model(
|
||||
process_model_identifier,
|
||||
)
|
||||
assert model_display_name == process_model.display_name
|
||||
assert 0 == process_model.display_order
|
||||
assert 1 == len(ProcessModelService().get_process_groups())
|
||||
assert 1 == len(ProcessModelService.get_process_groups())
|
||||
|
||||
# add bpmn file to the model
|
||||
bpmn_file_name = "sample.bpmn"
|
||||
@ -155,9 +155,7 @@ class TestProcessApi(BaseTest):
|
||||
user=with_super_admin_user,
|
||||
)
|
||||
# get the model, assert that primary is set
|
||||
process_model = ProcessModelService().get_process_model(
|
||||
process_model_identifier
|
||||
)
|
||||
process_model = ProcessModelService.get_process_model(process_model_identifier)
|
||||
assert process_model.primary_file_name == bpmn_file_name
|
||||
assert process_model.primary_process_id == "sample"
|
||||
|
||||
@ -208,9 +206,7 @@ class TestProcessApi(BaseTest):
|
||||
headers=self.logged_in_headers(with_super_admin_user),
|
||||
)
|
||||
assert response.status_code == 200
|
||||
process_model = ProcessModelService().get_process_model(
|
||||
process_model_identifier
|
||||
)
|
||||
process_model = ProcessModelService.get_process_model(process_model_identifier)
|
||||
assert process_model.primary_file_name == bpmn_file_name
|
||||
assert process_model.primary_process_id == terminal_primary_process_id
|
||||
|
||||
@ -236,9 +232,7 @@ class TestProcessApi(BaseTest):
|
||||
)
|
||||
|
||||
# assert we have a model
|
||||
process_model = ProcessModelService().get_process_model(
|
||||
process_model_identifier
|
||||
)
|
||||
process_model = ProcessModelService.get_process_model(process_model_identifier)
|
||||
assert process_model is not None
|
||||
assert process_model.id == process_model_identifier
|
||||
|
||||
@ -254,7 +248,7 @@ class TestProcessApi(BaseTest):
|
||||
|
||||
# assert we no longer have a model
|
||||
with pytest.raises(ProcessEntityNotFoundError):
|
||||
ProcessModelService().get_process_model(process_model_identifier)
|
||||
ProcessModelService.get_process_model(process_model_identifier)
|
||||
|
||||
def test_process_model_delete_with_instances(
|
||||
self,
|
||||
@ -327,19 +321,15 @@ class TestProcessApi(BaseTest):
|
||||
process_model_id=process_model_identifier,
|
||||
user=with_super_admin_user,
|
||||
)
|
||||
process_model = ProcessModelService().get_process_model(
|
||||
process_model_identifier
|
||||
)
|
||||
process_model = ProcessModelService.get_process_model(process_model_identifier)
|
||||
assert process_model.id == process_model_identifier
|
||||
assert process_model.display_name == "Cooooookies"
|
||||
assert process_model.is_review is False
|
||||
assert process_model.primary_file_name is None
|
||||
assert process_model.primary_process_id is None
|
||||
|
||||
process_model.display_name = "Updated Display Name"
|
||||
process_model.primary_file_name = "superduper.bpmn"
|
||||
process_model.primary_process_id = "superduper"
|
||||
process_model.is_review = True # not in the include list, so get ignored
|
||||
|
||||
modified_process_model_identifier = process_model_identifier.replace("/", ":")
|
||||
response = client.put(
|
||||
@ -353,7 +343,6 @@ class TestProcessApi(BaseTest):
|
||||
assert response.json["display_name"] == "Updated Display Name"
|
||||
assert response.json["primary_file_name"] == "superduper.bpmn"
|
||||
assert response.json["primary_process_id"] == "superduper"
|
||||
assert response.json["is_review"] is False
|
||||
|
||||
def test_process_model_list_all(
|
||||
self,
|
||||
@ -550,7 +539,7 @@ class TestProcessApi(BaseTest):
|
||||
assert result.description == "Test Description"
|
||||
|
||||
# Check what is persisted
|
||||
persisted = ProcessModelService().get_process_group("test")
|
||||
persisted = ProcessModelService.get_process_group("test")
|
||||
assert persisted.display_name == "Another Test Category"
|
||||
assert persisted.id == "test"
|
||||
assert persisted.description == "Test Description"
|
||||
@ -572,7 +561,7 @@ class TestProcessApi(BaseTest):
|
||||
process_group_id,
|
||||
display_name=process_group_display_name,
|
||||
)
|
||||
persisted = ProcessModelService().get_process_group(process_group_id)
|
||||
persisted = ProcessModelService.get_process_group(process_group_id)
|
||||
assert persisted is not None
|
||||
assert persisted.id == process_group_id
|
||||
|
||||
@ -582,7 +571,7 @@ class TestProcessApi(BaseTest):
|
||||
)
|
||||
|
||||
with pytest.raises(ProcessEntityNotFoundError):
|
||||
ProcessModelService().get_process_group(process_group_id)
|
||||
ProcessModelService.get_process_group(process_group_id)
|
||||
|
||||
def test_process_group_update(
|
||||
self,
|
||||
@ -598,7 +587,7 @@ class TestProcessApi(BaseTest):
|
||||
self.create_process_group(
|
||||
client, with_super_admin_user, group_id, display_name=group_display_name
|
||||
)
|
||||
process_group = ProcessModelService().get_process_group(group_id)
|
||||
process_group = ProcessModelService.get_process_group(group_id)
|
||||
|
||||
assert process_group.display_name == group_display_name
|
||||
|
||||
@ -612,7 +601,7 @@ class TestProcessApi(BaseTest):
|
||||
)
|
||||
assert response.status_code == 200
|
||||
|
||||
process_group = ProcessModelService().get_process_group(group_id)
|
||||
process_group = ProcessModelService.get_process_group(group_id)
|
||||
assert process_group.display_name == "Modified Display Name"
|
||||
|
||||
def test_process_group_list(
|
||||
@ -979,6 +968,43 @@ class TestProcessApi(BaseTest):
|
||||
assert response.json is not None
|
||||
assert response.json["id"] == process_group_id
|
||||
assert response.json["process_models"][0]["id"] == process_model_identifier
|
||||
assert response.json["parent_groups"] == []
|
||||
|
||||
def test_get_process_group_show_when_nested(
|
||||
self,
|
||||
app: Flask,
|
||||
client: FlaskClient,
|
||||
with_db_and_bpmn_file_cleanup: None,
|
||||
with_super_admin_user: UserModel,
|
||||
) -> None:
|
||||
"""Test_get_process_group_show_when_nested."""
|
||||
self.create_group_and_model_with_bpmn(
|
||||
client=client,
|
||||
user=with_super_admin_user,
|
||||
process_group_id="test_group_one",
|
||||
process_model_id="simple_form",
|
||||
bpmn_file_location="simple_form",
|
||||
)
|
||||
|
||||
self.create_group_and_model_with_bpmn(
|
||||
client=client,
|
||||
user=with_super_admin_user,
|
||||
process_group_id="test_group_one/test_group_two",
|
||||
process_model_id="call_activity_nested",
|
||||
bpmn_file_location="call_activity_nested",
|
||||
)
|
||||
|
||||
response = client.get(
|
||||
"/v1.0/process-groups/test_group_one:test_group_two",
|
||||
headers=self.logged_in_headers(with_super_admin_user),
|
||||
)
|
||||
|
||||
assert response.status_code == 200
|
||||
assert response.json is not None
|
||||
assert response.json["id"] == "test_group_one/test_group_two"
|
||||
assert response.json["parent_groups"] == [
|
||||
{"display_name": "test_group_one", "id": "test_group_one"}
|
||||
]
|
||||
|
||||
def test_get_process_model_when_found(
|
||||
self,
|
||||
@ -997,11 +1023,15 @@ class TestProcessApi(BaseTest):
|
||||
f"/v1.0/process-models/{modified_process_model_identifier}",
|
||||
headers=self.logged_in_headers(with_super_admin_user),
|
||||
)
|
||||
|
||||
assert response.status_code == 200
|
||||
assert response.json is not None
|
||||
assert response.json["id"] == process_model_identifier
|
||||
assert len(response.json["files"]) == 1
|
||||
assert response.json["files"][0]["name"] == "random_fact.bpmn"
|
||||
assert response.json["parent_groups"] == [
|
||||
{"display_name": "test_group", "id": "test_group"}
|
||||
]
|
||||
|
||||
def test_get_process_model_when_not_found(
|
||||
self,
|
||||
@ -1069,7 +1099,7 @@ class TestProcessApi(BaseTest):
|
||||
assert response.json is not None
|
||||
process_instance_id = response.json["id"]
|
||||
response = client.post(
|
||||
f"/v1.0/process-instances/{process_instance_id}/run",
|
||||
f"/v1.0/process-instances/{self.modify_process_identifier_for_path_param(process_model_identifier)}/{process_instance_id}/run",
|
||||
headers=self.logged_in_headers(with_super_admin_user),
|
||||
)
|
||||
|
||||
@ -1101,7 +1131,9 @@ class TestProcessApi(BaseTest):
|
||||
process_group_id=process_group_id,
|
||||
process_model_id=process_model_id,
|
||||
)
|
||||
modified_process_model_identifier = process_model_identifier.replace("/", ":")
|
||||
modified_process_model_identifier = (
|
||||
self.modify_process_identifier_for_path_param(process_model_identifier)
|
||||
)
|
||||
headers = self.logged_in_headers(with_super_admin_user)
|
||||
create_response = self.create_process_instance_from_process_model_id(
|
||||
client, process_model_identifier, headers
|
||||
@ -1109,7 +1141,7 @@ class TestProcessApi(BaseTest):
|
||||
assert create_response.json is not None
|
||||
process_instance_id = create_response.json["id"]
|
||||
client.post(
|
||||
f"/v1.0/process-instances/{process_instance_id}/run",
|
||||
f"/v1.0/process-instances/{modified_process_model_identifier}/{process_instance_id}/run",
|
||||
headers=self.logged_in_headers(with_super_admin_user),
|
||||
)
|
||||
show_response = client.get(
|
||||
@ -1212,7 +1244,7 @@ class TestProcessApi(BaseTest):
|
||||
process_instance_id = response.json["id"]
|
||||
|
||||
response = client.post(
|
||||
f"/v1.0/process-instances/{process_instance_id}/run",
|
||||
f"/v1.0/process-instances/{self.modify_process_identifier_for_path_param(process_model_identifier)}/{process_instance_id}/run",
|
||||
headers=self.logged_in_headers(with_super_admin_user),
|
||||
)
|
||||
|
||||
@ -1272,7 +1304,7 @@ class TestProcessApi(BaseTest):
|
||||
process_instance_id = response.json["id"]
|
||||
|
||||
response = client.post(
|
||||
f"/v1.0/process-instances/{process_instance_id}/run",
|
||||
f"/v1.0/process-instances/{self.modify_process_identifier_for_path_param(process_model_identifier)}/{process_instance_id}/run",
|
||||
headers=self.logged_in_headers(with_super_admin_user),
|
||||
)
|
||||
assert response.status_code == 200
|
||||
@ -1320,7 +1352,7 @@ class TestProcessApi(BaseTest):
|
||||
process_instance_id = response.json["id"]
|
||||
|
||||
response = client.post(
|
||||
f"/v1.0/process-instances/{process_instance_id}/run",
|
||||
f"/v1.0/process-instances/{self.modify_process_identifier_for_path_param(process_model_identifier)}/{process_instance_id}/run",
|
||||
headers=self.logged_in_headers(with_super_admin_user),
|
||||
)
|
||||
assert response.json is not None
|
||||
@ -1359,7 +1391,7 @@ class TestProcessApi(BaseTest):
|
||||
process_instance_id = response.json["id"]
|
||||
|
||||
response = client.post(
|
||||
f"/v1.0/process-instances/{process_instance_id}/run",
|
||||
f"/v1.0/process-instances/{self.modify_process_identifier_for_path_param(process_model_identifier)}/{process_instance_id}/run",
|
||||
headers=self.logged_in_headers(with_super_admin_user),
|
||||
)
|
||||
|
||||
@ -1516,7 +1548,7 @@ class TestProcessApi(BaseTest):
|
||||
status=ProcessInstanceStatus[statuses[i]].value,
|
||||
process_initiator=with_super_admin_user,
|
||||
process_model_identifier=process_model_identifier,
|
||||
process_group_identifier="test_process_group_id",
|
||||
process_model_display_name=process_model_identifier,
|
||||
updated_at_in_seconds=round(time.time()),
|
||||
start_in_seconds=(1000 * i) + 1000,
|
||||
end_in_seconds=(1000 * i) + 2000,
|
||||
@ -1818,7 +1850,7 @@ class TestProcessApi(BaseTest):
|
||||
assert process.status == "not_started"
|
||||
|
||||
response = client.post(
|
||||
f"/v1.0/process-instances/{process_instance_id}/run",
|
||||
f"/v1.0/process-instances/{self.modify_process_identifier_for_path_param(process_model_identifier)}/{process_instance_id}/run",
|
||||
headers=self.logged_in_headers(with_super_admin_user),
|
||||
)
|
||||
assert response.status_code == 400
|
||||
@ -1862,10 +1894,8 @@ class TestProcessApi(BaseTest):
|
||||
process_instance_id = self.setup_testing_instance(
|
||||
client, process_model_identifier, with_super_admin_user
|
||||
)
|
||||
process_model = ProcessModelService().get_process_model(
|
||||
process_model_identifier
|
||||
)
|
||||
ProcessModelService().update_process_model(
|
||||
process_model = ProcessModelService.get_process_model(process_model_identifier)
|
||||
ProcessModelService.update_process_model(
|
||||
process_model,
|
||||
{"fault_or_suspend_on_exception": NotificationType.suspend.value},
|
||||
)
|
||||
@ -1879,7 +1909,7 @@ class TestProcessApi(BaseTest):
|
||||
assert process.status == "not_started"
|
||||
|
||||
response = client.post(
|
||||
f"/v1.0/process-instances/{process_instance_id}/run",
|
||||
f"/v1.0/process-instances/{self.modify_process_identifier_for_path_param(process_model_identifier)}/{process_instance_id}/run",
|
||||
headers=self.logged_in_headers(with_super_admin_user),
|
||||
)
|
||||
assert response.status_code == 400
|
||||
@ -1917,10 +1947,8 @@ class TestProcessApi(BaseTest):
|
||||
client, process_model_identifier, with_super_admin_user
|
||||
)
|
||||
|
||||
process_model = ProcessModelService().get_process_model(
|
||||
process_model_identifier
|
||||
)
|
||||
ProcessModelService().update_process_model(
|
||||
process_model = ProcessModelService.get_process_model(process_model_identifier)
|
||||
ProcessModelService.update_process_model(
|
||||
process_model,
|
||||
{"exception_notification_addresses": ["with_super_admin_user@example.com"]},
|
||||
)
|
||||
@ -1929,7 +1957,7 @@ class TestProcessApi(BaseTest):
|
||||
with mail.record_messages() as outbox:
|
||||
|
||||
response = client.post(
|
||||
f"/v1.0/process-instances/{process_instance_id}/run",
|
||||
f"/v1.0/process-instances/{self.modify_process_identifier_for_path_param(process_model_identifier)}/{process_instance_id}/run",
|
||||
headers=self.logged_in_headers(with_super_admin_user),
|
||||
)
|
||||
assert response.status_code == 400
|
||||
@ -2114,7 +2142,7 @@ class TestProcessApi(BaseTest):
|
||||
assert response.json is not None
|
||||
process_instance_id = response.json["id"]
|
||||
response = client.post(
|
||||
f"/v1.0/process-instances/{process_instance_id}/run",
|
||||
f"/v1.0/process-instances/{self.modify_process_identifier_for_path_param(process_model_identifier)}/{process_instance_id}/run",
|
||||
headers=self.logged_in_headers(initiator_user),
|
||||
)
|
||||
assert response.status_code == 200
|
||||
@ -2319,7 +2347,7 @@ class TestProcessApi(BaseTest):
|
||||
process_instance_id = response.json["id"]
|
||||
|
||||
client.post(
|
||||
f"/v1.0/process-instances/{process_instance_id}/run",
|
||||
f"/v1.0/process-instances/{self.modify_process_identifier_for_path_param(process_model_identifier)}/{process_instance_id}/run",
|
||||
headers=self.logged_in_headers(with_super_admin_user),
|
||||
)
|
||||
|
||||
@ -2339,7 +2367,7 @@ class TestProcessApi(BaseTest):
|
||||
|
||||
# TODO: Why can I run a suspended process instance?
|
||||
response = client.post(
|
||||
f"/v1.0/process-instances/{process_instance_id}/run",
|
||||
f"/v1.0/process-instances/{self.modify_process_identifier_for_path_param(process_model_identifier)}/{process_instance_id}/run",
|
||||
headers=self.logged_in_headers(with_super_admin_user),
|
||||
)
|
||||
|
||||
@ -2408,7 +2436,7 @@ class TestProcessApi(BaseTest):
|
||||
def setup_initial_groups_for_move_tests(
|
||||
self, client: FlaskClient, with_super_admin_user: UserModel
|
||||
) -> None:
|
||||
"""setup_initial_groups_for_move_tests."""
|
||||
"""Setup_initial_groups_for_move_tests."""
|
||||
groups = ["group_a", "group_b", "group_b/group_bb"]
|
||||
# setup initial groups
|
||||
for group in groups:
|
||||
@ -2417,7 +2445,7 @@ class TestProcessApi(BaseTest):
|
||||
)
|
||||
# make sure initial groups exist
|
||||
for group in groups:
|
||||
persisted = ProcessModelService().get_process_group(group)
|
||||
persisted = ProcessModelService.get_process_group(group)
|
||||
assert persisted is not None
|
||||
assert persisted.id == group
|
||||
|
||||
@ -2428,7 +2456,7 @@ class TestProcessApi(BaseTest):
|
||||
with_db_and_bpmn_file_cleanup: None,
|
||||
with_super_admin_user: UserModel,
|
||||
) -> None:
|
||||
"""test_move_model."""
|
||||
"""Test_move_model."""
|
||||
self.setup_initial_groups_for_move_tests(client, with_super_admin_user)
|
||||
|
||||
process_model_id = "test_model"
|
||||
@ -2443,7 +2471,7 @@ class TestProcessApi(BaseTest):
|
||||
process_model_display_name=process_model_id,
|
||||
process_model_description=process_model_id,
|
||||
)
|
||||
persisted = ProcessModelService().get_process_model(original_process_model_path)
|
||||
persisted = ProcessModelService.get_process_model(original_process_model_path)
|
||||
assert persisted is not None
|
||||
assert persisted.id == original_process_model_path
|
||||
|
||||
@ -2463,11 +2491,11 @@ class TestProcessApi(BaseTest):
|
||||
|
||||
# make sure the original model does not exist
|
||||
with pytest.raises(ProcessEntityNotFoundError) as e:
|
||||
ProcessModelService().get_process_model(original_process_model_path)
|
||||
ProcessModelService.get_process_model(original_process_model_path)
|
||||
assert e.value.args[0] == "process_model_not_found"
|
||||
|
||||
# make sure the new model does exist
|
||||
new_process_model = ProcessModelService().get_process_model(
|
||||
new_process_model = ProcessModelService.get_process_model(
|
||||
new_process_model_path
|
||||
)
|
||||
assert new_process_model is not None
|
||||
@ -2480,7 +2508,7 @@ class TestProcessApi(BaseTest):
|
||||
with_db_and_bpmn_file_cleanup: None,
|
||||
with_super_admin_user: UserModel,
|
||||
) -> None:
|
||||
"""test_move_group."""
|
||||
"""Test_move_group."""
|
||||
self.setup_initial_groups_for_move_tests(client, with_super_admin_user)
|
||||
|
||||
# add sub group to `group_a`
|
||||
@ -2491,7 +2519,7 @@ class TestProcessApi(BaseTest):
|
||||
client, with_super_admin_user, original_sub_path, display_name=sub_group_id
|
||||
)
|
||||
# make sure original subgroup exists
|
||||
persisted = ProcessModelService().get_process_group(original_sub_path)
|
||||
persisted = ProcessModelService.get_process_group(original_sub_path)
|
||||
assert persisted is not None
|
||||
assert persisted.id == original_sub_path
|
||||
|
||||
@ -2508,11 +2536,11 @@ class TestProcessApi(BaseTest):
|
||||
|
||||
# make sure the original subgroup does not exist
|
||||
with pytest.raises(ProcessEntityNotFoundError) as e:
|
||||
ProcessModelService().get_process_group(original_sub_path)
|
||||
ProcessModelService.get_process_group(original_sub_path)
|
||||
|
||||
assert e.value.args[0] == "process_group_not_found"
|
||||
assert e.value.args[1] == f"Process Group Id: {original_sub_path}"
|
||||
|
||||
# make sure the new subgroup does exist
|
||||
new_process_group = ProcessModelService().get_process_group(new_sub_path)
|
||||
new_process_group = ProcessModelService.get_process_group(new_sub_path)
|
||||
assert new_process_group.id == new_sub_path
|
||||
|
@ -52,7 +52,7 @@ class SecretServiceTestHelpers(BaseTest):
|
||||
process_model_description=self.test_process_model_description,
|
||||
user=user,
|
||||
)
|
||||
process_model_info = ProcessModelService().get_process_model(
|
||||
process_model_info = ProcessModelService.get_process_model(
|
||||
process_model_identifier
|
||||
)
|
||||
return process_model_info
|
||||
|
@ -0,0 +1,45 @@
|
||||
"""Test_get_localtime."""
|
||||
from flask.app import Flask
|
||||
from flask.testing import FlaskClient
|
||||
from tests.spiffworkflow_backend.helpers.base_test import BaseTest
|
||||
from tests.spiffworkflow_backend.helpers.test_data import load_test_spec
|
||||
|
||||
from spiffworkflow_backend.models.process_instance_metadata import (
|
||||
ProcessInstanceMetadataModel,
|
||||
)
|
||||
from spiffworkflow_backend.models.user import UserModel
|
||||
from spiffworkflow_backend.services.process_instance_processor import (
|
||||
ProcessInstanceProcessor,
|
||||
)
|
||||
|
||||
|
||||
class TestSaveProcessInstanceMetadata(BaseTest):
|
||||
"""TestSaveProcessInstanceMetadata."""
|
||||
|
||||
def test_can_save_process_instance_metadata(
|
||||
self,
|
||||
app: Flask,
|
||||
client: FlaskClient,
|
||||
with_db_and_bpmn_file_cleanup: None,
|
||||
with_super_admin_user: UserModel,
|
||||
) -> None:
|
||||
"""Test_can_save_process_instance_metadata."""
|
||||
initiator_user = self.find_or_create_user("initiator_user")
|
||||
self.create_process_group(
|
||||
client, with_super_admin_user, "test_group", "test_group"
|
||||
)
|
||||
process_model = load_test_spec(
|
||||
process_model_id="save_process_instance_metadata/save_process_instance_metadata",
|
||||
bpmn_file_name="save_process_instance_metadata.bpmn",
|
||||
process_model_source_directory="save_process_instance_metadata",
|
||||
)
|
||||
process_instance = self.create_process_instance_from_process_model(
|
||||
process_model=process_model, user=initiator_user
|
||||
)
|
||||
processor = ProcessInstanceProcessor(process_instance)
|
||||
processor.do_engine_steps(save=True)
|
||||
|
||||
process_instance_metadata = ProcessInstanceMetadataModel.query.filter_by(
|
||||
process_instance_id=process_instance.id
|
||||
).all()
|
||||
assert len(process_instance_metadata) == 3
|
@ -1,13 +1,38 @@
|
||||
"""Test_acceptance_test_fixtures."""
|
||||
import os
|
||||
|
||||
from flask.app import Flask
|
||||
|
||||
from spiffworkflow_backend.models.process_group import ProcessGroup
|
||||
from spiffworkflow_backend.models.process_model import ProcessModelInfo
|
||||
from spiffworkflow_backend.services.acceptance_test_fixtures import (
|
||||
load_acceptance_test_fixtures,
|
||||
)
|
||||
from spiffworkflow_backend.services.process_model_service import ProcessModelService
|
||||
|
||||
|
||||
def test_start_dates_are_one_hour_apart(app: Flask) -> None:
|
||||
"""Test_start_dates_are_one_hour_apart."""
|
||||
process_model_identifier = (
|
||||
"misc/acceptance-tests-group-one/acceptance-tests-model-1"
|
||||
)
|
||||
group_identifier = os.path.dirname(process_model_identifier)
|
||||
parent_group_identifier = os.path.dirname(group_identifier)
|
||||
if not ProcessModelService.is_group(parent_group_identifier):
|
||||
process_group = ProcessGroup(
|
||||
id=parent_group_identifier, display_name=parent_group_identifier
|
||||
)
|
||||
ProcessModelService.add_process_group(process_group)
|
||||
if not ProcessModelService.is_group(group_identifier):
|
||||
process_group = ProcessGroup(id=group_identifier, display_name=group_identifier)
|
||||
ProcessModelService.add_process_group(process_group)
|
||||
if not ProcessModelService.is_model(process_model_identifier):
|
||||
process_model = ProcessModelInfo(
|
||||
id=process_model_identifier,
|
||||
display_name=process_model_identifier,
|
||||
description="hey",
|
||||
)
|
||||
ProcessModelService.add_process_model(process_model)
|
||||
process_instances = load_acceptance_test_fixtures()
|
||||
|
||||
assert len(process_instances) > 2
|
||||
|
@ -113,7 +113,7 @@ class TestAuthorizationService(BaseTest):
|
||||
bpmn_file_location="model_with_lanes",
|
||||
)
|
||||
|
||||
process_model = ProcessModelService().get_process_model(
|
||||
process_model = ProcessModelService.get_process_model(
|
||||
process_model_id=process_model_identifier
|
||||
)
|
||||
process_instance = self.create_process_instance_from_process_model(
|
||||
|
@ -44,7 +44,7 @@ class TestMessageInstance(BaseTest):
|
||||
client, with_super_admin_user
|
||||
)
|
||||
|
||||
process_model = ProcessModelService().get_process_model(
|
||||
process_model = ProcessModelService.get_process_model(
|
||||
process_model_id=process_model_identifier
|
||||
)
|
||||
process_instance = self.create_process_instance_from_process_model(
|
||||
@ -81,7 +81,7 @@ class TestMessageInstance(BaseTest):
|
||||
client, with_super_admin_user
|
||||
)
|
||||
|
||||
process_model = ProcessModelService().get_process_model(
|
||||
process_model = ProcessModelService.get_process_model(
|
||||
process_model_id=process_model_identifier
|
||||
)
|
||||
process_instance = self.create_process_instance_from_process_model(
|
||||
@ -127,7 +127,7 @@ class TestMessageInstance(BaseTest):
|
||||
client, with_super_admin_user
|
||||
)
|
||||
|
||||
process_model = ProcessModelService().get_process_model(
|
||||
process_model = ProcessModelService.get_process_model(
|
||||
process_model_id=process_model_identifier
|
||||
)
|
||||
process_instance = self.create_process_instance_from_process_model(
|
||||
@ -174,7 +174,7 @@ class TestMessageInstance(BaseTest):
|
||||
client, with_super_admin_user
|
||||
)
|
||||
|
||||
process_model = ProcessModelService().get_process_model(
|
||||
process_model = ProcessModelService.get_process_model(
|
||||
process_model_id=process_model_identifier
|
||||
)
|
||||
process_instance = self.create_process_instance_from_process_model(
|
||||
|
@ -47,7 +47,7 @@ class TestMessageService(BaseTest):
|
||||
bpmn_file_name="message_sender.bpmn",
|
||||
)
|
||||
|
||||
process_instance_sender = ProcessInstanceService.create_process_instance(
|
||||
process_instance_sender = ProcessInstanceService.create_process_instance_from_process_model_identifier(
|
||||
process_model_sender.id,
|
||||
with_super_admin_user,
|
||||
)
|
||||
@ -154,7 +154,7 @@ class TestMessageService(BaseTest):
|
||||
|
||||
user = self.find_or_create_user()
|
||||
|
||||
process_instance_sender = ProcessInstanceService.create_process_instance(
|
||||
process_instance_sender = ProcessInstanceService.create_process_instance_from_process_model_identifier(
|
||||
process_model_sender.id,
|
||||
user,
|
||||
# process_group_identifier=process_model_sender.process_group_id,
|
||||
|
@ -9,8 +9,7 @@ def test_there_is_at_least_one_group_after_we_create_one(
|
||||
app: Flask, with_db_and_bpmn_file_cleanup: None
|
||||
) -> None:
|
||||
"""Test_there_is_at_least_one_group_after_we_create_one."""
|
||||
process_model_service = ProcessModelService()
|
||||
process_group = ProcessGroup(id="hey", display_name="sure")
|
||||
process_model_service.add_process_group(process_group)
|
||||
process_groups = ProcessModelService().get_process_groups()
|
||||
ProcessModelService.add_process_group(process_group)
|
||||
process_groups = ProcessModelService.get_process_groups()
|
||||
assert len(process_groups) > 0
|
||||
|
@ -161,6 +161,7 @@ class TestProcessInstanceProcessor(BaseTest):
|
||||
)
|
||||
processor = ProcessInstanceProcessor(process_instance)
|
||||
processor.do_engine_steps(save=True)
|
||||
processor.save()
|
||||
|
||||
assert len(process_instance.active_tasks) == 1
|
||||
active_task = process_instance.active_tasks[0]
|
||||
@ -241,3 +242,42 @@ class TestProcessInstanceProcessor(BaseTest):
|
||||
)
|
||||
|
||||
assert process_instance.status == ProcessInstanceStatus.complete.value
|
||||
|
||||
def test_does_not_recreate_active_tasks_on_multiple_saves(
|
||||
self,
|
||||
app: Flask,
|
||||
client: FlaskClient,
|
||||
with_db_and_bpmn_file_cleanup: None,
|
||||
with_super_admin_user: UserModel,
|
||||
) -> None:
|
||||
"""Test_sets_permission_correctly_on_active_task_when_using_dict."""
|
||||
self.create_process_group(
|
||||
client, with_super_admin_user, "test_group", "test_group"
|
||||
)
|
||||
initiator_user = self.find_or_create_user("initiator_user")
|
||||
finance_user_three = self.find_or_create_user("testuser3")
|
||||
assert initiator_user.principal is not None
|
||||
assert finance_user_three.principal is not None
|
||||
AuthorizationService.import_permissions_from_yaml_file()
|
||||
|
||||
finance_group = GroupModel.query.filter_by(identifier="Finance Team").first()
|
||||
assert finance_group is not None
|
||||
|
||||
process_model = load_test_spec(
|
||||
process_model_id="test_group/model_with_lanes",
|
||||
bpmn_file_name="lanes_with_owner_dict.bpmn",
|
||||
process_model_source_directory="model_with_lanes",
|
||||
)
|
||||
process_instance = self.create_process_instance_from_process_model(
|
||||
process_model=process_model, user=initiator_user
|
||||
)
|
||||
processor = ProcessInstanceProcessor(process_instance)
|
||||
processor.do_engine_steps(save=True)
|
||||
assert len(process_instance.active_tasks) == 1
|
||||
initial_active_task_id = process_instance.active_tasks[0].id
|
||||
|
||||
# save again to ensure we go attempt to process the active tasks again
|
||||
processor.save()
|
||||
|
||||
assert len(process_instance.active_tasks) == 1
|
||||
assert initial_active_task_id == process_instance.active_tasks[0].id
|
||||
|
@ -32,7 +32,7 @@ class TestProcessModelService(BaseTest):
|
||||
primary_process_id = process_model.primary_process_id
|
||||
assert primary_process_id == "Process_HelloWorld"
|
||||
|
||||
ProcessModelService().update_process_model(
|
||||
ProcessModelService.update_process_model(
|
||||
process_model, {"display_name": "new_name"}
|
||||
)
|
||||
|
||||
|
@ -188,7 +188,7 @@ class TestSpecFileService(BaseTest):
|
||||
# ,
|
||||
# process_model_source_directory="call_activity_nested",
|
||||
# )
|
||||
process_model_info = ProcessModelService().get_process_model(
|
||||
process_model_info = ProcessModelService.get_process_model(
|
||||
process_model_identifier
|
||||
)
|
||||
files = SpecFileService.get_files(process_model_info)
|
||||
|
@ -28,7 +28,7 @@ class TestVariousBpmnConstructs(BaseTest):
|
||||
"timer_intermediate_catch_event",
|
||||
)
|
||||
|
||||
process_model = ProcessModelService().get_process_model(
|
||||
process_model = ProcessModelService.get_process_model(
|
||||
process_model_id=process_model_identifier
|
||||
)
|
||||
|
||||
|
@ -19,18 +19,12 @@ describe('process-groups', () => {
|
||||
cy.url().should('include', `process-groups/${groupId}`);
|
||||
cy.contains(`Process Group: ${groupDisplayName}`);
|
||||
|
||||
cy.contains('Edit process group').click();
|
||||
cy.getBySel('edit-process-group-button').click();
|
||||
cy.get('input[name=display_name]').clear().type(newGroupDisplayName);
|
||||
cy.contains('Submit').click();
|
||||
cy.contains(`Process Group: ${newGroupDisplayName}`);
|
||||
|
||||
cy.contains('Edit process group').click();
|
||||
cy.get('input[name=display_name]').should(
|
||||
'have.value',
|
||||
newGroupDisplayName
|
||||
);
|
||||
|
||||
cy.contains('Delete').click();
|
||||
cy.getBySel('delete-process-group-button').click();
|
||||
cy.contains('Are you sure');
|
||||
cy.getBySel('delete-process-group-button-modal-confirmation-dialog')
|
||||
.find('.cds--btn--danger')
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { modifyProcessModelPath } from '../../src/helpers';
|
||||
import { modifyProcessIdentifierForPathParam } from '../../src/helpers';
|
||||
|
||||
describe('process-models', () => {
|
||||
beforeEach(() => {
|
||||
@ -16,25 +16,22 @@ describe('process-models', () => {
|
||||
const modelDisplayName = `Test Model 2 ${id}`;
|
||||
const modelId = `test-model-2-${id}`;
|
||||
const newModelDisplayName = `${modelDisplayName} edited`;
|
||||
cy.contains('Misc').click();
|
||||
cy.contains('99-Shared Resources').click();
|
||||
cy.wait(500);
|
||||
cy.contains(groupDisplayName).click();
|
||||
cy.createModel(groupId, modelId, modelDisplayName);
|
||||
cy.url().should(
|
||||
'include',
|
||||
`process-models/${modifyProcessModelPath(groupId)}:${modelId}`
|
||||
`process-models/${modifyProcessIdentifierForPathParam(
|
||||
groupId
|
||||
)}:${modelId}`
|
||||
);
|
||||
cy.contains(`Process Model: ${modelDisplayName}`);
|
||||
|
||||
cy.contains('Edit process model').click();
|
||||
cy.getBySel('edit-process-model-button').click();
|
||||
cy.get('input[name=display_name]').clear().type(newModelDisplayName);
|
||||
cy.contains('Submit').click();
|
||||
cy.contains(`Process Model: ${groupId}/${modelId}`);
|
||||
cy.contains('Submit').click();
|
||||
cy.get('input[name=display_name]').should(
|
||||
'have.value',
|
||||
newModelDisplayName
|
||||
);
|
||||
cy.contains(`Process Model: ${newModelDisplayName}`);
|
||||
|
||||
// go back to process model show by clicking on the breadcrumb
|
||||
cy.contains(modelId).click();
|
||||
@ -46,7 +43,7 @@ describe('process-models', () => {
|
||||
.click();
|
||||
cy.url().should(
|
||||
'include',
|
||||
`process-groups/${modifyProcessModelPath(groupId)}`
|
||||
`process-groups/${modifyProcessIdentifierForPathParam(groupId)}`
|
||||
);
|
||||
cy.contains(modelId).should('not.exist');
|
||||
});
|
||||
@ -64,15 +61,17 @@ describe('process-models', () => {
|
||||
const dmnFileName = `dmn_test_file_${id}`;
|
||||
const jsonFileName = `json_test_file_${id}`;
|
||||
|
||||
cy.contains('Misc').click();
|
||||
cy.contains('99-Shared Resources').click();
|
||||
cy.wait(500);
|
||||
cy.contains(groupDisplayName).click();
|
||||
cy.createModel(groupId, modelId, modelDisplayName);
|
||||
cy.contains(directParentGroupId).click();
|
||||
cy.contains(modelId).click();
|
||||
cy.contains(modelDisplayName).click();
|
||||
cy.url().should(
|
||||
'include',
|
||||
`process-models/${modifyProcessModelPath(groupId)}:${modelId}`
|
||||
`process-models/${modifyProcessIdentifierForPathParam(
|
||||
groupId
|
||||
)}:${modelId}`
|
||||
);
|
||||
cy.contains(`Process Model: ${modelDisplayName}`);
|
||||
cy.contains(`${bpmnFileName}.bpmn`).should('not.exist');
|
||||
@ -135,8 +134,12 @@ describe('process-models', () => {
|
||||
cy.getBySel('delete-process-model-button-modal-confirmation-dialog')
|
||||
.find('.cds--btn--danger')
|
||||
.click();
|
||||
cy.url().should('include', `process-groups/${modifyProcessModelPath(groupId)}`);
|
||||
cy.url().should(
|
||||
'include',
|
||||
`process-groups/${modifyProcessIdentifierForPathParam(groupId)}`
|
||||
);
|
||||
cy.contains(modelId).should('not.exist');
|
||||
cy.contains(modelDisplayName).should('not.exist');
|
||||
});
|
||||
|
||||
it('can upload and run a bpmn file', () => {
|
||||
@ -148,17 +151,19 @@ describe('process-models', () => {
|
||||
const modelDisplayName = `Test Model 2 ${id}`;
|
||||
const modelId = `test-model-2-${id}`;
|
||||
cy.contains('Add a process group');
|
||||
cy.contains('Misc').click();
|
||||
cy.contains('99-Shared Resources').click();
|
||||
cy.wait(500);
|
||||
cy.contains(groupDisplayName).click();
|
||||
cy.createModel(groupId, modelId, modelDisplayName);
|
||||
|
||||
cy.contains(`${directParentGroupId}`).click();
|
||||
cy.contains('Add a process model');
|
||||
cy.contains(modelId).click();
|
||||
cy.contains(modelDisplayName).click();
|
||||
cy.url().should(
|
||||
'include',
|
||||
`process-models/${modifyProcessModelPath(groupId)}:${modelId}`
|
||||
`process-models/${modifyProcessIdentifierForPathParam(
|
||||
groupId
|
||||
)}:${modelId}`
|
||||
);
|
||||
cy.contains(`Process Model: ${modelDisplayName}`);
|
||||
|
||||
@ -190,17 +195,19 @@ describe('process-models', () => {
|
||||
.click();
|
||||
cy.url().should(
|
||||
'include',
|
||||
`process-groups/${modifyProcessModelPath(groupId)}`
|
||||
`process-groups/${modifyProcessIdentifierForPathParam(groupId)}`
|
||||
);
|
||||
cy.contains(modelId).should('not.exist');
|
||||
cy.contains(modelDisplayName).should('not.exist');
|
||||
});
|
||||
|
||||
it('can paginate items', () => {
|
||||
cy.contains('Misc').click();
|
||||
cy.wait(500);
|
||||
cy.contains('Acceptance Tests Group One').click();
|
||||
cy.basicPaginationTest();
|
||||
});
|
||||
// process models no longer has pagination post-tiles
|
||||
// it.only('can paginate items', () => {
|
||||
// cy.contains('99-Shared Resources').click();
|
||||
// cy.wait(500);
|
||||
// cy.contains('Acceptance Tests Group One').click();
|
||||
// cy.basicPaginationTest();
|
||||
// });
|
||||
|
||||
it('can allow searching for model', () => {
|
||||
cy.getBySel('process-model-selection').click().type('model-3');
|
||||
|
@ -1,5 +1,5 @@
|
||||
import { string } from 'prop-types';
|
||||
import { modifyProcessModelPath } from '../../src/helpers';
|
||||
import { modifyProcessIdentifierForPathParam } from '../../src/helpers';
|
||||
|
||||
// ***********************************************
|
||||
// This example commands.js shows you how to
|
||||
@ -78,8 +78,7 @@ Cypress.Commands.add('createModel', (groupId, modelId, modelDisplayName) => {
|
||||
|
||||
cy.url().should(
|
||||
'include',
|
||||
`process-models/${modifyProcessModelPath(groupId)}:${modelId}`
|
||||
// `process-models/${groupId}:${modelId}`
|
||||
`process-models/${modifyProcessIdentifierForPathParam(groupId)}:${modelId}`
|
||||
);
|
||||
cy.contains(`Process Model: ${modelDisplayName}`);
|
||||
});
|
||||
@ -104,12 +103,12 @@ Cypress.Commands.add(
|
||||
'navigateToProcessModel',
|
||||
(groupDisplayName, modelDisplayName, modelIdentifier) => {
|
||||
cy.navigateToAdmin();
|
||||
cy.contains('Misc').click();
|
||||
cy.contains(`Process Group: 99-Misc`, { timeout: 10000 });
|
||||
cy.contains('99-Shared Resources').click();
|
||||
cy.contains(`Process Group: 99-Shared Resources`, { timeout: 10000 });
|
||||
cy.contains(groupDisplayName).click();
|
||||
cy.contains(`Process Group: ${groupDisplayName}`);
|
||||
// https://stackoverflow.com/q/51254946/6090676
|
||||
cy.getBySel('process-model-show-link').contains(modelIdentifier).click();
|
||||
cy.getBySel('process-model-show-link').contains(modelDisplayName).click();
|
||||
cy.contains(`Process Model: ${modelDisplayName}`);
|
||||
}
|
||||
);
|
||||
@ -133,8 +132,3 @@ Cypress.Commands.add('assertAtLeastOneItemInPaginatedResults', () => {
|
||||
Cypress.Commands.add('assertNoItemInPaginatedResults', () => {
|
||||
cy.contains(/\b0–0 of 0 items/);
|
||||
});
|
||||
|
||||
Cypress.Commands.add('modifyProcessModelPath', (path) => {
|
||||
path.replace('/', ':');
|
||||
return path;
|
||||
});
|
||||
|
4
spiffworkflow-frontend/package-lock.json
generated
4
spiffworkflow-frontend/package-lock.json
generated
@ -7980,7 +7980,7 @@
|
||||
},
|
||||
"node_modules/bpmn-js-spiffworkflow": {
|
||||
"version": "0.0.8",
|
||||
"resolved": "git+ssh://git@github.com/sartography/bpmn-js-spiffworkflow.git#e92f48da7cb4416310af71bb1699caaca87324cd",
|
||||
"resolved": "git+ssh://git@github.com/sartography/bpmn-js-spiffworkflow.git#aca23dc56e5d37aa1ed0a3cf11acb55f76a36da7",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"inherits": "^2.0.4",
|
||||
@ -37138,7 +37138,7 @@
|
||||
}
|
||||
},
|
||||
"bpmn-js-spiffworkflow": {
|
||||
"version": "git+ssh://git@github.com/sartography/bpmn-js-spiffworkflow.git#e92f48da7cb4416310af71bb1699caaca87324cd",
|
||||
"version": "git+ssh://git@github.com/sartography/bpmn-js-spiffworkflow.git#aca23dc56e5d37aa1ed0a3cf11acb55f76a36da7",
|
||||
"from": "bpmn-js-spiffworkflow@sartography/bpmn-js-spiffworkflow#main",
|
||||
"requires": {
|
||||
"inherits": "^2.0.4",
|
||||
|
@ -8,6 +8,8 @@ export default function MyCompletedInstances() {
|
||||
filtersEnabled={false}
|
||||
paginationQueryParamPrefix={paginationQueryParamPrefix}
|
||||
perPageOptions={[2, 5, 25]}
|
||||
reportIdentifier="system_report_instances_initiated_by_me"
|
||||
showReports={false}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
@ -163,6 +163,7 @@ export default function NavigationBar() {
|
||||
</Can>
|
||||
{configurationElement()}
|
||||
<HeaderMenuItem
|
||||
hidden
|
||||
href="/admin/process-instances/reports"
|
||||
isCurrentPage={isActivePage('/admin/process-instances/reports')}
|
||||
>
|
||||
|
@ -14,6 +14,7 @@ type OwnProps = {
|
||||
pagination: PaginationObject | null;
|
||||
tableToDisplay: any;
|
||||
paginationQueryParamPrefix?: string;
|
||||
paginationClassName?: string;
|
||||
};
|
||||
|
||||
export default function PaginationForTable({
|
||||
@ -23,6 +24,7 @@ export default function PaginationForTable({
|
||||
pagination,
|
||||
tableToDisplay,
|
||||
paginationQueryParamPrefix,
|
||||
paginationClassName,
|
||||
}: OwnProps) {
|
||||
const PER_PAGE_OPTIONS = [2, 10, 50, 100];
|
||||
const [searchParams, setSearchParams] = useSearchParams();
|
||||
@ -44,6 +46,7 @@ export default function PaginationForTable({
|
||||
<>
|
||||
{tableToDisplay}
|
||||
<Pagination
|
||||
className={paginationClassName}
|
||||
data-qa="pagination-options"
|
||||
backwardText="Previous page"
|
||||
forwardText="Next page"
|
||||
|
@ -3,13 +3,13 @@ import { BrowserRouter } from 'react-router-dom';
|
||||
import ProcessBreadcrumb from './ProcessBreadcrumb';
|
||||
|
||||
test('renders home link', () => {
|
||||
render(
|
||||
<BrowserRouter>
|
||||
<ProcessBreadcrumb />
|
||||
</BrowserRouter>
|
||||
);
|
||||
const homeElement = screen.getByText(/Process Groups/);
|
||||
expect(homeElement).toBeInTheDocument();
|
||||
// render(
|
||||
// <BrowserRouter>
|
||||
// <ProcessBreadcrumb />
|
||||
// </BrowserRouter>
|
||||
// );
|
||||
// const homeElement = screen.getByText(/Process Groups/);
|
||||
// expect(homeElement).toBeInTheDocument();
|
||||
});
|
||||
|
||||
test('renders hotCrumbs', () => {
|
||||
|
@ -1,123 +1,118 @@
|
||||
// @ts-ignore
|
||||
import { Breadcrumb, BreadcrumbItem } from '@carbon/react';
|
||||
import { splitProcessModelId } from '../helpers';
|
||||
import { HotCrumbItem } from '../interfaces';
|
||||
import { useEffect, useState } from 'react';
|
||||
import { modifyProcessIdentifierForPathParam } from '../helpers';
|
||||
import {
|
||||
HotCrumbItem,
|
||||
ProcessGroup,
|
||||
ProcessGroupLite,
|
||||
ProcessModel,
|
||||
} from '../interfaces';
|
||||
import HttpService from '../services/HttpService';
|
||||
|
||||
type OwnProps = {
|
||||
processModelId?: string;
|
||||
processGroupId?: string;
|
||||
linkProcessModel?: boolean;
|
||||
hotCrumbs?: HotCrumbItem[];
|
||||
};
|
||||
|
||||
const explodeCrumb = (crumb: HotCrumbItem) => {
|
||||
const url: string = crumb[1] || '';
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
const [endingUrlType, processModelId, link] = url.split(':');
|
||||
const processModelIdSegments = splitProcessModelId(processModelId);
|
||||
const paths: string[] = [];
|
||||
const lastPathItem = processModelIdSegments.pop();
|
||||
const breadcrumbItems = processModelIdSegments.map(
|
||||
(processModelIdSegment: string) => {
|
||||
paths.push(processModelIdSegment);
|
||||
const fullUrl = `/admin/process-groups/${paths.join(':')}`;
|
||||
return (
|
||||
<BreadcrumbItem key={processModelIdSegment} href={fullUrl}>
|
||||
{processModelIdSegment}
|
||||
</BreadcrumbItem>
|
||||
);
|
||||
}
|
||||
);
|
||||
if (link === 'link') {
|
||||
if (lastPathItem !== undefined) {
|
||||
paths.push(lastPathItem);
|
||||
}
|
||||
// process_model to process-models
|
||||
const lastUrl = `/admin/${endingUrlType
|
||||
.replace('_', '-')
|
||||
.replace(/s*$/, 's')}/${paths.join(':')}`;
|
||||
breadcrumbItems.push(
|
||||
<BreadcrumbItem key={lastPathItem} href={lastUrl}>
|
||||
{lastPathItem}
|
||||
</BreadcrumbItem>
|
||||
);
|
||||
} else {
|
||||
breadcrumbItems.push(
|
||||
<BreadcrumbItem isCurrentPage key={lastPathItem}>
|
||||
{lastPathItem}
|
||||
</BreadcrumbItem>
|
||||
);
|
||||
}
|
||||
return breadcrumbItems;
|
||||
};
|
||||
export default function ProcessBreadcrumb({ hotCrumbs }: OwnProps) {
|
||||
const [processEntity, setProcessEntity] = useState<
|
||||
ProcessGroup | ProcessModel | null
|
||||
>(null);
|
||||
|
||||
export default function ProcessBreadcrumb({
|
||||
processModelId,
|
||||
processGroupId,
|
||||
hotCrumbs,
|
||||
linkProcessModel = false,
|
||||
}: OwnProps) {
|
||||
let processGroupBreadcrumb = null;
|
||||
let processModelBreadcrumb = null;
|
||||
if (hotCrumbs) {
|
||||
const leadingCrumbLinks = hotCrumbs.map((crumb: any) => {
|
||||
const valueLabel = crumb[0];
|
||||
const url = crumb[1];
|
||||
if (!url) {
|
||||
return (
|
||||
<BreadcrumbItem isCurrentPage key={valueLabel}>
|
||||
{valueLabel}
|
||||
</BreadcrumbItem>
|
||||
);
|
||||
useEffect(() => {
|
||||
const explodeCrumbItemObject = (crumb: HotCrumbItem) => {
|
||||
if ('entityToExplode' in crumb) {
|
||||
const { entityToExplode, entityType } = crumb;
|
||||
if (entityType === 'process-model-id') {
|
||||
HttpService.makeCallToBackend({
|
||||
path: `/process-models/${modifyProcessIdentifierForPathParam(
|
||||
entityToExplode as string
|
||||
)}`,
|
||||
successCallback: setProcessEntity,
|
||||
});
|
||||
} else if (entityType === 'process-group-id') {
|
||||
HttpService.makeCallToBackend({
|
||||
path: `/process-groups/${modifyProcessIdentifierForPathParam(
|
||||
entityToExplode as string
|
||||
)}`,
|
||||
successCallback: setProcessEntity,
|
||||
});
|
||||
} else {
|
||||
setProcessEntity(entityToExplode as any);
|
||||
}
|
||||
}
|
||||
if (url && url.match(/^process[_-](model|group)s?:/)) {
|
||||
return explodeCrumb(crumb);
|
||||
}
|
||||
return (
|
||||
<BreadcrumbItem key={valueLabel} href={url}>
|
||||
{valueLabel}
|
||||
</BreadcrumbItem>
|
||||
);
|
||||
});
|
||||
return <Breadcrumb noTrailingSlash>{leadingCrumbLinks}</Breadcrumb>;
|
||||
}
|
||||
if (processModelId) {
|
||||
if (linkProcessModel) {
|
||||
processModelBreadcrumb = (
|
||||
<BreadcrumbItem
|
||||
href={`/admin/process-models/${processGroupId}/${processModelId}`}
|
||||
>
|
||||
{`Process Model: ${processModelId}`}
|
||||
</BreadcrumbItem>
|
||||
);
|
||||
} else {
|
||||
processModelBreadcrumb = (
|
||||
<BreadcrumbItem isCurrentPage>
|
||||
{`Process Model: ${processModelId}`}
|
||||
</BreadcrumbItem>
|
||||
);
|
||||
};
|
||||
if (hotCrumbs) {
|
||||
hotCrumbs.forEach(explodeCrumbItemObject);
|
||||
}
|
||||
processGroupBreadcrumb = (
|
||||
<BreadcrumbItem
|
||||
data-qa="process-group-breadcrumb-link"
|
||||
href={`/admin/process-groups/${processGroupId}`}
|
||||
>
|
||||
{`Process Group: ${processGroupId}`}
|
||||
</BreadcrumbItem>
|
||||
);
|
||||
} else if (processGroupId) {
|
||||
processGroupBreadcrumb = (
|
||||
<BreadcrumbItem isCurrentPage>
|
||||
{`Process Group: ${processGroupId}`}
|
||||
</BreadcrumbItem>
|
||||
);
|
||||
}
|
||||
}, [setProcessEntity, hotCrumbs]);
|
||||
|
||||
return (
|
||||
<Breadcrumb noTrailingSlash>
|
||||
<BreadcrumbItem href="/admin">Process Groups</BreadcrumbItem>
|
||||
{processGroupBreadcrumb}
|
||||
{processModelBreadcrumb}
|
||||
</Breadcrumb>
|
||||
);
|
||||
// eslint-disable-next-line sonarjs/cognitive-complexity
|
||||
const hotCrumbElement = () => {
|
||||
if (hotCrumbs) {
|
||||
const leadingCrumbLinks = hotCrumbs.map((crumb: any) => {
|
||||
if (
|
||||
'entityToExplode' in crumb &&
|
||||
processEntity &&
|
||||
processEntity.parent_groups
|
||||
) {
|
||||
const breadcrumbs = processEntity.parent_groups.map(
|
||||
(parentGroup: ProcessGroupLite) => {
|
||||
const fullUrl = `/admin/process-groups/${modifyProcessIdentifierForPathParam(
|
||||
parentGroup.id
|
||||
)}`;
|
||||
return (
|
||||
<BreadcrumbItem key={parentGroup.id} href={fullUrl}>
|
||||
{parentGroup.display_name}
|
||||
</BreadcrumbItem>
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
if (crumb.linkLastItem) {
|
||||
let apiBase = '/admin/process-groups';
|
||||
if (crumb.entityType.startsWith('process-model')) {
|
||||
apiBase = '/admin/process-models';
|
||||
}
|
||||
const fullUrl = `${apiBase}/${modifyProcessIdentifierForPathParam(
|
||||
processEntity.id
|
||||
)}`;
|
||||
breadcrumbs.push(
|
||||
<BreadcrumbItem key={processEntity.id} href={fullUrl}>
|
||||
{processEntity.display_name}
|
||||
</BreadcrumbItem>
|
||||
);
|
||||
} else {
|
||||
breadcrumbs.push(
|
||||
<BreadcrumbItem key={processEntity.id} isCurrentPage>
|
||||
{processEntity.display_name}
|
||||
</BreadcrumbItem>
|
||||
);
|
||||
}
|
||||
return breadcrumbs;
|
||||
}
|
||||
const valueLabel = crumb[0];
|
||||
const url = crumb[1];
|
||||
if (!url && valueLabel) {
|
||||
return (
|
||||
<BreadcrumbItem isCurrentPage key={valueLabel}>
|
||||
{valueLabel}
|
||||
</BreadcrumbItem>
|
||||
);
|
||||
}
|
||||
if (url && valueLabel) {
|
||||
return (
|
||||
<BreadcrumbItem key={valueLabel} href={url}>
|
||||
{valueLabel}
|
||||
</BreadcrumbItem>
|
||||
);
|
||||
}
|
||||
return null;
|
||||
});
|
||||
return <Breadcrumb noTrailingSlash>{leadingCrumbLinks}</Breadcrumb>;
|
||||
}
|
||||
return null;
|
||||
};
|
||||
|
||||
return <Breadcrumb noTrailingSlash>{hotCrumbElement()}</Breadcrumb>;
|
||||
}
|
||||
|
@ -5,7 +5,6 @@ import { Button, ButtonSet, Form, Stack, TextInput } from '@carbon/react';
|
||||
import { modifyProcessIdentifierForPathParam, slugifyString } from '../helpers';
|
||||
import HttpService from '../services/HttpService';
|
||||
import { ProcessGroup } from '../interfaces';
|
||||
import ButtonWithConfirmation from './ButtonWithConfirmation';
|
||||
|
||||
type OwnProps = {
|
||||
mode: string;
|
||||
@ -35,24 +34,10 @@ export default function ProcessGroupForm({
|
||||
}
|
||||
};
|
||||
|
||||
const navigateToProcessGroups = (_result: any) => {
|
||||
navigate(`/admin/process-groups`);
|
||||
};
|
||||
|
||||
const hasValidIdentifier = (identifierToCheck: string) => {
|
||||
return identifierToCheck.match(/^[a-z0-9][0-9a-z-]+[a-z0-9]$/);
|
||||
};
|
||||
|
||||
const deleteProcessGroup = () => {
|
||||
HttpService.makeCallToBackend({
|
||||
path: `/process-groups/${modifyProcessIdentifierForPathParam(
|
||||
processGroup.id
|
||||
)}`,
|
||||
successCallback: navigateToProcessGroups,
|
||||
httpMethod: 'DELETE',
|
||||
});
|
||||
};
|
||||
|
||||
const handleFormSubmission = (event: any) => {
|
||||
const searchParams = new URLSearchParams(document.location.search);
|
||||
const parentGroupId = searchParams.get('parentGroupId');
|
||||
@ -172,17 +157,6 @@ export default function ProcessGroupForm({
|
||||
|
||||
const formButtons = () => {
|
||||
const buttons = [<Button type="submit">Submit</Button>];
|
||||
if (mode === 'edit') {
|
||||
buttons.push(
|
||||
<ButtonWithConfirmation
|
||||
data-qa="delete-process-group-button"
|
||||
description={`Delete Process Group ${processGroup.id}?`}
|
||||
onConfirmation={deleteProcessGroup}
|
||||
buttonLabel="Delete"
|
||||
confirmButtonLabel="Delete"
|
||||
/>
|
||||
);
|
||||
}
|
||||
return <ButtonSet>{buttons}</ButtonSet>;
|
||||
};
|
||||
|
||||
|
@ -33,6 +33,7 @@ import {
|
||||
getPageInfoFromSearchParams,
|
||||
getProcessModelFullIdentifierFromSearchParams,
|
||||
modifyProcessIdentifierForPathParam,
|
||||
refreshAtInterval,
|
||||
} from '../helpers';
|
||||
|
||||
import PaginationForTable from './PaginationForTable';
|
||||
@ -47,15 +48,24 @@ import {
|
||||
PaginationObject,
|
||||
ProcessModel,
|
||||
ProcessInstanceReport,
|
||||
ProcessInstance,
|
||||
} from '../interfaces';
|
||||
import ProcessModelSearch from './ProcessModelSearch';
|
||||
import ProcessInstanceReportSearch from './ProcessInstanceReportSearch';
|
||||
|
||||
const REFRESH_INTERVAL = 5;
|
||||
const REFRESH_TIMEOUT = 600;
|
||||
|
||||
type OwnProps = {
|
||||
filtersEnabled?: boolean;
|
||||
processModelFullIdentifier?: string;
|
||||
paginationQueryParamPrefix?: string;
|
||||
perPageOptions?: number[];
|
||||
showReports?: boolean;
|
||||
reportIdentifier?: string;
|
||||
textToShowIfEmpty?: string;
|
||||
paginationClassName?: string;
|
||||
autoReload?: boolean;
|
||||
};
|
||||
|
||||
interface dateParameters {
|
||||
@ -67,6 +77,11 @@ export default function ProcessInstanceListTable({
|
||||
processModelFullIdentifier,
|
||||
paginationQueryParamPrefix,
|
||||
perPageOptions,
|
||||
showReports = true,
|
||||
reportIdentifier,
|
||||
textToShowIfEmpty,
|
||||
paginationClassName,
|
||||
autoReload = false,
|
||||
}: OwnProps) {
|
||||
const params = useParams();
|
||||
const [searchParams] = useSearchParams();
|
||||
@ -171,9 +186,14 @@ export default function ProcessInstanceListTable({
|
||||
queryParamString += `&user_filter=${userAppliedFilter}`;
|
||||
}
|
||||
|
||||
const reportIdentifier = searchParams.get('report_identifier');
|
||||
if (reportIdentifier) {
|
||||
queryParamString += `&report_identifier=${reportIdentifier}`;
|
||||
let reportIdentifierToUse: any = reportIdentifier;
|
||||
|
||||
if (!reportIdentifierToUse) {
|
||||
reportIdentifierToUse = searchParams.get('report_identifier');
|
||||
}
|
||||
|
||||
if (reportIdentifierToUse) {
|
||||
queryParamString += `&report_identifier=${reportIdentifierToUse}`;
|
||||
}
|
||||
|
||||
Object.keys(dateParametersToAlwaysFilterBy).forEach(
|
||||
@ -250,17 +270,24 @@ export default function ProcessInstanceListTable({
|
||||
|
||||
getProcessInstances();
|
||||
}
|
||||
const checkFiltersAndRun = () => {
|
||||
if (filtersEnabled) {
|
||||
// populate process model selection
|
||||
HttpService.makeCallToBackend({
|
||||
path: `/process-models?per_page=1000&recursive=true`,
|
||||
successCallback: processResultForProcessModels,
|
||||
});
|
||||
} else {
|
||||
getProcessInstances();
|
||||
}
|
||||
};
|
||||
|
||||
if (filtersEnabled) {
|
||||
// populate process model selection
|
||||
HttpService.makeCallToBackend({
|
||||
path: `/process-models?per_page=1000&recursive=true`,
|
||||
successCallback: processResultForProcessModels,
|
||||
});
|
||||
} else {
|
||||
getProcessInstances();
|
||||
checkFiltersAndRun();
|
||||
if (autoReload) {
|
||||
refreshAtInterval(REFRESH_INTERVAL, REFRESH_TIMEOUT, checkFiltersAndRun);
|
||||
}
|
||||
}, [
|
||||
autoReload,
|
||||
searchParams,
|
||||
params,
|
||||
oneMonthInSeconds,
|
||||
@ -271,6 +298,7 @@ export default function ProcessInstanceListTable({
|
||||
paginationQueryParamPrefix,
|
||||
processModelFullIdentifier,
|
||||
perPageOptions,
|
||||
reportIdentifier,
|
||||
]);
|
||||
|
||||
// This sets the filter data using the saved reports returned from the initial instance_list query.
|
||||
@ -596,10 +624,12 @@ export default function ProcessInstanceListTable({
|
||||
const buildTable = () => {
|
||||
const headerLabels: Record<string, string> = {
|
||||
id: 'Id',
|
||||
process_model_identifier: 'Process Model',
|
||||
process_model_identifier: 'Process',
|
||||
process_model_display_name: 'Process',
|
||||
start_in_seconds: 'Start Time',
|
||||
end_in_seconds: 'End Time',
|
||||
status: 'Status',
|
||||
username: 'Started By',
|
||||
spiff_step: 'SpiffWorkflow Step',
|
||||
};
|
||||
const getHeaderLabel = (header: string) => {
|
||||
@ -610,13 +640,14 @@ export default function ProcessInstanceListTable({
|
||||
return getHeaderLabel((column as any).Header);
|
||||
});
|
||||
|
||||
const formatProcessInstanceId = (row: any, id: any) => {
|
||||
const formatProcessInstanceId = (row: ProcessInstance, id: number) => {
|
||||
const modifiedProcessModelId: String =
|
||||
modifyProcessIdentifierForPathParam(row.process_model_identifier);
|
||||
return (
|
||||
<Link
|
||||
data-qa="process-instance-show-link"
|
||||
to={`/admin/process-models/${modifiedProcessModelId}/process-instances/${row.id}`}
|
||||
to={`/admin/process-models/${modifiedProcessModelId}/process-instances/${id}`}
|
||||
title={`View process instance ${id}`}
|
||||
>
|
||||
{id}
|
||||
</Link>
|
||||
@ -633,6 +664,23 @@ export default function ProcessInstanceListTable({
|
||||
</Link>
|
||||
);
|
||||
};
|
||||
|
||||
const formatProcessModelDisplayName = (
|
||||
row: ProcessInstance,
|
||||
displayName: string
|
||||
) => {
|
||||
return (
|
||||
<Link
|
||||
to={`/admin/process-models/${modifyProcessIdentifierForPathParam(
|
||||
row.process_model_identifier
|
||||
)}`}
|
||||
title={row.process_model_identifier}
|
||||
>
|
||||
{displayName}
|
||||
</Link>
|
||||
);
|
||||
};
|
||||
|
||||
const formatSecondsForDisplay = (_row: any, seconds: any) => {
|
||||
return convertSecondsToFormattedDateTime(seconds) || '-';
|
||||
};
|
||||
@ -643,6 +691,7 @@ export default function ProcessInstanceListTable({
|
||||
const columnFormatters: Record<string, any> = {
|
||||
id: formatProcessInstanceId,
|
||||
process_model_identifier: formatProcessModelIdentifier,
|
||||
process_model_display_name: formatProcessModelDisplayName,
|
||||
start_in_seconds: formatSecondsForDisplay,
|
||||
end_in_seconds: formatSecondsForDisplay,
|
||||
};
|
||||
@ -704,12 +753,15 @@ export default function ProcessInstanceListTable({
|
||||
};
|
||||
|
||||
const reportSearchComponent = () => {
|
||||
return (
|
||||
<ProcessInstanceReportSearch
|
||||
onChange={processInstanceReportDidChange}
|
||||
selectedItem={processInstanceReportSelection}
|
||||
/>
|
||||
);
|
||||
if (showReports) {
|
||||
return (
|
||||
<ProcessInstanceReportSearch
|
||||
onChange={processInstanceReportDidChange}
|
||||
selectedItem={processInstanceReportSelection}
|
||||
/>
|
||||
);
|
||||
}
|
||||
return null;
|
||||
};
|
||||
|
||||
const filterComponent = () => {
|
||||
@ -720,13 +772,13 @@ export default function ProcessInstanceListTable({
|
||||
<>
|
||||
<Grid fullWidth>
|
||||
<Column
|
||||
className="filterIcon"
|
||||
sm={{ span: 1, offset: 3 }}
|
||||
md={{ span: 1, offset: 7 }}
|
||||
lg={{ span: 1, offset: 15 }}
|
||||
>
|
||||
<Button
|
||||
data-qa="filter-section-expand-toggle"
|
||||
kind="ghost"
|
||||
renderIcon={Filter}
|
||||
iconDescription="Filter Options"
|
||||
hasIconOnly
|
||||
@ -740,7 +792,7 @@ export default function ProcessInstanceListTable({
|
||||
);
|
||||
};
|
||||
|
||||
if (pagination) {
|
||||
if (pagination && (!textToShowIfEmpty || pagination.total > 0)) {
|
||||
// eslint-disable-next-line prefer-const
|
||||
let { page, perPage } = getPageInfoFromSearchParams(
|
||||
searchParams,
|
||||
@ -763,10 +815,18 @@ export default function ProcessInstanceListTable({
|
||||
tableToDisplay={buildTable()}
|
||||
paginationQueryParamPrefix={paginationQueryParamPrefix}
|
||||
perPageOptions={perPageOptions}
|
||||
paginationClassName={paginationClassName}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
}
|
||||
if (textToShowIfEmpty) {
|
||||
return (
|
||||
<p className="no-results-message with-large-bottom-margin">
|
||||
{textToShowIfEmpty}
|
||||
</p>
|
||||
);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
@ -4,21 +4,78 @@ import {
|
||||
Button,
|
||||
// @ts-ignore
|
||||
} from '@carbon/react';
|
||||
import { ProcessModel } from '../interfaces';
|
||||
import { Can } from '@casl/react';
|
||||
import {
|
||||
PermissionsToCheck,
|
||||
ProcessModel,
|
||||
RecentProcessModel,
|
||||
} from '../interfaces';
|
||||
import HttpService from '../services/HttpService';
|
||||
import ErrorContext from '../contexts/ErrorContext';
|
||||
import { modifyProcessIdentifierForPathParam } from '../helpers';
|
||||
import { usePermissionFetcher } from '../hooks/PermissionService';
|
||||
|
||||
const storeRecentProcessModelInLocalStorage = (
|
||||
processModelForStorage: ProcessModel
|
||||
) => {
|
||||
// All values stored in localStorage are strings.
|
||||
// Grab our recentProcessModels string from localStorage.
|
||||
const stringFromLocalStorage = window.localStorage.getItem(
|
||||
'recentProcessModels'
|
||||
);
|
||||
|
||||
// adapted from https://stackoverflow.com/a/59424458/6090676
|
||||
// If that value is null (meaning that we've never saved anything to that spot in localStorage before), use an empty array as our array. Otherwise, use the value we parse out.
|
||||
let array: RecentProcessModel[] = [];
|
||||
if (stringFromLocalStorage !== null) {
|
||||
// Then parse that string into an actual value.
|
||||
array = JSON.parse(stringFromLocalStorage);
|
||||
}
|
||||
|
||||
// Here's the value we want to add
|
||||
const value = {
|
||||
processModelIdentifier: processModelForStorage.id,
|
||||
processModelDisplayName: processModelForStorage.display_name,
|
||||
};
|
||||
|
||||
// anything with a processGroupIdentifier is old and busted. leave it behind.
|
||||
array = array.filter((item) => item.processGroupIdentifier === undefined);
|
||||
|
||||
// If our parsed/empty array doesn't already have this value in it...
|
||||
const matchingItem = array.find(
|
||||
(item) => item.processModelIdentifier === value.processModelIdentifier
|
||||
);
|
||||
if (matchingItem === undefined) {
|
||||
// add the value to the beginning of the array
|
||||
array.unshift(value);
|
||||
|
||||
// Keep the array to 3 items
|
||||
if (array.length > 3) {
|
||||
array.pop();
|
||||
}
|
||||
}
|
||||
|
||||
// once the old and busted serializations are gone, we can put these two statements inside the above if statement
|
||||
|
||||
// turn the array WITH THE NEW VALUE IN IT into a string to prepare it to be stored in localStorage
|
||||
const stringRepresentingArray = JSON.stringify(array);
|
||||
|
||||
// and store it in localStorage as "recentProcessModels"
|
||||
window.localStorage.setItem('recentProcessModels', stringRepresentingArray);
|
||||
};
|
||||
|
||||
type OwnProps = {
|
||||
processModel: ProcessModel;
|
||||
onSuccessCallback: Function;
|
||||
className?: string;
|
||||
checkPermissions?: boolean;
|
||||
};
|
||||
|
||||
export default function ProcessInstanceRun({
|
||||
processModel,
|
||||
onSuccessCallback,
|
||||
className,
|
||||
checkPermissions = true,
|
||||
}: OwnProps) {
|
||||
const navigate = useNavigate();
|
||||
const setErrorMessage = (useContext as any)(ErrorContext)[1];
|
||||
@ -26,6 +83,17 @@ export default function ProcessInstanceRun({
|
||||
processModel.id
|
||||
);
|
||||
|
||||
const processInstanceActionPath = `/v1.0/process-models/${modifiedProcessModelId}/process-instances`;
|
||||
let permissionRequestData: PermissionsToCheck = {
|
||||
[processInstanceActionPath]: ['POST'],
|
||||
};
|
||||
|
||||
if (!checkPermissions) {
|
||||
permissionRequestData = {};
|
||||
}
|
||||
|
||||
const { ability } = usePermissionFetcher(permissionRequestData);
|
||||
|
||||
const onProcessInstanceRun = (processInstance: any) => {
|
||||
// FIXME: ensure that the task is actually for the current user as well
|
||||
const processInstanceId = (processInstance as any).id;
|
||||
@ -38,8 +106,9 @@ export default function ProcessInstanceRun({
|
||||
|
||||
const processModelRun = (processInstance: any) => {
|
||||
setErrorMessage(null);
|
||||
storeRecentProcessModelInLocalStorage(processModel);
|
||||
HttpService.makeCallToBackend({
|
||||
path: `/process-instances/${processInstance.id}/run`,
|
||||
path: `/process-instances/${modifiedProcessModelId}/${processInstance.id}/run`,
|
||||
successCallback: onProcessInstanceRun,
|
||||
failureCallback: setErrorMessage,
|
||||
httpMethod: 'POST',
|
||||
@ -48,19 +117,23 @@ export default function ProcessInstanceRun({
|
||||
|
||||
const processInstanceCreateAndRun = () => {
|
||||
HttpService.makeCallToBackend({
|
||||
path: `/process-models/${modifiedProcessModelId}/process-instances`,
|
||||
path: processInstanceActionPath,
|
||||
successCallback: processModelRun,
|
||||
httpMethod: 'POST',
|
||||
});
|
||||
};
|
||||
|
||||
if (checkPermissions) {
|
||||
return (
|
||||
<Can I="POST" a={processInstanceActionPath} ability={ability}>
|
||||
<Button onClick={processInstanceCreateAndRun} className={className}>
|
||||
Start
|
||||
</Button>
|
||||
</Can>
|
||||
);
|
||||
}
|
||||
return (
|
||||
<Button
|
||||
onClick={processInstanceCreateAndRun}
|
||||
variant="primary"
|
||||
className={className}
|
||||
>
|
||||
Run
|
||||
<Button onClick={processInstanceCreateAndRun} className={className}>
|
||||
Start
|
||||
</Button>
|
||||
);
|
||||
}
|
||||
|
@ -51,12 +51,15 @@ export default function ProcessModelForm({
|
||||
if (hasErrors) {
|
||||
return;
|
||||
}
|
||||
const path = `/process-models/${modifyProcessIdentifierForPathParam(
|
||||
let path = `/process-models/${modifyProcessIdentifierForPathParam(
|
||||
processGroupId || ''
|
||||
)}`;
|
||||
let httpMethod = 'POST';
|
||||
if (mode === 'edit') {
|
||||
httpMethod = 'PUT';
|
||||
path = `/process-models/${modifyProcessIdentifierForPathParam(
|
||||
processModel.id
|
||||
)}`;
|
||||
}
|
||||
const postBody = {
|
||||
display_name: processModel.display_name,
|
||||
|
@ -15,11 +15,13 @@ import ProcessInstanceRun from './ProcessInstanceRun';
|
||||
type OwnProps = {
|
||||
headerElement?: ReactElement;
|
||||
processGroup?: ProcessGroup;
|
||||
checkPermissions?: boolean;
|
||||
};
|
||||
|
||||
export default function ProcessModelListTiles({
|
||||
headerElement,
|
||||
processGroup,
|
||||
checkPermissions = true,
|
||||
}: OwnProps) {
|
||||
const [searchParams] = useSearchParams();
|
||||
const [processModels, setProcessModels] = useState<ProcessModel[] | null>(
|
||||
@ -33,9 +35,11 @@ export default function ProcessModelListTiles({
|
||||
setProcessModels(result.results);
|
||||
};
|
||||
// only allow 10 for now until we get the backend only returning certain models for user execution
|
||||
let queryParams = '?per_page=100';
|
||||
let queryParams = '?per_page=20';
|
||||
if (processGroup) {
|
||||
queryParams = `${queryParams}&process_group_identifier=${processGroup.id}`;
|
||||
} else {
|
||||
queryParams = `${queryParams}&recursive=true&filter_runnable_by_user=true`;
|
||||
}
|
||||
HttpService.makeCallToBackend({
|
||||
path: `/process-models${queryParams}`,
|
||||
@ -73,12 +77,19 @@ export default function ProcessModelListTiles({
|
||||
<Tile
|
||||
id={`process-model-tile-${row.id}`}
|
||||
className="tile-process-group"
|
||||
href={`/admin/process-models/${modifyProcessIdentifierForPathParam(
|
||||
row.id
|
||||
)}`}
|
||||
>
|
||||
<div className="tile-process-group-content-container">
|
||||
<div className="tile-title-top">{row.display_name}</div>
|
||||
<div className="tile-title-top">
|
||||
<a
|
||||
title={row.id}
|
||||
data-qa="process-model-show-link"
|
||||
href={`/admin/process-models/${modifyProcessIdentifierForPathParam(
|
||||
row.id
|
||||
)}`}
|
||||
>
|
||||
{row.display_name}
|
||||
</a>
|
||||
</div>
|
||||
<p className="tile-description">
|
||||
{truncateString(row.description || '', 100)}
|
||||
</p>
|
||||
@ -86,6 +97,7 @@ export default function ProcessModelListTiles({
|
||||
processModel={row}
|
||||
onSuccessCallback={setProcessInstance}
|
||||
className="tile-pin-bottom"
|
||||
checkPermissions={checkPermissions}
|
||||
/>
|
||||
</div>
|
||||
</Tile>
|
||||
|
@ -569,7 +569,7 @@ export default function ReactDiagramEditor({
|
||||
a={targetUris.processModelFileShowPath}
|
||||
ability={ability}
|
||||
>
|
||||
<Button onClick={downloadXmlFile}>Download xml</Button>
|
||||
<Button onClick={downloadXmlFile}>Download</Button>
|
||||
</Can>
|
||||
</>
|
||||
);
|
||||
|
@ -0,0 +1,17 @@
|
||||
// @ts-ignore
|
||||
import { TimeAgo } from '../helpers/timeago';
|
||||
import { convertSecondsToFormattedDateTime } from '../helpers';
|
||||
|
||||
type OwnProps = {
|
||||
timeInSeconds: number;
|
||||
};
|
||||
|
||||
export default function TableCellWithTimeAgoInWords({
|
||||
timeInSeconds,
|
||||
}: OwnProps) {
|
||||
return (
|
||||
<td title={convertSecondsToFormattedDateTime(timeInSeconds) || '-'}>
|
||||
{timeInSeconds ? TimeAgo.inWords(timeInSeconds) : '-'}
|
||||
</td>
|
||||
);
|
||||
}
|
@ -7,12 +7,16 @@ import {
|
||||
convertSecondsToFormattedDateTime,
|
||||
getPageInfoFromSearchParams,
|
||||
modifyProcessIdentifierForPathParam,
|
||||
refreshAtInterval,
|
||||
} from '../helpers';
|
||||
import HttpService from '../services/HttpService';
|
||||
import { PaginationObject } from '../interfaces';
|
||||
import TableCellWithTimeAgoInWords from './TableCellWithTimeAgoInWords';
|
||||
|
||||
const PER_PAGE_FOR_TASKS_ON_HOME_PAGE = 5;
|
||||
const paginationQueryParamPrefix = 'tasks_for_my_open_processes';
|
||||
const REFRESH_INTERVAL = 5;
|
||||
const REFRESH_TIMEOUT = 600;
|
||||
|
||||
export default function MyOpenProcesses() {
|
||||
const [searchParams] = useSearchParams();
|
||||
@ -20,20 +24,24 @@ export default function MyOpenProcesses() {
|
||||
const [pagination, setPagination] = useState<PaginationObject | null>(null);
|
||||
|
||||
useEffect(() => {
|
||||
const { page, perPage } = getPageInfoFromSearchParams(
|
||||
searchParams,
|
||||
PER_PAGE_FOR_TASKS_ON_HOME_PAGE,
|
||||
undefined,
|
||||
paginationQueryParamPrefix
|
||||
);
|
||||
const setTasksFromResult = (result: any) => {
|
||||
setTasks(result.results);
|
||||
setPagination(result.pagination);
|
||||
const getTasks = () => {
|
||||
const { page, perPage } = getPageInfoFromSearchParams(
|
||||
searchParams,
|
||||
PER_PAGE_FOR_TASKS_ON_HOME_PAGE,
|
||||
undefined,
|
||||
paginationQueryParamPrefix
|
||||
);
|
||||
const setTasksFromResult = (result: any) => {
|
||||
setTasks(result.results);
|
||||
setPagination(result.pagination);
|
||||
};
|
||||
HttpService.makeCallToBackend({
|
||||
path: `/tasks/for-my-open-processes?per_page=${perPage}&page=${page}`,
|
||||
successCallback: setTasksFromResult,
|
||||
});
|
||||
};
|
||||
HttpService.makeCallToBackend({
|
||||
path: `/tasks/for-my-open-processes?per_page=${perPage}&page=${page}`,
|
||||
successCallback: setTasksFromResult,
|
||||
});
|
||||
getTasks();
|
||||
refreshAtInterval(REFRESH_INTERVAL, REFRESH_TIMEOUT, getTasks);
|
||||
}, [searchParams]);
|
||||
|
||||
const buildTable = () => {
|
||||
@ -46,18 +54,20 @@ export default function MyOpenProcesses() {
|
||||
<tr key={rowToUse.id}>
|
||||
<td>
|
||||
<Link
|
||||
data-qa="process-model-show-link"
|
||||
to={`/admin/process-models/${modifiedProcessModelIdentifier}`}
|
||||
data-qa="process-instance-show-link"
|
||||
to={`/admin/process-models/${modifiedProcessModelIdentifier}/process-instances/${rowToUse.process_instance_id}`}
|
||||
title={`View process instance ${rowToUse.process_instance_id}`}
|
||||
>
|
||||
{rowToUse.process_model_display_name}
|
||||
{rowToUse.process_instance_id}
|
||||
</Link>
|
||||
</td>
|
||||
<td>
|
||||
<Link
|
||||
data-qa="process-instance-show-link"
|
||||
to={`/admin/process-models/${modifiedProcessModelIdentifier}/process-instances/${rowToUse.process_instance_id}`}
|
||||
data-qa="process-model-show-link"
|
||||
to={`/admin/process-models/${modifiedProcessModelIdentifier}`}
|
||||
title={rowToUse.process_model_identifier}
|
||||
>
|
||||
View {rowToUse.process_instance_id}
|
||||
{rowToUse.process_model_display_name}
|
||||
</Link>
|
||||
</td>
|
||||
<td
|
||||
@ -65,18 +75,15 @@ export default function MyOpenProcesses() {
|
||||
>
|
||||
{rowToUse.task_title}
|
||||
</td>
|
||||
<td>{rowToUse.process_instance_status}</td>
|
||||
<td>{rowToUse.group_identifier || '-'}</td>
|
||||
<td>
|
||||
{convertSecondsToFormattedDateTime(
|
||||
rowToUse.created_at_in_seconds
|
||||
) || '-'}
|
||||
</td>
|
||||
<td>
|
||||
{convertSecondsToFormattedDateTime(
|
||||
rowToUse.updated_at_in_seconds
|
||||
) || '-'}
|
||||
</td>
|
||||
<TableCellWithTimeAgoInWords
|
||||
timeInSeconds={rowToUse.updated_at_in_seconds}
|
||||
/>
|
||||
<td>
|
||||
<Button
|
||||
variant="primary"
|
||||
@ -94,13 +101,12 @@ export default function MyOpenProcesses() {
|
||||
<Table striped bordered>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Process Model</th>
|
||||
<th>Process Instance</th>
|
||||
<th>Task Name</th>
|
||||
<th>Process Instance Status</th>
|
||||
<th>Assigned Group</th>
|
||||
<th>Process Started</th>
|
||||
<th>Process Updated</th>
|
||||
<th>Id</th>
|
||||
<th>Process</th>
|
||||
<th>Task</th>
|
||||
<th>Waiting For</th>
|
||||
<th>Date Started</th>
|
||||
<th>Last Updated</th>
|
||||
<th>Actions</th>
|
||||
</tr>
|
||||
</thead>
|
||||
@ -111,7 +117,11 @@ export default function MyOpenProcesses() {
|
||||
|
||||
const tasksComponent = () => {
|
||||
if (pagination && pagination.total < 1) {
|
||||
return null;
|
||||
return (
|
||||
<p className="no-results-message with-large-bottom-margin">
|
||||
There are no tasks for processes you started at this time.
|
||||
</p>
|
||||
);
|
||||
}
|
||||
const { page, perPage } = getPageInfoFromSearchParams(
|
||||
searchParams,
|
||||
@ -120,22 +130,27 @@ export default function MyOpenProcesses() {
|
||||
paginationQueryParamPrefix
|
||||
);
|
||||
return (
|
||||
<>
|
||||
<h1>Tasks for my open processes</h1>
|
||||
<PaginationForTable
|
||||
page={page}
|
||||
perPage={perPage}
|
||||
perPageOptions={[2, PER_PAGE_FOR_TASKS_ON_HOME_PAGE, 25]}
|
||||
pagination={pagination}
|
||||
tableToDisplay={buildTable()}
|
||||
paginationQueryParamPrefix={paginationQueryParamPrefix}
|
||||
/>
|
||||
</>
|
||||
<PaginationForTable
|
||||
page={page}
|
||||
perPage={perPage}
|
||||
perPageOptions={[2, PER_PAGE_FOR_TASKS_ON_HOME_PAGE, 25]}
|
||||
pagination={pagination}
|
||||
tableToDisplay={buildTable()}
|
||||
paginationQueryParamPrefix={paginationQueryParamPrefix}
|
||||
paginationClassName="with-large-bottom-margin"
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
if (pagination) {
|
||||
return tasksComponent();
|
||||
}
|
||||
return null;
|
||||
return (
|
||||
<>
|
||||
<h2>My open instances</h2>
|
||||
<p className="data-table-description">
|
||||
These tasks are for processes you started which are not complete. You
|
||||
may not have an action to take at this time. See below for tasks waiting
|
||||
on you.
|
||||
</p>
|
||||
{tasksComponent()}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
@ -10,6 +10,7 @@ import {
|
||||
} from '../helpers';
|
||||
import HttpService from '../services/HttpService';
|
||||
import { PaginationObject } from '../interfaces';
|
||||
import TableCellWithTimeAgoInWords from './TableCellWithTimeAgoInWords';
|
||||
|
||||
const PER_PAGE_FOR_TASKS_ON_HOME_PAGE = 5;
|
||||
|
||||
@ -45,18 +46,20 @@ export default function TasksWaitingForMe() {
|
||||
<tr key={rowToUse.id}>
|
||||
<td>
|
||||
<Link
|
||||
data-qa="process-model-show-link"
|
||||
to={`/admin/process-models/${modifiedProcessModelIdentifier}`}
|
||||
data-qa="process-instance-show-link"
|
||||
to={`/admin/process-models/${modifiedProcessModelIdentifier}/process-instances/${rowToUse.process_instance_id}`}
|
||||
title={`View process instance ${rowToUse.process_instance_id}`}
|
||||
>
|
||||
{rowToUse.process_model_display_name}
|
||||
{rowToUse.process_instance_id}
|
||||
</Link>
|
||||
</td>
|
||||
<td>
|
||||
<Link
|
||||
data-qa="process-instance-show-link"
|
||||
to={`/admin/process-models/${modifiedProcessModelIdentifier}/process-instances/${rowToUse.process_instance_id}`}
|
||||
data-qa="process-model-show-link"
|
||||
to={`/admin/process-models/${modifiedProcessModelIdentifier}`}
|
||||
title={rowToUse.process_model_identifier}
|
||||
>
|
||||
View {rowToUse.process_instance_id}
|
||||
{rowToUse.process_model_display_name}
|
||||
</Link>
|
||||
</td>
|
||||
<td
|
||||
@ -65,18 +68,15 @@ export default function TasksWaitingForMe() {
|
||||
{rowToUse.task_title}
|
||||
</td>
|
||||
<td>{rowToUse.username}</td>
|
||||
<td>{rowToUse.process_instance_status}</td>
|
||||
<td>{rowToUse.group_identifier || '-'}</td>
|
||||
<td>
|
||||
{convertSecondsToFormattedDateTime(
|
||||
rowToUse.created_at_in_seconds
|
||||
) || '-'}
|
||||
</td>
|
||||
<td>
|
||||
{convertSecondsToFormattedDateTime(
|
||||
rowToUse.updated_at_in_seconds
|
||||
) || '-'}
|
||||
</td>
|
||||
<TableCellWithTimeAgoInWords
|
||||
timeInSeconds={rowToUse.updated_at_in_seconds}
|
||||
/>
|
||||
<td>
|
||||
<Button
|
||||
variant="primary"
|
||||
@ -94,14 +94,13 @@ export default function TasksWaitingForMe() {
|
||||
<Table striped bordered>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Process Model</th>
|
||||
<th>Process Instance</th>
|
||||
<th>Task Name</th>
|
||||
<th>Process Started By</th>
|
||||
<th>Process Instance Status</th>
|
||||
<th>Assigned Group</th>
|
||||
<th>Process Started</th>
|
||||
<th>Process Updated</th>
|
||||
<th>Id</th>
|
||||
<th>Process</th>
|
||||
<th>Task</th>
|
||||
<th>Started By</th>
|
||||
<th>Waiting For</th>
|
||||
<th>Date Started</th>
|
||||
<th>Last Updated</th>
|
||||
<th>Actions</th>
|
||||
</tr>
|
||||
</thead>
|
||||
@ -112,7 +111,11 @@ export default function TasksWaitingForMe() {
|
||||
|
||||
const tasksComponent = () => {
|
||||
if (pagination && pagination.total < 1) {
|
||||
return null;
|
||||
return (
|
||||
<p className="no-results-message with-large-bottom-margin">
|
||||
You have no task assignments at this time.
|
||||
</p>
|
||||
);
|
||||
}
|
||||
const { page, perPage } = getPageInfoFromSearchParams(
|
||||
searchParams,
|
||||
@ -121,22 +124,26 @@ export default function TasksWaitingForMe() {
|
||||
'tasks_waiting_for_me'
|
||||
);
|
||||
return (
|
||||
<>
|
||||
<h1>Tasks waiting for me</h1>
|
||||
<PaginationForTable
|
||||
page={page}
|
||||
perPage={perPage}
|
||||
perPageOptions={[2, PER_PAGE_FOR_TASKS_ON_HOME_PAGE, 25]}
|
||||
pagination={pagination}
|
||||
tableToDisplay={buildTable()}
|
||||
paginationQueryParamPrefix="tasks_waiting_for_me"
|
||||
/>
|
||||
</>
|
||||
<PaginationForTable
|
||||
page={page}
|
||||
perPage={perPage}
|
||||
perPageOptions={[2, PER_PAGE_FOR_TASKS_ON_HOME_PAGE, 25]}
|
||||
pagination={pagination}
|
||||
tableToDisplay={buildTable()}
|
||||
paginationQueryParamPrefix="tasks_waiting_for_me"
|
||||
paginationClassName="with-large-bottom-margin"
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
if (pagination) {
|
||||
return tasksComponent();
|
||||
}
|
||||
return null;
|
||||
return (
|
||||
<>
|
||||
<h2>Tasks waiting for me</h2>
|
||||
<p className="data-table-description">
|
||||
These processes are waiting on you to complete the next task. All are
|
||||
processes created by others that are now actionable by you.
|
||||
</p>
|
||||
{tasksComponent()}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
@ -7,33 +7,41 @@ import {
|
||||
convertSecondsToFormattedDateTime,
|
||||
getPageInfoFromSearchParams,
|
||||
modifyProcessIdentifierForPathParam,
|
||||
refreshAtInterval,
|
||||
} from '../helpers';
|
||||
import HttpService from '../services/HttpService';
|
||||
import { PaginationObject } from '../interfaces';
|
||||
import TableCellWithTimeAgoInWords from './TableCellWithTimeAgoInWords';
|
||||
|
||||
const PER_PAGE_FOR_TASKS_ON_HOME_PAGE = 5;
|
||||
const paginationQueryParamPrefix = 'tasks_waiting_for_my_groups';
|
||||
const REFRESH_INTERVAL = 5;
|
||||
const REFRESH_TIMEOUT = 600;
|
||||
|
||||
export default function TasksForWaitingForMyGroups() {
|
||||
export default function TasksWaitingForMyGroups() {
|
||||
const [searchParams] = useSearchParams();
|
||||
const [tasks, setTasks] = useState([]);
|
||||
const [pagination, setPagination] = useState<PaginationObject | null>(null);
|
||||
|
||||
useEffect(() => {
|
||||
const { page, perPage } = getPageInfoFromSearchParams(
|
||||
searchParams,
|
||||
PER_PAGE_FOR_TASKS_ON_HOME_PAGE,
|
||||
undefined,
|
||||
paginationQueryParamPrefix
|
||||
);
|
||||
const setTasksFromResult = (result: any) => {
|
||||
setTasks(result.results);
|
||||
setPagination(result.pagination);
|
||||
const getTasks = () => {
|
||||
const { page, perPage } = getPageInfoFromSearchParams(
|
||||
searchParams,
|
||||
PER_PAGE_FOR_TASKS_ON_HOME_PAGE,
|
||||
undefined,
|
||||
paginationQueryParamPrefix
|
||||
);
|
||||
const setTasksFromResult = (result: any) => {
|
||||
setTasks(result.results);
|
||||
setPagination(result.pagination);
|
||||
};
|
||||
HttpService.makeCallToBackend({
|
||||
path: `/tasks/for-my-groups?per_page=${perPage}&page=${page}`,
|
||||
successCallback: setTasksFromResult,
|
||||
});
|
||||
};
|
||||
HttpService.makeCallToBackend({
|
||||
path: `/tasks/for-my-groups?per_page=${perPage}&page=${page}`,
|
||||
successCallback: setTasksFromResult,
|
||||
});
|
||||
getTasks();
|
||||
refreshAtInterval(REFRESH_INTERVAL, REFRESH_TIMEOUT, getTasks);
|
||||
}, [searchParams]);
|
||||
|
||||
const buildTable = () => {
|
||||
@ -46,18 +54,20 @@ export default function TasksForWaitingForMyGroups() {
|
||||
<tr key={rowToUse.id}>
|
||||
<td>
|
||||
<Link
|
||||
data-qa="process-model-show-link"
|
||||
to={`/admin/process-models/${modifiedProcessModelIdentifier}`}
|
||||
data-qa="process-instance-show-link"
|
||||
to={`/admin/process-models/${modifiedProcessModelIdentifier}/process-instances/${rowToUse.process_instance_id}`}
|
||||
title={`View process instance ${rowToUse.process_instance_id}`}
|
||||
>
|
||||
{rowToUse.process_model_display_name}
|
||||
{rowToUse.process_instance_id}
|
||||
</Link>
|
||||
</td>
|
||||
<td>
|
||||
<Link
|
||||
data-qa="process-instance-show-link"
|
||||
to={`/admin/process-models/${modifiedProcessModelIdentifier}/process-instances/${rowToUse.process_instance_id}`}
|
||||
data-qa="process-model-show-link"
|
||||
to={`/admin/process-models/${modifiedProcessModelIdentifier}`}
|
||||
title={rowToUse.process_model_identifier}
|
||||
>
|
||||
View {rowToUse.process_instance_id}
|
||||
{rowToUse.process_model_display_name}
|
||||
</Link>
|
||||
</td>
|
||||
<td
|
||||
@ -66,18 +76,15 @@ export default function TasksForWaitingForMyGroups() {
|
||||
{rowToUse.task_title}
|
||||
</td>
|
||||
<td>{rowToUse.username}</td>
|
||||
<td>{rowToUse.process_instance_status}</td>
|
||||
<td>{rowToUse.group_identifier || '-'}</td>
|
||||
<td>
|
||||
{convertSecondsToFormattedDateTime(
|
||||
rowToUse.created_at_in_seconds
|
||||
) || '-'}
|
||||
</td>
|
||||
<td>
|
||||
{convertSecondsToFormattedDateTime(
|
||||
rowToUse.updated_at_in_seconds
|
||||
) || '-'}
|
||||
</td>
|
||||
<TableCellWithTimeAgoInWords
|
||||
timeInSeconds={rowToUse.updated_at_in_seconds}
|
||||
/>
|
||||
<td>
|
||||
<Button
|
||||
variant="primary"
|
||||
@ -95,14 +102,13 @@ export default function TasksForWaitingForMyGroups() {
|
||||
<Table striped bordered>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Process Model</th>
|
||||
<th>Process Instance</th>
|
||||
<th>Task Name</th>
|
||||
<th>Process Started By</th>
|
||||
<th>Process Instance Status</th>
|
||||
<th>Assigned Group</th>
|
||||
<th>Process Started</th>
|
||||
<th>Process Updated</th>
|
||||
<th>Id</th>
|
||||
<th>Process</th>
|
||||
<th>Task</th>
|
||||
<th>Started By</th>
|
||||
<th>Waiting For</th>
|
||||
<th>Date Started</th>
|
||||
<th>Last Updated</th>
|
||||
<th>Actions</th>
|
||||
</tr>
|
||||
</thead>
|
||||
@ -113,7 +119,11 @@ export default function TasksForWaitingForMyGroups() {
|
||||
|
||||
const tasksComponent = () => {
|
||||
if (pagination && pagination.total < 1) {
|
||||
return null;
|
||||
return (
|
||||
<p className="no-results-message">
|
||||
Your groups have no task assignments at this time.
|
||||
</p>
|
||||
);
|
||||
}
|
||||
const { page, perPage } = getPageInfoFromSearchParams(
|
||||
searchParams,
|
||||
@ -122,22 +132,25 @@ export default function TasksForWaitingForMyGroups() {
|
||||
paginationQueryParamPrefix
|
||||
);
|
||||
return (
|
||||
<>
|
||||
<h1>Tasks waiting for my groups</h1>
|
||||
<PaginationForTable
|
||||
page={page}
|
||||
perPage={perPage}
|
||||
perPageOptions={[2, PER_PAGE_FOR_TASKS_ON_HOME_PAGE, 25]}
|
||||
pagination={pagination}
|
||||
tableToDisplay={buildTable()}
|
||||
paginationQueryParamPrefix={paginationQueryParamPrefix}
|
||||
/>
|
||||
</>
|
||||
<PaginationForTable
|
||||
page={page}
|
||||
perPage={perPage}
|
||||
perPageOptions={[2, PER_PAGE_FOR_TASKS_ON_HOME_PAGE, 25]}
|
||||
pagination={pagination}
|
||||
tableToDisplay={buildTable()}
|
||||
paginationQueryParamPrefix={paginationQueryParamPrefix}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
if (pagination) {
|
||||
return tasksComponent();
|
||||
}
|
||||
return null;
|
||||
return (
|
||||
<>
|
||||
<h2>Tasks waiting for my groups</h2>
|
||||
<p className="data-table-description">
|
||||
This is a list of tasks for groups you belong to that can be completed
|
||||
by any member of the group.
|
||||
</p>
|
||||
{tasksComponent()}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
62
spiffworkflow-frontend/src/helpers/timeago.js
Normal file
62
spiffworkflow-frontend/src/helpers/timeago.js
Normal file
@ -0,0 +1,62 @@
|
||||
/* eslint-disable no-restricted-syntax */
|
||||
// https://gist.github.com/caiotarifa/30ae974f2293c761f3139dd194abd9e5
|
||||
export const TimeAgo = (function awesomeFunc() {
|
||||
const self = {};
|
||||
|
||||
// Public Methods
|
||||
self.locales = {
|
||||
prefix: '',
|
||||
sufix: 'ago',
|
||||
|
||||
seconds: 'less than a minute',
|
||||
minute: 'about a minute',
|
||||
minutes: '%d minutes',
|
||||
hour: 'about an hour',
|
||||
hours: 'about %d hours',
|
||||
day: 'a day',
|
||||
days: '%d days',
|
||||
month: 'about a month',
|
||||
months: '%d months',
|
||||
year: 'about a year',
|
||||
years: '%d years',
|
||||
};
|
||||
|
||||
self.inWords = function inWords(timeAgo) {
|
||||
const milliseconds = timeAgo * 1000;
|
||||
const seconds = Math.floor(
|
||||
(new Date() - parseInt(milliseconds, 10)) / 1000
|
||||
);
|
||||
const separator = this.locales.separator || ' ';
|
||||
let words = this.locales.prefix + separator;
|
||||
let interval = 0;
|
||||
const intervals = {
|
||||
year: seconds / 31536000,
|
||||
month: seconds / 2592000,
|
||||
day: seconds / 86400,
|
||||
hour: seconds / 3600,
|
||||
minute: seconds / 60,
|
||||
};
|
||||
|
||||
let distance = this.locales.seconds;
|
||||
|
||||
// eslint-disable-next-line guard-for-in
|
||||
for (const key in intervals) {
|
||||
interval = Math.floor(intervals[key]);
|
||||
|
||||
if (interval > 1) {
|
||||
distance = this.locales[`${key}s`];
|
||||
break;
|
||||
} else if (interval === 1) {
|
||||
distance = this.locales[key];
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
distance = distance.replace(/%d/i, interval);
|
||||
words += distance + separator + this.locales.sufix;
|
||||
|
||||
return words.trim();
|
||||
};
|
||||
|
||||
return self;
|
||||
})();
|
@ -1,7 +1,7 @@
|
||||
// We may need to update usage of Ability when we update.
|
||||
// They say they are going to rename PureAbility to Ability and remove the old class.
|
||||
import { AbilityBuilder, Ability } from '@casl/ability';
|
||||
import { useContext, useEffect } from 'react';
|
||||
import { useContext, useEffect, useState } from 'react';
|
||||
import { AbilityContext } from '../contexts/Can';
|
||||
import { PermissionCheckResponseBody, PermissionsToCheck } from '../interfaces';
|
||||
import HttpService from '../services/HttpService';
|
||||
@ -10,6 +10,7 @@ export const usePermissionFetcher = (
|
||||
permissionsToCheck: PermissionsToCheck
|
||||
) => {
|
||||
const ability = useContext(AbilityContext);
|
||||
const [permissionsLoaded, setPermissionsLoaded] = useState<boolean>(false);
|
||||
|
||||
useEffect(() => {
|
||||
const processPermissionResult = (result: PermissionCheckResponseBody) => {
|
||||
@ -34,15 +35,17 @@ export const usePermissionFetcher = (
|
||||
}
|
||||
});
|
||||
ability.update(rules);
|
||||
setPermissionsLoaded(true);
|
||||
};
|
||||
|
||||
HttpService.makeCallToBackend({
|
||||
path: `/permissions-check`,
|
||||
httpMethod: 'POST',
|
||||
successCallback: processPermissionResult,
|
||||
postBody: { requests_to_check: permissionsToCheck },
|
||||
});
|
||||
if (Object.keys(permissionsToCheck).length !== 0) {
|
||||
HttpService.makeCallToBackend({
|
||||
path: `/permissions-check`,
|
||||
httpMethod: 'POST',
|
||||
successCallback: processPermissionResult,
|
||||
postBody: { requests_to_check: permissionsToCheck },
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
return { ability };
|
||||
return { ability, permissionsLoaded };
|
||||
};
|
||||
|
@ -1,20 +1,25 @@
|
||||
import { useMemo } from 'react';
|
||||
import { useParams } from 'react-router-dom';
|
||||
|
||||
export const useUriListForPermissions = () => {
|
||||
const params = useParams();
|
||||
const targetUris = {
|
||||
authenticationListPath: `/v1.0/authentications`,
|
||||
messageInstanceListPath: '/v1.0/messages',
|
||||
processGroupListPath: '/v1.0/process-groups',
|
||||
processGroupShowPath: `/v1.0/process-groups/${params.process_group_id}`,
|
||||
processInstanceActionPath: `/v1.0/process-models/${params.process_model_id}/process-instances`,
|
||||
processInstanceListPath: '/v1.0/process-instances',
|
||||
processModelCreatePath: `/v1.0/process-models/${params.process_group_id}`,
|
||||
processModelFileCreatePath: `/v1.0/process-models/${params.process_model_id}/files`,
|
||||
processModelFileShowPath: `/v1.0/process-models/${params.process_model_id}/files/${params.file_name}`,
|
||||
processModelShowPath: `/v1.0/process-models/${params.process_model_id}`,
|
||||
secretListPath: `/v1.0/secrets`,
|
||||
};
|
||||
const targetUris = useMemo(() => {
|
||||
return {
|
||||
authenticationListPath: `/v1.0/authentications`,
|
||||
messageInstanceListPath: '/v1.0/messages',
|
||||
processGroupListPath: '/v1.0/process-groups',
|
||||
processGroupShowPath: `/v1.0/process-groups/${params.process_group_id}`,
|
||||
processInstanceActionPath: `/v1.0/process-models/${params.process_model_id}/process-instances`,
|
||||
processInstanceListPath: '/v1.0/process-instances',
|
||||
processInstanceTaskListPath: `/v1.0/process-instances/${params.process_model_id}/${params.process_instance_id}/tasks`,
|
||||
processInstanceReportListPath: '/v1.0/process-instances/reports',
|
||||
processModelCreatePath: `/v1.0/process-models/${params.process_group_id}`,
|
||||
processModelFileCreatePath: `/v1.0/process-models/${params.process_model_id}/files`,
|
||||
processModelFileShowPath: `/v1.0/process-models/${params.process_model_id}/files/${params.file_name}`,
|
||||
processModelShowPath: `/v1.0/process-models/${params.process_model_id}`,
|
||||
secretListPath: `/v1.0/secrets`,
|
||||
};
|
||||
}, [params]);
|
||||
|
||||
return { targetUris };
|
||||
};
|
||||
|
@ -5,6 +5,10 @@
|
||||
color: white;
|
||||
}
|
||||
|
||||
.megacondensed {
|
||||
padding-left: 0px;
|
||||
}
|
||||
|
||||
/* defaults to 3rem, which isn't long sufficient for "elizabeth" */
|
||||
.cds--header__action.username-header-text {
|
||||
width: 5rem;
|
||||
@ -143,6 +147,14 @@ h1.with-icons {
|
||||
margin-bottom: 1em;
|
||||
}
|
||||
|
||||
.with-top-margin {
|
||||
margin-top: 1em;
|
||||
}
|
||||
|
||||
.with-large-bottom-margin {
|
||||
margin-bottom: 3em;
|
||||
}
|
||||
|
||||
.diagram-viewer-canvas {
|
||||
border:1px solid #000000;
|
||||
height:70vh;
|
||||
@ -248,3 +260,40 @@ in on this with the react-jsonschema-form repo. This is just a patch fix to allo
|
||||
position: absolute;
|
||||
bottom: 1em;
|
||||
}
|
||||
|
||||
.cds--tabs .cds--tabs__nav-link {
|
||||
max-width: 20rem;
|
||||
}
|
||||
|
||||
.clear-left {
|
||||
clear: left;
|
||||
}
|
||||
|
||||
td.actions-cell {
|
||||
width: 1em;
|
||||
}
|
||||
|
||||
.no-results-message {
|
||||
font-style: italic;
|
||||
margin-left: 2em;
|
||||
margin-top: 1em;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.data-table-description {
|
||||
font-size: 14px;
|
||||
line-height: 18px;
|
||||
letter-spacing: 0.16px;
|
||||
color: #525252;
|
||||
margin-bottom: 1em;
|
||||
}
|
||||
|
||||
/* top and bottom margin since this is sort of the middle of three sections on the process model show page */
|
||||
.process-model-files-section {
|
||||
margin: 2em 0;
|
||||
}
|
||||
|
||||
.filterIcon {
|
||||
text-align: right;
|
||||
padding-bottom: 10px;
|
||||
}
|
||||
|
@ -46,12 +46,18 @@ export interface ProcessInstanceReport {
|
||||
display_name: string;
|
||||
}
|
||||
|
||||
export interface ProcessGroupLite {
|
||||
id: string;
|
||||
display_name: string;
|
||||
}
|
||||
|
||||
export interface ProcessModel {
|
||||
id: string;
|
||||
description: string;
|
||||
display_name: string;
|
||||
primary_file_name: string;
|
||||
files: ProcessFile[];
|
||||
parent_groups?: ProcessGroupLite[];
|
||||
}
|
||||
|
||||
export interface ProcessGroup {
|
||||
@ -60,10 +66,19 @@ export interface ProcessGroup {
|
||||
description?: string | null;
|
||||
process_models?: ProcessModel[];
|
||||
process_groups?: ProcessGroup[];
|
||||
parent_groups?: ProcessGroupLite[];
|
||||
}
|
||||
|
||||
export interface HotCrumbItemObject {
|
||||
entityToExplode: ProcessModel | ProcessGroup | string;
|
||||
entityType: string;
|
||||
linkLastItem?: boolean;
|
||||
}
|
||||
|
||||
export type HotCrumbItemArray = [displayValue: string, url?: string];
|
||||
|
||||
// tuple of display value and URL
|
||||
export type HotCrumbItem = [displayValue: string, url?: string];
|
||||
export type HotCrumbItem = HotCrumbItemArray | HotCrumbItemObject;
|
||||
|
||||
export interface ErrorForDisplay {
|
||||
message: string;
|
||||
|
@ -54,7 +54,7 @@ export default function AuthenticationList() {
|
||||
<Table striped bordered>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>ID</th>
|
||||
<th>Id</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>{rows}</tbody>
|
||||
|
@ -1,5 +1,48 @@
|
||||
import MyCompletedInstances from '../components/MyCompletedInstances';
|
||||
import ProcessInstanceListTable from '../components/ProcessInstanceListTable';
|
||||
|
||||
export default function CompletedInstances() {
|
||||
return <MyCompletedInstances />;
|
||||
return (
|
||||
<>
|
||||
<h2>My completed instances</h2>
|
||||
<p className="data-table-description">
|
||||
This is a list of instances you started that are now complete.
|
||||
</p>
|
||||
<ProcessInstanceListTable
|
||||
filtersEnabled={false}
|
||||
paginationQueryParamPrefix="my_completed_instances"
|
||||
perPageOptions={[2, 5, 25]}
|
||||
reportIdentifier="system_report_instances_initiated_by_me"
|
||||
showReports={false}
|
||||
textToShowIfEmpty="You have no completed instances at this time."
|
||||
paginationClassName="with-large-bottom-margin"
|
||||
autoReload
|
||||
/>
|
||||
<h2>Tasks completed by me</h2>
|
||||
<p className="data-table-description">
|
||||
This is a list of instances where you have completed tasks.
|
||||
</p>
|
||||
<ProcessInstanceListTable
|
||||
filtersEnabled={false}
|
||||
paginationQueryParamPrefix="my_completed_tasks"
|
||||
perPageOptions={[2, 5, 25]}
|
||||
reportIdentifier="system_report_instances_with_tasks_completed_by_me"
|
||||
showReports={false}
|
||||
textToShowIfEmpty="You have no completed tasks at this time."
|
||||
paginationClassName="with-large-bottom-margin"
|
||||
/>
|
||||
<h2>Tasks completed by my groups</h2>
|
||||
<p className="data-table-description">
|
||||
This is a list of instances with tasks that were completed by groups you
|
||||
belong to.
|
||||
</p>
|
||||
<ProcessInstanceListTable
|
||||
filtersEnabled={false}
|
||||
paginationQueryParamPrefix="group_completed_tasks"
|
||||
perPageOptions={[2, 5, 25]}
|
||||
reportIdentifier="system_report_instances_with_tasks_completed_by_my_groups"
|
||||
showReports={false}
|
||||
textToShowIfEmpty="Your group has no completed tasks at this time."
|
||||
/>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
@ -3,7 +3,8 @@ import ProcessModelListTiles from '../components/ProcessModelListTiles';
|
||||
export default function CreateNewInstance() {
|
||||
return (
|
||||
<ProcessModelListTiles
|
||||
headerElement={<h1>Process models available to you</h1>}
|
||||
headerElement={<h2>Processes I can start</h2>}
|
||||
checkPermissions={false}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
@ -1,15 +1,15 @@
|
||||
import TasksForMyOpenProcesses from '../components/TasksForMyOpenProcesses';
|
||||
import TasksWaitingForMe from '../components/TasksWaitingForMe';
|
||||
import TasksForWaitingForMyGroups from '../components/TasksWaitingForMyGroups';
|
||||
import TasksWaitingForMyGroups from '../components/TasksWaitingForMyGroups';
|
||||
|
||||
export default function GroupedTasks() {
|
||||
return (
|
||||
<>
|
||||
{/* be careful moving these around since the first two have with-large-bottom-margin in order to get some space between the three table sections. */}
|
||||
{/* i wish Stack worked to add space just between top-level elements */}
|
||||
<TasksForMyOpenProcesses />
|
||||
<br />
|
||||
<TasksWaitingForMe />
|
||||
<br />
|
||||
<TasksForWaitingForMyGroups />
|
||||
<TasksWaitingForMyGroups />
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
@ -18,12 +18,10 @@ export default function HomePageRoutes() {
|
||||
useEffect(() => {
|
||||
setErrorMessage(null);
|
||||
let newSelectedTabIndex = 0;
|
||||
if (location.pathname.match(/^\/tasks\/grouped\b/)) {
|
||||
if (location.pathname.match(/^\/tasks\/completed-instances\b/)) {
|
||||
newSelectedTabIndex = 1;
|
||||
} else if (location.pathname.match(/^\/tasks\/completed-instances\b/)) {
|
||||
newSelectedTabIndex = 2;
|
||||
} else if (location.pathname.match(/^\/tasks\/create-new-instance\b/)) {
|
||||
newSelectedTabIndex = 3;
|
||||
newSelectedTabIndex = 2;
|
||||
}
|
||||
setSelectedTabIndex(newSelectedTabIndex);
|
||||
}, [location, setErrorMessage]);
|
||||
@ -36,13 +34,13 @@ export default function HomePageRoutes() {
|
||||
<>
|
||||
<Tabs selectedIndex={selectedTabIndex}>
|
||||
<TabList aria-label="List of tabs">
|
||||
<Tab onClick={() => navigate('/tasks/my-tasks')}>My Tasks</Tab>
|
||||
<Tab onClick={() => navigate('/tasks/grouped')}>Grouped Tasks</Tab>
|
||||
{/* <Tab onClick={() => navigate('/tasks/my-tasks')}>My Tasks</Tab> */}
|
||||
<Tab onClick={() => navigate('/tasks/grouped')}>In Progress</Tab>
|
||||
<Tab onClick={() => navigate('/tasks/completed-instances')}>
|
||||
Completed Instances
|
||||
Completed
|
||||
</Tab>
|
||||
<Tab onClick={() => navigate('/tasks/create-new-instance')}>
|
||||
Create New Instance +
|
||||
Start New +
|
||||
</Tab>
|
||||
</TabList>
|
||||
</Tabs>
|
||||
@ -55,7 +53,7 @@ export default function HomePageRoutes() {
|
||||
<>
|
||||
{renderTabs()}
|
||||
<Routes>
|
||||
<Route path="/" element={<MyTasks />} />
|
||||
<Route path="/" element={<GroupedTasks />} />
|
||||
<Route path="my-tasks" element={<MyTasks />} />
|
||||
<Route path=":process_instance_id/:task_id" element={<TaskShow />} />
|
||||
<Route path="grouped" element={<GroupedTasks />} />
|
||||
|
@ -8,7 +8,6 @@ import {
|
||||
convertSecondsToFormattedDateString,
|
||||
getPageInfoFromSearchParams,
|
||||
modifyProcessIdentifierForPathParam,
|
||||
unModifyProcessIdentifierForPathParam,
|
||||
} from '../helpers';
|
||||
import HttpService from '../services/HttpService';
|
||||
|
||||
@ -102,12 +101,11 @@ export default function MessageInstanceList() {
|
||||
<ProcessBreadcrumb
|
||||
hotCrumbs={[
|
||||
['Process Groups', '/admin'],
|
||||
[
|
||||
`Process Model: ${params.process_model_id}`,
|
||||
`process_model:${unModifyProcessIdentifierForPathParam(
|
||||
searchParams.get('process_model_id') || ''
|
||||
)}:link`,
|
||||
],
|
||||
{
|
||||
entityToExplode: searchParams.get('process_model_id') || '',
|
||||
entityType: 'process-model-id',
|
||||
linkLastItem: true,
|
||||
},
|
||||
[
|
||||
`Process Instance: ${searchParams.get('process_instance_id')}`,
|
||||
`/admin/process-models/${searchParams.get(
|
||||
|
@ -9,16 +9,24 @@ import {
|
||||
refreshAtInterval,
|
||||
} from '../helpers';
|
||||
import HttpService from '../services/HttpService';
|
||||
import { PaginationObject, RecentProcessModel } from '../interfaces';
|
||||
import {
|
||||
PaginationObject,
|
||||
ProcessInstance,
|
||||
ProcessModel,
|
||||
RecentProcessModel,
|
||||
} from '../interfaces';
|
||||
import ProcessInstanceRun from '../components/ProcessInstanceRun';
|
||||
|
||||
const PER_PAGE_FOR_TASKS_ON_HOME_PAGE = 5;
|
||||
const REFRESH_INTERVAL = 10;
|
||||
const REFRESH_INTERVAL = 5;
|
||||
const REFRESH_TIMEOUT = 600;
|
||||
|
||||
export default function MyTasks() {
|
||||
const [searchParams] = useSearchParams();
|
||||
const [tasks, setTasks] = useState([]);
|
||||
const [pagination, setPagination] = useState<PaginationObject | null>(null);
|
||||
const [processInstance, setProcessInstance] =
|
||||
useState<ProcessInstance | null>(null);
|
||||
|
||||
useEffect(() => {
|
||||
const getTasks = () => {
|
||||
@ -40,6 +48,28 @@ export default function MyTasks() {
|
||||
refreshAtInterval(REFRESH_INTERVAL, REFRESH_TIMEOUT, getTasks);
|
||||
}, [searchParams]);
|
||||
|
||||
const processInstanceRunResultTag = () => {
|
||||
if (processInstance) {
|
||||
return (
|
||||
<div className="alert alert-success" role="alert">
|
||||
<p>
|
||||
Process Instance {processInstance.id} kicked off (
|
||||
<Link
|
||||
to={`/admin/process-models/${modifyProcessIdentifierForPathParam(
|
||||
processInstance.process_model_identifier
|
||||
)}/process-instances/${processInstance.id}`}
|
||||
data-qa="process-instance-show-link"
|
||||
>
|
||||
view
|
||||
</Link>
|
||||
).
|
||||
</p>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
return null;
|
||||
};
|
||||
|
||||
let recentProcessModels: RecentProcessModel[] = [];
|
||||
const recentProcessModelsString = localStorage.getItem('recentProcessModels');
|
||||
if (recentProcessModelsString !== null) {
|
||||
@ -67,7 +97,7 @@ export default function MyTasks() {
|
||||
data-qa="process-instance-show-link"
|
||||
to={`/admin/process-models/${modifiedProcessModelIdentifier}/process-instances/${rowToUse.process_instance_id}`}
|
||||
>
|
||||
View {rowToUse.process_instance_id}
|
||||
{rowToUse.process_instance_id}
|
||||
</Link>
|
||||
</td>
|
||||
<td
|
||||
@ -107,33 +137,44 @@ export default function MyTasks() {
|
||||
};
|
||||
|
||||
const buildRecentProcessModelSection = () => {
|
||||
const rows = recentProcessModels.map((row) => {
|
||||
const rowToUse = row as any;
|
||||
const rows = recentProcessModels.map((row: RecentProcessModel) => {
|
||||
const processModel: ProcessModel = {
|
||||
id: row.processModelIdentifier,
|
||||
description: '',
|
||||
display_name: '',
|
||||
primary_file_name: '',
|
||||
files: [],
|
||||
};
|
||||
const modifiedProcessModelId = modifyProcessIdentifierForPathParam(
|
||||
rowToUse.processModelIdentifier
|
||||
row.processModelIdentifier
|
||||
);
|
||||
return (
|
||||
<tr
|
||||
key={`${rowToUse.processGroupIdentifier}/${rowToUse.processModelIdentifier}`}
|
||||
>
|
||||
<tr key={`${row.processGroupIdentifier}/${row.processModelIdentifier}`}>
|
||||
<td>
|
||||
<Link
|
||||
data-qa="process-model-show-link"
|
||||
to={`/admin/process-models/${modifiedProcessModelId}`}
|
||||
>
|
||||
{rowToUse.processModelDisplayName}
|
||||
{row.processModelDisplayName}
|
||||
</Link>
|
||||
</td>
|
||||
<td className="actions-cell">
|
||||
<ProcessInstanceRun
|
||||
processModel={processModel}
|
||||
onSuccessCallback={setProcessInstance}
|
||||
/>
|
||||
</td>
|
||||
</tr>
|
||||
);
|
||||
});
|
||||
return (
|
||||
<>
|
||||
<h1>Recently viewed process models</h1>
|
||||
<h1>Recently instantiated process models</h1>
|
||||
<Table striped bordered>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Process Model</th>
|
||||
<th>Actions</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>{rows}</tbody>
|
||||
@ -175,6 +216,7 @@ export default function MyTasks() {
|
||||
}
|
||||
return (
|
||||
<>
|
||||
{processInstanceRunResultTag()}
|
||||
{tasksWaitingForMe}
|
||||
<br />
|
||||
{relevantProcessModelSection}
|
||||
|
@ -27,10 +27,11 @@ export default function ProcessGroupEdit() {
|
||||
<ProcessBreadcrumb
|
||||
hotCrumbs={[
|
||||
['Process Groups', '/admin'],
|
||||
[
|
||||
`Process Group: ${processGroup.id}:link`,
|
||||
`process_group:${processGroup.id}:link`,
|
||||
],
|
||||
{
|
||||
entityToExplode: processGroup,
|
||||
entityType: 'process-group',
|
||||
linkLastItem: true,
|
||||
},
|
||||
]}
|
||||
/>
|
||||
<h1>Edit Process Group: {(processGroup as any).id}</h1>
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user