[ACTIVITI-3720] form variables enhancements (#5028)

* support form variables on the model level

* take 'variables.name' convention into account

* move API to a proper place

* unit tests and code fixes

* unit tests for form field model

* process variable interface
This commit is contained in:
Denys Vuika
2019-09-02 11:20:54 +01:00
committed by Eugenio Romano
parent 42c9a2e833
commit ce50e9a3d3
12 changed files with 422 additions and 1828 deletions

File diff suppressed because it is too large Load Diff

View File

@@ -117,7 +117,7 @@ export class FormFieldComponent implements OnInit, OnDestroy {
if (this.field && this.field.params) { if (this.field && this.field.params) {
const wrappedField = this.field.params.field; const wrappedField = this.field.params.field;
if (wrappedField && wrappedField.type) { if (wrappedField && wrappedField.type) {
return wrappedField; return wrappedField as FormFieldModel;
} }
} }
return this.field; return this.field;

View File

@@ -23,4 +23,10 @@ export interface FormFieldMetadata {
[key: string]: any; [key: string]: any;
fileSource?: FormFieldFileSource; fileSource?: FormFieldFileSource;
link?: boolean; link?: boolean;
field?: {
id: string;
name: string;
type: string;
};
responseVariable?: boolean;
} }

View File

@@ -443,4 +443,74 @@ describe('FormFieldModel', () => {
field.updateForm(); field.updateForm();
expect(form.values['dropdown_field'].name).toEqual('Option 1'); expect(form.values['dropdown_field'].name).toEqual('Option 1');
}); });
describe('variables', () => {
let form: FormModel;
beforeEach(() => {
form = new FormModel({
variables: [
{
'id': 'bfca9766-7bc1-45cc-8ecf-cdad551e36e2',
'name': 'name2',
'type': 'string',
'value': 'default hello'
}
],
processVariables: [
{
'serviceName': 'denys-variable-mapping-rb',
'serviceFullName': 'denys-variable-mapping-rb',
'serviceVersion': '',
'appName': 'denys-variable-mapping',
'appVersion': '',
'serviceType': null,
'id': 3,
'type': 'string',
'name': 'variables.name1',
'createTime': 1566989626284,
'lastUpdatedTime': 1566989626284,
'executionId': null,
'value': 'hello',
'markedAsDeleted': false,
'processInstanceId': '1be4785f-c982-11e9-bdd8-96d6903e4e44',
'taskId': '1beab9f6-c982-11e9-bdd8-96d6903e4e44',
'taskVariable': true
}
]
});
});
it('it should get a process value for readonly field', () => {
const field = new FormFieldModel(form, {
type: FormFieldTypes.DISPLAY_VALUE,
params: {
field: {
id: 'name1',
name: 'name1',
type: 'string'
}
}
});
expect(field.value).toBe('hello');
});
it('it should fallback to a form variable for readonly field', () => {
const field = new FormFieldModel(form, {
type: FormFieldTypes.DISPLAY_VALUE,
params: {
responseVariable: true,
field: {
id: 'name2',
name: 'name2',
type: 'string'
}
}
});
expect(field.value).toBe('default hello');
});
});
}); });

View File

