backoffice: add multi-action support for status manual jump action (#37983)

This commit is contained in:
Frédéric Péters 2020-05-04 16:59:24 +02:00 committed by Serghei Mihai
parent 6b69a5a1b6
commit 5cf979eb02
4 changed files with 169 additions and 67 deletions

View File

@ -317,8 +317,8 @@ def test_backoffice_listing(pub):
# check status filter <select>
resp = app.get('/backoffice/management/form-title/')
resp.forms[0]['filter'] = 'all'
resp = resp.forms[0].submit()
resp.forms['listing-settings']['filter'] = 'all'
resp = resp.forms['listing-settings'].submit()
if pub.is_using_postgresql():
assert resp.text.count('data-link') == 20
else:
@ -327,8 +327,8 @@ def test_backoffice_listing(pub):
# check status filter <select>
resp = app.get('/backoffice/management/form-title/')
resp.forms[0]['filter'] = 'done'
resp = resp.forms[0].submit()
resp.forms['listing-settings']['filter'] = 'done'
resp = resp.forms['listing-settings'].submit()
if pub.is_using_postgresql():
assert resp.text.count('data-link') == 20
resp = resp.click('Next Page')
@ -361,8 +361,8 @@ def test_backoffice_listing(pub):
resp = app.get('/backoffice/management/form-title/')
assert resp.text.count('data-link') == 9
resp.forms[0]['filter'] = 'pending'
resp = resp.forms[0].submit()
resp.forms['listing-settings']['filter'] = 'pending'
resp = resp.forms['listing-settings'].submit()
assert resp.text.count('data-link') == 17
# check status forced as endpoints are not part of the "actionable" list.
@ -392,8 +392,8 @@ def test_backoffice_listing(pub):
resp = app.get('/backoffice/management/form-title/')
assert resp.text.count('data-link') == 17
resp.forms[0]['filter'] = 'pending'
resp = resp.forms[0].submit()
resp.forms['listing-settings']['filter'] = 'pending'
resp = resp.forms['listing-settings'].submit()
assert resp.text.count('data-link') == 17
# mark status as an endpoint
@ -403,8 +403,8 @@ def test_backoffice_listing(pub):
resp = app.get('/backoffice/management/form-title/')
assert resp.text.count('data-link') == 9
resp.forms[0]['filter'] = 'pending'
resp = resp.forms[0].submit()
resp.forms['listing-settings']['filter'] = 'pending'
resp = resp.forms['listing-settings'].submit()
assert resp.text.count('data-link') == 9
@ -542,10 +542,10 @@ def test_backoffice_columns(pub):
app = login(get_app(pub))
resp = app.get('/backoffice/management/form-title/')
assert resp.text.count('</th>') == 8 # six columns
resp.forms[0]['1'].checked = False
assert not 'submission_channel' in resp.forms[0].fields
assert 'last_update_time' in resp.forms[0].fields
resp = resp.forms[0].submit()
resp.forms['listing-settings']['1'].checked = False
assert not 'submission_channel' in resp.forms['listing-settings'].fields
assert 'last_update_time' in resp.forms['listing-settings'].fields
resp = resp.forms['listing-settings'].submit()
assert resp.text.count('</th>') == 7 # fixe columns
assert resp.text.count('data-link') == 17 # 17 rows
assert resp.text.count('FOO BAR') == 0 # no field 1 column
@ -570,8 +570,8 @@ def test_backoffice_channel_column(pub):
app = login(get_app(pub))
resp = app.get('/backoffice/management/form-title/')
assert resp.text.count('</th>') == 8 # six columns
resp.forms[0]['submission_channel'].checked = True
resp = resp.forms[0].submit()
resp.forms['listing-settings']['submission_channel'].checked = True
resp = resp.forms['listing-settings'].submit()
assert resp.text.count('</th>') == 9 # seven columns
assert resp.text.count('data-link') == 17 # 17 rows
assert resp.text.count('<td>Web</td>') == 17
@ -640,42 +640,42 @@ def test_backoffice_filter(pub):
create_environment(pub)
app = login(get_app(pub))
resp = app.get('/backoffice/management/form-title/')
assert resp.forms[0]['filter-status'].checked == True
resp.forms[0]['filter-status'].checked = False
resp.forms[0]['filter-2'].checked = True
resp = resp.forms[0].submit()
assert resp.forms['listing-settings']['filter-status'].checked == True
resp.forms['listing-settings']['filter-status'].checked = False
resp.forms['listing-settings']['filter-2'].checked = True
resp = resp.forms['listing-settings'].submit()
assert '<select name="filter">' not in resp.text
resp.forms[0]['filter-2-value'] = 'baz'
resp = resp.forms[0].submit()
resp.forms['listing-settings']['filter-2-value'] = 'baz'
resp = resp.forms['listing-settings'].submit()
assert resp.text.count('<td>baz</td>') == 8
assert resp.text.count('<td>foo</td>') == 0
assert resp.text.count('<td>bar</td>') == 0
resp.forms[0]['filter-start'].checked = True
resp = resp.forms[0].submit()
resp.forms[0]['filter-start-value'] = datetime.datetime(2015, 2, 1).strftime('%Y-%m-%d')
resp = resp.forms[0].submit()
resp.forms['listing-settings']['filter-start'].checked = True
resp = resp.forms['listing-settings'].submit()
resp.forms['listing-settings']['filter-start-value'] = datetime.datetime(2015, 2, 1).strftime('%Y-%m-%d')
resp = resp.forms['listing-settings'].submit()
assert resp.text.count('<td>baz</td>') == 0
resp.forms[0]['filter-start-value'] = datetime.datetime(2014, 2, 1).strftime('%Y-%m-%d')
resp = resp.forms[0].submit()
resp.forms['listing-settings']['filter-start-value'] = datetime.datetime(2014, 2, 1).strftime('%Y-%m-%d')
resp = resp.forms['listing-settings'].submit()
assert resp.text.count('<td>baz</td>') == 8
# check there's no crash on invalid date values
resp.forms[0]['filter-start-value'] = 'whatever'
resp = resp.forms[0].submit()
resp.forms['listing-settings']['filter-start-value'] = 'whatever'
resp = resp.forms['listing-settings'].submit()
assert resp.text.count('<td>baz</td>') == 8
# check two-digit years are handled correctly
resp.forms[0]['filter-start-value'] = datetime.datetime(2014, 2, 1).strftime('%y-%m-%d')
resp = resp.forms[0].submit()
resp.forms['listing-settings']['filter-start-value'] = datetime.datetime(2014, 2, 1).strftime('%y-%m-%d')
resp = resp.forms['listing-settings'].submit()
assert resp.text.count('<td>baz</td>') == 8
# check it's also ok for end filter
resp.forms[0]['filter-end'].checked = True
resp = resp.forms[0].submit()
resp.forms[0]['filter-end-value'] = datetime.datetime(2014, 2, 2).strftime('%y-%m-%d')
resp = resp.forms[0].submit()
resp.forms['listing-settings']['filter-end'].checked = True
resp = resp.forms['listing-settings'].submit()
resp.forms['listing-settings']['filter-end-value'] = datetime.datetime(2014, 2, 2).strftime('%y-%m-%d')
resp = resp.forms['listing-settings'].submit()
assert resp.text.count('<td>baz</td>') == 0
@ -1447,7 +1447,7 @@ def test_backoffice_multi_actions(pub):
app = login(get_app(pub))
resp = app.get('/backoffice/management/form-title/')
assert not 'id="multi-actions"' in resp.text
assert 'id="multi-actions"' in resp.text # always there
workflow = Workflow.get_default_workflow()
workflow.id = '2'
@ -1462,7 +1462,7 @@ def test_backoffice_multi_actions(pub):
formdef.store()
resp = app.get('/backoffice/management/form-title/')
assert not 'id="multi-actions"' in resp.text
assert 'id="multi-actions"' in resp.text
trigger.roles = [x.id for x in Role.select() if x.name == 'foobar']
workflow.store()
@ -1550,6 +1550,51 @@ def test_backoffice_multi_actions(pub):
assert formdata.status != 'wf-accepted'
def test_backoffice_multi_actions_jump(pub):
create_superuser(pub)
create_environment(pub)
formdef = FormDef.get_by_urlname('form-title')
app = login(get_app(pub))
resp = app.get('/backoffice/management/form-title/')
workflow = Workflow.get_default_workflow()
workflow.id = '2'
workflow.store()
formdef.workflow_id = workflow.id
formdef.store()
resp = app.get('/backoffice/management/form-title/')
assert 'select[]' not in resp.forms['multi-actions'].fields
resp.forms['listing-settings']['filter'] = 'new'
resp = resp.forms['listing-settings'].submit()
assert 'select[]' not in resp.forms['multi-actions'].fields
# add identifier to jumps
workflow.get_status('new').items[1].identifier = 'accept'
workflow.get_status('new').items[2].identifier = 'reject'
workflow.get_status('new').items[2].require_confirmation = True
workflow.store()
resp = resp.forms['listing-settings'].submit()
assert 'select[]' in resp.forms['multi-actions'].fields
assert len(resp.pyquery.find('#multi-actions div.buttons button')) == 2
assert len(resp.pyquery.find('#multi-actions div.buttons button[data-ask-for-confirmation]')) == 1
ids = []
for checkbox in resp.forms[0].fields['select[]'][1:6]:
ids.append(checkbox._value)
checkbox.checked = True
resp = resp.forms['multi-actions'].submit('button-action-st-accept')
assert '?job=' in resp.location
resp = resp.follow()
assert 'Executing task &quot;Accept&quot; on forms' in resp.text
assert '>completed<' in resp.text
for id in ids:
assert formdef.data_class().get(id).status == 'wf-accepted'
def test_backoffice_statistics_with_no_formdefs(pub):
create_user(pub)
create_environment(pub)
@ -6442,7 +6487,7 @@ def test_backoffice_custom_view(pub):
resp.forms['listing-settings']['user-label'].checked = False
resp = resp.forms['listing-settings'].submit()
# filters
resp.forms[0]['filter-2'].checked = True
resp.forms['listing-settings']['filter-2'].checked = True
resp = resp.forms['listing-settings'].submit()
resp.forms['listing-settings']['filter-2-value'] = 'baz'

View File

@ -1646,19 +1646,32 @@ class FormPage(Directory):
def listing_top_actions(self):
return ''
def get_multi_actions(self, user):
# filter global manual actions to get those that can be run by the
# user, either because of actual roles, or because the action is
# accessible to functions.
def get_multi_actions(self, user, status_filter):
global_actions = self.formdef.workflow.get_global_manual_actions()
if status_filter not in ('open', 'waiting', 'done', 'all'):
# when the listing is filtered on a specific status, include
# manual jumps with identifiers
try:
status = self.formdef.workflow.get_status(status_filter)
except KeyError:
status = None
else:
global_actions.extend(status.get_status_manual_actions())
mass_actions = []
for action_dict in global_actions:
action_dict['roles'] = [x for x in user.get_roles() if x in action_dict.get('roles') or []]
# filter actions to get those that can be run by the user,
# either because of actual roles, or because the action is
# accessible to functions.
if not logged_users_role().id in action_dict.get('roles') or []:
action_dict['roles'] = [x for x in user.get_roles() if x in action_dict.get('roles') or []]
if action_dict['roles']:
# action is accessible with user roles, remove mentions of functions
action_dict['functions'] = []
if action_dict['functions'] or action_dict['roles']:
mass_actions.append(action_dict)
return mass_actions
def _q_index(self):
@ -1692,19 +1705,22 @@ class FormPage(Directory):
if get_request().get_query():
qs = '?' + get_request().get_query()
multi_actions = self.get_multi_actions(get_request().user)
multi_actions = self.get_multi_actions(get_request().user,
status_filter=selected_filter)
multi_form = Form(id='multi-actions')
for action in multi_actions:
attrs = {}
if action.get('functions'):
for function in action.get('functions'):
# dashes are replaced by underscores to prevent HTML5
# normalization to CamelCase.
attrs['data-visible_for_%s' % function.replace('-', '_')] = 'true'
else:
attrs['data-visible_for_all'] = 'true'
if getattr(action['action'], 'require_confirmation', False):
attrs['data-ask-for-confirmation'] = 'true'
multi_form.add_submit('button-action-%s' % action['action'].id, action['action'].name, attrs=attrs)
if not get_request().form.get('ajax') == 'true':
multi_form = Form(id='multi-actions')
for action in multi_actions:
attrs = {}
if action.get('functions'):
for function in action.get('functions'):
# dashes are replaced by underscores to prevent HTML5
# normalization to CamelCase.
attrs['data-visible_for_%s' % function.replace('-', '_')] = 'true'
else:
attrs['data-visible_for_all'] = 'true'
multi_form.add_submit('button-action-%s' % action['action'].id, action['action'].name, attrs=attrs)
if multi_form.is_submitted() and get_request().form.get('select[]'):
for action in multi_actions:
if multi_form.get_submit() == 'button-action-%s' % action['action'].id:
@ -1719,13 +1735,17 @@ class FormPage(Directory):
limit=int(limit), offset=int(offset), query=query,
order_by=order_by, criterias=criterias,
include_checkboxes=bool(multi_actions))
if get_response().status_code == 302:
# catch early redirect
return table
multi_form.widgets.append(HtmlWidget(table))
if not multi_actions:
multi_form.widgets.append(HtmlWidget('<div class="buttons"></div>'))
if get_request().form.get('ajax') == 'true':
get_response().filter = {'raw': True}
return table
return multi_form.render()
view_name = self.view.title if self.view else _('Listing')
html_top('management', '%s - %s' % (view_name, self.formdef.name))
@ -1735,11 +1755,7 @@ class FormPage(Directory):
r += get_session().display_message()
r += self.listing_top_actions()
r += htmltext('</div>')
if multi_actions:
multi_form.widgets.append(HtmlWidget(table))
r += multi_form.render()
else:
r += table
r += multi_form.render()
get_response().filter['sidebar'] = self.get_formdata_sidebar(qs) + \
self.get_fields_sidebar(selected_filter, fields, limit=limit,
@ -1765,7 +1781,13 @@ class FormPage(Directory):
publisher.substitutions.feed(publisher)
publisher.substitutions.feed(self.formdef)
publisher.substitutions.feed(formdata)
formdata.perform_global_action(self.action['action'].id, self.user)
if getattr(self.action['action'], 'status_action', False):
# manual jump action
from wcs.wf.jump import jump_and_perform
jump_and_perform(formdata, self.action['action'].action)
else:
# global action
formdata.perform_global_action(self.action['action'].id, self.user)
item_ids = get_request().form['select[]']
if '_all' in item_ids:

View File

@ -151,9 +151,19 @@ $(document).on('backoffice-filter-change', function(event, listing_settings) {
beforeSend: function() { $('#more-user-links, #listing, #statistics').addClass('activity'); },
complete: function() { $('#more-user-links, #listing, #statistics').removeClass('activity'); },
success: function(html) {
$('#page-links').remove();
$('#listing').replaceWith(html);
$('#statistics').replaceWith(html);
var $html = $(html);
var $listing = $html;
if ($listing.is('form')) {
// mass action
$listing = $listing.find('#listing');
$('#multi-actions div.buttons').replaceWith($html.find('div.buttons'));
$('#listing').replaceWith($listing);
$('#page-links').replaceWith($html.find('#page-links'));
} else {
$('#page-links').remove();
$('#listing').replaceWith($listing);
}
$('#statistics').replaceWith($html);
if (typeof(wcs_draw_graphs) !== 'undefined') {
wcs_draw_graphs();
}

View File

@ -469,7 +469,6 @@ class Workflow(StorableObject):
actions.append({'action': action, 'roles': roles, 'functions': functions})
return actions
def get_global_actions_for_user(self, formdata, user):
if not user:
return []
@ -1650,6 +1649,32 @@ class WorkflowStatus(object):
waitpoint = item.waitpoint or waitpoint
return bool(endpoint or waitpoint)
def get_status_manual_actions(self):
actions = []
status_id = self.id
class StatusAction:
def __init__(self, action):
self.id = 'st-%s' % action.identifier
self.status_id = status_id
self.action_id = action.identifier
self.name = action.get_label()
self.status_action = True
self.require_confirmation = action.require_confirmation
self.action = action
for action in self.items or []:
if not isinstance(action, ChoiceWorkflowStatusItem):
continue
if not action.identifier:
continue
roles = action.by
functions = [x for x in roles if x in self.parent.roles]
roles = [x for x in roles if x not in self.parent.roles]
if functions or roles:
actions.append({'action': StatusAction(action), 'roles': roles, 'functions': functions})
return actions
def get_contrast_color(self):
colour = self.colour or 'ffffff'
return misc.get_foreground_colour(colour)