import '../Calculator/Calculator.es';
import '../Page/PageRenderer.es';
import 'clay-alert';
import 'clay-button';
import 'clay-modal';
import Component from 'metal-component';
import Soy from 'metal-soy';
import templates from './RuleEditor.soy.js';
import {Config} from 'metal-state';
import {getFieldProperty} from '../LayoutProvider/util/fields.es';
import {makeFetch} from '../../util/fetch.es';
import {maxPageIndex, pageOptions} from '../../util/pageSupport.es';
import {PagesVisitor} from '../../util/visitors.es';
const fieldOptionStructure = Config.shapeOf(
{
dataType: Config.string(),
name: Config.string(),
options: Config.arrayOf(
Config.shapeOf(
{
label: Config.string(),
name: Config.string(),
value: Config.string()
}
)
),
type: Config.string(),
value: Config.string()
}
);
/**
* RuleEditor.
* @extends Component
*/
class RuleEditor extends Component {
static STATE = {
actions: Config.arrayOf(
Config.shapeOf(
{
action: Config.string(),
calculatorFields: Config.arrayOf(fieldOptionStructure).value([]),
expression: Config.string(),
hasRequiredInputs: Config.bool(),
inputs: Config.object(),
label: Config.string(),
outputs: Config.object(),
target: Config.string()
}
)
).internal().setter('_setActions').value([]),
actionsFieldOptions: Config.arrayOf(fieldOptionStructure).internal().valueFn('_actionsFieldOptionsValueFn'),
actionTypes: Config.arrayOf(
Config.shapeOf(
{
label: Config.string(),
value: Config.string()
}
)
).internal().value(
[
{
label: Liferay.Language.get('show'),
value: 'show'
},
{
label: Liferay.Language.get('enable'),
value: 'enable'
},
{
label: Liferay.Language.get('require'),
value: 'require'
},
{
label: Liferay.Language.get('autofill'),
value: 'auto-fill'
},
{
label: Liferay.Language.get('calculate'),
value: 'calculate'
},
{
label: Liferay.Language.get('jump-to-page'),
value: 'jump-to-page'
}
]
),
/**
* Used for tracking which action we are currently focused on
* when trying to delete an action.
* @default 0
* @instance
* @memberof RuleEditor
* @type {Number}
*/
activeActionIndex: Config.number().value(-1),
/**
* Used for tracking which condition we are currently focused on
* when trying to delete a condition.
* @default 0
* @instance
* @memberof RuleEditor
* @type {Number}
*/
activeConditionIndex: Config.number().value(-1),
calculatorFunctions: Config.arrayOf(
Config.shapeOf(
{
label: Config.string(),
tooltip: Config.string(),
value: Config.string()
}
)
).internal().value([]),
calculatorResultOptions: Config.arrayOf(fieldOptionStructure).internal().valueFn('_calculatorResultOptionsValueFn'),
/**
* @default 0
* @instance
* @memberof RuleEditor
* @type {?array}
*/
conditions: Config.arrayOf(
Config.shapeOf(
{
operands: Config.arrayOf(
Config.shapeOf(
{
dataType: Config.string(),
label: Config.string(),
repeatable: Config.bool(),
type: Config.string(),
value: Config.string()
}
)
),
operator: Config.string()
}
)
).internal().setter('_setConditions').value([]),
conditionsFieldOptions: Config.arrayOf(fieldOptionStructure).internal().valueFn('_conditionsFieldOptionsValueFn'),
dataProvider: Config.arrayOf(
Config.shapeOf(
{
id: Config.string(),
name: Config.string(),
uuid: Config.string()
}
)
).internal(),
dataProviderInstanceParameterSettingsURL: Config.string().required(),
dataProviderInstancesURL: Config.string().required(),
deletedFields: Config.arrayOf(Config.string()).value([]),
fixedOptions: Config.arrayOf(
fieldOptionStructure
).value(
[
{
dataType: 'user',
label: Liferay.Language.get('user'),
name: 'user',
value: 'user'
}
]
),
functionsMetadata: Config.shapeOf(
{
number: Config.arrayOf(
Config.shapeOf(
{
label: Config.string(),
name: Config.string(),
parameterTypes: Config.array(),
returnType: Config.string()
}
)
),
text: Config.arrayOf(
Config.shapeOf(
{
label: Config.string(),
name: Config.string(),
parameterTypes: Config.array(),
returnType: Config.string()
}
)
),
user: Config.arrayOf(
Config.shapeOf(
{
label: Config.string(),
name: Config.string(),
parameterTypes: Config.array(),
returnType: Config.string()
}
)
)
}
),
functionsURL: Config.string(),
invalidRule: Config.bool().value(true),
loadingDataProviderOptions: Config.bool(),
logicalOperator: Config.string().internal().value('or'),
pageOptions: Config.arrayOf(
Config.shapeOf(
{
dataType: Config.string(),
name: Config.string(),
options: Config.arrayOf(
Config.shapeOf(
{
label: Config.string(),
name: Config.string(),
value: Config.string()
}
)
),
type: Config.string(),
value: Config.string()
}
)
).internal().value([]),
pages: Config.array().required(),
readOnly: Config.bool().value(false),
roles: Config.arrayOf(
Config.shapeOf(
{
id: Config.string(),
name: Config.string()
}
)
).valueFn('_rolesValueFn'),
/**
* @default 0
* @instance
* @memberof RuleEditor
* @type {?array}
*/
rule: Config.shapeOf(
{
actions: Config.arrayOf(
Config.shapeOf(
{
action: Config.string(),
calculatorFields: Config.arrayOf(fieldOptionStructure).value([]),
ddmDataProviderInstanceUUID: Config.string(),
expression: Config.string(),
inputs: Config.object(),
label: Config.string(),
outputs: Config.object(),
target: Config.string()
}
)
),
conditions: Config.arrayOf(
Config.shapeOf(
{
operands: Config.arrayOf(
Config.shapeOf(
{
label: Config.string(),
repeatable: Config.bool(),
type: Config.string(),
value: Config.string()
}
)
),
operator: Config.string()
}
)
),
['logical-operator']: Config.string()
}
),
ruleEditedIndex: Config.number(),
secondOperandList: Config.arrayOf(
Config.shapeOf(
{
name: Config.string(),
value: Config.string()
}
)
).value(
[
{
value: Liferay.Language.get('value')
},
{
name: 'field',
value: Liferay.Language.get('other-field')
}
]
),
/**
* @default undefined
* @instance
* @memberof RuleEditor
* @type {!string}
*/
spritemap: Config.string().required()
}
convertAutoFillDataToArray(action, type) {
const data = action[type];
const originalData = action[`${type}Data`];
return Object.keys(data).map(
(key, index) => {
const {label, name, required, type} = originalData[index];
const fieldsTypes = this.getTypesByFieldType(type);
const actionsFieldOptions = this.getFieldsByTypes(this.actionsFieldOptions, fieldsTypes);
return {
actionsFieldOptions,
label,
name,
required,
type,
value: data[key]
};
}
);
}
created() {
if (this.rule) {
this._prepareRuleEditor();
}
this._fetchFunctionsURL();
this._setDataProviderTarget();
this._setActionsInputsOutputs();
}
disposed() {
this.setState(
{
actions: [],
conditions: [],
logicalOperator: ''
}
);
}
formatDataProviderParameter(actionParameters, parameters) {
return parameters.reduce(
(result, {name, value}, index) => ({
...result,
[name]: Object.keys(actionParameters).indexOf(name) !== -1 ? actionParameters[name] : value
}),
{}
);
}
getDataProviderOptions(id, index) {
let promise;
if (id) {
promise = this._fetchDataProviderParameters(id, index)
.then(
({inputs, outputs}) => {
let actions = this.actions;
if (!this.isDisposed()) {
actions = actions.map(
(action, currentIndex) => {
return index == currentIndex ? ({
...action,
ddmDataProviderInstanceUUID: this.dataProvider.find(
data => {
return data.id == id;
}
).uuid,
hasRequiredInputs: inputs.some(
input => input.required
),
inputs: this.formatDataProviderParameter(action.inputs, inputs),
inputsData: inputs,
outputs: this.formatDataProviderParameter(action.outputs, outputs),
outputsData: outputs
}) : action;
}
);
}
return actions[index];
}
).catch(
error => {
throw new Error(error);
}
);
}
else {
promise = Promise.resolve(this.actions[index]);
}
return promise;
}
getFieldOptions(fieldName) {
let options = [];
const visitor = new PagesVisitor(this.pages);
const field = visitor.findField(
field => {
return field.fieldName === fieldName;
}
);
options = field.options;
return options;
}
getFieldsByTypes(fields, types) {
return fields.filter(field => types.some(fieldType => field.type == fieldType));
}
getTypesByFieldType(fieldType) {
let list = [];
if (fieldType == 'list') {
list = ['checkbox_multiple', 'radio', 'select'];
}
else if (fieldType == 'text') {
list = [
'checkbox_multiple',
'date',
'numeric',
'radio',
'select',
'text'
];
}
else if (fieldType == 'number') {
list = ['numeric'];
}
return list;
}
isValueOperand({type}) {
return type !== 'field' && type !== 'user';
}
populateActionTargetValue(id, index) {
const {actions, pageOptions} = this;
const previousTarget = actions[index].target;
actions[index].target = id;
actions[index].label = id;
if (actions[index].action == 'jump-to-page') {
const selectedOption = pageOptions.find(
option => {
return option.value == id;
}
);
actions[index].label = selectedOption.label;
}
if (id === undefined) {
actions[index].target = '';
}
else if (id === '') {
actions[index].inputs = {};
actions[index].outputs = {};
actions[index].hasRequiredInputs = false;
}
else if (previousTarget !== id && actions[index].action == 'calculate') {
actions[index].calculatorFields = this._updateCalculatorFields(this.actions[index], id);
}
this.setState(
{
actions
}
);
}
populateDataProviderOptions(id, index) {
const {actions} = this;
actions[index].target = id;
actions[index].label = id;
this.getDataProviderOptions(id, index).then(
actionData => {
if (!this.isDisposed()) {
this.setState(
{
actions: actions.map(
(action, currentIndex) => {
return index == currentIndex ? actionData : action;
}
)
}
);
}
}
).catch(
error => {
throw new Error(error);
}
);
}
prepareStateForRender(state) {
const {pages} = this;
const actions = state.loadingDataProviderOptions ? [] : state.actions.map(
action => ({
...action,
inputs: action.inputs ? this.convertAutoFillDataToArray(action, 'inputs') : [],
outputs: action.outputs ? this.convertAutoFillDataToArray(action, 'outputs') : []
})
);
const conditions = state.conditions.map(
condition => {
const fieldName = condition.operands[0].value;
let firstOperandOptions = [];
let operators = [];
if (fieldName) {
const {dataType} = this._getFieldTypeByFieldName(fieldName);
operators = this._getOperatorsByFieldType(dataType);
firstOperandOptions = this.getFieldOptions(fieldName);
}
return {
...condition,
binaryOperator: this._isBinary(condition.operator),
firstOperandOptions,
operands: condition.operands.map(
(operand, index) => {
if (index === 1 && this.isValueOperand(operand)) {
operand = {
...operand,
dataType: getFieldProperty(pages, condition.operands[0].value, 'dataType'),
type: getFieldProperty(pages, condition.operands[0].value, 'type')
};
}
return operand;
}
),
operators
};
}
);
let {actionTypes} = this;
if (pages.length < 3) {
actionTypes = this.actionTypes.filter(
obj => {
return obj.value !== 'jump-to-page';
}
);
}
return {
...state,
actions,
actionTypes,
conditions
};
}
syncPages(pages) {
const {actions} = this;
let {conditions} = this;
const visitor = new PagesVisitor(pages);
conditions.forEach(
(condition, index) => {
let firstOperandFieldExists = false;
const secondOperand = condition.operands[1];
let secondOperandFieldExists = false;
visitor.mapFields(
({fieldName}) => {
if (condition.operands[0].value === fieldName) {
firstOperandFieldExists = true;
}
if (secondOperand && secondOperand.value === fieldName) {
secondOperandFieldExists = true;
}
}
);
if (condition.operands[0].value === 'user') {
firstOperandFieldExists = true;
}
if (!firstOperandFieldExists) {
conditions = this._clearAllConditionFieldValues(index);
}
if (!secondOperandFieldExists && secondOperand && secondOperand.type == 'field') {
conditions = this._clearSecondOperandValue(conditions, index);
}
}
);
const maxPage = maxPageIndex(conditions, pages);
this.setState(
{
actions: this._syncActions(actions),
actionsFieldOptions: this._actionsFieldOptionsValueFn(),
calculatorResultOptions: this._calculatorResultOptionsValueFn(),
conditions,
conditionsFieldOptions: this._conditionsFieldOptionsValueFn(['paragraph']),
deletedFields: this._getDeletedFields(visitor),
pageOptions: pageOptions(pages, maxPage),
roles: this._rolesValueFn()
}
);
}
syncVisible(visible) {
const addButton = document.querySelector('#addFieldButton');
super.syncVisible(visible);
if (addButton && visible) {
addButton.classList.add('hide');
}
}
willUpdate() {
this.setState(
{
invalidRule: !this._validateConditionsFilling() || !this._validateActionsFilling()
}
);
}
_calculatorResultOptionsValueFn() {
const {pages} = this;
const fields = [];
const visitor = new PagesVisitor(pages);
visitor.mapFields(
field => {
if (field.type == 'numeric') {
fields.push(
{
...field,
options: field.options ? field.options : [],
value: field.fieldName
}
);
}
}
);
return fields;
}
_clearAction(index) {
const {actions} = this;
return actions.map(
(action, currentIndex) => {
return currentIndex === index ? {
action: '',
calculatorFields: [],
expression: '',
inputs: {},
label: '',
outputs: {},
target: ''
} : action;
}
);
}
_clearAllConditionFieldValues(index) {
const {secondOperandSelectedList} = this;
let {conditions} = this;
conditions = this._clearFirstOperandValue(conditions, index);
conditions = this._clearOperatorValue(conditions, index);
conditions = this._clearSecondOperandValue(conditions, index);
this.setState(
{
conditions,
secondOperandSelectedList
}
);
return conditions;
}
_clearFirstOperandValue(conditions, index) {
if (conditions[index] && conditions[index].operands[0]) {
conditions[index].operands[0].type = '';
conditions[index].operands[0].value = '';
}
return conditions;
}
_clearOperatorValue(conditions, index) {
if (conditions[index]) {
conditions[index].operator = '';
}
return conditions;
}
_clearSecondOperandValue(conditions, index) {
if (conditions[index] && conditions[index].operands[1]) {
conditions[index].operands = [conditions[index].operands[0]];
}
return conditions;
}
_clearSelectedSecondOperand(secondOperandSelectedList, index) {
return secondOperandSelectedList;
}
_fetchDataProviderParameters(id) {
const {dataProviderInstanceParameterSettingsURL} = this;
return makeFetch(
{
method: 'GET',
url: `${dataProviderInstanceParameterSettingsURL}?ddmDataProviderInstanceId=${id}`
}
).catch(
error => {
throw new Error(error);
}
);
}
_fetchFunctionsURL() {
const {functionsURL} = this;
makeFetch(
{
method: 'GET',
url: functionsURL
}
).then(
responseData => {
if (!this.isDisposed()) {
this.setState(
{
calculatorFunctions: responseData
}
);
}
}
).catch(
error => {
throw new Error(error);
}
);
}
_actionsFieldOptionsValueFn() {
return this._getFieldOptions();
}
_conditionsFieldOptionsValueFn(omittedFieldsList) {
return this._getFieldOptions(omittedFieldsList);
}
_getDeletedFields(visitor) {
const existentFields = [];
const {actionsFieldOptions} = this;
let deletedFields = [];
actionsFieldOptions.forEach(
field => {
visitor.mapFields(
({fieldName}) => {
if (field.fieldName === fieldName) {
existentFields.push(fieldName);
}
}
);
}
);
const oldFields = actionsFieldOptions.map(field => field.fieldName);
deletedFields = oldFields.filter(
field => {
return existentFields.indexOf(field) > -1 ? false : field;
}
);
return deletedFields;
}
_getFieldOptions(omittedFieldsList = []) {
const {pages} = this;
const fields = [];
const visitor = new PagesVisitor(pages);
visitor.mapFields(
field => {
if (omittedFieldsList.indexOf(field.type) < 0) {
fields.push(
{
...field,
options: field.options ? field.options : [],
value: field.fieldName
}
);
}
}
);
return fields;
}
_getFieldLabel(fieldName) {
const pages = this.pages;
let fieldLabel;
if (pages) {
for (let page = 0; page < pages.length; page++) {
const rows = pages[page].rows;
for (let row = 0; row < rows.length; row++) {
const cols = rows[row].columns;
for (let col = 0; col < cols.length; col++) {
const fields = cols[col].fields;
for (let field = 0; field < fields.length; field++) {
if (pages[page].rows[row].columns[col].fields[field].fieldName === fieldName) {
fieldLabel = pages[page].rows[row].columns[col].fields[field].label;
break;
}
}
}
}
}
}
return fieldLabel;
}
_getFieldTypeByFieldName(fieldName) {
let dataType = '';
let repeatable = false;
let type = '';
if (fieldName === 'user') {
dataType = 'user';
}
else {
const selectedField = this.actionsFieldOptions.find(
field => field.value === fieldName
);
if (selectedField) {
dataType = selectedField.dataType;
repeatable = selectedField.repeatable;
type = selectedField.type;
}
}
return {dataType, repeatable, type};
}
_getIndex(fieldInstance, fieldClass) {
const firstOperand = fieldInstance.element.closest(fieldClass);
return firstOperand.getAttribute(`${fieldClass.substring(1)}-index`);
}
_getOperatorsByFieldType(fieldType) {
if (fieldType === 'integer' || fieldType === 'double') {
fieldType = 'number';
}
if (!this.functionsMetadata.hasOwnProperty(fieldType)) {
fieldType = 'text';
}
return this.functionsMetadata[fieldType].map(
metadata => {
return {
...metadata,
value: metadata.name
};
}
);
}
_handleActionAdded() {
const {actions} = this;
const newAction = {action: '', calculatorFields: [], expression: '', inputs: {}, label: '', outputs: {}, target: ''};
if (actions.length == 0) {
actions.push(newAction);
}
actions.push(newAction);
this.setState(
{
actions,
invalidRule: true
}
);
}
_handleActionSelection(event) {
const {fieldInstance, value} = event;
const index = parseInt(this._getIndex(fieldInstance, '.action-type'), 10);
const {actions, conditions} = this;
let newActions = [...actions];
if (value && value.length > 0 && value[0]) {
const fieldName = value[0];
if (actions.length > 0) {
const previousAction = actions[index].action;
if (fieldName !== previousAction) {
newActions = newActions.map(
(action, currentIndex) => {
return currentIndex === index ? {
...action,
action: fieldName,
calculatorFields: [],
label: '',
source: conditions[0].operands[0].source,
target: ''
} : action;
}
);
}
}
else {
newActions.push({action: fieldName});
}
}
else {
newActions = this._clearAction(index);
}
this.setState(
{
actions: newActions
}
);
}
_handleCancelRule(event) {
this.emit(
'ruleCancel',
{}
);
}
_handleConditionAdded() {
const {conditions} = this;
conditions.push(
{
operands: [
{
type: '',
value: ''
}
],
operator: ''
}
);
this.setState(
{
conditions
}
);
}
_handleDataProviderInputEdited(event) {
const {fieldInstance, value} = event;
const {actions} = this;
const actionIndex = this._getIndex(fieldInstance, '.action');
const inputIndex = this._getIndex(fieldInstance, '.container-input-field');
const editedInput = Object.keys(actions[actionIndex].inputs)[inputIndex];
actions[actionIndex].inputs[editedInput] = value[0];
this.setState(
{
actions
}
);
}
_handleDataProviderOutputEdited(event) {
const {fieldInstance, value} = event;
const actionIndex = this._getIndex(fieldInstance, '.action');
const outputIndex = this._getIndex(fieldInstance, '.container-output-field');
const {actions} = this;
const editedOutput = Object.keys(actions[actionIndex].outputs)[outputIndex];
actions[actionIndex].outputs[editedOutput] = value[0];
this.setState(
{
actions
}
);
}
_handleDeleteAction(event) {
const {currentTarget} = event;
const index = currentTarget.getAttribute('data-index');
this.refs.confirmationModalAction.show();
this.setState(
{
activeActionIndex: parseInt(index, 10)
}
);
}
_handleDeleteCondition(event) {
const {currentTarget} = event;
const index = currentTarget.getAttribute('data-index');
this.refs.confirmationModalCondition.show();
this.setState(
{
activeConditionIndex: parseInt(index, 10)
}
);
}
_handleEditExpression({expression, index}) {
const {actions} = this;
actions[index].expression = expression;
this.setState({actions});
}
_handleFirstOperandFieldEdited(event) {
const {fieldInstance, value} = event;
const index = this._getIndex(fieldInstance, '.condition-if');
const {actions, pages} = this;
let {conditions} = this;
if (value && value.length > 0 && value[0]) {
const fieldName = value[0];
const {dataType, repeatable} = this._getFieldTypeByFieldName(fieldName);
const firstOperand = {
label: this._getFieldLabel(fieldName),
repeatable,
type: dataType == 'user' ? 'user' : 'field',
value: fieldName
};
if (conditions.length === 0) {
const operands = [firstOperand];
conditions.push({operands});
}
else {
if (fieldName !== conditions[index].operands[0].value) {
conditions[index].operator = '';
this._clearSecondOperandValue(conditions, index);
}
conditions[index].operands[0] = firstOperand;
}
}
else {
conditions = this._clearAllConditionFieldValues(index);
}
let maxPageIndex;
const visitor = new PagesVisitor(pages);
if (conditions[index].operands[0].value != '') {
visitor.mapFields(
(field, fieldIndex, columnIndex, rowIndex, pageIndex) => {
if (field.fieldName === conditions[index].operands[0].value) {
maxPageIndex = pageIndex;
conditions[index].operands[0].source = pageIndex;
}
}
);
}
if (actions && actions[0].action != '') {
actions.map(
action => {
if (action.action == 'jump-to-page') {
action.source = conditions[index].operands[0].source;
}
return {
action
};
}
);
}
this.setState(
{
actions,
conditions,
pageOptions: pageOptions(pages, maxPageIndex)
}
);
}
_handleLogicalOperationChange(event) {
const {target} = event;
const {value} = target.dataset;
if (value !== this.logicalOperator) {
this.setState(
{
logicalOperator: value
}
);
}
}
_handleModalButtonClicked(event) {
event.stopPropagation();
if (!event.target.classList.contains('close-modal')) {
const activeActionIndex = this.activeActionIndex;
const activeConditionIndex = this.activeConditionIndex;
const {actions, conditions} = this;
if (activeConditionIndex > -1) {
conditions.splice(activeConditionIndex, 1);
}
if (activeActionIndex > -1) {
actions.splice(activeActionIndex, 1);
}
if (this.refs.confirmationModalAction.visible) {
this.refs.confirmationModalAction.emit('hide');
}
if (this.refs.confirmationModalCondition.visible) {
this.refs.confirmationModalCondition.emit('hide');
}
this.setState(
{
actions,
activeActionIndex: -1,
activeConditionIndex: -1,
conditions
}
);
}
}
_handleOperatorEdited(event) {
const {fieldInstance, value} = event;
let {conditions} = this;
let operatorValue = '';
if (value && value.length > 0 && value[0]) {
operatorValue = value[0];
}
const index = this._getIndex(fieldInstance, '.condition-operator');
if (!operatorValue || !this._isBinary(operatorValue)) {
conditions = this._clearSecondOperandValue(conditions, index);
conditions[index].operator = operatorValue;
}
else {
conditions[index].operator = operatorValue;
}
this.setState(
{
conditions
}
);
}
_handleRuleAdded(event) {
const actions = this._removeActionInternalProperties();
const conditions = this._removeConditionInternalProperties();
const {ruleEditedIndex} = this;
this.emit(
'ruleAdded',
{
actions,
conditions,
['logical-operator']: this.logicalOperator,
ruleEditedIndex
}
);
}
_handleSecondOperandFieldEdited(event) {
const {conditions} = this;
const {fieldInstance, value} = event;
let fieldValue = '';
if (value && typeof (value) == 'object' && value[0]) {
fieldValue = value[0];
}
else if (value && typeof (value) == 'string') {
fieldValue = value;
}
let index;
if (fieldInstance.element.closest('.condition-type-value')) {
index = this._getIndex(fieldInstance, '.condition-type-value');
}
let secondOperand = conditions[index].operands[1];
if (!secondOperand) {
secondOperand = {
dataType: fieldInstance.dataType,
type: fieldInstance.type
};
}
let userType = '';
if (conditions[index].operands[0].type === 'user') {
userType = conditions[index].operands[0].type;
}
conditions[index].operands[1] = {
...secondOperand,
dataType: fieldInstance.dataType,
label: fieldValue,
type: userType ? userType : fieldInstance.type,
value: fieldValue
};
this.setState(
{
conditions
}
);
}
_handleSecondOperandTypeEdited(event) {
let {conditions} = this;
const {fieldInstance, value} = event;
const index = this._getIndex(fieldInstance, '.condition-type');
const {operands} = conditions[index];
const secondOperand = operands[1];
let secondOperandType = 'field';
let valueType = 'field';
if (value[0] == 'value') {
valueType = 'string';
secondOperandType = this._getFieldTypeByFieldName(operands[0].value).dataType;
}
if (secondOperand && ((secondOperand.type === secondOperandType)) && value[0] !== '') {
return;
}
if ((value[0] == '')) {
conditions = this._clearSecondOperandValue(conditions, index);
}
else if (secondOperand && secondOperand.dataType != valueType) {
conditions[index].operands[1].type = '';
conditions[index].operands[1].value = '';
}
if (secondOperand) {
secondOperand.type = secondOperandType;
}
else if ((value[0] !== '')) {
conditions[index].operands.push(
{
type: secondOperandType,
value: ''
}
);
}
this.setState(
{
conditions
}
);
}
_handleSecondOperandValueEdited(event) {
const {conditions} = this;
const {fieldInstance, value} = event;
const index = this._getIndex(fieldInstance, '.condition-type-value');
const secondOperandValue = Array.isArray(value) ? value[0] : value;
this.setState(
{
conditions: conditions.map(
(condition, conditionIndex) => {
const operands = [...condition.operands];
if (index == conditionIndex) {
operands[1] = {
...operands[1],
value: secondOperandValue
};
}
return {
...condition,
operands
};
}
)
}
);
}
_handleTargetSelection(event) {
const {fieldInstance, value} = event;
const {actions} = this;
const id = value[0];
const index = this._getIndex(fieldInstance, '.target-action');
const previousTarget = actions[index].target;
if (previousTarget !== id && actions[index].action == 'auto-fill') {
this.populateDataProviderOptions(id, index);
}
else {
this.populateActionTargetValue(id, index);
}
}
_isBinary(value) {
return (
value === 'equals-to' ||
value === 'not-equals-to' ||
value === 'contains' ||
value === 'not-contains' ||
value === 'belongs-to' ||
value === 'greater-than' ||
value === 'greater-than-equals' ||
value === 'less-than' ||
value === 'less-than-equals'
);
}
_isFieldAction(fieldName) {
return (fieldName == 'enable' || fieldName == 'show' || fieldName == 'require');
}
_prepareAutofillOutputs(action) {
if (Array.isArray(action.outputs)) {
action.outputs.forEach(
output => {
delete output.actionsFieldOptions;
delete output.name;
delete output.type;
}
);
}
return action.outputs;
}
_prepareRuleEditor() {
const {rule} = this;
const newRule = rule;
let newActions = rule.actions.map(
action => {
const newAction = {...action};
if (action.action == 'jump-to-page') {
newAction.target = (parseInt(action.target, 10) + 1).toString();
}
return newAction;
}
);
newActions = this._syncActions(newActions);
newRule.actions = newActions;
this.setState(
{
actions: newActions,
conditions: rule.conditions,
logicalOperator: rule['logical-operator'],
rule: newRule
}
);
}
_removeActionInternalProperties() {
const {actions} = this;
return actions.map(
action => {
const {
action: actionType,
ddmDataProviderInstanceUUID,
expression,
inputs,
label,
outputs,
source,
target
} = action;
let newAction = {
action: actionType,
label,
target
};
if (actionType == 'auto-fill') {
newAction = {
...newAction,
ddmDataProviderInstanceUUID,
inputs,
outputs
};
}
else if (actionType == 'calculate') {
newAction = {
...newAction,
expression
};
}
else if (actionType == 'jump-to-page') {
newAction = {
...newAction,
source: `${source}`,
target: `${parseInt(target, 10) - 1}`
};
}
return newAction;
}
);
}
_removeConditionInternalProperties() {
const {conditions} = this;
conditions.forEach(
condition => {
if (condition.operands[0].type == 'user') {
condition.operands[0].label = condition.operands[0].value;
condition.operands[1].type = 'list';
}
if (condition.operands[1]) {
condition.operands[1].label = condition.operands[1].value;
}
}
);
return conditions;
}
_rolesValueFn() {
const {roles} = this;
return roles.map(
role => {
return {
...role,
value: role.label
};
}
);
}
_setActions(actions) {
if (actions.length == 0) {
actions.push(
{
action: '',
calculatorFields: [],
expression: '',
hasRequiredInputs: false,
inputs: {},
label: '',
outputs: {},
target: ''
}
);
}
return actions;
}
_setActionsInputsOutputs() {
const {rule} = this;
if (rule) {
this.setState(
{
loadingDataProviderOptions: true
}
);
Promise.all(
rule.actions.map(
(action, index) => {
let newAction = {...action};
if (action.ddmDataProviderInstanceUUID) {
const {id} = this.dataProvider.find(
dataProvider => {
let dataProviderId;
if (dataProvider.uuid === action.ddmDataProviderInstanceUUID) {
dataProviderId = dataProvider.id;
}
return dataProviderId;
}
);
newAction = this.getDataProviderOptions(id, index);
}
newAction.calculatorFields = this._updateCalculatorFields(newAction, newAction.target);
return newAction;
}
)
).then(
actions => {
this.setState(
{
actions,
loadingDataProviderOptions: false
}
);
}
).catch(
error => {
throw new Error(error);
}
);
}
}
_setConditions(conditions) {
if (conditions.length === 0) {
conditions.push(
{
operands: [
{
type: '',
value: ''
}
],
operator: ''
}
);
}
return conditions;
}
_setDataProviderTarget() {
const {dataProvider, rule} = this;
if (!rule) {
return;
}
this.setState(
{
actions: rule.actions.map(
action => {
if (action.action == 'auto-fill') {
const {id} = dataProvider.find(
({uuid}) => uuid === action.ddmDataProviderInstanceUUID
);
action.target = id;
}
return action;
}
)
}
);
}
_syncActions(actions) {
const {pages, rule} = this;
const visitor = new PagesVisitor(pages);
actions.forEach(
(action, index) => {
let targetFieldExists = false;
visitor.mapFields(
({fieldName}) => {
if (action.target === fieldName) {
targetFieldExists = true;
}
}
);
action.calculatorFields = this._updateCalculatorFields(action, action.target);
if (
action.action !== 'auto-fill' &&
action.action !== 'jump-to-page' &&
!targetFieldExists
) {
action.target = '';
}
else if (action.action == 'auto-fill') {
action = {
...rule.actions[index],
calculatorFields: []
};
}
}
);
return actions;
}
_updateCalculatorFields(action, id) {
const {calculatorResultOptions} = this;
return calculatorResultOptions.reduce(
(prev, option) => {
return (option.fieldName === id) ? prev : [
...prev,
{
...option,
title: option.fieldName,
type: 'item'
}
];
},
[]
);
}
_validateActionsAutoFill(autoFillActions, type) {
return autoFillActions.every(
action => {
const parameterKeys = Object.keys(action[type]);
let validation = parameterKeys.every(key => action[type][key]);
if (type === 'inputs' && !parameterKeys.length) {
validation = Object.keys(action.outputs).length;
}
return validation;
}
);
}
_validateActionsCalculateFilling(calculateActions) {
let allFieldsFilled = true;
calculateActions.forEach(
({expression}) => {
if (expression && expression.length == 0) {
allFieldsFilled = false;
}
}
);
return allFieldsFilled;
}
_validateActionsFilling() {
const {actions} = this;
let allFieldsFilled = true;
const autofillActions = actions.filter(
action => {
return action.action == 'auto-fill';
}
);
const calculateActions = actions.filter(
action => {
return action.action == 'calculate';
}
);
if (actions) {
actions.forEach(
currentAction => {
const {action, target} = currentAction;
if (action == '') {
allFieldsFilled = false;
}
else if (target == '') {
allFieldsFilled = false;
}
}
);
if (allFieldsFilled) {
if (autofillActions && autofillActions.length > 0 && calculateActions && calculateActions.length > 0) {
allFieldsFilled = this._validateInputOutputs(autofillActions) && this._validateActionsCalculateFilling(calculateActions);
}
else if (autofillActions && autofillActions.length > 0) {
allFieldsFilled = this._validateActionsAutoFill(autofillActions, 'inputs') &&
this._validateActionsAutoFill(autofillActions, 'outputs');
}
else if (calculateActions && calculateActions.length > 0) {
allFieldsFilled = this._validateActionsCalculateFilling(calculateActions);
}
}
}
return allFieldsFilled;
}
_validateConditionsFilling() {
const {conditions} = this;
let allFieldsFilled = true;
for (const condition of conditions) {
const {operands, operator} = condition;
if (operands[0].value == '') {
allFieldsFilled = false;
break;
}
else if (!operator) {
allFieldsFilled = false;
break;
}
else if (operator && this._isBinary(operator)) {
allFieldsFilled = operands[1] && !!operands[1].value && operands[1].value != '';
if (!allFieldsFilled) {
break;
}
}
}
return allFieldsFilled;
}
_validateInputOutputs(autofillActions) {
return this._validateActionsAutoFill(autofillActions, 'inputs') &&
this._validateActionsAutoFill(autofillActions, 'outputs');
}
}
Soy.register(RuleEditor, templates);
export default RuleEditor;