/*
* Copyright 2023 Anyware Services
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/**
* Actions on Workflow transtitions
*/
Ext.define('Ametys.plugins.workflow.actions.WorkflowTransitionAction', {
singleton: true,
/**
* @private
* @property {String} _mode 'create', 'add' or 'edit'
*/
/**
* @private
* @property {String} _workflowName id of the workflow
*/
/**
* @private
* @property {String} _stepId id of current step parent
*/
/**
* @private
* @property {String} _actionId id of current transition
*/
/**
* @private
* @property {Ametys.window.DialogBox} _addDialogBox the addition dialog box for transitions
*/
/**
* @private
* @property {Ametys.window.DialogBox} _createOrEditDialogBox the common dialog box for transitions edition and creation
*/
/**
* Create a new transition
* @param {Ametys.ribbon.element.ui.ButtonController} controller The controller calling this function
*/
create: function(controller)
{
var target = Ametys.message.MessageTargetHelper.findTarget(controller.getMatchingTargets(), Ametys.message.MessageTarget.WORKFLOW_STEP);
if (!target)
{
return;
}
var targetParameters = target.getParameters();
this._stepId = targetParameters.id;
this._workflowName = targetParameters.workflowId;
this._mode = "create";
if (!this._createOrEditDialogBox)
{
this._createOrEditDialogBox = this._createDialogBox(this._getCommonItems());
}
else
{
this._createOrEditDialogBox.setTitle("{{i18n PLUGINS_WORKFLOW_CREATE_TRANSITION_DIALOG_TITLE}}");
}
Ametys.plugins.workflow.dao.WorkflowTransitionDAO.getTransitionInfos([this._workflowName, null], Ext.bind(this._setCreationFieldValues, this));
},
/**
* Create an existing transition to selected step
* @param {Ametys.ribbon.element.ui.ButtonController} controller The controller calling this function
*/
add: function(controller)
{
var target = Ametys.message.MessageTargetHelper.findTarget(controller.getMatchingTargets(), Ametys.message.MessageTarget.WORKFLOW_STEP);
if (!target)
{
return;
}
var targetParameters = target.getParameters();
this._stepId = targetParameters.id;
this._workflowName = targetParameters.workflowId;
this._mode = "add";
if (!this._addDialogBox)
{
this._addDialogBox = this._createDialogBox(this._getAddBoxItems());
}
var availableActions = this._addDialogBox.getComponent('availableAction');
availableActions.getStore().load({
callback: function(){
var selectByDefault = availableActions.getStore().getData().items[0];
if (selectByDefault)
{
availableActions.setValue(selectByDefault.getId());
}
else
{
availableActions.setValue("");
}
}
});
this._addDialogBox.show();
},
/**
* @private
* Get box fields for adding an existing transition
* @returns {Ext.form.field[]} the box items
*/
_getAddBoxItems: function()
{
return [
{
xtype: 'tagfield',
name: 'availableAction',
itemId: 'availableAction',
fieldLabel: "{{i18n PLUGINS_WORKFLOW_ADD_TRANSITION_AVAILABLE_LABEL}} *",
ametysDescription: "{{i18n PLUGINS_WORKFLOW_ADD_TRANSITION_AVAILABLE_DESC}}",
store: this._getAvailableActions(),
queryMode: 'local',
displayField: 'label',
valueField: 'id',
value: "-1",
allowBlank: false,
multiselect: true
}
];
},
/**
* @private
* Get common items between new and edit mode
* @returns {Ext.form.field[]} the box items
*/
_getCommonItems: function()
{
return [
{
xtype: "multilingualstring",
name: 'label',
itemId: 'label',
allowBlank: false,
fieldLabel: "{{i18n PLUGINS_WORKFLOW_CREATE_TRANSITION_DIALOG_LABEL}} *",
ametysDescription: "{{i18n PLUGINS_WORKFLOW_CREATE_TRANSITION_DIALOG_LABEL_DESC}}"
},
{
xtype: "numberfield",
name: 'id',
itemId: 'id',
allowBlank: false,
minValue: 0,
fieldLabel: "{{i18n PLUGINS_WORKFLOW_CREATE_TRANSITION_DIALOG_ID}} *",
ametysDescription: "{{i18n PLUGINS_WORKFLOW_CREATE_TRANSITION_DIALOG_ID_DESC}}",
allowDecimals: false,
validator: Ext.bind(this._idValidator, this)
},
{
xtype: "combobox",
name: 'finalStep',
itemId: 'finalStep',
fieldLabel: "{{i18n PLUGINS_WORKFLOW_CREATE_TRANSITION_FINAL_STATE_LABEL}} *",
ametysDescription: "{{i18n PLUGINS_WORKFLOW_CREATE_TRANSITION_DIALOG_FINAL_STATE_DESC}}",
store: this._getStates(),
queryMode: 'local',
displayField: 'label',
valueField: 'id',
allowBlank: false,
editable: false
}
];
},
_idValidator: function(id)
{
return this.unavailableIds.includes(parseInt(id))
? "{{i18n PLUGINS_WORKFLOW_CREATE_TRANSITION_DIALOG_ID_ERROR}}"
: true;
},
/**
* @private
* Get the final states combobox's store data
* @returns {Ext.data.Store} the final states store
*/
_getStates: function()
{
var store = Ext.create('Ext.data.Store', {
model: 'Ametys.plugins.workflow.models.WorkflowElementModel',
proxy: {
type: 'ametys',
role: 'org.ametys.plugins.workflow.dao.WorkflowStepDAO',
methodName: 'getStatesToJson',
methodArguments: ["workflowName", "actionId", "isInitialState"],
reader: {
type: 'json',
rootProperty: 'data'
}
},
autoLoad: true,
listeners: {
'beforeload': {fn: this._onBeforeLoad, scope: this},
}
})
return store;
},
/**
* @private
* Get the available actions combobox's store data
* @returns {Ext.data.Store} the available actions store
*/
_getAvailableActions: function()
{
var store = Ext.create('Ext.data.Store', {
model: 'Ametys.plugins.workflow.models.WorkflowElementModel',
proxy: {
type: 'ametys',
role: 'org.ametys.plugins.workflow.dao.WorkflowTransitionDAO',
methodName: 'getAvailableActions',
methodArguments: ["workflowName", "stepId"],
reader: {
type: 'json',
rootProperty: 'data'
}
},
autoLoad: true,
listeners: {
'beforeload': {fn: this._onBeforeLoad, scope: this},
}
})
return store;
},
/**
* @private
* Set the workflow parameter to the request
* @param {Ext.data.Store} store The store
* @param {Ext.data.operation.Operation} operation The object that will be passed to the Proxy to load the store
*/
_onBeforeLoad: function(store, operation)
{
operation.setParams(Ext.apply(operation.getParams() || {}, {
workflowName: this._workflowName,
stepId: parseInt(this._stepId),
actionId: this._actionId ? parseInt(this._actionId) : null,
isInitialState: this._isInitialStep()
}));
},
/**
* @private
* Return true if current step is the initial step
* @returns {Boolean} true if current step is the initial step
*/
_isInitialStep: function()
{
return this._stepId == "0";
},
/**
* @private
* Create a dialog box
* @param {Ext.form.field[]} items the fields for the box
* @return {Ametys.window.DialogBox} the dialog box
*/
_createDialogBox(items)
{
return Ext.create('Ametys.window.DialogBox', {
title : this._mode =="edit" ? "{{i18n PLUGINS_WORKFLOW_EDIT_TRANSITION_DIALOG_TITLE}}" : "{{i18n PLUGINS_WORKFLOW_CREATE_TRANSITION_DIALOG_TITLE}}",
iconCls: "ametysicon-squares",
width:400,
defaultFocus: 'label',
selectDefaultFocus: true,
layout: 'anchor',
defaults:
{
msgTarget: "side",
cls: 'ametys',
padding: 5,
anchor: '100%',
labelAlign: "top",
labelSeparator: "",
labelStyle: "font-weight:bold",
},
items : items,
closeAction: 'hide',
referenceHolder: true,
defaultButton: 'validate',
buttons : [
{
reference: 'validate',
text: "{{i18n PLUGINS_WORKFLOW_CREATE_DIALOG_OK_BTN}}",
handler: Ext.bind(this._ok, this)
},
{
text: "{{i18n PLUGINS_WORKFLOW_CREATE_DIALOG_CANCEL_BTN}}",
handler: Ext.bind(this._hideDialogBox, this)
}
]
});
},
/**
* @private
* Create or edit transition with field values
*/
_ok: function ()
{
if (this._mode =="edit" || this._mode == "create")
{
if (this._hasInvalid(this._createOrEditDialogBox))
{
return;
}
var id = this._createOrEditDialogBox.getComponent('id').getValue();
var label = this._createOrEditDialogBox.getComponent('label').getValue();
var finalStepId = this._createOrEditDialogBox.getComponent('finalStep').getValue();
if (this._mode == "create")
{
Ametys.plugins.workflow.dao.WorkflowTransitionDAO.createTransition([this._workflowName, parseInt(this._stepId), id, label, finalStepId], Ext.bind(this._hideDialogBox, this));
}
else
{
var actionId = parseInt(this._actionId);
this._actionId = null
Ametys.plugins.workflow.dao.WorkflowTransitionDAO.editTransition([this._workflowName, parseInt(this._stepId), actionId, id, label, finalStepId], Ext.bind(this._hideDialogBox, this));
}
}
else
{
var availableActionField = this._addDialogBox.getComponent('availableAction');
if (!availableActionField.isValid())
{
return;
}
var records = availableActionField.getValueRecords();
var transitionIds = [];
for (var i = 0; i < records.length; i++)
{
transitionIds.push(records[i].data.id)
}
Ametys.plugins.workflow.dao.WorkflowTransitionDAO.addTransitions([this._workflowName, parseInt(this._stepId), transitionIds], Ext.bind(this._hideDialogBox, this));
}
},
/**
* @private
* Return true if a field is invalid
* {Ametys.window.DialogBox} the dialog box
*/
_hasInvalid: function(box)
{
var fields = box.query("[isFormField]");
var hasInvalid = false;
for (let field of fields)
{
if (!field.isValid())
{
hasInvalid = true;
}
}
return hasInvalid;
},
/**
* Hide the dialog box
* @param {Object} response the server response
* @param {String} response.message an error message if defined
*/
_hideDialogBox: function (response)
{
if (!response || !response.message)
{
var dialogBox = this._mode == "add" ? this._addDialogBox : this._createOrEditDialogBox;
dialogBox.hide();
}
},
/**
* @private
* Set the create or edit box's fields initial values
* @param {Object} response the server response
* @param {String} response.labels the transitions's labels
* @param {String} response.id the transitions's id
* @param {String[]} response.ids an array of all the workflow's transitions id
*/
_setCreationFieldValues: function(response)
{
var label = response.labels;
this.unavailableIds = response.ids;
this._createOrEditDialogBox.getComponent('label').setValue(!Ext.isEmpty(label) ? label : '');
var finalStep = this._createOrEditDialogBox.getComponent('finalStep');
finalStep.getStore().load({
callback: function(){
var selectByDefault = finalStep.getStore().getData().items[0];
if (selectByDefault)
{
finalStep.setValue(selectByDefault.getId());
}
}
});
//generate unique id
var id = this._getUniqueId()
this._createOrEditDialogBox.getComponent('id').setValue(id);
this._createOrEditDialogBox.show();
},
/**
* @private
* Get a unique id
* @returns {Number} a unique id
*/
_getUniqueId: function()
{
var id;
var i = 1;
while (Ext.isEmpty(id))
{
if(!this.unavailableIds.includes(i))
{
id = i;
}
i++;
}
return id;
},
/**
* Edit the transition
* @param {Ametys.message.MessageTarget} target The transition target
*/
edit: function(target)
{
var targetParameters = target.getParameters();
this._actionId = targetParameters.id;
this._stepId = targetParameters.stepId;
this._workflowName = targetParameters.workflowId;
this._mode = "edit";
if (!this._createOrEditDialogBox)
{
this._createOrEditDialogBox = this._createDialogBox(this._getCommonItems());
}
else
{
this._createOrEditDialogBox.setTitle("{{i18n PLUGINS_WORKFLOW_EDIT_TRANSITION_DIALOG_TITLE}}");
}
Ametys.plugins.workflow.dao.WorkflowTransitionDAO.getTransitionInfos([this._workflowName, parseInt(this._actionId)], Ext.bind(this._setEditionFieldValues, this));
},
/**
* @private
* Set the edition form's fields with current transition values
* @param {Object} response the server response
* @param {String} response.labels the transitions's label
* @param {String} response.id the transitions's id
* @param {String} response.finalStep the transitions's iunconditional step
* @param {String[]} response.ids an array of all the workflow's transitions id
*/
_setEditionFieldValues: function(response)
{
var label = response.labels;
var id = response.id;
var finalStep = response.finalStep;
var finalStepCombo = this._createOrEditDialogBox.getComponent('finalStep');
this.unavailableIds = response.ids;
this._createOrEditDialogBox.getComponent('label').setValue(!Ext.isEmpty(label) ? label : '');
this._createOrEditDialogBox.getComponent('id').setValue(id);
finalStepCombo.getStore().load({
callback: function()
{
finalStepCombo.setValue(finalStep);
}
});
this._createOrEditDialogBox.show();
},
/**
* Remove transition from parent step
* @param {Ametys.message.MessageTarget} target The transition target
* @param {Ametys.message.MessageTarget} stepTarget The parent step target
*/
removeTransition: function(target, stepTarget)
{
var targetParameters = target.getParameters();
this._actionId = targetParameters.id;
this._stepId = targetParameters.stepId;
this._workflowName = targetParameters.workflowId;
Ametys.plugins.workflow.dao.WorkflowTransitionDAO.getNumberOfUse([this._workflowName, parseInt(this._actionId)], Ext.bind(this._removeTransitionCB, this, [target, stepTarget], 1))
},
/**
* @private
* Callback of removeTransition, send confirmation message for removal
* @param {Number} numberOfUses, how many steps use this transition
* @param {Ametys.message.MessageTarget} target the transition to remove
* @param {Ametys.message.MessageTarget} stepTarget The parent step target
*/
_removeTransitionCB(numberOfUses, target, stepTarget)
{
var actionLabel = target.getParameters().label;
var stepLabel = stepTarget.getParameters().label;;
var msg = Ext.String.format("{{i18n PLUGINS_WORKFLOW_DELETE_TRANSITION_CONFIRM}}", actionLabel, this._actionId, stepLabel, this._stepId);
if (numberOfUses == 1)
{
msg += "{{i18n PLUGINS_WORKFLOW_DELETE_TRANSITION_DEFINITLY_CONFIRM}}";
}
Ametys.Msg.confirm("{{i18n PLUGINS_WORKFLOW_DELETE_TRANSITION_LABEL}}",
msg,
Ext.bind(this._doRemove, this, [target], 1),
this
);
},
/**
* @private
* Proceed to the transition removal
* @param {String} btn The pressed button. Can only be 'yes'/'no'
* @param {Ametys.message.MessageTarget} target The transition target to remove.
*/
_doRemove: function(btn, target)
{
if (btn == 'yes')
{
var id = parseInt(this._actionId);
this._actionId = null
Ametys.plugins.workflow.dao.WorkflowTransitionDAO.removeTransition([this._workflowName, parseInt(this._stepId), id]);
}
}
});
/**
* Data model for filling workflow element combobox store
*/
Ext.define('Ametys.plugins.workflow.models.WorkflowElementModel', {
extend: 'Ext.data.Model',
fields: [
{name: 'id', type: 'number'},
{name: 'label', type: 'string'}
]
});