Skip to content

Commit

Permalink
feat: add NoApi configuration and validation for data dependency
Browse files Browse the repository at this point in the history
  • Loading branch information
awinberg-aws committed Jul 21, 2023
1 parent df2633b commit 56518d3
Show file tree
Hide file tree
Showing 11 changed files with 636 additions and 6 deletions.

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -10248,6 +10248,43 @@ export default function TextFieldPrimitive<Multiline extends boolean>(
"
`;

exports[`amplify render tests renderer configurations with NoApi should render component without data binding successfully 1`] = `
"/* eslint-disable */
import * as React from \\"react\\";
import {
EscapeHatchProps,
getOverrideProps,
} from \\"@aws-amplify/ui-react/internal\\";
import { Button, ButtonProps } from \\"@aws-amplify/ui-react\\";

export declare type PrimitiveOverrideProps<T> = Partial<T> &
React.DOMAttributes<HTMLDivElement>;
export declare type CustomButtonOverridesProps = {
CustomButton?: PrimitiveOverrideProps<ButtonProps>;
} & EscapeHatchProps;
export type CustomButtonProps = React.PropsWithChildren<
Partial<ButtonProps> & {
overrides?: CustomButtonOverridesProps | undefined | null;
}
>;
export default function CustomButton(
props: CustomButtonProps
): React.ReactElement {
const { overrides, ...rest } = props;
return (
/* @ts-ignore: TS2322 */
<Button
color=\\"#ff0000\\"
width={20}
isDisabled={true}
{...getOverrideProps(overrides, \\"CustomButton\\")}
{...rest}
></Button>
);
}
"
`;

exports[`amplify render tests sample code snippet tests should generate a sample code snippet for components 1`] = `
"/* eslint-disable */
import * as React from \\"react\\";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -167,3 +167,9 @@ export const rendererConfigWithGraphQL: ReactRenderConfig = {
fragmentsFilePath: '../graphql/fragments',
},
};

export const rendererConfigWithNoApi: ReactRenderConfig = {
apiConfiguration: {
dataApi: 'NoApi',
},
};
Original file line number Diff line number Diff line change
Expand Up @@ -14,13 +14,15 @@
limitations under the License.
*/
/* eslint-disable no-template-curly-in-string */
import { NoApiError } from '@aws-amplify/codegen-ui';
import { ImportSource } from '../imports';
import { ReactRenderConfig } from '../react-render-config';
import {
defaultCLIRenderConfig,
generateComponentOnlyWithAmplifyFormRenderer,
generateWithAmplifyFormRenderer,
rendererConfigWithGraphQL,
rendererConfigWithNoApi,
} from './__utils__';

describe('amplify form renderer tests', () => {
Expand Down Expand Up @@ -1025,6 +1027,32 @@ describe('amplify form renderer tests', () => {
});
});

describe('NoApi form tests', () => {
it('should throw if form has data dependency with no configured API', () => {
expect(() => {
generateWithAmplifyFormRenderer(
'forms/comment-datastore-create',
'datastore/comment-hasMany-belongsTo-relationships',
{ ...defaultCLIRenderConfig, ...rendererConfigWithNoApi },
{ isNonModelSupported: true, isRelationshipSupported: true },
);
}).toThrow(NoApiError);
});

it('should render custom data form successfully with no configured API', () => {
const { componentText, declaration } = generateWithAmplifyFormRenderer(
'forms/custom-with-array-field',
undefined,
{ ...defaultCLIRenderConfig, ...rendererConfigWithNoApi },
{ isNonModelSupported: true, isRelationshipSupported: true },
);
expect(componentText).not.toContain('DataStore.save');
expect(componentText).toContain('resetStateValues();');
expect(componentText).toMatchSnapshot();
expect(declaration).toMatchSnapshot();
});
});

