Skip to content

Commit

Permalink
Feature: Trigger to validate fields using a lodash template (#2692)
Browse files Browse the repository at this point in the history
* Added template based validator trigger for records

* reinstated missing function

* Added tests for the validateFieldsUsingTemplate trigger

* Removed verbose error message

* Added forceRun to the test configuration to allow it to pass the triggerCondition

* Moved util functions to the template execution context as functions cannot be passed as imports

* Fixed expectations for failed test
  • Loading branch information
andrewbrazzatti authored Dec 16, 2024
1 parent 58ba0d3 commit 4025cb1
Show file tree
Hide file tree
Showing 2 changed files with 130 additions and 8 deletions.
52 changes: 52 additions & 0 deletions test/unit/services/TriggerService.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ describe('The TriggerService', function () {
fieldLanguageCode: "title-required",
arrayObjFieldDBName: 'row-item',
trimLeadingAndTrailingSpacesBeforeValidation: true,
forceRun: true
// caseSensitive: true, - default
// allowNulls: true, - default
};
Expand All @@ -31,6 +32,7 @@ describe('The TriggerService', function () {
trimLeadingAndTrailingSpacesBeforeValidation: false,
caseSensitive: true,
allowNulls: false,
forceRun: true
};
try {
await TriggerService.validateFieldUsingRegex(oid, record, options);
Expand All @@ -55,6 +57,7 @@ describe('The TriggerService', function () {
trimLeadingAndTrailingSpacesBeforeValidation: false,
caseSensitive: false,
allowNulls: true,
forceRun: true
};

try {
Expand All @@ -77,6 +80,7 @@ describe('The TriggerService', function () {
trimLeadingAndTrailingSpacesBeforeValidation: false,
caseSensitive: false,
allowNulls: true,
forceRun: true
};
const result = await TriggerService.validateFieldUsingRegex(oid, record, options);
expect(result).to.eql(record);
Expand All @@ -92,6 +96,7 @@ describe('The TriggerService', function () {
trimLeadingAndTrailingSpacesBeforeValidation: false,
caseSensitive: false,
allowNulls: false,
forceRun: true
};
try {
await TriggerService.validateFieldUsingRegex(oid, record, options);
Expand All @@ -103,4 +108,51 @@ describe('The TriggerService', function () {
}
});
});


describe('should validate fields using lodash template', function () {
it('valid value passes', async function () {
const oid = "triggerservice-template-validpasses";
const record = {'testing-field': 'valid-value'};
const options = {
template: `<% let errorList = []
if (_.get(record,'testing-field') !== 'valid-value') {
addError(errorList, 'testing-field', 'title-required', 'invalid-format' );
}
return errorList; %>`,
forceRun: true
};

try {
const result = await TriggerService.validateFieldsUsingTemplate(oid, record, options);
expect(result).to.eql({'testing-field': 'valid-value'});
} catch (err) {
expect.fail("Should not have thrown error");
}

});

it('invalid value fails', async function () {
const oid = "triggerservice-template-validpasses";
const record = {'testing-field': 'invalid-value'};
const options = {
template: `<% let errorList = []
if (_.get(record,'testing-field') !== 'valid-value') {
addError(errorList, 'testing-field', 'title-required', 'invalid-format' );
}
return errorList; %>`,
forceRun: true
};
try {
const result = await TriggerService.validateFieldsUsingTemplate(oid, record, options);
} catch (err) {
expect(err).to.be.an('error');
expect(err.name).to.eq("RBValidationError");
const errorMap = JSON.parse(err.message)
expect(errorMap.errorFieldList[0].label).to.eq("Title is required");
}

});

});
});
86 changes: 78 additions & 8 deletions typescript/api/services/TriggerService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import {
RBValidationError,
BrandingModel,
Services as services,
PopulateExportedMethods,
} from '@researchdatabox/redbox-core-types';
import { Sails, Model } from "sails";
import { default as moment } from 'moment';
Expand All @@ -43,16 +44,9 @@ export module Services {
* Author: <a href='https://github.com/shilob' target='_blank'>Shilo Banihit</a>
*
*/
@PopulateExportedMethods
export class Trigger extends services.Core.Service {

protected _exportedMethods: any = [
'transitionWorkflow',
'runHooksSync',
'validateFieldUsingRegex',
'applyFieldLevelPermissions',
'validateFieldMapUsingRegex',
'runTemplatesOnRelatedRecord'
];

/**
* Used in changing the workflow stages automatically based on configuration.
Expand Down Expand Up @@ -285,6 +279,82 @@ export module Services {
return record;
}

/**
* Trigger function that will run a lodash template that can be used to validate a record.
* The trigger expects the template to return an array of error objects of the format:
* {
* name: 'the field name that is in validation error',
* label: 'the human readable label for the field',
* error: 'optional error message'
* }
*
* An empty array should be returned if no errors are found.
*
* @param oid
* @param record
* @param options
* @returns
*/
public async validateFieldsUsingTemplate(oid, record, options) {
sails.log.verbose('validateFieldsUsingTemplate - enter');
if (this.metTriggerCondition(oid, record, options) === "true") {

sails.log.verbose('validateFieldsUsingTemplate - metTriggerCondition');


const getErrorMessage = function (errorLanguageCode: string) {
let baseErrorMessage = TranslationService.t(errorLanguageCode);
return baseErrorMessage;
}

const addError = function (errorFieldList, name, label, errorLabel) {
let errorField:any = {};
_.set(errorField,'name', name);
_.set(errorField,'label',getErrorMessage(label));
let error = getErrorMessage(errorLabel);
if(error != '') {
_.set(errorField,'error',error);
}
errorFieldList.push(errorField);
}

let template = _.get(options,'template',"<% return []; %>");

const imports = {
moment: moment,
numeral: numeral,
_ : _,
TranslationService: TranslationService
}

let altErrorMessage = _.get(options,'altErrorMessage',[]);

if(_.isString(template)) {
const compiledTemplate = _.template(template, imports);
options.template = compiledTemplate;
template = compiledTemplate;
}

const errorFieldList = template({oid:oid, record: record, options: options, addError: addError,
getErrorMessage: getErrorMessage});


const errorMap = {
altErrorMessage: altErrorMessage,
errorFieldList: errorFieldList
};

if(!_.isEmpty(errorMap.errorFieldList)) {
let customError: RBValidationError;
customError = new RBValidationError(JSON.stringify(errorMap));
throw customError;
}

sails.log.debug('validateFieldsUsingTemplate data value passed check');
}
return record;
}

public async validateFieldMapUsingRegex(oid, record, options) {
sails.log.verbose('validateFieldMapUsingRegex - enter');
if (this.metTriggerCondition(oid, record, options) === "true") {
Expand Down

0 comments on commit 4025cb1

Please sign in to comment.