From 17be135df2400fa162f3279dfcda2ee295b1c9e1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20P=C3=A9ters?= Date: Thu, 25 May 2017 10:48:20 +0200 Subject: [PATCH] workflows: add possibility to jump to previous status (#16473) --- tests/test_backoffice_pages.py | 91 ++++++++++++++++++++++++++++++++++++++++++ wcs/admin/workflows.py | 3 +- wcs/formdata.py | 15 +++++++ wcs/wf/jump.py | 2 +- wcs/workflows.py | 45 +++++++++++++-------- 5 files changed, 137 insertions(+), 19 deletions(-) diff --git a/tests/test_backoffice_pages.py b/tests/test_backoffice_pages.py index 48d48c3c..c37feceb 100644 --- a/tests/test_backoffice_pages.py +++ b/tests/test_backoffice_pages.py @@ -7,6 +7,7 @@ import shutil import StringIO import time import hashlib +import random import pytest from webtest import Upload @@ -3153,3 +3154,93 @@ def test_backoffice_fields(pub): resp = app.get(formdata.get_url(backoffice=True)) assert 'Backoffice Data' in resp.body assert 'Not set' in resp.body + +def test_workflow_jump_previous(pub): + user = create_user(pub) + create_environment(pub) + + wf = Workflow(name='jump around') + # North + # / \ + # West <----> East + # | | + # | autojump + # | | + # \ / + # South + + st1 = wf.add_status('North') + st1.id = 'north' + st2 = wf.add_status('West') + st2.id = 'west' + st3 = wf.add_status('East') + st3.id = 'east' + st4 = wf.add_status('Autojump') + st4.id = 'autojump' + st5 = wf.add_status('South') + st5.id = 'south' + + button_by_id = {} + + def add_jump(label, src, dst_id): + jump = ChoiceWorkflowStatusItem() + jump.id = str(random.random()) + jump.label = label + jump.by = ['logged-users'] + jump.status = dst_id + src.items.append(jump) + jump.parent = src + button_by_id[label] = 'button%s' % jump.id + + add_jump('Go West', st1, st2.id) + add_jump('Go East', st1, st3.id) + add_jump('Go South', st2, st5.id) + add_jump('Go Autojump', st3, st4.id) + add_jump('Go Back', st5, '_previous') + + add_jump('Jump West', st3, st2.id) + add_jump('Jump East', st2, st3.id) + + jump = JumpWorkflowStatusItem() + jump.id = '_auto-jump' + jump.status = st5.id + st4.items.append(jump) + jump.parent = st4 + + 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) + + # jump around using buttons + resp = resp.form.submit(button_by_id['Go West']).follow() + assert formdef.data_class().get(formdata.id).status == 'wf-%s' % st2.id + resp = resp.form.submit(button_by_id['Go South']).follow() + assert formdef.data_class().get(formdata.id).status == 'wf-%s' % st5.id + resp = resp.form.submit(button_by_id['Go Back']).follow() + assert formdef.data_class().get(formdata.id).status == 'wf-%s' % st2.id + resp = resp.form.submit(button_by_id['Go South']).follow() + assert formdef.data_class().get(formdata.id).status == 'wf-%s' % st5.id + resp = resp.form.submit(button_by_id['Go Back']).follow() + assert formdef.data_class().get(formdata.id).status == 'wf-%s' % st2.id + resp = resp.form.submit(button_by_id['Jump East']).follow() + assert formdef.data_class().get(formdata.id).status == 'wf-%s' % st3.id + resp = resp.form.submit(button_by_id['Go Autojump']).follow() + assert formdef.data_class().get(formdata.id).status == 'wf-%s' % st5.id + resp = resp.form.submit(button_by_id['Go Back']).follow() + assert formdef.data_class().get(formdata.id).status == 'wf-%s' % st3.id + + # and do a last jump using the API + formdata = formdef.data_class().get(formdata.id) + formdata.jump_status('_previous') + assert formdata.status == 'wf-%s' % st5.id diff --git a/wcs/admin/workflows.py b/wcs/admin/workflows.py index 965023f4..e4b0f2d8 100644 --- a/wcs/admin/workflows.py +++ b/wcs/admin/workflows.py @@ -152,7 +152,8 @@ def graphviz(workflow, url_prefix='', select=None, svg=True, for status in workflow.possible_status: i = status.id for item in status.items: - next_status_ids = [x.id for x in item.get_target_status() if x.id != status.id] + next_status_ids = [x.id for x in item.get_target_status() + if x.id and x.id != status.id] if not next_status_ids: next_status_ids = [status.id] done = {} diff --git a/wcs/formdata.py b/wcs/formdata.py index c7bd1b2d..7f0bef20 100644 --- a/wcs/formdata.py +++ b/wcs/formdata.py @@ -452,7 +452,22 @@ class FormData(StorableObject): return None return wf_status.handle_form(form, self, user) + def get_previous_status(self): + for evolution in reversed(self.evolution[:-1]): + status_id = evolution.status + if not status_id: + continue + status = self.formdef.workflow.get_status(status_id[3:]) + if not status.is_waitpoint(): + continue + return status + return None + def jump_status(self, status_id): + if status_id == '_previous': + previous_status = self.get_previous_status() + assert previous_status, 'failed to compute previous status' + status_id = previous_status.id evo = Evolution() evo.time = time.localtime() evo.status = 'wf-%s' % status_id diff --git a/wcs/wf/jump.py b/wcs/wf/jump.py index af4dcf49..59d2f57b 100644 --- a/wcs/wf/jump.py +++ b/wcs/wf/jump.py @@ -190,7 +190,7 @@ class JumpWorkflowStatusItem(WorkflowStatusJumpItem): return if self.must_jump(formdata): - wf_status = self.get_target_status() + wf_status = self.get_target_status(formdata) if wf_status: formdata.status = 'wf-%s' % wf_status[0].id diff --git a/wcs/workflows.py b/wcs/workflows.py index fb6581b1..3a16377c 100644 --- a/wcs/workflows.py +++ b/wcs/workflows.py @@ -421,19 +421,7 @@ class Workflow(StorableObject): # a waitpoint status is a status waiting for an event (be it user # interaction or something else), but can also be an endpoint (where # the user would wait, infinitely). - waitpoint_status = [] - for status in self.possible_status: - waitpoint = False - endpoint = True - if status.forced_endpoint: - endpoint = True - else: - for item in status.items: - endpoint = item.endpoint and endpoint - waitpoint = item.waitpoint or waitpoint - if endpoint or waitpoint: - waitpoint_status.append(status) - return waitpoint_status + return [x for x in self.possible_status if x.is_waitpoint()] def get_endpoint_status(self): not_endpoint_status = self.get_not_endpoint_status() @@ -1411,6 +1399,17 @@ class WorkflowStatus(object): return True return False + def is_waitpoint(self): + waitpoint = False + endpoint = True + if self.forced_endpoint: + endpoint = True + else: + for item in self.items: + endpoint = item.endpoint and endpoint + waitpoint = item.waitpoint or waitpoint + return bool(endpoint or waitpoint) + def __getstate__(self): odict = self.__dict__.copy() if odict.has_key('parent'): @@ -1634,11 +1633,21 @@ class WorkflowStatusItem(XmlSerialisable): def get_substitution_variables(self, formdata): return {} - def get_target_status(self): + def get_target_status(self, formdata=None): """Returns a list of status this item can lead to.""" if not getattr(self, 'status', None): return [] + if self.status == '_previous': + if formdata is None: + # must be in a formdata to compute destination, just give a + # fake status for presentation purpose + return [WorkflowStatus(_('Previous Status'))] + previous_status = formdata.get_previous_status() + if previous_status: + return [previous_status] + return [] + try: return [x for x in self.parent.parent.possible_status if x.id == self.status] except IndexError: @@ -1689,7 +1698,9 @@ class WorkflowStatusJumpItem(WorkflowStatusItem): def add_parameters_widgets(self, form, parameters, prefix='', formdef=None): if 'status' in parameters: form.add(SingleSelectWidget, '%sstatus' % prefix, title = _('Status'), value = self.status, - options = [(None, '---')] + [(x.id, x.name) for x in self.parent.parent.possible_status]) + options = [(None, '---')] + + [(x.id, x.name) for x in self.parent.parent.possible_status] + + [('_previous', _('Previous Status'))]) def get_parameters(self): return ('status',) @@ -1892,7 +1903,7 @@ class ChoiceWorkflowStatusItem(WorkflowStatusJumpItem): def submit_form(self, form, formdata, user, evo): if form.get_submit() == 'button%s' % self.id: - wf_status = self.get_target_status() + wf_status = self.get_target_status(formdata) if wf_status: evo.status = 'wf-%s' % wf_status[0].id form.clear_errors() @@ -1940,7 +1951,7 @@ class JumpOnSubmitWorkflowStatusItem(WorkflowStatusJumpItem): def submit_form(self, form, formdata, user, evo): if form.is_submitted() and not form.has_errors(): - wf_status = self.get_target_status() + wf_status = self.get_target_status(formdata) if wf_status: evo.status = 'wf-%s' % wf_status[0].id -- 2.11.0