From 68b8d693ecc42389baac9853669c80216ece67b0 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_formdata.py | 39 +++++++++++ tests/test_sql.py | 56 +++++++++++++++- 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 | 42 ++++++++++++ wcs/formdef.py | 2 +- wcs/forms/backoffice.py | 24 ++++++- wcs/forms/common.py | 14 ---- wcs/qommon/static/css/dc2/admin.css | 34 ++++++++++ wcs/qommon/static/js/biglist.js | 1 + wcs/sql.py | 26 +++++++- wcs/wf/criticality.py | 77 +++++++++++++++++++++ wcs/workflows.py | 52 +++++++++++++-- 18 files changed, 765 insertions(+), 34 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..b5c1dc4 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.set_criticality_level(1) + formdata1.store() + formdata1_str = '>%s<' % formdata1.get_display_id() + formdata2.set_criticality_level(2) + formdata2.store() + formdata2_str = '>%s<' % formdata2.get_display_id() + formdata3.set_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.set_criticality_level(1) + formdata1.store() + formdata1_str = '>%s<' % formdata1.get_display_id() + formdata2.set_criticality_level(2) + formdata2.store() + formdata2_str = '>%s<' % formdata2.get_display_id() + formdata3.set_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.set_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.set_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_formdata.py b/tests/test_formdata.py index 0720b63..b4f6671 100644 --- a/tests/test_formdata.py +++ b/tests/test_formdata.py @@ -10,6 +10,8 @@ from wcs.qommon.http_request import HTTPRequest from wcs import fields, formdef from wcs.formdef import FormDef from wcs.formdata import Evolution +from wcs.workflows import Workflow, WorkflowCriticalityLevel +from wcs.wf.anonymise import AnonymiseWorkflowStatusItem from utilities import create_temporary_pub, clean_temporary_pub @@ -240,3 +242,40 @@ def test_clean_drafts(pub): clean_drafts(pub) assert formdef.data_class().count() == 1 assert formdef.data_class().select()[0].id == d_id1 + +def test_criticality_levels(pub): + workflow = Workflow(name='criticality') + workflow.criticality_levels = [ + WorkflowCriticalityLevel(name='green'), + WorkflowCriticalityLevel(name='yellow'), + WorkflowCriticalityLevel(name='red'), + ] + workflow.store() + + formdef = FormDef() + formdef.name = 'foo' + formdef.fields = [] + formdef.workflow_id = workflow.id + formdef.store() + formdef.data_class().wipe() + + d = formdef.data_class()() + assert d.get_criticality_level_object().name == 'green' + d.increase_criticality_level() + assert d.get_criticality_level_object().name == 'yellow' + d.increase_criticality_level() + assert d.get_criticality_level_object().name == 'red' + d.increase_criticality_level() + assert d.get_criticality_level_object().name == 'red' + d.decrease_criticality_level() + assert d.get_criticality_level_object().name == 'yellow' + d.decrease_criticality_level() + assert d.get_criticality_level_object().name == 'green' + d.decrease_criticality_level() + assert d.get_criticality_level_object().name == 'green' + d.set_criticality_level(1) + assert d.get_criticality_level_object().name == 'yellow' + d.set_criticality_level(2) + assert d.get_criticality_level_object().name == 'red' + d.set_criticality_level(4) + assert d.get_criticality_level_object().name == 'red' diff --git a/tests/test_sql.py b/tests/test_sql.py index 4e03ba1..343ae6d 100644 --- a/tests/test_sql.py +++ b/tests/test_sql.py @@ -10,7 +10,7 @@ from quixote import cleanup from wcs import formdef, publisher, fields from wcs.formdef import FormDef from wcs.formdata import Evolution -from wcs.workflows import Workflow, CommentableWorkflowStatusItem +from wcs.workflows import Workflow, CommentableWorkflowStatusItem, WorkflowCriticalityLevel from wcs import sql import wcs.qommon.storage as st @@ -1351,3 +1351,57 @@ def test_materialized_view(): assert sql.get_yearly_totals() == [(str(datetime.date.today().year), 2)] sql.refresh_materialized_views() assert sql.get_yearly_totals() == [(str(datetime.date.today().year), 3)] + +@postgresql +def test_criticality_levels(): + drop_formdef_tables() + conn, cur = sql.get_connection_and_cursor() + + workflow1 = Workflow(name='criticality1') + workflow1.criticality_levels = [ + WorkflowCriticalityLevel(name='green'), + WorkflowCriticalityLevel(name='yellow'), + WorkflowCriticalityLevel(name='red'), + WorkflowCriticalityLevel(name='redder'), + WorkflowCriticalityLevel(name='reddest'), + ] + workflow1.store() + + workflow2 = Workflow(name='criticality2') + workflow2.criticality_levels = [ + WorkflowCriticalityLevel(name='green'), + WorkflowCriticalityLevel(name='reddest'), + ] + workflow2.store() + + formdef1 = FormDef() + formdef1.name = 'test criticality levels 1' + formdef1.fields = [] + formdef1.workflow_id = workflow1.id + formdef1.store() + + formdef2 = FormDef() + formdef2.name = 'test criticality levels 2' + formdef2.fields = [] + formdef2.workflow_id = workflow2.id + formdef2.store() + + data_class = formdef1.data_class(mode='sql') + for i in range(5): + formdata = data_class() + formdata.set_criticality_level(i) + formdata.store() + + data_class = formdef2.data_class(mode='sql') + for i in range(2): + formdata = data_class() + formdata.set_criticality_level(i) + formdata.store() + + objects = sql.AnyFormData.select(order_by='-criticality_level') + # make sure first two formdata are the highest priority ones, and the last + # two formdata are the lowest priority ones. + assert objects[0].get_criticality_level_object().name == 'reddest' + assert objects[1].get_criticality_level_object().name == 'reddest' + assert objects[-1].get_criticality_level_object().name == 'green' + assert objects[-2].get_criticality_level_object().name == 'green' 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..ad62a57 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.get_criticality_level_object().name == 'yellow' + item.perform(formdata) + assert formdata.get_criticality_level_object().name == 'red' + item.perform(formdata) + assert formdata.get_criticality_level_object().name == 'red' + + item.mode = MODE_DEC + item.perform(formdata) + assert formdata.get_criticality_level_object().name == 'yellow' + item.perform(formdata) + assert formdata.get_criticality_level_object().name == 'green' + item.perform(formdata) + assert formdata.get_criticality_level_object().name == 'green' + + item.mode = MODE_SET + item.absolute_value = 2 + item.perform(formdata) + assert formdata.get_criticality_level_object().name == 'red' + item.absolute_value = 0 + item.perform(formdata) + assert formdata.get_criticality_level_object().name == 'green' 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('