workflows: be explicit about jump modes (#83418)

This commit is contained in:
Frédéric Péters 2024-03-29 19:44:31 +01:00
parent db8e6bc0a5
commit 5365983716
14 changed files with 126 additions and 8 deletions

View File

@ -164,6 +164,7 @@ def test_deprecations(pub):
timeout_jump = st0.add_action('jump')
timeout_jump.timeout = '213'
timeout_jump.mode = 'timeout'
timeout_jump.condition = {'type': 'python', 'value': 'True'}
for klass in (

View File

@ -1538,6 +1538,7 @@ def test_workflows_edit_jump_previous(pub):
jump = st1.add_action('jump', id='_jump')
jump.timeout = 86400
jump.mode = 'timeout'
ac1 = workflow.add_global_action('Action', 'ac1')
@ -1584,6 +1585,7 @@ def test_workflows_edit_jump_timeout(pub):
jump = st1.add_action('jump', id='_jump')
jump.status = '1'
jump.timeout = 86400
jump.mode = 'timeout'
workflow.store()
@ -1639,6 +1641,7 @@ def test_workflows_jump_target_links(pub):
jump = st1.add_action('jump', id='_jump')
jump.timeout = 86400
jump.mode = 'timeout'
jump.status = st2.id
workflow.store()
@ -3920,6 +3923,7 @@ def test_workflows_inspect_view(pub):
jump = baz_status.add_action('jump', id='_jump')
jump.timeout = 86400
jump.mode = 'timeout'
jump.status = foo_status.id
jump.condition = {'type': 'django', 'value': '1 == 1'}

View File

@ -449,6 +449,7 @@ def test_formdef_schema(pub, access):
jump = st1.add_action('jump')
jump.status = 'st2'
jump.timeout = 100
jump.mode = 'timeout'
st2 = workflow.add_status('Status2', 'st2')
jump = st2.add_action('jump')
jump.status = 'st3'

View File

@ -59,6 +59,7 @@ def formdef(pub):
jump = new_status.add_action('jump', id='_jump')
jump.status = '2'
jump.timeout = 86400
jump.mode = 'timeout'
jump = new_status.add_action('jump', id='_jump')
jump.status = '3'
jump = middle_status1.add_action('jump', id='_jump')

View File

@ -46,6 +46,7 @@ def test_workflow_trigger(pub, local_user):
st1 = workflow.add_status('Status1', 'st1')
jump = st1.add_action('jump')
jump.trigger = 'XXX'
jump.mode = 'trigger'
jump.status = 'st2'
workflow.add_status('Status2', 'st2')
workflow.store()
@ -122,6 +123,7 @@ def test_workflow_trigger_with_data(pub, local_user):
st1 = workflow.add_status('Status1', 'st1')
jump = st1.add_action('jump')
jump.trigger = 'xx-yy'
jump.mode = 'trigger'
jump.status = 'st2'
workflow.add_status('Status2', 'st2')
workflow.store()
@ -224,6 +226,7 @@ def test_workflow_trigger_with_file_data(pub, local_user):
st1 = workflow.add_status('Status1', 'st1')
jump = st1.add_action('jump')
jump.trigger = 'XXX'
jump.mode = 'trigger'
jump.status = 'st2'
st2 = workflow.add_status('Status2', 'st2')
@ -268,6 +271,7 @@ def test_workflow_trigger_with_condition(pub, local_user):
st1 = workflow.add_status('Status1', 'st1')
jump = st1.add_action('jump')
jump.trigger = 'XXX'
jump.mode = 'trigger'
jump.condition = {'type': 'django', 'value': 'form_var_foo == "bar"'}
jump.status = 'st2'
workflow.add_status('Status2', 'st2')
@ -307,9 +311,11 @@ def test_workflow_trigger_jump_once(pub, local_user):
workflow.add_status('Status3', 'st3')
jump = st1.add_action('jump')
jump.trigger = 'XXX'
jump.mode = 'trigger'
jump.status = 'st2'
jump = st2.add_action('jump')
jump.trigger = 'XXX'
jump.mode = 'trigger'
jump.status = 'st3'
workflow.store()
@ -347,6 +353,7 @@ def test_workflow_trigger_api_access(pub, local_user):
st1 = workflow.add_status('Status1', 'st1')
jump = st1.add_action('jump')
jump.trigger = 'XXX'
jump.mode = 'trigger'
jump.status = 'st2'
workflow.add_status('Status2', 'st2')
workflow.store()
@ -400,6 +407,7 @@ def test_workflow_trigger_http_auth_access(pub, local_user):
st1 = workflow.add_status('Status1', 'st1')
jump = st1.add_action('jump')
jump.trigger = 'XXX'
jump.mode = 'trigger'
jump.status = 'st2'
workflow.add_status('Status2', 'st2')
workflow.store()

View File

@ -271,6 +271,7 @@ def test_backoffice_forms(pub):
st1 = workflow.add_status('Status1')
jump = st1.add_action('jump', id='_jump')
jump.timeout = 86400
jump.mode = 'timeout'
jump.status = 'finished'
workflow.store()
@ -384,6 +385,7 @@ def test_backoffice_listing(pub):
st1.id = 'plop'
jump = st1.add_action('jump', id='_jump')
jump.timeout = 86400
jump.mode = 'timeout'
jump.status = 'finished'
workflow.store()

View File

@ -38,6 +38,7 @@ def test_workflow_inspect_page(pub):
st1 = workflow.add_status('Status1')
jump = st1.add_action('jump', id='_jump')
jump.timeout = '=86400'
jump.mode = 'timeout'
jump.status = 'finished'
workflow.store()

View File

@ -2863,10 +2863,12 @@ def test_form_workflow_trigger(pub):
st1 = workflow.add_status('Status1', 'st1')
jump = st1.add_action('jump')
jump.trigger = 'XXX'
jump.mode = 'trigger'
jump.status = 'st2'
jump2 = st1.add_action('jump')
jump2.trigger = 'YYY'
jump2.mode = 'trigger'
jump2.status = 'st3'
jump2.set_marker_on_status = True
@ -2935,6 +2937,7 @@ def test_form_worklow_multiple_identical_status(pub):
st1.extra_css_class = 'CSS-STATUS1'
jump = st1.add_action('jump')
jump.trigger = 'XXX'
jump.mode = 'trigger'
jump.status = 'st1'
workflow.store()
@ -3341,6 +3344,7 @@ def test_form_worklow_multiple_identical_status_with_journal_error(pub):
st1 = workflow.add_status('Status1', 'st1')
jump = st1.add_action('jump')
jump.trigger = 'XXX'
jump.mode = 'trigger'
jump.status = 'st1'
workflow.store()

View File

@ -159,6 +159,7 @@ def test_trigger_jumps(pub):
st1 = workflow.add_status('Status1', 'st1')
jump = st1.add_action('jump')
jump.trigger = 'goto2'
jump.mode = 'trigger'
jump.status = 'st2'
st2 = workflow.add_status('Status2', 'st2')
workflow.store()

View File

@ -2167,6 +2167,7 @@ def test_view_performances(pub):
jump = status.add_action('jump', id='_jump%s' % j)
jump.by = []
jump.timeout = 5
jump.mode = 'timeout'
jump.status = 'st%s' % (j + 1)
workflow.store()
workflows.append(workflow)

View File

@ -478,6 +478,7 @@ def test_workflow_tests_automatic_jump_timeout(pub):
jump = new_status.add_action('jump')
jump.status = stalled_status.id
jump.timeout = 120 * 60 # 2 hours
jump.mode = 'timeout'
jump.condition = {'type': 'django', 'value': 'form_receipt_datetime|age_in_days >= 1'}
sendmail = new_status.add_action('sendmail')
@ -1267,6 +1268,7 @@ def test_workflow_tests_create_from_formdata(pub, http_requests, freezer):
jump = status_with_timeout_jump.add_action('jump')
jump.status = status_with_button.id
jump.timeout = '{{ 1 }} day'
jump.mode = 'timeout'
choice = status_with_button.add_action('choice')
choice.label = 'Accept'

View File

@ -94,6 +94,7 @@ def test_get_json_export_dict(pub):
jump = st1.add_action('jump', id='_jump')
jump.by = ['_submitter', '_receiver']
jump.timeout = 0.1
jump.mode = 'timeout'
jump.status = 'st2'
workflow.roles['_other'] = 'Other Function'
@ -122,6 +123,7 @@ def test_action_repr(pub):
jump = st1.add_action('jump', id='_jump')
jump.by = ['_submitter', '_receiver']
jump.timeout = 0.1
jump.mode = 'timeout'
jump.status = 'st2'
action = workflow.add_global_action('Timeout')

View File

@ -46,6 +46,28 @@ def rewind(formdata, seconds):
formdata.evolution[-1].time = formdata.evolution[-1].time - datetime.timedelta(seconds=seconds)
def test_jump_migrate_mode(pub):
item = JumpWorkflowStatusItem()
item.migrate()
assert item.mode == 'immediate'
item = JumpWorkflowStatusItem()
item.trigger = 'plop'
item.migrate()
assert item.mode == 'trigger'
item = JumpWorkflowStatusItem()
item.timeout = '12'
item.migrate()
assert item.mode == 'timeout'
item = JumpWorkflowStatusItem()
item.trigger = 'plop'
item.timeout = '12'
item.migrate()
assert item.mode == 'trigger'
def test_jump_nothing(pub):
FormDef.wipe()
formdef = FormDef()
@ -246,8 +268,8 @@ def test_timeout(pub):
jump = st1.add_action('jump', id='_jump')
jump.by = ['_submitter', '_receiver']
jump.timeout = 30 * 60 # 30 minutes
jump.mode = 'timeout'
jump.status = 'st2'
workflow.store()
formdef = FormDef()
@ -311,6 +333,7 @@ def test_timeout_with_humantime_template(pub):
jump = st1.add_action('jump', id='_jump')
jump.by = ['_submitter', '_receiver']
jump.timeout = '{{ 30 }} minutes'
jump.mode = 'timeout'
jump.status = 'st2'
workflow.store()
@ -385,6 +408,7 @@ def test_legacy_timeout(pub):
jump = st1.add_action('timeout', id='_jump')
jump.timeout = 30 * 60 # 30 minutes
jump.mode = 'timeout'
jump.status = 'st2'
workflow.store()
@ -415,6 +439,7 @@ def test_timeout_then_remove(pub):
jump = st1.add_action('jump', id='_jump')
jump.by = ['_submitter', '_receiver']
jump.timeout = 30 * 60 # 30 minutes
jump.mode = 'timeout'
jump.status = 'st2'
st2.add_action('remove')
@ -453,6 +478,7 @@ def test_timeout_with_mark(pub):
jump = st1.add_action('jump', id='_jump')
jump.by = ['_submitter', '_receiver']
jump.timeout = 30 * 60 # 30 minutes
jump.mode = 'timeout'
jump.status = 'st2'
jump.set_marker_on_status = True
@ -484,6 +510,7 @@ def test_timeout_on_anonymised(pub):
jump = st1.add_action('timeout', id='_jump')
jump.timeout = 30 * 60 # 30 minutes
jump.mode = 'timeout'
jump.status = 'st2'
workflow.store()
@ -518,6 +545,7 @@ def test_jump_missing_previous_mark(pub):
jump.by = ['_submitter', '_receiver']
jump.status = '_previous'
jump.timeout = 30 * 60 # 30 minutes
jump.mode = 'timeout'
workflow.store()
@ -575,6 +603,7 @@ def test_timeout_tracing(pub, admin_user):
jump = st1.add_action('timeout', id='_jump')
jump.timeout = 30 * 60 # 30 minutes
jump.mode = 'timeout'
jump.status = 'st2'
add_message = st2.add_action('register-comment')
@ -616,6 +645,7 @@ def test_jump_self_timeout(pub):
jump = st1.add_action('jump')
jump.timeout = 30 * 60 # 30 minutes
jump.mode = 'timeout'
jump.status = 'st1'
workflow.store()
@ -645,6 +675,7 @@ def test_timeout_cron_debug_log(pub):
jump = st1.add_action('jump', id='_jump')
jump.by = ['_submitter', '_receiver']
jump.timeout = 30 * 60 # 30 minutes
jump.mode = 'timeout'
jump.status = 'st2'
workflow.store()

View File

@ -38,7 +38,13 @@ from wcs.workflows import (
from ..qommon import _, errors, force_str, misc
from ..qommon.cron import CronJob
from ..qommon.form import ComputedExpressionWidget, SingleSelectWidget, StringWidget, WidgetList
from ..qommon.form import (
ComputedExpressionWidget,
RadiobuttonsWidget,
SingleSelectWidget,
StringWidget,
WidgetList,
)
from ..qommon.humantime import humanduration2seconds, seconds2humanduration, timewords
from ..qommon.publisher import get_publisher_class
from ..qommon.template import Template
@ -94,9 +100,7 @@ class TriggerDirectory(Directory):
for item in self.wfstatus.items:
if not isinstance(item, JumpWorkflowStatusItem):
continue
if not hasattr(item, 'trigger'):
continue
if component == item.trigger:
if item.mode == 'trigger' and item.trigger == component:
if not item.get_target_status():
raise errors.PublishError('broken jump / missing target')
if not get_request().get_method() == 'POST':
@ -152,6 +156,7 @@ class JumpWorkflowStatusItem(WorkflowStatusJumpItem):
key = 'jump'
by = []
mode = None
condition = None
trigger = None
timeout = None
@ -160,6 +165,18 @@ class JumpWorkflowStatusItem(WorkflowStatusJumpItem):
directory_name = 'jump'
directory_class = JumpDirectory
def migrate(self):
changed = super().migrate()
if not self.mode: # 2024-03-29
if self.trigger:
self.mode = 'trigger'
elif self.timeout:
self.mode = 'timeout'
else:
self.mode = 'immediate'
changed = True
return changed
def timeout_init_with_xml(self, elem, include_id=False, snapshot=False):
if elem is None or elem.text is None:
self.timeout = None
@ -217,12 +234,38 @@ class JumpWorkflowStatusItem(WorkflowStatusJumpItem):
def get_parameters(self):
if hasattr(self, 'parent') and isinstance(self.parent, WorkflowGlobalAction):
return ('status', 'condition', 'set_marker_on_status')
return ('status', 'condition', 'trigger', 'by', 'timeout', 'set_marker_on_status')
return ('status', 'condition', 'mode', 'trigger', 'by', 'timeout', 'set_marker_on_status')
def get_inspect_parameters(self):
parameters = list(self.get_parameters())
if self.mode != 'trigger' and 'trigger' in parameters:
parameters.remove('trigger')
if self.mode != 'trigger' and 'by' in parameters:
parameters.remove('by')
if self.mode != 'timeout' and 'timeout' in parameters:
parameters.remove('timeout')
return parameters
def add_parameters_widgets(self, form, parameters, prefix='', formdef=None, **kwargs):
super().add_parameters_widgets(form, parameters, prefix, formdef, **kwargs)
if 'condition' in parameters:
form.get_widget('%scondition' % prefix).advanced = False
form.get_widget('%scondition' % prefix).tab = ('general', _('General'))
if 'mode' in parameters:
form.add(
RadiobuttonsWidget,
'%smode' % prefix,
title=_('Execution mode'),
options=[
('immediate', _('Immediate'), 'immediate'),
('timeout', _('After timeout delay'), 'timeout'),
('trigger', _('After call to webservice trigger'), 'trigger'),
],
value=self.mode if self.mode else 'immediate',
default_value='immediate',
attrs={'data-dynamic-display-parent': 'true'},
extra_css_class='widget-inline-radio',
)
if 'trigger' in parameters:
form.add(
StringWidget,
@ -234,6 +277,10 @@ class JumpWorkflowStatusItem(WorkflowStatusJumpItem):
),
value=self.trigger,
size=40,
attrs={
'data-dynamic-display-child-of': '%smode' % prefix,
'data-dynamic-display-value': 'trigger',
},
)
if 'by' in parameters:
if get_publisher().has_site_option('workflow-functions-only'):
@ -251,6 +298,10 @@ class JumpWorkflowStatusItem(WorkflowStatusJumpItem):
'render_br': False,
'options': [(None, '---', None)] + self.get_list_of_roles(include_logged_in_users=False),
},
attrs={
'data-dynamic-display-child-of': '%smode' % prefix,
'data-dynamic-display-value': 'trigger',
},
)
if 'timeout' in parameters:
_hint = htmltext(
@ -267,6 +318,10 @@ class JumpWorkflowStatusItem(WorkflowStatusJumpItem):
title=_('Timeout'),
value=self.timeout,
hint=_hint,
attrs={
'data-dynamic-display-child-of': '%smode' % prefix,
'data-dynamic-display-value': 'timeout',
},
)
else:
form.add(
@ -275,6 +330,10 @@ class JumpWorkflowStatusItem(WorkflowStatusJumpItem):
title=_('Timeout'),
value=seconds2humanduration(self.timeout),
hint=_hint,
attrs={
'data-dynamic-display-child-of': '%smode' % prefix,
'data-dynamic-display-value': 'timeout',
},
)
def timeout_parse(self, value):
@ -302,7 +361,7 @@ class JumpWorkflowStatusItem(WorkflowStatusJumpItem):
if not result:
return False
if self.timeout:
if self.mode == 'timeout' and self.timeout:
timeout_str = self.compute(self.timeout)
try:
timeout_seconds = float(timeout_str)
@ -327,7 +386,7 @@ class JumpWorkflowStatusItem(WorkflowStatusJumpItem):
if diff < timeout_seconds:
return False
if self.trigger:
if self.mode == 'trigger' and self.trigger:
if trigger is None or trigger != self.trigger:
return False