workflows: display triggers dynamically in external workflow action (#51244) #970

Merged
fpeters merged 1 commits from wip/51244-external-workflow-action-button-label into main 2024-01-29 12:11:43 +01:00
3 changed files with 85 additions and 36 deletions

View File

@ -3005,6 +3005,8 @@ def test_workflows_external_workflow_action_config(pub):
trigger = action.append_trigger('webservice')
trigger.identifier = 'test'
action.add_action('remove')
trigger = action.append_trigger('webservice')
trigger.identifier = 'test2'
external_wf.store()
FormDef.wipe()
@ -3013,6 +3015,11 @@ def test_workflows_external_workflow_action_config(pub):
formdef.workflow = external_wf
formdef.store()
formdef2 = FormDef()
formdef2.name = 'other'
formdef2.workflow = external_wf
formdef2.store()
wf = Workflow(name='foo')
st = wf.add_status('New')
st.add_action('external_workflow_global_action')
@ -3020,21 +3027,16 @@ def test_workflows_external_workflow_action_config(pub):
app = login(get_app(pub))
resp = app.get('/backoffice/workflows/%s/status/%s/items/1/' % (wf.id, st.id))
# only action error: custom error message
resp.forms[0]['slug'] = 'formdef:external'
resp = resp.forms[0].submit('submit')
assert 'There were errors processing your form. See below for details.' not in resp
assert 'This action is configured in two steps. See below for details.' in resp
assert 'required field' in resp
# multiple errors: do as usual
resp.forms[0]['slug'] = 'formdef:external'
resp.forms[0]['condition$type'] = 'django'
resp.forms[0]['condition$value_django'] = '{{ 42 }}'
assert [
(x.attrib.get('value'), x.attrib.get('data-slugs'))
for x in resp.pyquery('#form_trigger_id option[data-slugs]')
] == [
('action:test', 'formdef:external|formdef:other'),
('action:test2', 'formdef:external|formdef:other'),
]
resp = resp.forms[0].submit('submit')
assert 'There were errors processing your form. See below for details.' in resp
assert 'This action is configured in two steps. See below for details.' not in resp
assert 'required field' in resp
assert "syntax error: Could not parse the remainder: '{{' from '{{'" in resp
def test_workflows_create_formdata(pub):

View File

@ -44,6 +44,35 @@ $(function() {
this.jumpto_button.hide();
}
}).trigger('change');
$('[data-filter-trigger-select]').on('change', function() {
var option = $(this).val();
$('#form_trigger_id option').each(function(idx, elem) {
if (elem.dataset.slugs === undefined) return;
var option_slugs = elem.dataset.slugs.split('|');
if (option == '' || option_slugs.indexOf(option) == -1) {
$(elem).hide();
} else {
$(elem).show();
}
});
if ($('#form_trigger_id option:selected')[0].style.display == 'none') {
$('#form_trigger_id').val('');
}
});
if ($('#form_trigger_id').length) {
var current_objectdef = $('[data-filter-trigger-select]').val();
var current_val = $('#form_trigger_id').val();
$('[data-filter-trigger-select]').trigger('change');
if (current_val) { // may have been reset when filtering
$('#form_trigger_id option').each(function(idx, elem) {
if (elem.dataset.slugs === undefined) return;
var option_slugs = elem.dataset.slugs.split('|');
if (elem.value == current_val && option_slugs.indexOf(current_objectdef) !== -1) {
$('#form_trigger_id')[0].selectedIndex = idx;
}
});
}
}
/* focus tab with error */
$('.form-with-tabs .error').first().closest('[role=tabpanel]').each(function(idx, elem) {

View File

@ -22,7 +22,7 @@ from quixote.html import TemplateIO, htmltext
from wcs.carddef import CardDef
from wcs.formdef import FormDef
from wcs.qommon import _
from wcs.qommon.form import ComputedExpressionWidget, Form, RadiobuttonsWidget, SingleSelectWidget
from wcs.qommon.form import ComputedExpressionWidget, RadiobuttonsWidget, SingleSelectWidget
from wcs.variables import LazyFormData, LazyFormDefObjectsManager
from wcs.workflows import (
AbortOnRemovalException,
@ -115,22 +115,51 @@ class ExternalWorkflowGlobalAction(WorkflowStatusItem):
def add_parameters_widgets(self, form, parameters, prefix='', formdef=None, **kwargs):
super().add_parameters_widgets(form, parameters, prefix=prefix, formdef=formdef, **kwargs)
if 'slug' in parameters:
if 'slug' in parameters or 'trigger_id' in parameters:
objects = [(None, '---', '', {})]
trigger_options = []
is_admin_accessible = {
'forms': get_publisher().get_backoffice_root().is_accessible('forms'),
'cards': get_publisher().get_backoffice_root().is_accessible('cards'),
}
for wf in Workflow.select():
if any(self.get_workflow_webservice_triggers(wf)):
for objectdef in wf.formdefs(lightweight=True) + wf.carddefs(lightweight=True):
object_slug = '%s:%s' % (objectdef.__class__.__name__.lower(), objectdef.url_name)
objects.append((object_slug, objectdef.name, object_slug, {}))
if is_admin_accessible[objectdef.backoffice_section]:
objects[-1][-1]['data-goto-url'] = objectdef.get_admin_url()
# preload all cards/forms
objectdefs = FormDef.select(order_by='id', lightweight=True) + CardDef.select(
order_by='id', lightweight=True
)
# get workflows with external actions
workflows = {}
for workflow in Workflow.select(ignore_migration=True):
external_triggers = list(self.get_workflow_webservice_triggers(workflow))
if external_triggers:
workflows[workflow.id] = workflow
for trigger in external_triggers:
object_slugs = [
f'{x.__class__.__name__.lower()}:{x.url_name}'
for x in objectdefs
if x.workflow_id == workflow.id
]
trigger_id = 'action:%s' % trigger.identifier
trigger_options.append(
(trigger_id, trigger.parent.name, trigger_id, {'data-slugs': '|'.join(object_slugs)})
)
# list cards/forms with workflows with external actions
for objectdef in objectdefs:
workflow = workflows.get(objectdef.workflow_id)
if not workflow:
continue
object_slug = '%s:%s' % (objectdef.__class__.__name__.lower(), objectdef.url_name)
objects.append((object_slug, objectdef.name, object_slug, {}))
if is_admin_accessible[objectdef.backoffice_section]:
objects[-1][-1]['data-goto-url'] = objectdef.get_admin_url()
if len(objects) == 1:
form.add_global_errors([_('No workflow with external triggerable global action.')])
return
if 'slug' in parameters:
objects.sort(key=lambda x: x[1])
form.add(
SingleSelectWidget,
@ -139,6 +168,7 @@ class ExternalWorkflowGlobalAction(WorkflowStatusItem):
value=self.slug,
required=True,
options=objects,
**{'data-filter-trigger-select': 'true'},
)
if 'target_mode' in parameters:
@ -169,32 +199,20 @@ class ExternalWorkflowGlobalAction(WorkflowStatusItem):
allow_python=getattr(self, 'allow_python', True),
)
if 'trigger_id' in parameters and form.get('%sslug' % prefix):
object_def = self.get_object_def(form.get('%sslug' % prefix))
if not object_def:
return
triggers_names = [(None, '---', '')]
for trigger in self.get_workflow_webservice_triggers(object_def.workflow):
trigger_id = 'action:%s' % trigger.identifier
triggers_names.append((trigger_id, trigger.parent.name, trigger_id))
if 'trigger_id' in parameters:
trigger_options.sort(key=lambda x: x[1])
form.add(
SingleSelectWidget,
'%strigger_id' % prefix,
title=_('Action'),
value=self.trigger_id,
required=True,
options=triggers_names,
options=[(None, '---', '', {})] + trigger_options,
)
if kwargs.get('orig') == 'variable_widget':
return
errors = [w.name for w in form.get_all_widgets() if w.has_error()]
if set(errors) == {'%strigger_id' % prefix}:
form.ERROR_NOTICE = _('This action is configured in two steps. See below for details.')
else:
form.ERROR_NOTICE = Form.ERROR_NOTICE
def get_line_details(self):
if self.slug and self.trigger_id:
objectdef = self.get_object_def()