workflows : add an identifier to automatic and on submit jumps (#74723) #161

Open
ecazenave wants to merge 2 commits from wip/74723-last-jump into main
9 changed files with 152 additions and 20 deletions

View File

@ -2373,8 +2373,8 @@ def test_backoffice_wscall_attachment(http_requests, pub):
assert resp.text == '<?xml version="1.0"><foo/>'
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'

View File

@ -5438,7 +5438,7 @@ def test_rich_commentable_action(pub):
assert '<p>hello <i>world</i></p>' in resp.text
formdata = formdef.data_class().select()[0]
assert formdata.evolution[-1].parts[-1].comment == '<p>hello <i>world</i></p>'
assert formdata.evolution[-1].parts[-2].comment == '<p>hello <i>world</i></p>'
# check link
resp.form['comment'] = '<p>hello <a href="http://localhost/">link</a>.</p>'
@ -5446,7 +5446,7 @@ def test_rich_commentable_action(pub):
assert '<p>hello <a href="http://localhost/" rel="nofollow">link</a>.</p>' in resp.text
formdata = formdef.data_class().select()[0]
assert (
formdata.evolution[-1].parts[-1].comment
formdata.evolution[-1].parts[-2].comment
== '<p>hello <a href="http://localhost/" rel="nofollow">link</a>.</p>'
)
@ -5455,7 +5455,7 @@ def test_rich_commentable_action(pub):
resp = resp.form.submit('button_x1').follow()
assert '<p>hello evil</p>' in resp.text
formdata = formdef.data_class().select()[0]
assert formdata.evolution[-1].parts[-1].comment == '<p>hello evil</p>'
assert formdata.evolution[-1].parts[-2].comment == '<p>hello evil</p>'
resp.form['comment'] = '<p></p>' # left empty
resp = resp.form.submit('button_x1')
@ -5486,7 +5486,7 @@ def test_rich_commentable_action(pub):
resp.form['comment'] = '<p>hello<br>world</p>'
resp = resp.form.submit('button_x1').follow()
formdata = formdef.data_class().select()[0]
assert formdata.evolution[-1].parts[-1].comment == '<p>hello<br>world</p>'
assert formdata.evolution[-1].parts[-2].comment == '<p>hello<br>world</p>'
pub.substitutions.feed(formdata)
context = pub.substitutions.get_context_variables(mode='lazy')
tmpl = Template('{{form_comment}}')

View File

@ -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

Sinon le tests crash sur add_jump_part qui ne trouve pas d'évolution.

L'absence d'evolution me parait être une situation artificielle du test, d'où l'ajout du just_created.

Sinon le tests crash sur add_jump_part qui ne trouve pas d'évolution. L'absence d'evolution me parait être une situation artificielle du test, d'où l'ajout du just_created.
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)

View File

@ -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

View File

@ -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)

View File

@ -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)

On passe l'evolution qui vient d'être créée sinon pas de persistance de la JumpEvolutionPart (voir plus bas).

On passe l'evolution qui vient d'être créée sinon pas de persistance de la JumpEvolutionPart (voir plus bas).
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,

View File

@ -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)

View File

@ -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)

View File

@ -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()