From 60484c9ac0b11611af9805e403c65a80a7808b2e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20P=C3=A9ters?= Date: Mon, 7 Mar 2016 21:28:20 +0100 Subject: [PATCH] general: add handling of criticality levels (#10134) --- tests/test_admin_pages.py | 62 +++++++++++++++++ tests/test_backoffice_pages.py | 122 +++++++++++++++++++++++++++++++++- tests/test_workflow_import.py | 15 ++++- tests/test_workflows.py | 49 +++++++++++++- tests/utilities.py | 1 + wcs/admin/workflows.py | 129 ++++++++++++++++++++++++++++++++++-- wcs/backoffice/management.py | 54 ++++++++++++++- wcs/formdata.py | 3 + wcs/forms/backoffice.py | 24 ++++++- wcs/forms/common.py | 14 ---- wcs/qommon/static/css/dc2/admin.css | 35 ++++++++++ wcs/qommon/static/js/biglist.js | 1 + wcs/sql.py | 16 +++-- wcs/wf/criticality.py | 84 +++++++++++++++++++++++ wcs/workflows.py | 41 ++++++++++++ 15 files changed, 621 insertions(+), 29 deletions(-) create mode 100644 wcs/wf/criticality.py diff --git a/tests/test_admin_pages.py b/tests/test_admin_pages.py index c5c852e..665c824 100644 --- a/tests/test_admin_pages.py +++ b/tests/test_admin_pages.py @@ -1378,6 +1378,8 @@ def test_workflows_delete(pub): assert Workflow.count() == 0 def test_workflows_add_all_actions(pub): + create_superuser(pub) + Workflow.wipe() workflow = Workflow(name='foo') workflow.add_status(name='baz') @@ -1705,6 +1707,66 @@ def test_workflows_global_actions_edit(pub): resp = resp.form.submit('submit') assert Workflow.get(workflow.id).global_actions[0].triggers[0].roles == ['_receiver'] +def test_workflows_criticality_levels(pub): + create_superuser(pub) + create_role() + + Workflow.wipe() + workflow = Workflow(name='foo') + workflow.store() + + app = login(get_app(pub)) + resp = app.get('/backoffice/workflows/%s/' % workflow.id) + resp = resp.click('add criticality level') + resp = resp.forms[0].submit('cancel') + assert not Workflow.get(workflow.id).criticality_levels + + resp = app.get('/backoffice/workflows/%s/' % workflow.id) + resp = resp.click('add criticality level') + resp.forms[0]['name'] = 'vigilance' + resp = resp.forms[0].submit('submit') + assert len(Workflow.get(workflow.id).criticality_levels) == 1 + assert Workflow.get(workflow.id).criticality_levels[0].name == 'vigilance' + + # test rename + resp = app.get('/backoffice/workflows/%s/' % workflow.id) + resp = resp.click('vigilance') + resp = resp.forms[0].submit('cancel') + + resp = app.get('/backoffice/workflows/%s/' % workflow.id) + resp = resp.click('vigilance') + resp.forms[0]['name'] = 'Vigilance' + resp = resp.forms[0].submit('submit') + assert len(Workflow.get(workflow.id).criticality_levels) == 1 + assert Workflow.get(workflow.id).criticality_levels[0].name == 'Vigilance' + + # add a second level + resp = app.get('/backoffice/workflows/%s/' % workflow.id) + resp = resp.click('add criticality level') + resp.forms[0]['name'] = 'Alerte attentat' + resp = resp.forms[0].submit('submit') + assert len(Workflow.get(workflow.id).criticality_levels) == 2 + assert Workflow.get(workflow.id).criticality_levels[0].name == 'Vigilance' + assert Workflow.get(workflow.id).criticality_levels[1].name == 'Alerte attentat' + + # test reorder + level1_id = Workflow.get(workflow.id).criticality_levels[0].id + level2_id = Workflow.get(workflow.id).criticality_levels[1].id + app.get('/backoffice/workflows/%s/update_criticality_levels_order?order=%s;%s;' % ( + workflow.id, level1_id, level2_id)) + assert Workflow.get(workflow.id).criticality_levels[0].id == level1_id + assert Workflow.get(workflow.id).criticality_levels[1].id == level2_id + app.get('/backoffice/workflows/%s/update_criticality_levels_order?order=%s;%s;' % ( + workflow.id, level2_id, level1_id)) + assert Workflow.get(workflow.id).criticality_levels[0].id == level2_id + assert Workflow.get(workflow.id).criticality_levels[1].id == level1_id + + # test removal + resp = app.get('/backoffice/workflows/%s/' % workflow.id) + resp = resp.click('Vigilance') + resp = resp.forms[0].submit('delete-level') + assert len(Workflow.get(workflow.id).criticality_levels) == 1 + def test_workflows_wscall_label(pub): create_superuser(pub) create_role() diff --git a/tests/test_backoffice_pages.py b/tests/test_backoffice_pages.py index b68aa67..7ee75c5 100644 --- a/tests/test_backoffice_pages.py +++ b/tests/test_backoffice_pages.py @@ -18,7 +18,7 @@ from wcs.qommon.http_request import HTTPRequest from wcs.roles import Role from wcs.workflows import (Workflow, CommentableWorkflowStatusItem, ChoiceWorkflowStatusItem, EditableWorkflowStatusItem, - JumpOnSubmitWorkflowStatusItem) + JumpOnSubmitWorkflowStatusItem, WorkflowCriticalityLevel) from wcs.wf.dispatch import DispatchWorkflowStatusItem from wcs.wf.wscall import WebserviceCallStatusItem from wcs.wf.register_comment import RegisterCommenterWorkflowStatusItem @@ -1835,3 +1835,123 @@ def test_backoffice_resubmit(pub): assert resp.form['f2'].value == 'XXX' assert 'Original form' in resp.body assert formdata.get_url(backoffice=True) in resp.body + +def test_backoffice_criticality_in_formdef_listing(pub): + if not pub.is_using_postgresql(): + pytest.skip('this requires SQL') + return + user = create_user(pub) + create_environment(pub) + + wf = Workflow.get_default_workflow() + wf.id = '2' + wf.criticality_levels = [ + WorkflowCriticalityLevel(name='green'), + WorkflowCriticalityLevel(name='yellow'), + WorkflowCriticalityLevel(name='red'), + ] + wf.store() + formdef = FormDef.get_by_urlname('form-title') + formdef.workflow_id = wf.id + formdef.store() + + formdata1, formdata2, formdata3, formdata4 = [ + x for x in formdef.data_class().select() if x.status == 'wf-new'][:4] + + formdata1.criticality_level = 1 + formdata1.store() + formdata1_str = '>%s<' % formdata1.get_display_id() + formdata2.criticality_level = 2 + formdata2.store() + formdata2_str = '>%s<' % formdata2.get_display_id() + formdata3.criticality_level = 2 + formdata3.store() + formdata3_str = '>%s<' % formdata3.get_display_id() + formdata4_str = '>%s<' % formdata4.get_display_id() + + app = login(get_app(pub)) + resp = app.get('/backoffice/management/form-title/?order_by=-criticality_level&limit=100') + assert resp.body.index(formdata1_str) > resp.body.index(formdata2_str) + assert resp.body.index(formdata1_str) > resp.body.index(formdata3_str) + assert resp.body.index(formdata1_str) < resp.body.index(formdata4_str) + + resp = app.get('/backoffice/management/form-title/?order_by=criticality_level&limit=100') + assert resp.body.index(formdata1_str) < resp.body.index(formdata2_str) + assert resp.body.index(formdata1_str) < resp.body.index(formdata3_str) + assert resp.body.index(formdata1_str) > resp.body.index(formdata4_str) + +def test_backoffice_criticality_in_global_listing(pub): + if not pub.is_using_postgresql(): + pytest.skip('this requires SQL') + return + + user = create_user(pub) + create_environment(pub) + + wf = Workflow.get_default_workflow() + wf.id = '2' + wf.criticality_levels = [ + WorkflowCriticalityLevel(name='green'), + WorkflowCriticalityLevel(name='yellow'), + WorkflowCriticalityLevel(name='red'), + ] + wf.store() + formdef = FormDef.get_by_urlname('form-title') + formdef.workflow_id = wf.id + formdef.store() + + formdata1, formdata2, formdata3, formdata4 = [ + x for x in formdef.data_class().select() if x.status == 'wf-new'][:4] + + formdata1.criticality_level = 1 + formdata1.store() + formdata1_str = '>%s<' % formdata1.get_display_id() + formdata2.criticality_level = 2 + formdata2.store() + formdata2_str = '>%s<' % formdata2.get_display_id() + formdata3.criticality_level = 2 + formdata3.store() + formdata3_str = '>%s<' % formdata3.get_display_id() + formdata4_str = '>%s<' % formdata4.get_display_id() + + app = login(get_app(pub)) + resp = app.get('/backoffice/management/listing?order_by=-criticality_level&limit=100') + assert resp.body.index(formdata1_str) > resp.body.index(formdata2_str) + assert resp.body.index(formdata1_str) > resp.body.index(formdata3_str) + assert resp.body.index(formdata1_str) < resp.body.index(formdata4_str) + + resp = app.get('/backoffice/management/listing?order_by=criticality_level&limit=100') + assert resp.body.index(formdata1_str) < resp.body.index(formdata2_str) + assert resp.body.index(formdata1_str) < resp.body.index(formdata3_str) + assert resp.body.index(formdata1_str) > resp.body.index(formdata4_str) + +def test_backoffice_criticality_formdata_view(pub): + user = create_user(pub) + create_environment(pub) + + wf = Workflow.get_default_workflow() + wf.id = '2' + wf.criticality_levels = [ + WorkflowCriticalityLevel(name='green'), + WorkflowCriticalityLevel(name='yellow'), + WorkflowCriticalityLevel(name='red'), + ] + wf.store() + formdef = FormDef.get_by_urlname('form-title') + formdef.workflow_id = wf.id + formdef.store() + + formdef = FormDef.get_by_urlname('form-title') + formdata = [x for x in formdef.data_class().select() if x.status == 'wf-new'][0] + + formdata.criticality_level = 1 + formdata.store() + + app = login(get_app(pub)) + resp = app.get(formdata.get_url(backoffice=True)) + assert 'Criticality Level: yellow' in resp.body + + formdata.criticality_level = 2 + formdata.store() + resp = app.get(formdata.get_url(backoffice=True)) + assert 'Criticality Level: red' in resp.body diff --git a/tests/test_workflow_import.py b/tests/test_workflow_import.py index 0b06360..d4c95ac 100644 --- a/tests/test_workflow_import.py +++ b/tests/test_workflow_import.py @@ -6,7 +6,8 @@ import xml.etree.ElementTree as ET from quixote import cleanup from wcs import publisher -from wcs.workflows import Workflow, CommentableWorkflowStatusItem +from wcs.workflows import (Workflow, CommentableWorkflowStatusItem, + WorkflowCriticalityLevel) from wcs.wf.wscall import WebserviceCallStatusItem from wcs.wf.dispatch import DispatchWorkflowStatusItem from wcs.wf.register_comment import RegisterCommenterWorkflowStatusItem @@ -426,3 +427,15 @@ def test_display_message_action(): assert role_id in wf2.possible_status[0].items[0].to wf2 = assert_import_export_works(wf, include_id=True) + +def test_criticality_level(): + wf = Workflow(name='criticality level') + wf.criticality_levels = [ + WorkflowCriticalityLevel(name='green'), + WorkflowCriticalityLevel(name='yellow'), + WorkflowCriticalityLevel(name='red', colour='FF0000'), + ] + + wf2 = assert_import_export_works(wf) + assert wf2.criticality_levels[0].name == 'green' + assert wf2.criticality_levels[1].name == 'yellow' diff --git a/tests/test_workflows.py b/tests/test_workflows.py index 3c79e7c..0fc9d50 100644 --- a/tests/test_workflows.py +++ b/tests/test_workflows.py @@ -14,8 +14,9 @@ from wcs.roles import Role from wcs.workflows import (Workflow, WorkflowStatusItem, SendmailWorkflowStatusItem, SendSMSWorkflowStatusItem, DisplayMessageWorkflowStatusItem, - AbortActionException) + AbortActionException, WorkflowCriticalityLevel) from wcs.wf.anonymise import AnonymiseWorkflowStatusItem +from wcs.wf.criticality import ModifyCriticalityWorkflowStatusItem, MODE_INC, MODE_DEC, MODE_SET from wcs.wf.dispatch import DispatchWorkflowStatusItem from wcs.wf.form import FormWorkflowStatusItem, WorkflowFormFieldsFormDef from wcs.wf.jump import JumpWorkflowStatusItem, _apply_timeouts @@ -943,3 +944,49 @@ def test_workflow_roles(pub): assert emails.get('Foobar') assert set(emails.get('Foobar')['email_rcpt']) == set( ['foo@localhost', 'bar@localhost', 'baz@localhost']) + +def test_criticality(pub): + FormDef.wipe() + + workflow = Workflow(name='criticality') + workflow.criticality_levels = [ + WorkflowCriticalityLevel(name='green'), + WorkflowCriticalityLevel(name='yellow'), + WorkflowCriticalityLevel(name='red'), + ] + workflow.store() + + formdef = FormDef() + formdef.name = 'baz' + formdef.workflow_id = workflow.id + formdef.store() + + item = ModifyCriticalityWorkflowStatusItem() + + formdata = formdef.data_class()() + item.perform(formdata) + assert not formdata.criticality_level + + item.mode = MODE_INC + item.perform(formdata) + assert formdata.criticality_level == 1 + item.perform(formdata) + assert formdata.criticality_level == 2 + item.perform(formdata) + assert formdata.criticality_level == 2 + + item.mode = MODE_DEC + item.perform(formdata) + assert formdata.criticality_level == 1 + item.perform(formdata) + assert formdata.criticality_level == 0 + item.perform(formdata) + assert formdata.criticality_level == 0 + + item.mode = MODE_SET + item.absolute_value = 2 + item.perform(formdata) + assert formdata.criticality_level == 2 + item.absolute_value = 0 + item.perform(formdata) + assert formdata.criticality_level == 0 diff --git a/tests/utilities.py b/tests/utilities.py index e8407c9..a580bff 100644 --- a/tests/utilities.py +++ b/tests/utilities.py @@ -99,6 +99,7 @@ def create_temporary_pub(sql_mode=False): fd.write('formdef-captcha-option = true\n') fd.write('workflow-resubmit-action = true\n') fd.write('workflow-global-actions = true\n') + fd.write('workflow-criticality-levels = true\n') if sql_mode: fd.write('postgresql = true\n') conn = psycopg2.connect(user=os.environ['USER']) diff --git a/wcs/admin/workflows.py b/wcs/admin/workflows.py index 743b5c8..3337df6 100644 --- a/wcs/admin/workflows.py +++ b/wcs/admin/workflows.py @@ -949,6 +949,91 @@ class FunctionsDirectory(Directory): return redirect('..') +class CriticalityLevelsDirectory(Directory): + _q_exports = ['', 'new'] + + def __init__(self, workflow): + self.workflow = workflow + + def _q_traverse(self, path): + get_response().breadcrumb.append( + ('criticality-levels/', _('Criticality Levels'))) + return Directory._q_traverse(self, path) + + def new(self): + currentlevels = self.workflow.criticality_levels or [] + default_colours = ['FFFFFF', 'FFFF00', 'FF9900', 'FF6600', 'FF0000'] + try: + default_colour = default_colours[len(currentlevels)] + except IndexError: + default_colour = '000000' + form = Form(enctype='multipart/form-data') + form.add(StringWidget, 'name', title=_('Name'), required=True, size=50) + form.add(ColourWidget, 'colour', title=_('Colour'), required=False, + value=default_colour) + form.add_submit('submit', _('Add')) + form.add_submit('cancel', _('Cancel')) + if form.get_widget('cancel').parse(): + return redirect('..') + + if form.is_submitted() and not form.has_errors(): + if not self.workflow.criticality_levels: + self.workflow.criticality_levels = [] + level = WorkflowCriticalityLevel() + level.name = form.get_widget('name').parse() + level.colour = form.get_widget('colour').parse() + self.workflow.criticality_levels.append(level) + self.workflow.store() + return redirect('..') + + get_response().breadcrumb.append(('new', _('New Criticality Level'))) + html_top('workflows', title=_('New Criticality level')) + r = TemplateIO(html=True) + r += htmltext('

%s

') % _('New Criticality Level') + r += form.render() + return r.getvalue() + + def _q_lookup(self, component): + for level in (self.workflow.criticality_levels or []): + if level.id == component: + break + else: + raise errors.TraversalError() + + form = Form(enctype='multipart/form-data') + form.add(StringWidget, 'name', title=_('Name'), required=True, size=50, + value=level.name) + form.add(ColourWidget, 'colour', title=_('Colour'), required=False, + value=level.colour) + form.add_submit('submit', _('Submit')) + form.add_submit('cancel', _('Cancel')) + form.add_submit('delete-level', _('Delete')) + + if form.get_widget('cancel').parse(): + return redirect('..') + + if form.get_submit() == 'delete-level': + self.workflow.criticality_levels.remove(level) + self.workflow.store() + return redirect('..') + + if form.is_submitted() and not form.has_errors(): + level.name = form.get_widget('name').parse() + level.colour = form.get_widget('colour').parse() + self.workflow.store() + return redirect('..') + + get_response().breadcrumb.append(('new', _('Edit Criticality Level'))) + html_top('workflows', title=_('Edit Criticality Level')) + r = TemplateIO(html=True) + r += htmltext('

%s

') % _('Edit Criticality Level') + r += form.render() + return r.getvalue() + + def _q_index(self): + return redirect('..') + + class GlobalActionPage(WorkflowStatusPage): _q_exports = ['', 'new', 'delete', 'newitem', ('items', 'items_dir'), 'edit', ('triggers', 'triggers_dir'), @@ -1139,8 +1224,10 @@ class GlobalActionsDirectory(Directory): class WorkflowPage(Directory): _q_exports = ['', 'edit', 'delete', 'newstatus', ('status', 'status_dir'), 'update_order', 'duplicate', 'export', 'svg', ('variables', 'variables_dir'), - 'update_actions_order', - ('functions', 'functions_dir'), ('global-actions', 'global_actions_dir')] + 'update_actions_order', 'update_criticality_levels_order', + ('functions', 'functions_dir'), ('global-actions', 'global_actions_dir'), + ('criticality-levels', 'criticality_levels_dir'), + ] def __init__(self, component, html_top): try: @@ -1153,12 +1240,14 @@ class WorkflowPage(Directory): self.variables_dir = VariablesDirectory(self.workflow) self.functions_dir = FunctionsDirectory(self.workflow) self.global_actions_dir = GlobalActionsDirectory(self.workflow, html_top) + self.criticality_levels_dir = CriticalityLevelsDirectory(self.workflow) get_response().breadcrumb.append((component + '/', self.workflow.name)) def _q_index(self): self.html_top(title = _('Workflow - %s') % self.workflow.name) r = TemplateIO(html=True) - get_response().add_javascript(['jquery.js', 'jquery-ui.js', 'biglist.js', 'svg-pan-zoom.js']) + get_response().add_javascript(['jquery.js', 'jquery-ui.js', 'biglist.js', + 'svg-pan-zoom.js', 'jquery.colourpicker.js']) get_response().add_javascript_code('$(function () { svgPanZoom("svg", {controlIconsEnabled: true}); });') r += htmltext('

%s - ') % _('Workflow') r += htmltext('%s') % self.workflow.name @@ -1257,7 +1346,7 @@ class WorkflowPage(Directory): r += htmltext('