Skip to content

Commit

Permalink
feat: init composite key property on update form (#928)
Browse files Browse the repository at this point in the history
Co-authored-by: David Lopez <lopezbnd@amazon.com>
  • Loading branch information
letsbelopez and David Lopez committed Feb 23, 2023
1 parent 4096da2 commit 341882b
Show file tree
Hide file tree
Showing 11 changed files with 168 additions and 37 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -5491,7 +5491,7 @@ function ArrayField({
}
export default function UpdateCompositeDogForm(props) {
const {
name: nameProp,
id: idProp,
compositeDog,
onSuccess,
onError,
Expand Down Expand Up @@ -5561,8 +5561,8 @@ export default function UpdateCompositeDogForm(props) {
const canUnlinkCompositeVets = false;
React.useEffect(() => {
const queryData = async () => {
const record = nameProp
? await DataStore.query(CompositeDog, nameProp)
const record = idProp
? await DataStore.query(CompositeDog, idProp)
: compositeDog;
setCompositeDogRecord(record);
const CompositeBowlRecord = record
Expand All @@ -5589,7 +5589,7 @@ export default function UpdateCompositeDogForm(props) {
setLinkedCompositeVets(linkedCompositeVets);
};
queryData();
}, [nameProp, compositeDog]);
}, [idProp, compositeDog]);
React.useEffect(resetStateValues, [
compositeDogRecord,
CompositeBowl,
Expand Down Expand Up @@ -6351,7 +6351,7 @@ export default function UpdateCompositeDogForm(props) {
event.preventDefault();
resetStateValues();
}}
isDisabled={!(nameProp || compositeDog)}
isDisabled={!(idProp || compositeDog)}
{...getOverrideProps(overrides, \\"ResetButton\\")}
></Button>
<Flex
Expand All @@ -6363,7 +6363,7 @@ export default function UpdateCompositeDogForm(props) {
type=\\"submit\\"
variation=\\"primary\\"
isDisabled={
!(nameProp || compositeDog) ||
!(idProp || compositeDog) ||
Object.values(errors).some((e) => e?.hasError)
}
{...getOverrideProps(overrides, \\"SubmitButton\\")}
Expand Down Expand Up @@ -6415,7 +6415,10 @@ export declare type UpdateCompositeDogFormOverridesProps = {
export declare type UpdateCompositeDogFormProps = React.PropsWithChildren<{
overrides?: UpdateCompositeDogFormOverridesProps | undefined | null;
} & {
name?: string;
id?: {
name: string;
description: string;
};
compositeDog?: CompositeDog;
onSubmit?: (fields: UpdateCompositeDogFormInputValues) => UpdateCompositeDogFormInputValues;
onSuccess?: (fields: UpdateCompositeDogFormInputValues) => void;
Expand Down Expand Up @@ -8715,7 +8718,7 @@ function ArrayField({
}
export default function UpdateCompositeDogForm(props) {
const {
name: nameProp,
id: idProp,
compositeDog,
onSuccess,
onError,
Expand Down Expand Up @@ -8785,8 +8788,8 @@ export default function UpdateCompositeDogForm(props) {
const canUnlinkCompositeVets = false;
React.useEffect(() => {
const queryData = async () => {
const record = nameProp
? await DataStore.query(CompositeDog, nameProp)
const record = idProp
? await DataStore.query(CompositeDog, idProp)
: compositeDog;
setCompositeDogRecord(record);
const CompositeOwnerRecord = record
Expand All @@ -8813,7 +8816,7 @@ export default function UpdateCompositeDogForm(props) {
setCompositeDogCompositeBowlShape(compositeDogCompositeBowlShapeRecord);
};
queryData();
}, [nameProp, compositeDog]);
}, [idProp, compositeDog]);
React.useEffect(resetStateValues, [
compositeDogRecord,
CompositeOwner,
Expand Down Expand Up @@ -9551,7 +9554,7 @@ export default function UpdateCompositeDogForm(props) {
event.preventDefault();
resetStateValues();
}}
isDisabled={!(nameProp || compositeDog)}
isDisabled={!(idProp || compositeDog)}
{...getOverrideProps(overrides, \\"ResetButton\\")}
></Button>
<Flex
Expand All @@ -9563,7 +9566,7 @@ export default function UpdateCompositeDogForm(props) {
type=\\"submit\\"
variation=\\"primary\\"
isDisabled={
!(nameProp || compositeDog) ||
!(idProp || compositeDog) ||
Object.values(errors).some((e) => e?.hasError)
}
{...getOverrideProps(overrides, \\"SubmitButton\\")}
Expand Down Expand Up @@ -9615,7 +9618,10 @@ export declare type UpdateCompositeDogFormOverridesProps = {
export declare type UpdateCompositeDogFormProps = React.PropsWithChildren<{
overrides?: UpdateCompositeDogFormOverridesProps | undefined | null;
} & {
name?: string;
id?: {
name: string;
description: string;
};
compositeDog?: CompositeDog;
onSubmit?: (fields: UpdateCompositeDogFormInputValues) => UpdateCompositeDogFormInputValues;
onSuccess?: (fields: UpdateCompositeDogFormInputValues) => void;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,3 +27,19 @@ exports[`form-render utils should render cancel props if included cancel object
onValidate?: myCustomFormValidationValues;
}"
`;

exports[`form-render utils should render composite primary keys 1`] = `
"{
id?: {
myKey: string;
description: number;
};
post?: Post;
onSubmit?: (fields: mySampleFormInputValues) => mySampleFormInputValues;
onSuccess?: (fields: mySampleFormInputValues) => void;
onError?: (fields: mySampleFormInputValues, errorMessage: string) => void;
onCancel?: () => void;
onChange?: (fields: mySampleFormInputValues) => mySampleFormInputValues;
onValidate?: mySampleFormValidationValues;
}"
`;
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ describe('form-render utils', () => {
cta: {},
};

const propSignatures = buildFormPropNode(form);
const propSignatures = buildFormPropNode(form, {});
const node = printNode(propSignatures);
expect(node).toMatchSnapshot();
});
Expand All @@ -56,7 +56,7 @@ describe('form-render utils', () => {
style: {},
cta: {},
};
const propSignatures = buildFormPropNode(form);
const propSignatures = buildFormPropNode(form, {});
const node = printNode(propSignatures);
expect(node).toMatchSnapshot();
});
Expand All @@ -74,9 +74,35 @@ describe('form-render utils', () => {
cancel: {},
},
};
const propSignatures = buildFormPropNode(form);
const propSignatures = buildFormPropNode(form, {});
const node = printNode(propSignatures);
expect(node).toContain('onCancel?: () => void;');
expect(node).toMatchSnapshot();
});

it('should render composite primary keys', () => {
const form: StudioForm = {
id: '123',
name: 'mySampleForm',
formActionType: 'update',
dataType: { dataSourceType: 'DataStore', dataTypeName: 'Post' },
fields: {},
sectionalElements: {},
style: {},
cta: {
cancel: {},
},
};
const propSignatures = buildFormPropNode(
form,
{ description: { dataType: 'Int', validationRules: [], componentType: 'TextField' } },
undefined,
['myKey', 'description'],
);
const node = printNode(propSignatures);
expect(node).toContain('id?: {');
expect(node).toContain('myKey: string;');
expect(node).toContain('description: number;');
expect(node).toMatchSnapshot();
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ import { shouldWrapInArrayField } from './render-checkers';
import { getAutocompleteOptionsProp } from './model-values';
import { buildCtaLayoutProperties } from '../../react-component-render-helper';
import { lowerCaseFirst } from '../../helpers';
import { COMPOSITE_PRIMARY_KEY_PROP_NAME } from '../../utils/constants';

export const addFormAttributes = (
component: StudioComponent | StudioComponentChild,
Expand Down Expand Up @@ -165,8 +166,11 @@ export const addFormAttributes = (
resetStateValues();
}}
*/
const firstPrimaryKey = dataSchema?.models[dataTypeName]?.primaryKeys[0];
const idProp = firstPrimaryKey ? getPropName(firstPrimaryKey) : '';
// multiple primary keys means it is a composite key
const primaryKeys = dataSchema?.models[dataTypeName]?.primaryKeys?.length
? dataSchema.models[dataTypeName].primaryKeys
: [''];
const idProp = primaryKeys.length > 1 ? getPropName(COMPOSITE_PRIMARY_KEY_PROP_NAME) : getPropName(primaryKeys[0]);
if (componentName === 'ClearButton' || componentName === 'ResetButton') {
attributes.push(
factory.createJsxAttribute(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -54,21 +54,25 @@ import { getOnChangeValidationBlock } from './validation';
import { buildModelFieldObject } from './model-fields';
import { isModelDataType, shouldWrapInArrayField } from './render-checkers';
import { extractModelAndKeys, getMatchEveryModelFieldCallExpression } from './model-values';
import { COMPOSITE_PRIMARY_KEY_PROP_NAME } from '../../utils/constants';

export const buildMutationBindings = (form: StudioForm, primaryKey?: string) => {
export const buildMutationBindings = (form: StudioForm, primaryKeys: string[] = []) => {
const {
dataType: { dataSourceType, dataTypeName },
formActionType,
} = form;
const elements: BindingElement[] = [];
if (dataSourceType === 'DataStore' && primaryKey) {
if (dataSourceType === 'DataStore' && primaryKeys.length) {
if (formActionType === 'update') {
elements.push(
// id: idProp
factory.createBindingElement(
undefined,
factory.createIdentifier(primaryKey),
factory.createIdentifier(getPropName(primaryKey)),
// if greater than 1, it's a composite key. using 'id' for a composite key prop name.
factory.createIdentifier(primaryKeys.length > 1 ? COMPOSITE_PRIMARY_KEY_PROP_NAME : primaryKeys[0]),
factory.createIdentifier(
primaryKeys.length > 1 ? getPropName(COMPOSITE_PRIMARY_KEY_PROP_NAME) : getPropName(primaryKeys[0]),
),
undefined,
),
factory.createBindingElement(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import {
FormDefinition,
isValidVariableName,
shouldIncludeCancel,
InternalError,
} from '@aws-amplify/codegen-ui';
import {
factory,
Expand All @@ -38,6 +39,7 @@ import { lowerCaseFirst } from '../../helpers';
import { DATA_TYPE_TO_TYPESCRIPT_MAP, FIELD_TYPE_TO_TYPESCRIPT_MAP } from './typescript-type-map';
import { ImportCollection, ImportSource } from '../../imports';
import { PRIMITIVE_OVERRIDE_PROPS } from '../../primitive';
import { COMPOSITE_PRIMARY_KEY_PROP_NAME } from '../../utils/constants';

type Node<T> = {
[n: string]: T | Node<T>;
Expand Down Expand Up @@ -268,7 +270,12 @@ export const validationFunctionType = factory.createTypeAliasDeclaration(
- onSuccess(fields)
- onError(fields, errorMessage)
*/
export const buildFormPropNode = (form: StudioForm, modelName?: string, primaryKey?: string) => {
export const buildFormPropNode = (
form: StudioForm,
fieldConfigs: Record<string, FieldConfigMetadata>,
modelName?: string,
primaryKeys: string[] = [],
) => {
const {
name: formName,
dataType: { dataSourceType, dataTypeName },
Expand All @@ -277,15 +284,8 @@ export const buildFormPropNode = (form: StudioForm, modelName?: string, primaryK
const propSignatures: PropertySignature[] = [];
if (dataSourceType === 'DataStore') {
if (formActionType === 'update') {
if (primaryKey) {
propSignatures.push(
factory.createPropertySignature(
undefined,
factory.createIdentifier(primaryKey),
factory.createToken(SyntaxKind.QuestionToken),
factory.createKeywordTypeNode(SyntaxKind.StringKeyword),
),
);
if (primaryKeys.length >= 1) {
propSignatures.push(createPrimaryKeysTypeProp(primaryKeys, fieldConfigs));
}
propSignatures.push(
factory.createPropertySignature(
Expand Down Expand Up @@ -519,3 +519,46 @@ export const buildOverrideTypesBindings = (
]),
);
};

const createPrimaryKeysTypeProp = (
primaryKeys: string[],
fieldConfigs: Record<string, FieldConfigMetadata>,
): PropertySignature => {
// first element is the property name
if (primaryKeys.length === 1) {
return factory.createPropertySignature(
undefined,
factory.createIdentifier(primaryKeys[0]),
factory.createToken(SyntaxKind.QuestionToken),
factory.createKeywordTypeNode(SyntaxKind.StringKeyword),
);
}

if (primaryKeys.length <= 0) {
throw new InternalError('primaryKeys must not be empty');
}
// creates the type literal for a composite key
const compositeKeyTypeLiteral = primaryKeys.map((primaryKey: string) => {
let keywordType = SyntaxKind.StringKeyword;
const element = fieldConfigs[primaryKey];
if (element) {
const { dataType } = element;
// non-scalar & boolean are not supported for primary keys that leaves number and string
const stringDataType = typeof dataType === 'string' ? dataType : 'String';
keywordType = DATA_TYPE_TO_TYPESCRIPT_MAP[stringDataType] ?? SyntaxKind.StringKeyword;
}

return factory.createPropertySignature(
undefined,
factory.createIdentifier(primaryKey),
undefined,
factory.createKeywordTypeNode(keywordType),
);
});
return factory.createPropertySignature(
undefined,
factory.createIdentifier(COMPOSITE_PRIMARY_KEY_PROP_NAME),
factory.createToken(SyntaxKind.QuestionToken),
factory.createTypeLiteralNode(compositeKeyTypeLiteral),
);
};
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ export const DATA_TYPE_TO_TYPESCRIPT_MAP: { [key: string]: KeywordTypeSyntaxKind
Float: SyntaxKind.NumberKeyword,
Boolean: SyntaxKind.BooleanKeyword,
AWSTimestamp: SyntaxKind.NumberKeyword,
String: SyntaxKind.StringKeyword,
};

export const FIELD_TYPE_TO_TYPESCRIPT_MAP: { [key: string]: KeywordTypeSyntaxKind } = {
Expand Down
9 changes: 6 additions & 3 deletions packages/codegen-ui-react/lib/forms/react-form-renderer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,7 @@ import {
validationResponseType,
} from './form-renderer-helper/type-helper';
import { buildSelectedRecordsIdSet } from './form-renderer-helper/model-values';
import { COMPOSITE_PRIMARY_KEY_PROP_NAME } from '../utils/constants';

type RenderComponentOnlyResponse = {
compText: string;
Expand Down Expand Up @@ -351,7 +352,7 @@ export abstract class ReactFormTemplateRenderer extends StudioTemplateRenderer<
factory.createTypeReferenceNode(factory.createIdentifier('React.PropsWithChildren'), [
factory.createIntersectionTypeNode([
escapeHatchTypeNode,
buildFormPropNode(this.component, modelName, this.primaryKeys?.[0]),
buildFormPropNode(this.component, fieldConfigs, modelName, this.primaryKeys),
factory.createTypeReferenceNode(
factory.createQualifiedName(factory.createIdentifier('React'), factory.createIdentifier('CSSProperties')),
undefined,
Expand Down Expand Up @@ -397,7 +398,7 @@ export abstract class ReactFormTemplateRenderer extends StudioTemplateRenderer<

const elements: BindingElement[] = [
// add in hooks for before/complete with ds and basic onSubmit with props
...buildMutationBindings(this.component, this.primaryKeys?.[0]),
...buildMutationBindings(this.component, this.primaryKeys),
// onValidate prop
factory.createBindingElement(undefined, undefined, factory.createIdentifier('onValidate'), undefined),
// onChange prop
Expand Down Expand Up @@ -501,7 +502,9 @@ export abstract class ReactFormTemplateRenderer extends StudioTemplateRenderer<

// primaryKey should exist if DataStore update form. This condition is just for ts
if (this.primaryKeys) {
const destructuredPrimaryKey = getPropName(this.primaryKeys[0]);
// if there are multiple primaryKeys, it's a composite key and we're using 'id' for a composite key prop
const destructuredPrimaryKey =
this.primaryKeys.length > 1 ? getPropName(COMPOSITE_PRIMARY_KEY_PROP_NAME) : getPropName(this.primaryKeys[0]);
statements.push(
addUseEffectWrapper(
buildUpdateDatastoreQuery(modelName, lowerCaseDataTypeName, relatedModelStatements, destructuredPrimaryKey),
Expand Down
Loading

0 comments on commit 341882b

Please sign in to comment.