Skip to content

Commit

Permalink
chore: render indices on children of hasMany
Browse files Browse the repository at this point in the history
  • Loading branch information
Hein Jeong authored and hein-j committed Jan 19, 2023
1 parent f32882f commit 0b50f29
Show file tree
Hide file tree
Showing 15 changed files with 1,059 additions and 19 deletions.

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -499,6 +499,18 @@ describe('amplify form renderer tests', () => {
expect(componentText).not.toContain('CompositeToys');
expect(componentText).not.toContain('CompositeVets');
});

it('should render a create form for child of 1:m relationship', () => {
const { componentText, declaration } = generateWithAmplifyFormRenderer(
'forms/composite-toy-datastore-create',
'datastore/composite-relationships',
undefined,
{ isNonModelSupported: true, isRelationshipSupported: true },
);

expect(componentText).toMatchSnapshot();
expect(declaration).toMatchSnapshot();
});
});
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@
import { CallExpression, factory, IfStatement, NodeFlags, SyntaxKind } from 'typescript';
import {
FieldConfigMetadata,
GenericDataRelationshipType,
HasManyRelationshipType,
InternalError,
GenericDataModel,
Expand All @@ -30,11 +29,7 @@ import { isManyToManyRelationship } from './map-from-fieldConfigs';
import { extractModelAndKeys, getIDValueCallChain, getMatchEveryModelFieldCallExpression } from './model-values';
import { isModelDataType } from './render-checkers';

export const buildRelationshipQuery = (
relationship: GenericDataRelationshipType,
importCollection: ImportCollection,
) => {
const { relatedModelName } = relationship;
export const buildRelationshipQuery = (relatedModelName: string, importCollection: ImportCollection) => {
const itemsName = getRecordsName(relatedModelName);
const objectProperties = [
factory.createPropertyAssignment(factory.createIdentifier('type'), factory.createStringLiteral('collection')),
Expand Down
13 changes: 7 additions & 6 deletions packages/codegen-ui-react/lib/forms/react-form-renderer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,6 @@ import {
FormDefinition,
generateFormDefinition,
GenericDataSchema,
GenericDataRelationshipType,
handleCodegenErrors,
mapFormDefinitionToComponent,
mapFormMetadata,
Expand Down Expand Up @@ -518,7 +517,7 @@ export abstract class ReactFormTemplateRenderer extends StudioTemplateRenderer<

// Add value state and ref array type fields in ArrayField wrapper

const relationshipCollection: GenericDataRelationshipType[] = [];
const relatedModelNames: Set<string> = new Set();

Object.entries(formMetadata.fieldConfigs).forEach(([field, fieldConfig]) => {
const { sanitizedFieldName, componentType, dataType, relationship } = fieldConfig;
Expand Down Expand Up @@ -562,8 +561,8 @@ export abstract class ReactFormTemplateRenderer extends StudioTemplateRenderer<
);
}

if (relationship) {
relationshipCollection.push(relationship);
if (relationship && !relatedModelNames.has(relationship.relatedModelName)) {
relatedModelNames.add(relationship.relatedModelName);
}
});

Expand All @@ -590,10 +589,12 @@ export abstract class ReactFormTemplateRenderer extends StudioTemplateRenderer<
model: Author,
}).items;
*/
if (relationshipCollection.length) {
if (relatedModelNames.size) {
this.importCollection.addMappedImport(ImportValue.USE_DATA_STORE_BINDING);
statements.push(
...relationshipCollection.map((relationship) => buildRelationshipQuery(relationship, this.importCollection)),
...[...relatedModelNames].map((relatedModelName) =>
buildRelationshipQuery(relatedModelName, this.importCollection),
),
);
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
{
"name": "CreateCompositeToyForm",
"dataType": {
"dataSourceType": "DataStore",
"dataTypeName": "CompositeToy"
},
"formActionType": "create",
"fields": {},
"sectionalElements": {},
"style": {},
"cta": {}
}
Original file line number Diff line number Diff line change
Expand Up @@ -398,7 +398,12 @@ describe('mapModelFieldsConfigs', () => {
},
};

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

expect(formDefinition.elementMatrix).toStrictEqual([]);
expect(modelFieldsConfigs).toStrictEqual({
Expand All @@ -423,6 +428,139 @@ describe('mapModelFieldsConfigs', () => {
});
});

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

const dataSchema: GenericDataSchema = {
dataSourceType: 'DataStore',
enums: {},
nonModels: {},
models: {
Owner: {
primaryKeys: ['id'],
fields: {},
},
CompositeDog: {
primaryKeys: ['name', 'description'],
fields: {
name: {
dataType: 'ID',
required: true,
readOnly: false,
isArray: false,
},
description: {
dataType: 'String',
required: true,
readOnly: false,
isArray: false,
},
CompositeToys: {
dataType: {
model: 'CompositeToy',
},
required: false,
readOnly: false,
isArray: true,
relationship: {
type: 'HAS_MANY',
relatedModelName: 'CompositeToy',
relatedModelFields: ['compositeDogCompositeToysName', 'compositeDogCompositeToysDescription'],
},
},
},
},
CompositeToy: {
fields: {
kind: {
dataType: 'ID',
required: true,
readOnly: false,
isArray: false,
},
color: {
dataType: 'String',
required: true,
readOnly: false,
isArray: false,
},
compositeDogCompositeToysName: {
dataType: 'ID',
required: false,
readOnly: false,
isArray: false,
relationship: {
type: 'HAS_ONE',
relatedModelName: 'CompositeDog',
isHasManyIndex: true,
},
},
compositeDogCompositeToysDescription: {
dataType: 'String',
required: false,
readOnly: false,
isArray: false,
relationship: {
type: 'HAS_ONE',
relatedModelName: 'CompositeDog',
isHasManyIndex: true,
},
},
},
primaryKeys: ['kind', 'color'],
},
},
};

const modelFieldsConfigs = mapModelFieldsConfigs({
dataTypeName: 'CompositeToy',
formDefinition,
dataSchema,
featureFlags: { isRelationshipSupported: true },
});

expect(formDefinition.elementMatrix).toStrictEqual([
['kind'],
['color'],
['compositeDogCompositeToysName'],
['compositeDogCompositeToysDescription'],
]);
expect(modelFieldsConfigs.compositeDogCompositeToysName).toStrictEqual({
label: 'Composite dog composite toys name',
dataType: 'ID',
inputType: {
type: 'Autocomplete',
required: false,
readOnly: false,
name: 'compositeDogCompositeToysName',
value: 'compositeDogCompositeToysName',
isArray: false,
valueMappings: {
values: [{ value: { bindingProperties: { property: 'CompositeDog', field: 'name' } } }],
bindingProperties: { CompositeDog: { type: 'Data', bindingProperties: { model: 'CompositeDog' } } },
},
},
relationship: { type: 'HAS_ONE', relatedModelName: 'CompositeDog', isHasManyIndex: true },
});
expect(modelFieldsConfigs.compositeDogCompositeToysDescription).toStrictEqual({
label: 'Composite dog composite toys description',
dataType: 'String',
inputType: {
type: 'Autocomplete',
required: false,
readOnly: false,
name: 'compositeDogCompositeToysDescription',
value: 'compositeDogCompositeToysDescription',
isArray: false,
valueMappings: {
values: [{ value: { bindingProperties: { property: 'CompositeDog', field: 'description' } } }],
bindingProperties: { CompositeDog: { type: 'Data', bindingProperties: { model: 'CompositeDog' } } },
},
},
relationship: { type: 'HAS_ONE', relatedModelName: 'CompositeDog', isHasManyIndex: true },
});
});

it('should add nonModel field to matrix if nonModel enabled', () => {
const formDefinition: FormDefinition = getBasicFormDefinition();

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,7 @@ describe('getGenericFromDataStore', () => {
expect(genericSchema.models.Dog.fields.ownerID.relationship).toStrictEqual({
type: 'HAS_ONE',
relatedModelName: 'Owner',
isHasManyIndex: true,
});
});

Expand Down Expand Up @@ -175,6 +176,7 @@ describe('getGenericFromDataStore', () => {
expect(genericSchema.models.Dog.fields.ownerID.relationship).toStrictEqual({
type: 'HAS_ONE',
relatedModelName: 'Owner',
isHasManyIndex: true,
});
});

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,41 @@ import { ExtendedStudioGenericFieldConfig } from '../../types/form/form-definiti
import { StudioFormInputFieldProperty } from '../../types/form/input-config';
import { FIELD_TYPE_MAP } from './field-type-map';

function extractCorrespondingKey({
thisModel,
relatedModel,
relationshipFieldName,
}: {
thisModel: GenericDataModel;
relatedModel: GenericDataModel;
relationshipFieldName: string;
}): string {
const relationshipField = thisModel.fields[relationshipFieldName];
if (
relationshipField.relationship &&
'isHasManyIndex' in relationshipField.relationship &&
relationshipField.relationship.isHasManyIndex
) {
const correspondingFieldTuple = Object.entries(relatedModel.fields).find(
([, field]) =>
field.relationship?.type === 'HAS_MANY' &&
field.relationship?.relatedModelFields.includes(relationshipFieldName),
);
if (correspondingFieldTuple) {
const correspondingField = correspondingFieldTuple[1].relationship;
if (correspondingField?.type === 'HAS_MANY') {
const indexOfKey = correspondingField.relatedModelFields.indexOf(relationshipFieldName);
if (indexOfKey !== -1) {
return relatedModel.primaryKeys[indexOfKey];
}
}
}
}

// TODO: support other types
return relationshipFieldName;
}

export function getFieldTypeMapKey(field: GenericDataField): FieldTypeMapKeys {
if (typeof field.dataType === 'object' && 'enum' in field.dataType) {
return 'Enum';
Expand Down Expand Up @@ -96,11 +131,13 @@ function getModelDisplayValue({
}

function getValueMappings({
dataTypeName,
fieldName,
field,
enums,
allModels,
}: {
dataTypeName: string;
fieldName: string;
field: GenericDataField;
enums: GenericDataSchema['enums'];
Expand All @@ -126,8 +163,16 @@ function getValueMappings({
const modelName = field.relationship.relatedModelName;
const relatedModel = allModels[modelName];
const isModelType = typeof field.dataType === 'object' && 'model' in field.dataType;
// if model, store all keys; else, store field as key
const keys = isModelType ? relatedModel.primaryKeys : [fieldName];
// if model, store all keys; else, store corresponding primary key
const keys = isModelType
? relatedModel.primaryKeys
: [
extractCorrespondingKey({
thisModel: allModels[dataTypeName],
relatedModel,
relationshipFieldName: fieldName,
}),
];
const values: StudioFormValueMappings['values'] = keys.map((key) => ({
value: { bindingProperties: { property: modelName, field: key } },
}));
Expand All @@ -145,11 +190,13 @@ function getValueMappings({
}

export function getFieldConfigFromModelField({
dataTypeName,
fieldName,
field,
dataSchema,
setReadOnly,
}: {
dataTypeName: string;
fieldName: string;
field: GenericDataField;
dataSchema: GenericDataSchema;
Expand Down Expand Up @@ -197,7 +244,13 @@ export function getFieldConfigFromModelField({
config.inputType.placeholder = `Search ${field.relationship.relatedModelName}`;
}

const valueMappings = getValueMappings({ fieldName, field, enums: dataSchema.enums, allModels: dataSchema.models });
const valueMappings = getValueMappings({
dataTypeName,
fieldName,
field,
enums: dataSchema.enums,
allModels: dataSchema.models,
});
if (valueMappings) {
config.inputType.valueMappings = valueMappings;
}
Expand Down Expand Up @@ -234,7 +287,9 @@ export function mapModelFieldsConfigs({
field.readOnly ||
(fieldName === 'id' && field.dataType === 'ID' && field.required) ||
!checkIsSupportedAsFormField(field, featureFlags) ||
(field.relationship && !(typeof field.dataType === 'object' && 'model' in field.dataType));
(field.relationship &&
!(typeof field.dataType === 'object' && 'model' in field.dataType) &&
!('isHasManyIndex' in field.relationship && field.relationship.isHasManyIndex));

if (!isAutoExcludedField) {
formDefinition.elementMatrix.push([fieldName]);
Expand All @@ -243,6 +298,7 @@ export function mapModelFieldsConfigs({
const isPrimaryKey = model.primaryKeys.includes(fieldName);

modelFieldsConfigs[fieldName] = getFieldConfigFromModelField({
dataTypeName,
fieldName,
field,
dataSchema,
Expand Down
1 change: 1 addition & 0 deletions packages/codegen-ui/lib/generic-from-datastore.ts
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,7 @@ export function getGenericFromDataStore(dataStoreSchema: DataStoreSchema): Gener
addRelationship(fieldsWithImplicitRelationships, relatedModelName, associatedFieldName, {
type: 'HAS_ONE',
relatedModelName: model.name,
isHasManyIndex: true,
});
}
});
Expand Down
Loading

0 comments on commit 0b50f29

Please sign in to comment.