diff --git a/tests/backoffice_pages/test_all.py b/tests/backoffice_pages/test_all.py
index e16494a8c..71aa46eab 100644
--- a/tests/backoffice_pages/test_all.py
+++ b/tests/backoffice_pages/test_all.py
@@ -2373,8 +2373,8 @@ def test_backoffice_wscall_attachment(http_requests, pub):
assert resp.text == ''
formdata = formdef.data_class().get(number31.id)
- assert formdata.evolution[-1].parts[0].orig_filename == 'xxx.xml'
- assert formdata.evolution[-1].parts[0].content_type == 'text/xml'
+ assert formdata.evolution[-1].parts[1].orig_filename == 'xxx.xml'
+ assert formdata.evolution[-1].parts[1].content_type == 'text/xml'
assert formdata.get_substitution_variables()['attachments'].xxx.filename == 'xxx.xml'
resp = app.get(formdata.get_substitution_variables()['attachments'].xxx.url)
resp = resp.follow()
@@ -6104,3 +6104,90 @@ def test_status_visibility(pub):
wf.store()
resp = app.get(formdata.get_backoffice_url())
assert resp.pyquery('.status').text() == 'st1 st2 st3'
+
+
+def test_workflow_track_jumps(pub):
+ create_user(pub)
+ create_environment(pub)
+
+ wf = Workflow(name='blah')
+ st1 = wf.add_status('One')
+ st1.id = 'one'
+ st2 = wf.add_status('Two')
+ st2.id = 'two'
+
+ st3 = wf.add_status('Three')
+ st3.id = 'three'
+ st4 = wf.add_status('Four')
+ st4.id = 'four'
+ st5 = wf.add_status('Five')
+ st5.id = 'five'
+
+ commentable = st1.add_action('commentable', id='_commentable')
+ commentable.by = ['_submitter', '_receiver']
+ commentable.button_label = 'CLICK ME!'
+
+ jump = st1.add_action('jumponsubmit', id='_jump')
+ jump.status = st2.id
+ jump.identifier = 'to_two'
+
+ to_three = st2.add_action('choice', id='_tothree')
+ to_three.label = 'ToThree'
+ to_three.by = ['_receiver']
+ to_three.status = 'three'
+ to_three.identifier = 'to_three'
+
+ to_four = st3.add_action('choice', id='_tofour')
+ to_four.label = 'ToFour'
+ to_four.by = ['_receiver']
+ to_four.status = 'four'
+ to_four.identifier = 'to_four'
+
+ to_five = st4.add_action('jump', id='_jump')
+ to_five.status = 'five'
+ to_five.identifier = 'to_five'
+
+ wf.store()
+
+ formdef = FormDef.get_by_urlname('form-title')
+ formdef.data_class().wipe()
+ formdef.workflow = wf
+ formdef.store()
+
+ formdata = formdef.data_class()()
+ formdata.data = {}
+ formdata.just_created()
+ formdata.store()
+
+ app = login(get_app(pub))
+ resp = app.get('/backoffice/management/form-title/%s/' % formdata.id)
+ resp.form['comment'] = 'HELLO WORLD'
+ resp = resp.form.submit('button_commentable')
+ resp = resp.follow()
+
+ formdata.refresh_from_storage()
+ assert formdata.status == 'wf-two'
+ substitution_variables = formdata.get_substitution_variables()
+ assert substitution_variables['form_jumps'] == ['to_two']
+ assert substitution_variables['form_latest_jump'] == 'to_two'
+
+ resp = resp.form.submit('button_tothree')
+ resp = resp.follow()
+ formdata.refresh_from_storage()
+ assert formdata.status == 'wf-three'
+ substitution_variables = formdata.get_substitution_variables()
+ assert substitution_variables['form_jumps'] == ['to_two', 'to_three']
+ assert substitution_variables['form_latest_jump'] == 'to_three'
+
+ resp = resp.form.submit('button_tofour')
+ resp = resp.follow()
+ formdata.refresh_from_storage()
+ assert formdata.status == 'wf-five'
+ substitution_variables = formdata.get_substitution_variables()
+ assert substitution_variables['form_jumps'] == [
+ 'to_two',
+ 'to_three',
+ 'to_four',
+ 'to_five',
+ ]
+ assert substitution_variables['form_latest_jump'] == 'to_five'
diff --git a/tests/form_pages/test_all.py b/tests/form_pages/test_all.py
index 8b07a424e..72df3f98b 100644
--- a/tests/form_pages/test_all.py
+++ b/tests/form_pages/test_all.py
@@ -5438,7 +5438,7 @@ def test_rich_commentable_action(pub):
assert '
hello world
' in resp.text
formdata = formdef.data_class().select()[0]
- assert formdata.evolution[-1].parts[-1].comment == 'hello world
'
+ assert formdata.evolution[-1].parts[-2].comment == 'hello world
'
# check link
resp.form['comment'] = 'hello link.
'
@@ -5446,7 +5446,7 @@ def test_rich_commentable_action(pub):
assert 'hello link.
' in resp.text
formdata = formdef.data_class().select()[0]
assert (
- formdata.evolution[-1].parts[-1].comment
+ formdata.evolution[-1].parts[-2].comment
== 'hello link.
'
)
@@ -5455,7 +5455,7 @@ def test_rich_commentable_action(pub):
resp = resp.form.submit('button_x1').follow()
assert 'hello evil
' in resp.text
formdata = formdef.data_class().select()[0]
- assert formdata.evolution[-1].parts[-1].comment == 'hello evil
'
+ assert formdata.evolution[-1].parts[-2].comment == 'hello evil
'
resp.form['comment'] = '' # left empty
resp = resp.form.submit('button_x1')
@@ -5486,7 +5486,7 @@ def test_rich_commentable_action(pub):
resp.form['comment'] = 'hello
world
'
resp = resp.form.submit('button_x1').follow()
formdata = formdef.data_class().select()[0]
- assert formdata.evolution[-1].parts[-1].comment == 'hello
world
'
+ assert formdata.evolution[-1].parts[-2].comment == 'hello
world
'
pub.substitutions.feed(formdata)
context = pub.substitutions.get_context_variables(mode='lazy')
tmpl = Template('{{form_comment}}')
diff --git a/tests/test_ctl.py b/tests/test_ctl.py
index 242272008..23188a6fe 100644
--- a/tests/test_ctl.py
+++ b/tests/test_ctl.py
@@ -168,12 +168,14 @@ def test_trigger_jumps(pub):
formdata.id = 1
formdata.data = {'0': 'Alice', '1': 'alice@example.net'}
formdata.status = 'wf-%s' % st1.id
+ formdata.just_created()
formdata.store()
id1 = formdata.id
formdata = formdef.data_class()()
formdata.id = 2
formdata.data = {'0': 'Bob', '1': 'bob@example.net'}
formdata.status = 'wf-%s' % st1.id
+ formdata.just_created()
formdata.store()
id2 = formdata.id
select_and_jump_formdata(formdef, trigger, rows)
diff --git a/wcs/formdata.py b/wcs/formdata.py
index 8d9aa6c4b..9112f7ba9 100644
--- a/wcs/formdata.py
+++ b/wcs/formdata.py
@@ -790,7 +790,7 @@ class FormData(StorableObject):
return None
def jump_status(self, status_id, user_id=None):
- from wcs.workflows import ContentSnapshotPart
+ from wcs.workflows import ContentSnapshotPart, JumpEvolutionPart
if status_id == '_previous':
previous_status = self.pop_previous_marked_status()
@@ -811,7 +811,11 @@ class FormData(StorableObject):
self.status == status
and self.evolution[-1].status == status
and not self.evolution[-1].comment
- and not [x for x in self.evolution[-1].parts or [] if not isinstance(x, ContentSnapshotPart)]
+ and not [
+ x
+ for x in self.evolution[-1].parts or []
+ if not isinstance(x, (ContentSnapshotPart, JumpEvolutionPart))
+ ]
):
# if status do not change and last evolution is empty,
# just update last jump time on last evolution, do not add one
diff --git a/wcs/variables.py b/wcs/variables.py
index 0ef54211c..fec29f088 100644
--- a/wcs/variables.py
+++ b/wcs/variables.py
@@ -1011,6 +1011,29 @@ class LazyFormData(LazyFormDef):
data = self._formdata.workflow_data or {}
return {k: data[k] for k in data if not k.startswith('_')}
+ @property
+ def jumps(self):
+ from wcs.workflows import JumpEvolutionPart
+
+ jump_parts = []
+ for part in self._formdata.iter_evolution_parts():
+ if not isinstance(part, JumpEvolutionPart):
+ continue
+ jump_parts.append(part.identifier or 'undefined')
+ return jump_parts
+
+ @property
+ def latest_jump(self):
+ from wcs.workflows import JumpEvolutionPart
+
+ if self._formdata.evolution:
+ for evolution in reversed(self._formdata.evolution or []):
+ if evolution.parts:
+ for part in reversed(evolution.parts):
+ if isinstance(part, JumpEvolutionPart):
+ return part.identifier or 'undefined'
+ return ''
+
def export_to_json(self, include_files=True):
# this gets used to generate an email attachment :/
return self._formdata.export_to_json(include_files=include_files)
diff --git a/wcs/wf/choice.py b/wcs/wf/choice.py
index bfbba2867..7c0ce076e 100644
--- a/wcs/wf/choice.py
+++ b/wcs/wf/choice.py
@@ -21,7 +21,6 @@ from wcs.qommon.form import (
CheckboxWidget,
ComputedExpressionWidget,
SingleSelectWidget,
- VarnameWidget,
WidgetList,
WysiwygTextWidget,
)
@@ -35,7 +34,6 @@ class ChoiceWorkflowStatusItem(WorkflowStatusJumpItem):
ok_in_global_action = True
label = None
- identifier = None
by = []
backoffice_info_text = None
require_confirmation = False
@@ -116,6 +114,7 @@ class ChoiceWorkflowStatusItem(WorkflowStatusJumpItem):
formdata.record_workflow_event('button', action_item_id=self.id)
evo.status = 'wf-%s' % wf_status[0].id
self.handle_markers_stack(formdata)
+ self.add_jump_part(formdata, evo)
form.clear_errors()
return True # get out of processing loop
@@ -156,14 +155,6 @@ class ChoiceWorkflowStatusItem(WorkflowStatusJumpItem):
title=_('Information Text for Backoffice'),
value=self.backoffice_info_text,
)
- if 'identifier' in parameters:
- form.add(
- VarnameWidget,
- '%sidentifier' % prefix,
- title=_('Identifier'),
- value=self.identifier,
- advanced=True,
- )
if 'ignore_form_errors' in parameters:
form.add(
CheckboxWidget,
diff --git a/wcs/wf/jump.py b/wcs/wf/jump.py
index 09c55f2aa..0f6806cb2 100644
--- a/wcs/wf/jump.py
+++ b/wcs/wf/jump.py
@@ -42,6 +42,7 @@ JUMP_TIMEOUT_INTERVAL = max((60 // int(os.environ.get('WCS_JUMP_TIMEOUT_CHECKS',
def jump_and_perform(formdata, action, workflow_data=None):
action.handle_markers_stack(formdata)
+ action.add_jump_part(formdata)
if workflow_data:
formdata.update_workflow_data(workflow_data)
formdata.store()
@@ -186,7 +187,7 @@ 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', 'trigger', 'by', 'timeout', 'set_marker_on_status', 'identifier')
def add_parameters_widgets(self, form, parameters, prefix='', formdef=None, **kwargs):
super().add_parameters_widgets(form, parameters, prefix, formdef, **kwargs)
@@ -260,7 +261,9 @@ class JumpWorkflowStatusItem(WorkflowStatusJumpItem):
wf_status = self.get_target_status(formdata)
if wf_status:
self.handle_markers_stack(formdata)
+ self.add_jump_part(formdata)
formdata.status = 'wf-%s' % wf_status[0].id
+ formdata.store()
def check_condition(self, formdata, *args, trigger=None, **kwargs):
result = super().check_condition(formdata, *args, **kwargs)
diff --git a/wcs/wf/jump_on_submit.py b/wcs/wf/jump_on_submit.py
index e7ca9173e..29b92b561 100644
--- a/wcs/wf/jump_on_submit.py
+++ b/wcs/wf/jump_on_submit.py
@@ -45,9 +45,10 @@ class JumpOnSubmitWorkflowStatusItem(WorkflowStatusJumpItem):
if wf_status:
evo.status = 'wf-%s' % wf_status[0].id
self.handle_markers_stack(formdata)
+ self.add_jump_part(formdata, evo)
def get_parameters(self):
- return ('status', 'set_marker_on_status', 'condition')
+ return ('status', 'set_marker_on_status', 'condition', 'identifier')
register_item_class(JumpOnSubmitWorkflowStatusItem)
diff --git a/wcs/workflows.py b/wcs/workflows.py
index 42b7c4f85..ab632dae8 100644
--- a/wcs/workflows.py
+++ b/wcs/workflows.py
@@ -55,6 +55,7 @@ from .qommon.form import (
SingleSelectWidget,
SingleSelectWidgetWithOther,
StringWidget,
+ VarnameWidget,
WidgetList,
WidgetListOfRoles,
)
@@ -3230,11 +3231,17 @@ class WorkflowStatusItem(XmlSerialisable):
return '<%s>' % ' '.join(parts)
+class JumpEvolutionPart(EvolutionPart):
+ def __init__(self, identifier):
+ self.identifier = identifier
+
+
class WorkflowStatusJumpItem(WorkflowStatusItem):
status = None
endpoint = False
set_marker_on_status = False
category = 'status-change'
+ identifier = None
def add_parameters_widgets(self, form, parameters, prefix='', formdef=None, **kwargs):
super().add_parameters_widgets(form, parameters, prefix=prefix, formdef=formdef, **kwargs)
@@ -3256,9 +3263,23 @@ class WorkflowStatusJumpItem(WorkflowStatusItem):
advanced=True,
)
+ if 'identifier' in parameters:
+ form.add(
+ VarnameWidget,
+ '%sidentifier' % prefix,
+ title=_('Identifier'),
+ value=self.identifier,
+ advanced=True,
+ )
+
def get_parameters(self):
return ('status', 'set_marker_on_status', 'condition')
+ def add_jump_part(self, formdata, evo=None):
+ if evo is None:
+ evo = formdata.evolution[-1]
+ evo.add_part(JumpEvolutionPart(self.identifier))
+
class NoLongerAvailableAction(WorkflowStatusItem):
pass # marker class, loadable from pickle files but removed in migrate()