Skip to content

Commit

Permalink
feat: init form support for has-one relationship (#718)
Browse files Browse the repository at this point in the history
Co-authored-by: Hein Jeong <heinje@amazon.com>
  • Loading branch information
hein-j and Hein Jeong committed Oct 18, 2022
1 parent 1c5cf33 commit 1196013
Show file tree
Hide file tree
Showing 15 changed files with 287 additions and 78 deletions.
30 changes: 16 additions & 14 deletions packages/codegen-ui/lib/__tests__/check-support.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,13 +29,6 @@ describe('checkIsSupportedAsForm', () => {
const model: GenericDataModel = {
fields: {
nonModel: { dataType: { nonModel: 'myNonModel' }, required: false, readOnly: false, isArray: false },
relationship: {
dataType: 'ID',
required: false,
readOnly: false,
isArray: false,
relationship: { type: 'HAS_ONE', relatedModelName: 'RelatedModel' },
},
},
};

Expand All @@ -46,13 +39,6 @@ describe('checkIsSupportedAsForm', () => {
const model: GenericDataModel = {
fields: {
nonModel: { dataType: { nonModel: 'myNonModel' }, required: false, readOnly: false, isArray: false },
relationship: {
dataType: 'ID',
required: false,
readOnly: false,
isArray: false,
relationship: { type: 'HAS_ONE', relatedModelName: 'RelatedModel' },
},
supportedField: { dataType: 'Boolean', required: false, readOnly: false, isArray: false },
},
};
Expand All @@ -70,4 +56,20 @@ describe('checkIsSupportedAsForm', () => {

expect(checkIsSupportedAsForm(model)).toBe(false);
});

it('should support relationships', () => {
const model: GenericDataModel = {
fields: {
relationship: {
dataType: 'ID',
required: true,
readOnly: false,
isArray: false,
relationship: { type: 'HAS_ONE', relatedModelName: 'RelatedModel' },
},
},
};

expect(checkIsSupportedAsForm(model)).toBe(true);
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,6 @@ describe('generateFormDefinition', () => {
dataType: { dataSourceType: 'DataStore', dataTypeName: 'Dog' },
fields: {
tricks: {
dataType: 'String',
inputType: {
isArray: true,
type: 'TextField',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,13 +18,16 @@ import { mapFormFieldConfig, getFormDefinitionInputElement } from '../../../gene
import { mergeValueMappings } from '../../../generate-form-definition/helpers/form-field';
import {
FormDefinition,
GenericDataRelationshipType,
GenericValidationType,
ModelFieldsConfigs,
StringLengthValidationType,
StudioFormFieldConfig,
StudioFormValueMappings,
StudioGenericFieldConfig,
ValidationTypes,
} from '../../../types';
import { ExtendedStudioGenericFieldConfig } from '../../../types/form/form-definition';
import { getBasicFormDefinition } from '../../__utils__/basic-form-definition';

describe('mapFormFieldConfig', () => {
Expand Down Expand Up @@ -498,6 +501,46 @@ describe('getFormDefinitionInputElement', () => {
});
});

it('should get Autocomplete', () => {
const valueMappings: StudioFormValueMappings = {
values: [{ value: { bindingProperties: { property: 'Owner', field: 'id' } } }],
bindingProperties: { Owner: { type: 'Data', bindingProperties: { model: 'Owner' } } },
};

const dataType = { model: 'Owner' };

const relationship: GenericDataRelationshipType = { relatedModelName: 'Owner', type: 'HAS_ONE' };

const config: ExtendedStudioGenericFieldConfig = {
dataType,
inputType: {
name: 'Owner',
readOnly: false,
required: true,
type: 'Autocomplete',
value: 'Owner',
isArray: false,
valueMappings,
},
label: 'Owner',
relationship,
};

expect(getFormDefinitionInputElement(config)).toStrictEqual({
componentType: 'Autocomplete',
props: {
label: 'Owner',
isReadOnly: false,
isRequired: true,
},
valueMappings,
dataType,
relationship,
isArray: false,
validations: [{ type: ValidationTypes.REQUIRED, immutable: true, validationMessage: 'Owner is required.' }],
});
});

it('should return default valueMappings for RadioGroupField if no values available', () => {
const config = {
inputType: {
Expand All @@ -517,7 +560,7 @@ describe('getFormDefinitionInputElement', () => {
});

it('should handle valueMappings for RadioGroupField of Boolean type', () => {
const config: StudioFormFieldConfig = {
const config: ExtendedStudioGenericFieldConfig = {
dataType: 'Boolean',
inputType: {
type: 'RadioGroupField',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -163,7 +163,53 @@ describe('mapModelFieldsConfigs', () => {
});
});

it('should add relationship fields to configs but not to matrix', () => {
it('should add model-type relationship fields to configs and matrix', () => {
const formDefinition: FormDefinition = getBasicFormDefinition();

const dataSchema: GenericDataSchema = {
dataSourceType: 'DataStore',
enums: {},
nonModels: {},
models: {
Dog: {
fields: {
Owner: {
dataType: { model: 'Owner' },
readOnly: false,
required: false,
isArray: false,
relationship: { type: 'HAS_ONE', relatedModelName: 'Owner' },
},
},
},
},
};

const modelFieldsConfigs = mapModelFieldsConfigs({ dataTypeName: 'Dog', formDefinition, dataSchema });

expect(formDefinition.elementMatrix).toStrictEqual([['Owner']]);
expect(modelFieldsConfigs).toStrictEqual({
Owner: {
dataType: { model: 'Owner' },
inputType: {
name: 'Owner',
readOnly: false,
required: false,
type: 'Autocomplete',
value: 'Owner',
isArray: false,
valueMappings: {
values: [{ value: { bindingProperties: { property: 'Owner', field: 'id' } } }],
bindingProperties: { Owner: { type: 'Data', bindingProperties: { model: 'Owner' } } },
},
},
label: 'Owner',
relationship: { relatedModelName: 'Owner', type: 'HAS_ONE' },
},
});
});

it('should add not-model type relationship fields to configs but not to matrix', () => {
const formDefinition: FormDefinition = getBasicFormDefinition();

const dataSchema: GenericDataSchema = {
Expand Down Expand Up @@ -195,11 +241,12 @@ describe('mapModelFieldsConfigs', () => {
name: 'ownerId',
readOnly: false,
required: false,
type: 'SelectField',
type: 'Autocomplete',
value: 'ownerId',
isArray: false,
},
label: 'Owner id',
relationship: { relatedModelName: 'Owner', type: 'HAS_ONE' },
},
});
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,32 @@ describe('mapFormMetaData', () => {
expect(fieldConfigs.badges.isArray).toBe(true);
});

it('should map relationship if it exists', () => {
const dataSchema = getGenericFromDataStore(schemaWithAssumptions);

const form: StudioForm = {
name: 'DataStoreForm',
formActionType: 'create',
dataType: {
dataSourceType: 'DataStore',
dataTypeName: 'User',
},
fields: {},
sectionalElements: {},
style: {},
cta: {},
};

const { fieldConfigs } = mapFormMetadata(form, generateFormDefinition({ form, dataSchema }));

expect('friends' in fieldConfigs).toBe(true);
expect(fieldConfigs.friends.relationship).toStrictEqual({
type: 'HAS_MANY',
relatedModelName: 'Friend',
relatedModelField: 'friendId',
});
});

describe('generateUniqueFieldName tests', () => {
let usedFieldNames: Set<string>;
beforeEach(() => {
Expand Down
1 change: 0 additions & 1 deletion packages/codegen-ui/lib/check-support.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,6 @@ import { GenericDataField, GenericDataModel } from './types';
export const checkIsSupportedAsFormField = (field: GenericDataField): boolean => {
const unsupportedFieldMap: { [key: string]: (f: GenericDataField) => boolean } = {
nonModel: (f) => typeof f.dataType === 'object' && 'nonModel' in f.dataType,
relationship: (f) => !!f.relationship,
};

const unsupportedArray = Object.values(unsupportedFieldMap);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -86,8 +86,8 @@ export const FIELD_TYPE_MAP: {
supportedComponents: new Set(['RadioGroupField', 'SelectField']),
},
Relationship: {
defaultComponent: 'SelectField',
supportedComponents: new Set(['SelectField']),
defaultComponent: 'Autocomplete',
supportedComponents: new Set(['Autocomplete']),
},
NonModel: {
defaultComponent: 'TextAreaField',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,16 +16,17 @@
import {
FormDefinition,
FormDefinitionInputElement,
StudioGenericFieldConfig,
ModelFieldsConfigs,
StudioFormFieldConfig,
StudioFormValueMappings,
FieldValidationConfiguration,
ValidationTypes,
DataFieldDataType,
} from '../../types';
import { InternalError, InvalidInputError } from '../../errors';
import { FORM_DEFINITION_DEFAULTS } from './defaults';
import { deleteUndefined, getFirstDefinedValue, getFirstNumber, getFirstString } from './mapper-utils';
import { ExtendedStudioGenericFieldConfig } from '../../types/form/form-definition';

export function mergeValueMappings(
base?: StudioFormValueMappings,
Expand Down Expand Up @@ -58,8 +59,8 @@ export function mergeValueMappings(
}

function getRadioGroupFieldValueMappings(
config: StudioGenericFieldConfig,
baseConfig?: StudioGenericFieldConfig,
config: ExtendedStudioGenericFieldConfig,
baseConfig?: ExtendedStudioGenericFieldConfig,
): StudioFormValueMappings {
const valueMappings: StudioFormValueMappings =
baseConfig?.inputType?.valueMappings?.values.length || config?.inputType?.valueMappings?.values.length
Expand Down Expand Up @@ -91,16 +92,29 @@ function getRadioGroupFieldValueMappings(
return valueMappings;
}

function getRequiredValidationMessage(dataType?: DataFieldDataType): string | undefined {
if (typeof dataType === 'object' && 'model' in dataType) {
return `${dataType.model} is required.`;
}

return undefined;
}

// pure function that merges in validations in param with defaults
function getMergedValidations(
componentType: string,
validations: (FieldValidationConfiguration[] | undefined)[],
isRequired?: boolean,
dataType?: DataFieldDataType,
): (FieldValidationConfiguration & { immutable?: true })[] | undefined {
const mergedValidations: (FieldValidationConfiguration & { immutable?: true })[] = [];

if (isRequired) {
mergedValidations.push({ type: ValidationTypes.REQUIRED, immutable: true });
const requiredValidation: typeof mergedValidations[0] = { type: ValidationTypes.REQUIRED, immutable: true };
const requiredValidationMessage = getRequiredValidationMessage(dataType);
if (requiredValidationMessage) {
requiredValidation.validationMessage = requiredValidationMessage;
}
mergedValidations.push(requiredValidation);
}

const ComponentTypeToDefaultValidations: {
Expand Down Expand Up @@ -144,8 +158,8 @@ function getTextFieldType(componentType: string): string | undefined {
*/

export function getFormDefinitionInputElement(
config: StudioGenericFieldConfig,
baseConfig?: StudioGenericFieldConfig,
config: ExtendedStudioGenericFieldConfig,
baseConfig?: ExtendedStudioGenericFieldConfig,
): FormDefinitionInputElement {
const componentType = config.inputType?.type || baseConfig?.inputType?.type;

Expand Down Expand Up @@ -316,6 +330,22 @@ export function getFormDefinitionInputElement(
},
};
break;

case 'Autocomplete':
formDefinitionElement = {
componentType: 'Autocomplete',
props: {
label: config.label || baseConfig?.label || FORM_DEFINITION_DEFAULTS.field.inputType.label,
descriptiveText: config.inputType?.descriptiveText ?? baseConfig?.inputType?.descriptiveText,
isRequired: isRequiredValue,
isReadOnly: getFirstDefinedValue([config.inputType?.readOnly, baseConfig?.inputType?.readOnly]),
placeholder: config.inputType?.placeholder || baseConfig?.inputType?.placeholder,
defaultValue: defaultStringValue,
},
valueMappings: mergeValueMappings(baseConfig?.inputType?.valueMappings, config.inputType?.valueMappings),
};
break;

default:
throw new InvalidInputError(`componentType ${componentType} could not be mapped`);
}
Expand All @@ -324,11 +354,13 @@ export function getFormDefinitionInputElement(
componentType,
[baseConfig?.validations, config?.validations],
isRequiredValue,
config?.dataType ?? baseConfig?.dataType,
);

formDefinitionElement.validations = mergedValidations;
formDefinitionElement.dataType = config?.dataType || baseConfig?.dataType;
formDefinitionElement.isArray = baseConfig ? baseConfig.inputType?.isArray : config.inputType?.isArray;
formDefinitionElement.relationship = config.relationship ?? baseConfig?.relationship;

deleteUndefined(formDefinitionElement);
deleteUndefined(formDefinitionElement.props);
Expand Down
Loading

0 comments on commit 1196013

Please sign in to comment.