workflows: track jumps (#74723)
gitea/wcs/pipeline/head This commit looks good Details

This commit is contained in:
Emmanuel Cazenave 2023-07-04 18:23:45 +02:00
parent d69044f650
commit bc078bb906
9 changed files with 139 additions and 8 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
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

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

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()
@ -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,6 +45,7 @@ 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', 'identifier')

View File

@ -3231,6 +3231,11 @@ class WorkflowStatusItem(XmlSerialisable):
return '<%s>' % ' '.join(parts)
class JumpEvolutionPart(EvolutionPart):
def __init__(self, identifier):
self.identifier = identifier
class WorkflowStatusJumpItem(WorkflowStatusItem):
status = None
endpoint = False
@ -3270,6 +3275,11 @@ class WorkflowStatusJumpItem(WorkflowStatusItem):
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()