workflows: allow templates as global action timeout (#53548) #617

Merged
fpeters merged 1 commits from wip/53548-global-timeout-as-template into main 2023-09-04 12:22:46 +02:00
3 changed files with 78 additions and 5 deletions

View File

@ -2641,6 +2641,17 @@ def test_workflows_global_actions_timeout_triggers(pub):
resp = resp.form.submit('submit').follow()
assert Workflow.get(workflow.id).global_actions[0].triggers[0].timeout == '-2'
resp = resp.click(
href='triggers/%s/' % Workflow.get(workflow.id).global_actions[0].triggers[0].id, index=0
)
resp.form['timeout'] = 'xxx'
resp = resp.form.submit('submit')
assert 'wrong format' in resp.text
resp.form['timeout'] = '{{ xxx }}'
resp = resp.form.submit('submit').follow()
assert Workflow.get(workflow.id).global_actions[0].triggers[0].timeout == '{{ xxx }}'
def test_workflows_global_actions_webservice_trigger(pub):
create_superuser(pub)

View File

@ -4497,6 +4497,46 @@ def test_global_timeouts(pub, formdef_class):
assert formdef.data_class().get(formdata1.id).get_criticality_level_object().name == 'yellow'
formdata1.store()
# template as timeout value
trigger.anchor = 'latest-arrival'
trigger.anchor_status_latest = 'wf-accepted'
trigger.timeout = '{{ form_option_days }}'
workflow.store()
# * invalid value
pub.loggederror_class.wipe()
formdata1.jump_status('accepted')
formdata1.evolution[-1].time = time.localtime(time.time() - 1 * 86400)
formdata1.store()
pub.apply_global_action_timeouts()
assert formdef.data_class().get(formdata1.id).get_criticality_level_object().name == 'green'
formdata1.store()
assert pub.loggederror_class.count() == 1
assert [x.summary for x in pub.loggederror_class.select()][
0
] == "[TIMEOUTS] ValueError: invalid literal for int() with base 10: ''"
# * ok value but too short for timeout
pub.loggederror_class.wipe()
workflow.variables_formdef = WorkflowVariablesFieldsFormDef(workflow=workflow)
workflow.variables_formdef.fields = [
StringField(id='5', label='Days', varname='days'),
]
workflow.store()
formdef.workflow_options = {'days': '2'}
formdef.store()
formdata1.store()
pub.apply_global_action_timeouts()
assert formdef.data_class().get(formdata1.id).get_criticality_level_object().name == 'green'
assert pub.loggederror_class.count() == 0
# * ok value, and timeout is triggered
formdata1.evolution[-1].time = time.localtime(time.time() - 4 * 86400)
formdata1.store()
pub.apply_global_action_timeouts()
assert formdef.data_class().get(formdata1.id).get_criticality_level_object().name == 'yellow'
assert pub.loggederror_class.count() == 0
def test_global_timeouts_latest_arrival(pub):
FormDef.wipe()

View File

@ -22,6 +22,7 @@ import glob
import itertools
import os
import random
import re
import time
import uuid
import xml.etree.ElementTree as ET
@ -48,12 +49,12 @@ from .qommon import _, ezt, get_cfg, misc, pgettext_lazy, template
from .qommon.errors import UnknownReferencedErrorMixin
from .qommon.form import (
CheckboxWidget,
ComputedExpressionWidget,
ConditionWidget,
Form,
SingleSelectWidget,
SingleSelectWidgetWithOther,
StringWidget,
ValidatedStringWidget,
WidgetList,
WidgetListOfRoles,
)
@ -1761,19 +1762,26 @@ class WorkflowGlobalActionTimeoutTrigger(WorkflowGlobalActionTrigger):
},
)
def validate_timeout(value):
if Template.is_template_string(value):
return ComputedExpressionWidget.validate_template(value)
match = re.match(r'^-?\d+$', value or '')
if not match or not match.group() == value:
raise ValueError(_('wrong format'))
form.add(
ValidatedStringWidget,
StringWidget,
'timeout',
title=_('Delay (in days)'),
value=self.timeout,
regex=r'^-?\d+$',
validation_function=validate_timeout,
required=True,
hint=_(
'''
Number of days relative to the reference date. If the
reference date is computed from an expression, a negative
delay is accepted to trigger the action before the
date.'''
date. This can be a template.'''
),
)
@ -1868,7 +1876,21 @@ class WorkflowGlobalActionTimeoutTrigger(WorkflowGlobalActionTrigger):
if not anchor_date:
return False
anchor_date = anchor_date + datetime.timedelta(days=int(self.timeout))
timeout = self.timeout
if Template.is_template_string(self.timeout):
variables = get_publisher().substitutions.get_context_variables(mode='lazy')
timeout = Template(self.timeout, autoescape=False).render(variables)
try:
int(timeout)
except ValueError as e:
# get the variables in the locals() namespace so they are
# displayed within the trace.
expression = self.timeout # noqa pylint: disable=unused-variable
# noqa pylint: disable=unused-variable
get_publisher().record_error(exception=e, context='[TIMEOUTS]', notify=True)
return False
anchor_date = anchor_date + datetime.timedelta(days=int(timeout))
if not is_aware(anchor_date):
anchor_date = make_aware(anchor_date, is_dst=True)