@@ -173,23 +173,31 @@ export class FormFieldModel extends FormWidgetModel {
this.placeholder = json.placeholder; this.placeholder = json.placeholder;
} }
if (FormFieldTypes.isReadOnlyType(json.type)) { if (FormFieldTypes.isReadOnlyType(this.type)) {
if (json.params && json.params.field) { if (this.params && this.params.field) {
let valueFound = false;
if (form.processVariables) { if (form.processVariables) {
const processVariable = this.getProcessVariableValue(json.params.field, form); const processVariable = this.getProcessVariableValue(this.params.field, form);
if (processVariable) { if (processVariable) {
valueFound = true;
this.value = processVariable; this.value = processVariable;
} }
} else if (json.params.responseVariable && form.json.variables) { }
const formVariable = this.getVariablesValue(json.params.field.name, form);
if (formVariable) { if (!valueFound && this.params.responseVariable) {
this.value = formVariable; const defaultValue = form.getFormVariableValue(this.params.field.name);
if (defaultValue) {
valueFound = true;
this.value = defaultValue;
} }
} }
} }
} }
if (FormFieldTypes.isContainerType(json.type)) { if (FormFieldTypes.isContainerType(this.type)) {
this.containerFactory(json, form); this.containerFactory(json, form);
} }
} }
@@ -219,42 +227,12 @@ export class FormFieldModel extends FormWidgetModel {
return name += '_LABEL'; return name += '_LABEL';
} }
private getProcessVariableValue(field: any, form: FormModel) { private getProcessVariableValue(field: any, form: FormModel): any {
let fieldName = field.name; let fieldName = field.name;
if (this.isTypeaheadFieldType(field.type)) { if (this.isTypeaheadFieldType(field.type)) {
fieldName = this.getFieldNameWithLabel(field.id); fieldName = this.getFieldNameWithLabel(field.id);
} }
return this.findProcessVariableValue(fieldName, form); return form.getProcessVariableValue(fieldName);
}
private getVariablesValue(variableName: string, form: FormModel) {
const variable = form.json.variables.find((currentVariable) => {
return currentVariable.name === variableName;
});
if (variable) {
if (variable.type === 'boolean') {
return JSON.parse(variable.value);
}
return variable.value;
}
return null;
}
private findProcessVariableValue(variableName: string, form: FormModel) {
if (form.processVariables) {
const variable = form.processVariables.find((currentVariable) => {
return currentVariable.name === variableName;
});
if (variable) {
return variable.type === 'boolean' ? JSON.parse(variable.value) : variable.value;
}
}
return undefined;
} }
private containerFactory(json: any, form: FormModel): void { private containerFactory(json: any, form: FormModel): void {
@@ -270,7 +248,7 @@ export class FormFieldModel extends FormWidgetModel {
if (json.fields.hasOwnProperty(currentField)) { if (json.fields.hasOwnProperty(currentField)) {
const col = new ContainerColumnModel(); const col = new ContainerColumnModel();
const fields: FormFieldModel[] = (json.fields[currentField] || []).map((f) => new FormFieldModel(form, f)); const fields: FormFieldModel[] = (json.fields[currentField] || []).map((field) => new FormFieldModel(form, field));
col.fields = fields; col.fields = fields;
col.rowspan = json.fields[currentField].length; col.rowspan = json.fields[currentField].length;

View File

@@ -0,0 +1,23 @@
/*!
* @license
* Copyright 2019 Alfresco Software, Ltd.
*
* 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.
*/
export interface FormVariableModel {
id: string;
name: string;
type: string;
value?: any;
}

View File

@@ -26,7 +26,6 @@ import { FormModel } from './form.model';
import { TabModel } from './tab.model'; import { TabModel } from './tab.model';
describe('FormModel', () => { describe('FormModel', () => {
let formService: FormService; let formService: FormService;
beforeEach(() => { beforeEach(() => {
@@ -428,4 +427,135 @@ describe('FormModel', () => {
expect(form.fieldValidators.length).toBe(defaultLength + 1); expect(form.fieldValidators.length).toBe(defaultLength + 1);
expect(FORM_FIELD_VALIDATORS.length).toBe(defaultLength); expect(FORM_FIELD_VALIDATORS.length).toBe(defaultLength);
}); });
describe('variables', () => {
let form: FormModel;
beforeEach(() => {
const variables = [
{
'id': 'bfca9766-7bc1-45cc-8ecf-cdad551e36e2',
'name': 'name1',
'type': 'string',
'value': 'hello'
},
{
'id': '3ed9f28a-dbae-463f-b991-47ef06658bb6',
'name': 'name2',
'type': 'date',
'value': '29.09.2019'
},
{
'id': 'booleanVar',
'name': 'bool',
'type': 'boolean',
'value': 'true'
}
];
const processVariables = [
{
'serviceName': 'denys-variable-mapping-rb',
'serviceFullName': 'denys-variable-mapping-rb',
'serviceVersion': '',
'appName': 'denys-variable-mapping',
'appVersion': '',
'serviceType': null,
'id': 3,
'type': 'string',
'name': 'variables.name1',
'createTime': 1566989626284,
'lastUpdatedTime': 1566989626284,
'executionId': null,
'value': 'hello',
'markedAsDeleted': false,
'processInstanceId': '1be4785f-c982-11e9-bdd8-96d6903e4e44',
'taskId': '1beab9f6-c982-11e9-bdd8-96d6903e4e44',
'taskVariable': true
},
{
'serviceName': 'denys-variable-mapping-rb',
'serviceFullName': 'denys-variable-mapping-rb',
'serviceVersion': '',
'appName': 'denys-variable-mapping',
'appVersion': '',
'serviceType': null,
'id': 1,
'type': 'boolean',
'name': 'booleanVar',
'createTime': 1566989626283,
'lastUpdatedTime': 1566989626283,
'executionId': null,
'value': 'true',
'markedAsDeleted': false,
'processInstanceId': '1be4785f-c982-11e9-bdd8-96d6903e4e44',
'taskId': '1beab9f6-c982-11e9-bdd8-96d6903e4e44',
'taskVariable': true
}
];
form = new FormModel({
variables,
processVariables
});
});
it('should parse form variables', () => {
expect(form.variables.length).toBe(3);
expect(form.variables[0].id).toBe('bfca9766-7bc1-45cc-8ecf-cdad551e36e2');
expect(form.variables[1].id).toBe('3ed9f28a-dbae-463f-b991-47ef06658bb6');
expect(form.variables[2].id).toBe('booleanVar');
});
it('should find a variable by or name', () => {
const result1 = form.getFormVariable('bfca9766-7bc1-45cc-8ecf-cdad551e36e2');
const result2 = form.getFormVariable('name1');
expect(result1).toEqual(result2);
});
it('should not find a variable', () => {
expect(form.getFormVariable(null)).toBeUndefined();
expect(form.getFormVariable('')).toBeUndefined();
expect(form.getFormVariable('missing')).toBeUndefined();
});
it('should find a form variable value', () => {
const result1 = form.getFormVariableValue('name1');
const result2 = form.getFormVariableValue('bfca9766-7bc1-45cc-8ecf-cdad551e36e2');
expect(result1).toEqual(result2);
expect(result1).toEqual('hello');
});
it('should convert the date variable value', () => {
const value = form.getFormVariableValue('name2');
expect(value).toBe('29.09.2019T00:00:00.000Z');
});
it('should convert the boolean variable value', () => {
const value = form.getFormVariableValue('bool');
expect(value).toEqual(true);
});
it('should not find variable value', () => {
const value = form.getFormVariableValue('missing');
expect(value).toBeUndefined();
});
it('should find a process variable by form variable name', () => {
const value = form.getProcessVariableValue('variables.name1');
expect(value).toBe('hello');
});
it('should find a process variable by name', () => {
const value = form.getProcessVariableValue('booleanVar');
expect(value).toEqual(true);
});
it('should not find a process variable', () => {
const missing = form.getProcessVariableValue('missing');
expect(missing).toBeUndefined();
});
});
}); });

View File

@@ -35,6 +35,8 @@ import {
FormFieldValidator FormFieldValidator
} from './form-field-validator'; } from './form-field-validator';
import { FormBaseModel } from '../../form-base.model'; import { FormBaseModel } from '../../form-base.model';
import { FormVariableModel } from './form-variable.model';
import { ProcessVariableModel } from './process-variable.model';
export class FormModel extends FormBaseModel { export class FormModel extends FormBaseModel {
@@ -48,7 +50,8 @@ export class FormModel extends FormBaseModel {
fieldValidators: FormFieldValidator[] = [...FORM_FIELD_VALIDATORS]; fieldValidators: FormFieldValidator[] = [...FORM_FIELD_VALIDATORS];
readonly selectedOutcome: string; readonly selectedOutcome: string;
processVariables: any; processVariables: ProcessVariableModel[] = [];
variables: FormVariableModel[] = [];
constructor(formRepresentationJSON?: any, formValues?: FormValues, readOnly: boolean = false, protected formService?: FormService) { constructor(formRepresentationJSON?: any, formValues?: FormValues, readOnly: boolean = false, protected formService?: FormService) {
super(); super();
@@ -65,11 +68,11 @@ export class FormModel extends FormBaseModel {
this.customFieldTemplates = formRepresentationJSON.customFieldTemplates || {}; this.customFieldTemplates = formRepresentationJSON.customFieldTemplates || {};
this.selectedOutcome = formRepresentationJSON.selectedOutcome || {}; this.selectedOutcome = formRepresentationJSON.selectedOutcome || {};
this.className = formRepresentationJSON.className || ''; this.className = formRepresentationJSON.className || '';
this.variables = formRepresentationJSON.variables || [];
this.processVariables = formRepresentationJSON.processVariables || [];
const tabCache: FormWidgetModelCache<TabModel> = {}; const tabCache: FormWidgetModelCache<TabModel> = {};
this.processVariables = formRepresentationJSON.processVariables;
this.tabs = (formRepresentationJSON.tabs || []).map((t) => { this.tabs = (formRepresentationJSON.tabs || []).map((t) => {
const model = new TabModel(this, t); const model = new TabModel(this, t);
tabCache[model.id] = model; tabCache[model.id] = model;
@@ -226,4 +229,66 @@ export class FormModel extends FormBaseModel {
} }
} }
} }
/**
* Returns a form variable that matches the identifier.
* @param identifier The `name` or `id` value.
*/
getFormVariable(identifier: string): FormVariableModel {
if (identifier) {
return this.variables.find(
variable =>
variable.name === identifier ||
variable.id === identifier
);
}
return undefined;
}
/**
* Returns a value of the form variable that matches the identifier.
* Provides additional conversion of types (date, boolean).
* @param identifier The `name` or `id` value
*/
getFormVariableValue(identifier: string): any {
const variable = this.getFormVariable(identifier);
if (variable) {
switch (variable.type) {
case 'date':
return `${variable.value}T00:00:00.000Z`;
case 'boolean':
return JSON.parse(variable.value);
default:
return variable.value;
}
}
return undefined;
}
/**
* Returns a process variable value.
* @param name Variable name
*/
getProcessVariableValue(name: string): any {
if (this.processVariables) {
const names = [`variables.${name}`, name];
const variable = this.processVariables.find(
entry => names.includes(entry.name)
);
if (variable) {
switch (variable.type) {
case 'boolean':
return JSON.parse(variable.value);
default:
return variable.value;
}
}
}
return undefined;
}
} }

View File

@@ -36,3 +36,5 @@ export * from './error-message.model';
export * from './external-content'; export * from './external-content';
export * from './external-content-link'; export * from './external-content-link';
export * from './group.model'; export * from './group.model';
export * from './form-variable.model';
export * from './process-variable.model';

View File

@@ -0,0 +1,36 @@
/*!
* @license
* Copyright 2019 Alfresco Software, Ltd.
*
* 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.
*/
export interface ProcessVariableModel {
serviceName?: string;
serviceFullName?: string;
serviceVersion?: string;
appName?: string;
appVersion?: string;
serviceType?: string;
id?: number;
type?: string;
name?: string;
createTime?: number;
lastUpdatedTime?: number;
executionId?: string;
value?: any;
markedAsDeleted?: boolean;
processInstanceId?: string;
taskId?: string;
taskVariable?: boolean;
}

View File

@@ -193,40 +193,24 @@ export class WidgetVisibilityService {
getVariableValue(form: FormModel, name: string, processVarList: TaskProcessVariableModel[]): string { getVariableValue(form: FormModel, name: string, processVarList: TaskProcessVariableModel[]): string {
const processVariableValue = this.getProcessVariableValue(name, processVarList); const processVariableValue = this.getProcessVariableValue(name, processVarList);
const variableDefaultValue = this.getFormVariableDefaultValue(form, name); const variableDefaultValue = form.getFormVariableValue(name);
return (processVariableValue === undefined) ? variableDefaultValue : processVariableValue; return (processVariableValue === undefined) ? variableDefaultValue : processVariableValue;
} }
private getFormVariableDefaultValue(form: FormModel, identifier: string): string {
const variables = this.getFormVariables(form);
if (variables) {
const formVariable = variables.find((formVar) => {
return formVar.name === identifier || formVar.id === identifier;
});
let value;
if (formVariable) {
value = formVariable.value;
if (formVariable.type === 'date') {
value += 'T00:00:00.000Z';
}
}
return value;
}
}
private getFormVariables(form: FormModel): any[] {
return form.json.variables;
}
private getProcessVariableValue(name: string, processVarList: TaskProcessVariableModel[]): string { private getProcessVariableValue(name: string, processVarList: TaskProcessVariableModel[]): string {
if (processVarList) { if (processVarList) {
const processVariable = processVarList.find((variable) => variable.id === name); const processVariable = processVarList.find(
variable =>
variable.id === name ||
variable.id === `variables.${name}`
);
if (processVariable) { if (processVariable) {
return processVariable.value; return processVariable.value;
} }
} }
return undefined;
} }
evaluateLogicalOperation(logicOp, previousValue, newValue): boolean { evaluateLogicalOperation(logicOp, previousValue, newValue): boolean {

View File

@@ -15,23 +15,6 @@
* limitations under the License. * limitations under the License.
*/ */
/*!
* @license
* Copyright 2019 Alfresco Software, Ltd.
*
* 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.
*/
export class DemoForm { export class DemoForm {
easyForm = { easyForm = {
@@ -358,7 +341,25 @@ export class DemoForm {
'style': '', 'style': '',
'customFieldTemplates': {}, 'customFieldTemplates': {},
'metadata': {}, 'metadata': {},
'variables': [], 'variables': [
{
'id': 'bfca9766-7bc1-45cc-8ecf-cdad551e36e2',
'name': 'name1',
'type': 'string',
'value': ''
},
{
'id': '3ed9f28a-dbae-463f-b991-47ef06658bb6',
'name': 'name2',
'type': 'string',
'value': ''
},
{
'id': 'a7710978-1e9c-4b54-a19c-c6267d2b19a2',
'name': 'input02',
'type': 'integer'
}
],
'customFieldsValueInfo': {}, 'customFieldsValueInfo': {},
'gridsterForm': false, 'gridsterForm': false,
'globalDateFormat': 'D-M-YYYY' 'globalDateFormat': 'D-M-YYYY'
@@ -1458,7 +1459,25 @@ export class DemoForm {
'style': '', 'style': '',
'customFieldTemplates': {}, 'customFieldTemplates': {},
'metadata': {}, 'metadata': {},
'variables': [], 'variables': [
{
'id': 'bfca9766-7bc1-45cc-8ecf-cdad551e36e2',
'name': 'name1',
'type': 'string',
'value': ''
},
{
'id': '3ed9f28a-dbae-463f-b991-47ef06658bb6',
'name': 'name2',
'type': 'string',
'value': ''
},
{
'id': 'a7710978-1e9c-4b54-a19c-c6267d2b19a2',
'name': 'input02',
'type': 'integer'
}
],
'gridsterForm': false, 'gridsterForm': false,
'globalDateFormat': 'D-M-YYYY' 'globalDateFormat': 'D-M-YYYY'
}; };
@@ -1722,7 +1741,25 @@ export class DemoForm {
'style': '', 'style': '',
'customFieldTemplates': {}, 'customFieldTemplates': {},
'metadata': {}, 'metadata': {},
'variables': [], 'variables': [
{
'id': 'bfca9766-7bc1-45cc-8ecf-cdad551e36e2',
'name': 'name1',
'type': 'string',
'value': ''
},
{
'id': '3ed9f28a-dbae-463f-b991-47ef06658bb6',
'name': 'name2',
'type': 'string',
'value': ''
},
{
'id': 'a7710978-1e9c-4b54-a19c-c6267d2b19a2',
'name': 'input02',
'type': 'integer'
}
],
'customFieldsValueInfo': {}, 'customFieldsValueInfo': {},
'gridsterForm': false 'gridsterForm': false
} }