From a6363d9aefe4999d894b2c863004b329dbd0545b Mon Sep 17 00:00:00 2001 From: Enki Pontvianne Date: Wed, 18 Sep 2024 11:36:16 +0200 Subject: [PATCH] feat: add optional id in action form fields --- .../src/forest/customizations/card.ts | 5 +++ .../src/utils/forest-schema/action-values.ts | 2 +- .../utils/forest-schema/generator-actions.ts | 8 ++-- .../src/decorators/actions/collection.ts | 42 +++++++++++-------- .../src/decorators/actions/types/fields.ts | 7 ++++ .../src/interfaces/action.ts | 1 + .../forestadmin-client/src/schema/types.ts | 1 + .../plugin-export-advanced/test/index.test.ts | 3 ++ 8 files changed, 46 insertions(+), 23 deletions(-) diff --git a/packages/_example/src/forest/customizations/card.ts b/packages/_example/src/forest/customizations/card.ts index fac56f115e..28cfdcacbe 100644 --- a/packages/_example/src/forest/customizations/card.ts +++ b/packages/_example/src/forest/customizations/card.ts @@ -88,6 +88,11 @@ export default (collection: CardCustomizer) => defaultValue: 80, if: ctx => ['Gold'].includes(ctx.formValues.Plan), }, + { + type: 'Number', + id: 'test-price', + label: 'price', + }, { type: 'Layout', component: 'Separator' }, { type: 'Layout', component: 'HtmlBlock', content: '

constraints:

' }, { diff --git a/packages/agent/src/utils/forest-schema/action-values.ts b/packages/agent/src/utils/forest-schema/action-values.ts index 147816e1a9..b75d85a5be 100644 --- a/packages/agent/src/utils/forest-schema/action-values.ts +++ b/packages/agent/src/utils/forest-schema/action-values.ts @@ -19,7 +19,7 @@ export default class ForestValueConverter { const data = {}; for (const [key, value] of Object.entries(rawData)) { - const field = fields.find(f => f.label === key); + const field = fields.find(f => f.id === key); // Skip fields from the default form if (!SchemaGeneratorActions.defaultFields.map(f => f.field).includes(key)) { diff --git a/packages/agent/src/utils/forest-schema/generator-actions.ts b/packages/agent/src/utils/forest-schema/generator-actions.ts index b9b206b883..f0ede969c6 100644 --- a/packages/agent/src/utils/forest-schema/generator-actions.ts +++ b/packages/agent/src/utils/forest-schema/generator-actions.ts @@ -29,6 +29,7 @@ export default class SchemaGeneratorActions { static defaultFields: ForestServerActionField[] = [ { field: 'Loading...', + label: 'Loading...', type: 'String', isReadOnly: true, defaultValue: 'Form is loading', @@ -98,10 +99,9 @@ export default class SchemaGeneratorActions { /** Build schema for given field */ static buildFieldSchema(dataSource: DataSource, field: ActionField): ForestServerActionField { - const { label, description, isRequired, isReadOnly, watchChanges, type } = field; - const output = { description, isRequired, isReadOnly } as Record; + const { id, label, description, isRequired, isReadOnly, watchChanges, type } = field; + const output = { id, label, description, isRequired, isReadOnly } as Record; - output.field = label; output.value = ForestValueConverter.valueToForest(field, field.value); if (watchChanges) output.hook = 'changeHook'; @@ -215,7 +215,7 @@ export default class SchemaGeneratorActions { return { type: 'Layout', component: 'Input', - fieldId: element.label, + fieldId: element.id, }; } } diff --git a/packages/datasource-customizer/src/decorators/actions/collection.ts b/packages/datasource-customizer/src/decorators/actions/collection.ts index d84f9172a3..b2d8b655f5 100644 --- a/packages/datasource-customizer/src/decorators/actions/collection.ts +++ b/packages/datasource-customizer/src/decorators/actions/collection.ts @@ -16,8 +16,9 @@ import ActionContextSingle from './context/single'; import ResultBuilder from './result-builder'; import { ActionBulk, ActionDefinition, ActionGlobal, ActionSingle } from './types/actions'; import { - DynamicField, + DynamicFieldWithId, DynamicFormElement, + DynamicFormElementWithId, Handler, SearchOptionsHandler, ValueOrHandler, @@ -88,10 +89,13 @@ export default class ActionCollectionDecorator extends CollectionDecorator { ]; } - dynamicFields = await this.dropDefaults(context, dynamicFields, formValues); - if (!metas?.includeHiddenFields) dynamicFields = await this.dropIfs(context, dynamicFields); + let dynamicFieldsWithId = await this.dropDefaultsAndSetId(context, dynamicFields, formValues); - const fields = await this.dropDeferred(context, metas?.searchValues, dynamicFields); + if (!metas?.includeHiddenFields) { + dynamicFieldsWithId = await this.dropIfs(context, dynamicFieldsWithId); + } + + const fields = await this.dropDeferred(context, metas?.searchValues, dynamicFieldsWithId); this.setWatchChangesOnFields(formValues, used, fields); @@ -164,18 +168,20 @@ export default class ActionCollectionDecorator extends CollectionDecorator { ); } - private async dropDefaults( + private async dropDefaultsAndSetId( context: ActionContext, fields: DynamicFormElement[], data: Record, - ): Promise { + ): Promise { const promises = fields.map(async field => { if (field.type !== 'Layout') { - return this.dropDefault(context, field, data); + field.id = field.id || field.label; + + return this.dropDefault(context, field as DynamicFieldWithId, data); } await this.executeOnSubFields(field, subfields => - this.dropDefaults(context, subfields, data), + this.dropDefaultsAndSetId(context, subfields, data), ); return field; @@ -186,12 +192,12 @@ export default class ActionCollectionDecorator extends CollectionDecorator { private async dropDefault( context: ActionContext, - field: DynamicField, + field: DynamicFieldWithId, data: Record, - ): Promise { - if (data[field.label] === undefined) { + ): Promise { + if (data[field.id] === undefined) { const defaultValue = await this.evaluate(context, null, field.defaultValue); - data[field.label] = defaultValue; + data[field.id] = defaultValue; } delete field.defaultValue; @@ -201,8 +207,8 @@ export default class ActionCollectionDecorator extends CollectionDecorator { private async dropIfs( context: ActionContext, - fields: DynamicFormElement[], - ): Promise { + fields: DynamicFormElementWithId[], + ): Promise { // Remove fields which have falsy if const ifValues = await Promise.all( fields.map(async field => { @@ -235,7 +241,7 @@ export default class ActionCollectionDecorator extends CollectionDecorator { private async dropDeferred( context: ActionContext, searchValues: Record | null, - fields: DynamicFormElement[], + fields: DynamicFormElementWithId[], ): Promise { const newFields = fields.map(async (field): Promise => { await this.executeOnSubFields(field, subfields => @@ -245,7 +251,7 @@ export default class ActionCollectionDecorator extends CollectionDecorator { const keys = Object.keys(field); const values = await Promise.all( Object.values(field).map(value => { - const searchValue = field.type === 'Layout' ? null : searchValues?.[field.label]; + const searchValue = field.type === 'Layout' ? null : searchValues?.[field.id]; return this.evaluate(context, searchValue, value); }), @@ -291,10 +297,10 @@ export default class ActionCollectionDecorator extends CollectionDecorator { ) { if (field.type !== 'Layout') { // customer did not define a handler to rewrite the previous value => reuse current one. - if (field.value === undefined) field.value = formValues[field.label]; + if (field.value === undefined) field.value = formValues[field.id]; // fields that were accessed through the context.formValues.X getter should be watched. - field.watchChanges = used.has(field.label); + field.watchChanges = used.has(field.id); } return field; diff --git a/packages/datasource-customizer/src/decorators/actions/types/fields.ts b/packages/datasource-customizer/src/decorators/actions/types/fields.ts index 682dd7044f..908d120af5 100644 --- a/packages/datasource-customizer/src/decorators/actions/types/fields.ts +++ b/packages/datasource-customizer/src/decorators/actions/types/fields.ts @@ -22,6 +22,7 @@ export type ValueOrHandler = type BaseDynamicField = { type: Type; + id?: string; label: string; description?: ValueOrHandler; isRequired?: ValueOrHandler; @@ -268,3 +269,9 @@ export type DynamicLayoutElement = export type DynamicFormElement = | DynamicField | DynamicLayoutElement; + +export type DynamicFieldWithId = DynamicField & { id: string }; + +export type DynamicFormElementWithId = + | DynamicFieldWithId + | DynamicLayoutElement; diff --git a/packages/datasource-toolkit/src/interfaces/action.ts b/packages/datasource-toolkit/src/interfaces/action.ts index 40dc65161c..a5dbc97fea 100644 --- a/packages/datasource-toolkit/src/interfaces/action.ts +++ b/packages/datasource-toolkit/src/interfaces/action.ts @@ -26,6 +26,7 @@ export type ActionFormElementBase = { export interface ActionFieldBase extends ActionFormElementBase { type: ActionFieldType; widget?: ActionFieldWidget; + id: string; label: string; description?: string; isRequired?: boolean; diff --git a/packages/forestadmin-client/src/schema/types.ts b/packages/forestadmin-client/src/schema/types.ts index 664d308ccf..6ee32bad27 100644 --- a/packages/forestadmin-client/src/schema/types.ts +++ b/packages/forestadmin-client/src/schema/types.ts @@ -69,6 +69,7 @@ export type ForestServerActionFieldCommon< defaultValue: unknown; description: string | null; field: string; + label: string; hook: string; isReadOnly: boolean; isRequired: boolean; diff --git a/packages/plugin-export-advanced/test/index.test.ts b/packages/plugin-export-advanced/test/index.test.ts index 4d175febbf..c596e2ac1c 100644 --- a/packages/plugin-export-advanced/test/index.test.ts +++ b/packages/plugin-export-advanced/test/index.test.ts @@ -77,6 +77,7 @@ describe('plugin-export-advanced', () => { expect(form).toEqual([ { + id: 'Filename', label: 'Filename', type: 'String', value: `books - ${new Date().toISOString().substring(0, 10)}`, @@ -84,6 +85,7 @@ describe('plugin-export-advanced', () => { }, { enumValues: ['.csv', '.xlsx', '.json'], + id: 'Format', label: 'Format', type: 'Enum', value: '.csv', @@ -91,6 +93,7 @@ describe('plugin-export-advanced', () => { }, { enumValues: ['id', 'title', 'author:id', 'author:fullname'], + id: 'Fields', label: 'Fields', type: 'EnumList', value: ['id', 'title', 'author:id', 'author:fullname'],