it('should render form for child of bidirectional 1:m when field defined on parent', () => {
const { componentText, declaration } = generateWithAmplifyFormRenderer(
'forms/car-datastore-update',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,13 +13,15 @@
See the License for the specific language governing permissions and
limitations under the License.
*/
import { NoApiError } from '@aws-amplify/codegen-ui';
import { ModuleKind, ScriptTarget, ScriptKind } from '..';
import {
authorHasManySchema,
compositePersonSchema,
generateWithAmplifyRenderer,
rendererConfigWithGraphQL,
userSchema,
rendererConfigWithNoApi,
} from './__utils__';

describe('amplify render tests', () => {
Expand Down Expand Up @@ -98,6 +100,19 @@ describe('amplify render tests', () => {
});
});

describe('renderer configurations with NoApi', () => {
it('should throw if component has data binding', () => {
expect(() => {
generateWithAmplifyRenderer('workflow/dataStoreCreateItem', rendererConfigWithNoApi);
}).toThrow(NoApiError);
});

it('should render component without data binding successfully', () => {
const generatedCode = generateWithAmplifyRenderer('buttonGolden', rendererConfigWithNoApi);
expect(generatedCode.componentText).toMatchSnapshot();
});
});

describe('collection', () => {
it('should render collection with data binding', () => {
const generatedCode = generateWithAmplifyRenderer('collectionWithBinding');
Expand Down
7 changes: 7 additions & 0 deletions packages/codegen-ui-react/lib/forms/react-form-renderer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,8 @@ import {
StudioTemplateRenderer,
validateFormSchema,
FormFeatureFlags,
NoApiError,
formRequiresDataApi,
} from '@aws-amplify/codegen-ui';
import { EOL } from 'os';
import {
Expand Down Expand Up @@ -170,6 +172,11 @@ export abstract class ReactFormTemplateRenderer extends StudioTemplateRenderer<
this.primaryKeys = dataSchemaMetadata.models[dataTypeName].primaryKeys;
}
}

// validate inputs for renderer
if (formRequiresDataApi(component) && renderConfig.apiConfiguration?.dataApi === 'NoApi') {
throw new NoApiError('Form cannot be rendered without a data API');
}
}

@handleCodegenErrors
Expand Down
8 changes: 6 additions & 2 deletions packages/codegen-ui-react/lib/react-render-config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,15 +18,15 @@ import { ScriptKind, ScriptTarget, ModuleKind } from 'typescript';

export { ScriptKind, ScriptTarget, ModuleKind } from 'typescript';

export type DataApiKind = 'DataStore' | 'GraphQL';
export type DataApiKind = 'DataStore' | 'GraphQL' | 'NoApi';

export type ReactRenderConfig = FrameworkRenderConfig & {
script?: ScriptKind;
target?: ScriptTarget;
module?: ModuleKind;
renderTypeDeclarations?: boolean;
inlineSourceMap?: boolean;
apiConfiguration?: GraphqlRenderConfig | DataStoreRenderConfig;
apiConfiguration?: GraphqlRenderConfig | DataStoreRenderConfig | NoApiRenderConfig;
};

export type GraphqlRenderConfig = {
Expand All @@ -42,6 +42,10 @@ export type DataStoreRenderConfig = {
dataApi: 'DataStore';
};

export type NoApiRenderConfig = {
dataApi: 'NoApi';
};

export function scriptKindToFileExtension(scriptKind: ScriptKind): string {
switch (scriptKind) {
case ScriptKind.TSX:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,8 @@ import {
isValidVariableName,
InternalError,
resolveBetweenPredicateToMultiplePredicates,
NoApiError,
componentRequiresDataApi,
} from '@aws-amplify/codegen-ui';
import { EOL } from 'os';
import ts, {
Expand Down Expand Up @@ -153,7 +155,11 @@ export abstract class ReactStudioTemplateRenderer extends StudioTemplateRenderer
this.importCollection = new ImportCollection({ rendererConfig: renderConfig });
this.importCollection.ingestComponentMetadata(this.componentMetadata);
addBindingPropertiesImports(this.component, this.importCollection);

// TODO: throw warnings on invalid config combinations. i.e. CommonJS + JSX
if (componentRequiresDataApi(component) && renderConfig.apiConfiguration?.dataApi === 'NoApi') {
throw new NoApiError('Component cannot be rendered without a data API');
}
}

@handleCodegenErrors
Expand Down
4 changes: 2 additions & 2 deletions packages/codegen-ui-react/lib/utils/graphql.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ import { ImportCollection, ImportValue } from '../imports';
import { capitalizeFirstLetter, getSetNameIdentifier, lowerCaseFirst } from '../helpers';
import { isBoundProperty, isConcatenatedProperty } from '../react-component-render-helper';
import { Primitive } from '../primitive';
import { DataStoreRenderConfig, GraphqlRenderConfig } from '../react-render-config';
import { DataStoreRenderConfig, GraphqlRenderConfig, NoApiRenderConfig } from '../react-render-config';

export enum ActionType {
CREATE = 'create',
Expand All @@ -42,7 +42,7 @@ export enum ActionType {

/* istanbul ignore next */
export const isGraphqlConfig = (
apiConfiguration?: GraphqlRenderConfig | DataStoreRenderConfig,
apiConfiguration?: GraphqlRenderConfig | DataStoreRenderConfig | NoApiRenderConfig,
): apiConfiguration is GraphqlRenderConfig => {
return apiConfiguration?.dataApi === 'GraphQL';
};
Expand Down
4 changes: 2 additions & 2 deletions packages/codegen-ui/lib/errors/error-transformer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,10 @@
See the License for the specific language governing permissions and
limitations under the License.
*/
import { InternalError, InvalidInputError } from './error-types';
import { InternalError, InvalidInputError, NoApiError } from './error-types';

export const transformCodegenError = (error: any | unknown): InternalError | InvalidInputError => {
if (error instanceof InternalError || error instanceof InvalidInputError) {
if (error instanceof InternalError || error instanceof InvalidInputError || error instanceof NoApiError) {
return error;
}

Expand Down
10 changes: 10 additions & 0 deletions packages/codegen-ui/lib/errors/error-types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,3 +36,13 @@ export class InvalidInputError extends Error {
Object.setPrototypeOf(this, InvalidInputError.prototype);
}
}

/**
* Entity requires a working data API to produce a working component but no valid API configuration was provided.
*/
export class NoApiError extends Error {
constructor(message: string) {
super(message);
Object.setPrototypeOf(this, NoApiError.prototype);
}
}

0 comments on commit 56518d3

Please sign in to comment.