Skip to content

Commit

Permalink
feat: add optional id in action form fields
Browse files Browse the repository at this point in the history
  • Loading branch information
Enki Pontvianne committed Sep 18, 2024
1 parent 79f503b commit a6363d9
Show file tree
Hide file tree
Showing 8 changed files with 46 additions and 23 deletions.
5 changes: 5 additions & 0 deletions packages/_example/src/forest/customizations/card.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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: '<h3>constraints:</h3>' },
{
Expand Down
2 changes: 1 addition & 1 deletion packages/agent/src/utils/forest-schema/action-values.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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)) {
Expand Down
8 changes: 4 additions & 4 deletions packages/agent/src/utils/forest-schema/generator-actions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ export default class SchemaGeneratorActions {
static defaultFields: ForestServerActionField[] = [
{
field: 'Loading...',
label: 'Loading...',
type: 'String',
isReadOnly: true,
defaultValue: 'Form is loading',
Expand Down Expand Up @@ -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<string, unknown>;
const { id, label, description, isRequired, isReadOnly, watchChanges, type } = field;
const output = { id, label, description, isRequired, isReadOnly } as Record<string, unknown>;

output.field = label;
output.value = ForestValueConverter.valueToForest(field, field.value);

if (watchChanges) output.hook = 'changeHook';
Expand Down Expand Up @@ -215,7 +215,7 @@ export default class SchemaGeneratorActions {
return {
type: 'Layout',
component: 'Input',
fieldId: element.label,
fieldId: element.id,
};
}
}
42 changes: 24 additions & 18 deletions packages/datasource-customizer/src/decorators/actions/collection.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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);

Expand Down Expand Up @@ -164,18 +168,20 @@ export default class ActionCollectionDecorator extends CollectionDecorator {
);
}

private async dropDefaults(
private async dropDefaultsAndSetId(
context: ActionContext,
fields: DynamicFormElement[],
data: Record<string, unknown>,
): Promise<DynamicFormElement[]> {
): Promise<DynamicFormElementWithId[]> {
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;
Expand All @@ -186,12 +192,12 @@ export default class ActionCollectionDecorator extends CollectionDecorator {

private async dropDefault(
context: ActionContext,
field: DynamicField,
field: DynamicFieldWithId,
data: Record<string, unknown>,
): Promise<DynamicField> {
if (data[field.label] === undefined) {
): Promise<DynamicFieldWithId> {
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;
Expand All @@ -201,8 +207,8 @@ export default class ActionCollectionDecorator extends CollectionDecorator {

private async dropIfs(
context: ActionContext,
fields: DynamicFormElement[],
): Promise<DynamicFormElement[]> {
fields: DynamicFormElementWithId[],
): Promise<DynamicFormElementWithId[]> {
// Remove fields which have falsy if
const ifValues = await Promise.all(
fields.map(async field => {
Expand Down Expand Up @@ -235,7 +241,7 @@ export default class ActionCollectionDecorator extends CollectionDecorator {
private async dropDeferred(
context: ActionContext,
searchValues: Record<string, string | null> | null,
fields: DynamicFormElement[],
fields: DynamicFormElementWithId[],
): Promise<ActionFormElement[]> {
const newFields = fields.map(async (field): Promise<ActionFormElement> => {
await this.executeOnSubFields(field, subfields =>
Expand All @@ -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);
}),
Expand Down Expand Up @@ -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;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ export type ValueOrHandler<Context = unknown, Result = unknown> =

type BaseDynamicField<Type, Context, Result> = {
type: Type;
id?: string;
label: string;
description?: ValueOrHandler<Context, string>;
isRequired?: ValueOrHandler<Context, boolean>;
Expand Down Expand Up @@ -268,3 +269,9 @@ export type DynamicLayoutElement<Context = unknown> =
export type DynamicFormElement<Context = unknown> =
| DynamicField<Context>
| DynamicLayoutElement<Context>;

export type DynamicFieldWithId<Context = unknown> = DynamicField<Context> & { id: string };

export type DynamicFormElementWithId<Context = unknown> =
| DynamicFieldWithId<Context>
| DynamicLayoutElement<Context>;
1 change: 1 addition & 0 deletions packages/datasource-toolkit/src/interfaces/action.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ export type ActionFormElementBase = {
export interface ActionFieldBase extends ActionFormElementBase {
type: ActionFieldType;
widget?: ActionFieldWidget;
id: string;
label: string;
description?: string;
isRequired?: boolean;
Expand Down
1 change: 1 addition & 0 deletions packages/forestadmin-client/src/schema/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@ export type ForestServerActionFieldCommon<
defaultValue: unknown;
description: string | null;
field: string;
label: string;
hook: string;
isReadOnly: boolean;
isRequired: boolean;
Expand Down
3 changes: 3 additions & 0 deletions packages/plugin-export-advanced/test/index.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -77,20 +77,23 @@ describe('plugin-export-advanced', () => {

expect(form).toEqual([
{
id: 'Filename',
label: 'Filename',
type: 'String',
value: `books - ${new Date().toISOString().substring(0, 10)}`,
watchChanges: false,
},
{
enumValues: ['.csv', '.xlsx', '.json'],
id: 'Format',
label: 'Format',
type: 'Enum',
value: '.csv',
watchChanges: false,
},
{
enumValues: ['id', 'title', 'author:id', 'author:fullname'],
id: 'Fields',
label: 'Fields',
type: 'EnumList',
value: ['id', 'title', 'author:id', 'author:fullname'],
Expand Down

0 comments on commit a6363d9

Please sign in to comment.