workflows: add possibility to trigger global actions on finalized data (#33643)

This commit is contained in:
Frédéric Péters 2019-06-30 15:53:31 +02:00
parent cfb875a8a8
commit 234cd7999e
2 changed files with 87 additions and 8 deletions

View File

@ -3027,7 +3027,8 @@ def test_export_to_model_django_template(pub):
new_content = zipfile.ZipFile(open(formdata.evolution[0].parts[2].filename)).read('content.xml')
assert '>A &lt;&gt; name<' in new_content
def test_global_timeouts(pub):
def test_global_timeouts(two_pubs):
pub = two_pubs
FormDef.wipe()
Workflow.wipe()
@ -3133,8 +3134,43 @@ def test_global_timeouts(pub):
assert formdef.data_class().get(formdata1.id).get_criticality_level_object().name == 'yellow'
formdata1.store()
# check trigger is not run on finalized formdata
formdata1.jump_status('finished')
formdata1.evolution[-1].time = time.localtime(time.time()-4*86400)
formdata1.store()
trigger.anchor = 'creation'
workflow.store()
pub.apply_global_action_timeouts()
assert formdef.data_class().get(formdata1.id).get_criticality_level_object().name == 'green'
formdata1.store()
# check trigger is run on finalized formdata when anchor status is an
# endpoint
formdata1.jump_status('finished')
formdata1.evolution[-1].last_jump_datetime = None
formdata1.evolution[-1].time = time.localtime(time.time()-4*86400)
formdata1.store()
trigger.anchor = 'latest-arrival'
trigger.anchor_status_latest = 'wf-finished'
workflow.store()
pub.apply_global_action_timeouts()
assert formdef.data_class().get(formdata1.id).get_criticality_level_object().name == 'yellow'
formdata1.store()
# check "finalized" anchor
trigger.anchor = 'finalized'
workflow.store()
pub.apply_global_action_timeouts()
assert formdef.data_class().get(formdata1.id).get_criticality_level_object().name == 'yellow'
formdata1.store()
# use python expression as anchor
# timestamp
formdata1.jump_status('new')
formdata1.evolution[-1].time = time.localtime(time.time()-4*86400)
formdata1.evolution[-1].last_jump_datetime = None
formdata1.store()
trigger.anchor = 'python'
trigger.anchor_expression = repr(time.time())
workflow.store()

View File

@ -31,7 +31,7 @@ from quixote import get_request, get_response, redirect
from qommon import _
from qommon.misc import C_, get_as_datetime, file_digest, get_foreground_colour
from qommon.storage import StorableObject, atomic_write
from qommon.storage import StorableObject, atomic_write, NotEqual, Contains, Null
from qommon.form import *
from qommon.humantime import seconds2humanduration
from qommon import emails, get_cfg, get_logger
@ -384,6 +384,8 @@ class Workflow(StorableObject):
return status
def get_status(self, id):
if id.startswith('wf-'):
id = id[3:]
for status in self.possible_status:
if status.id == id:
return status
@ -1010,6 +1012,7 @@ class WorkflowGlobalActionTimeoutTrigger(WorkflowGlobalActionTrigger):
('creation', _('Creation')),
('1st-arrival', _('First arrival in status')),
('latest-arrival', _('Latest arrival in status')),
('finalized', _('Arrival in final status')),
('python', _('Python expression')),
])
@ -1033,6 +1036,7 @@ class WorkflowGlobalActionTimeoutTrigger(WorkflowGlobalActionTrigger):
form.add(StringWidget, 'anchor_expression', title=_('Expression'), size=80,
value=self.anchor_expression,
hint=_('This will only apply to open forms.'),
attrs={'data-dynamic-display-child-of': 'anchor',
'data-dynamic-display-value': _('Python expression')})
possible_status = [(None, _('Current Status'), None)]
@ -1062,7 +1066,13 @@ class WorkflowGlobalActionTimeoutTrigger(WorkflowGlobalActionTrigger):
return form
def must_trigger(self, formdata):
def must_trigger(self, formdata, endpoint_status_ids):
if formdata.status in endpoint_status_ids:
if not ((self.anchor == '1st-arrival' and self.anchor_status_first in endpoint_status_ids) or (
self.anchor == 'latest-arrival' and self.anchor_status_latest in endpoint_status_ids) or (
self.anchor == 'finalized')):
# don't trigger on finalized formdata (unless explicit anchor point)
return False
anchor_date = None
if self.anchor == 'creation':
anchor_date = formdata.receipt_time
@ -1078,6 +1088,15 @@ class WorkflowGlobalActionTimeoutTrigger(WorkflowGlobalActionTrigger):
if evolution.status == anchor_status:
anchor_date = evolution.last_jump_datetime or evolution.time
break
elif self.anchor == 'finalized':
if formdata.status in endpoint_status_ids:
for evolution in reversed(formdata.evolution):
if not evolution.status:
continue
if evolution.status in endpoint_status_ids:
anchor_date = evolution.time
else:
break
elif self.anchor == 'python':
variables = get_publisher().substitutions.get_context_variables()
try:
@ -1130,16 +1149,40 @@ class WorkflowGlobalActionTimeoutTrigger(WorkflowGlobalActionTrigger):
if not triggers:
return
formdefs = [x for x in FormDef.select() if x.workflow_id == workflow.id]
not_endpoint_status = workflow.get_not_endpoint_status()
not_endpoint_status_ids = ['wf-%s' % x.id for x in not_endpoint_status]
endpoint_status = workflow.get_endpoint_status()
endpoint_status_ids = ['wf-%s' % x.id for x in endpoint_status]
# check if triggers are defined relative to terminal status
run_on_finalized = False
for action, trigger in triggers:
if trigger.anchor == 'finalized':
run_on_finalized = True
elif (trigger.anchor == 'creation' and
workflow.possible_status and
workflow.possible_status[0] in endpoint_status):
run_on_finalized = True
elif (trigger.anchor == '1st-arrival' and
trigger.anchor_status_first and
workflow.get_status(trigger.anchor_status_first) in endpoint_status):
run_on_finalized = True
elif (trigger.anchor == 'latest-arrival' and
trigger.anchor_status_latest and
workflow.get_status(trigger.anchor_status_latest) in endpoint_status):
run_on_finalized = True
criterias = [NotEqual('status', 'draft'), Null('anonymised')]
if not run_on_finalized:
# limit to formdata that are not finalized
criterias.append(Contains('status', not_endpoint_status_ids))
formdefs = [x for x in FormDef.select() if x.workflow_id == workflow.id]
for formdef in formdefs:
open_formdata_ids = []
data_class = formdef.data_class()
for status in not_endpoint_status_ids:
open_formdata_ids.extend(data_class.get_ids_with_indexed_value('status', status))
for formdata in data_class.get_ids(open_formdata_ids, ignore_errors=True):
for formdata in data_class.select(criterias, iterator=True):
get_publisher().substitutions.reset()
get_publisher().substitutions.feed(get_publisher())
get_publisher().substitutions.feed(formdef)
@ -1154,7 +1197,7 @@ class WorkflowGlobalActionTimeoutTrigger(WorkflowGlobalActionTrigger):
for action, trigger in triggers:
if trigger.id in seen_triggers:
continue # already triggered
if trigger.must_trigger(formdata):
if trigger.must_trigger(formdata, endpoint_status_ids):
if not formdata.evolution:
continue
formdata.evolution[-1].add_part(