From 090b54316c448953ad6460ebaf8dbb62a09bf580 Mon Sep 17 00:00:00 2001 From: Nicolas Moreau Date: Tue, 15 Oct 2024 12:07:20 +0200 Subject: [PATCH] feat: support layout elements in smart action form hooks --- src/context/build-services.js | 1 + src/routes/actions.js | 9 +-- .../smart-action-form-layout-service.js | 79 +++++++++++++++++++ src/services/smart-action-hook-service.js | 16 ++-- 4 files changed, 94 insertions(+), 11 deletions(-) create mode 100644 src/services/smart-action-form-layout-service.js diff --git a/src/context/build-services.js b/src/context/build-services.js index b0c1f4cb0..e9aa772e5 100644 --- a/src/context/build-services.js +++ b/src/context/build-services.js @@ -52,5 +52,6 @@ module.exports.default = (context) => .addUsingClass('oidcClientManagerService', () => require('../services/oidc-client-manager')) .addUsingClass('authenticationService', () => require('../services/authentication')) .addUsingClass('smartActionFieldValidator', () => require('../services/smart-action-field-validator')) + .addUsingClass('smartActionFormLayoutService', () => require('../services/smart-action-form-layout-service')) .addUsingClass('smartActionHookService', () => require('../services/smart-action-hook-service')) .addUsingClass('smartActionHookDeserializer', () => require('../deserializers/smart-action-hook')); diff --git a/src/routes/actions.js b/src/routes/actions.js index 282c64fe0..5d4c37b3a 100644 --- a/src/routes/actions.js +++ b/src/routes/actions.js @@ -29,14 +29,13 @@ class Actions { getHookLoadController(action) { return async (request, response) => { try { - const loadedFields = await this.smartActionHookService.getResponse( + const hookResponse = await this.smartActionHookService.getResponse( action, action.hooks.load, action.fields, request, ); - - return response.status(200).send({ fields: loadedFields }); + return response.status(200).send(hookResponse); } catch (error) { this.logger.error('Error in smart load action hook: ', error); return response.status(500).send({ message: error.message }); @@ -58,7 +57,7 @@ class Actions { const { fields, changedField } = data; const fieldChanged = fields.find((field) => field.field === changedField); - const updatedFields = await this.smartActionHookService.getResponse( + const hookResponse = await this.smartActionHookService.getResponse( action, action.hooks.change[fieldChanged?.hook], fields, @@ -66,7 +65,7 @@ class Actions { fieldChanged, ); - return response.status(200).send({ fields: updatedFields }); + return response.status(200).send(hookResponse); } catch (error) { this.logger.error('Error in smart action change hook: ', error); return response.status(500).send({ message: error.message }); diff --git a/src/services/smart-action-form-layout-service.js b/src/services/smart-action-form-layout-service.js new file mode 100644 index 000000000..7610d8323 --- /dev/null +++ b/src/services/smart-action-form-layout-service.js @@ -0,0 +1,79 @@ +class SmartActionFormLayoutService { + static lowerCaseFirstLetter(string) { + return string.charAt(0).toLowerCase() + string.slice(1); + } + + static validateLayoutElement(element) { + const validLayoutComponents = ['Row', 'Page', 'Separator', 'HtmlBlock']; + if (validLayoutComponents.includes(element.component)) throw new Error(`${element.component} is not a valid component. Use ${validLayoutComponents.join(' or ')}`); + if (element.component === 'Page' && !Array.isArray(element.elements)) { + throw new Error('Page components must contain an array of fields or layout elements in property \'elements\''); + } + if (element.component === 'Row' && (!Array.isArray(element.fields))) { + throw new Error('Row components must contain an array of fields in property \'fields\''); + } + } + + static parseLayout( + element, + allFields, + ) { + if (element.type === 'Layout') { + SmartActionFormLayoutService.validateLayoutElement(element); + const subElementsKey = { + Row: 'fields', + Page: 'elements', + }; + + if (element.component === 'Row' || element.component === 'Page') { + const key = subElementsKey[element.component]; + const subElements = element[key].map( + (field) => SmartActionFormLayoutService.parseLayout(field, allFields), + ); + + return { + ...element, + component: SmartActionFormLayoutService.lowerCaseFirstLetter(element.component), + [key]: subElements, + }; + } + return { + ...element, + component: SmartActionFormLayoutService.lowerCaseFirstLetter(element.component), + }; + } + + allFields.push(element); + + return { + type: 'Layout', + component: 'input', + fieldId: element.field, + }; + } + + // eslint-disable-next-line class-methods-use-this + extractFieldsAndLayout(formElements) { + let hasLayout = false; + const fields = []; + let layout = []; + + if (!formElements) return { fields: [], layout: [] }; + + formElements.forEach((element) => { + if (element.type === 'Layout') { + hasLayout = true; + } + + layout.push(SmartActionFormLayoutService.parseLayout(element, fields)); + }); + + if (!hasLayout) { + layout = []; + } + + return { fields, layout }; + } +} + +module.exports = SmartActionFormLayoutService; diff --git a/src/services/smart-action-hook-service.js b/src/services/smart-action-hook-service.js index e95d74ce9..e6e5a5668 100644 --- a/src/services/smart-action-hook-service.js +++ b/src/services/smart-action-hook-service.js @@ -1,7 +1,8 @@ class SmartActionHookService { - constructor({ setFieldWidget, smartActionFieldValidator }) { + constructor({ setFieldWidget, smartActionFieldValidator, smartActionFormLayoutService }) { this.setFieldWidget = setFieldWidget; this.smartActionFieldValidator = smartActionFieldValidator; + this.smartActionFormLayoutService = smartActionFormLayoutService; } /** @@ -32,13 +33,16 @@ class SmartActionHookService { if (typeof hook !== 'function') throw new Error('hook must be a function'); // Call the user-defined load hook. - const result = await hook({ request, fields: fieldsForUser, changedField }); + const hookResult = await hook({ request, fields: fieldsForUser, changedField }); - if (!(result && Array.isArray(result))) { + const { fields: fieldHookResult, layout } = this.smartActionFormLayoutService + .extractFieldsAndLayout(hookResult); + + if (!(fieldHookResult && Array.isArray(fieldHookResult))) { throw new Error('hook must return an array'); } - return result.map((field) => { + const validFields = fieldHookResult.map((field) => { this.smartActionFieldValidator.validateField(field, action.name); this.smartActionFieldValidator .validateFieldChangeHook(field, action.name, action.hooks?.change); @@ -59,9 +63,9 @@ class SmartActionHookService { return { ...field, value: null }; } } - - return field; + return { ...field }; }); + return { fields: validFields, layout }; } }