diff --git a/packages/codegen-ui-react/lib/__tests__/__snapshots__/studio-ui-codegen-react-views.test.ts.snap b/packages/codegen-ui-react/lib/__tests__/__snapshots__/studio-ui-codegen-react-views.test.ts.snap
index e8d964779..8049c1577 100644
--- a/packages/codegen-ui-react/lib/__tests__/__snapshots__/studio-ui-codegen-react-views.test.ts.snap
+++ b/packages/codegen-ui-react/lib/__tests__/__snapshots__/studio-ui-codegen-react-views.test.ts.snap
@@ -1,6 +1,6 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
-exports[`amplify view renderer tests should generate a non-datastore table element 1`] = `
+exports[`amplify table renderer tests should generate a non-datastore table element 1`] = `
"
{!disableHeaders && (
@@ -32,7 +32,7 @@ exports[`amplify view renderer tests should generate a non-datastore table eleme
"
`;
-exports[`amplify view renderer tests should generate a table element 1`] = `
+exports[`amplify table renderer tests should generate a table element 1`] = `
"
{!disableHeaders && (
@@ -79,3 +79,299 @@ exports[`amplify view renderer tests should generate a table element 1`] = `
;
"
`;
+
+exports[`amplify view renderer tests should call util file if rendered 1`] = `
+"/* eslint-disable */
+import * as React from \\"react\\";
+import { formatter } from \\"./utils\\";
+import {
+ createDataStorePredicate,
+ useDataStoreBinding,
+} from \\"@aws-amplify/ui-react/internal\\";
+import { SortDirection } from \\"@aws-amplify/datastore\\";
+import {
+ Table,
+ TableBody,
+ TableCell,
+ TableHead,
+ TableRow,
+} from \\"@aws-amplify/ui-react\\";
+export default function MyPostTable(props) {
+ const {
+ items: itemsProps,
+ predicateOverride,
+ formatOverride,
+ highlightOnHover,
+ onRowClick,
+ disableHeaders,
+ ...rest
+ } = props;
+ const postFilter = {
+ and: [
+ { field: \\"username\\", operand: \\"Guy\\", operator: \\"notContains\\" },
+ { field: \\"createdAt\\", operand: \\"25\\", operator: \\"contains\\" },
+ ],
+ };
+ const postPredicate = createDataStorePredicate(postFilter);
+ const postPagination = { sort: (s) => s.username(SortDirection.ASCENDING) };
+ const MyPostTableDataStore = useDataStoreBinding({
+ type: \\"collection\\",
+ model: Post,
+ criteria: predicateOverride || postPredicate,
+ pagination: postPagination,
+ }).items;
+ const items = itemsProp !== undefined ? itemsProp : MyPostTableDataStore;
+ return (
+
+ {!disableHeaders && (
+
+
+ id
+ caption
+ username
+ post_url
+ profile_url
+ status
+ createdAt
+ updatedAt
+
+
+ )}
+
+ {items.map((item, index) => (
+ onRowClick(item, index) : null}>
+ {format?.id ? format.id(item?.id) : item?.id}
+
+ {format?.caption ? format.caption(item?.caption) : item?.caption}
+
+
+ {format?.username
+ ? format.username(item?.username)
+ : item?.username}
+
+
+ {format?.post_url
+ ? format.post_url(item?.post_url)
+ : item?.post_url}
+
+
+ {format?.profile_url
+ ? format.profile_url(item?.profile_url)
+ : item?.profile_url}
+
+
+ {format?.status ? format.status(item?.status) : item?.status}
+
+
+ {format?.createdAt
+ ? format.createdAt(item?.createdAt)
+ : formatter(item?.createdAt, {
+ type: \\"DateTimeFormat\\",
+ format: {
+ dateTimeFormat: {
+ dateFormat: \\"MM/DD/YYYY\\",
+ timeFormat: \\"locale\\",
+ },
+ },
+ })}
+
+
+ {format?.updatedAt
+ ? format.updatedAt(item?.updatedAt)
+ : item?.updatedAt}
+
+
+ ))}
+
+
+ );
+}
+"
+`;
+
+exports[`amplify view renderer tests should call util file if rendered 2`] = `
+"import * as React from \\"react\\";
+import { EscapeHatchProps } from \\"@aws-amplify/ui-react/internal\\";
+export declare type MyPostTableProps = React.PropsWithChildren<{
+ overrides?: EscapeHatchProps | undefined | null;
+}>;
+export default function MyPostTable(props: MyPostTableProps): React.ReactElement;
+"
+`;
+
+exports[`amplify view renderer tests should render view with custom datastore 1`] = `
+"/* eslint-disable */
+import * as React from \\"react\\";
+import {
+ Table,
+ TableBody,
+ TableCell,
+ TableHead,
+ TableRow,
+} from \\"@aws-amplify/ui-react\\";
+export default function CustomTable(props) {
+ const {
+ items,
+ formatOverride,
+ highlightOnHover,
+ onRowClick,
+ disableHeaders,
+ ...rest
+ } = props;
+ return (
+
+ {!disableHeaders && (
+
+
+ name
+ age
+ address
+ birthday
+
+
+ )}
+
+ {items.map((item, index) => (
+ onRowClick(item, index) : null}>
+
+ {format?.name ? format.name(item?.name) : item?.name}
+
+
+ {format?.age ? format.age(item?.age) : item?.age}
+
+
+ {format?.address ? format.address(item?.address) : item?.address}
+
+
+ {format?.birthday
+ ? format.birthday(item?.birthday)
+ : item?.birthday}
+
+
+ ))}
+
+
+ );
+}
+"
+`;
+
+exports[`amplify view renderer tests should render view with custom datastore 2`] = `
+"import * as React from \\"react\\";
+import { EscapeHatchProps } from \\"@aws-amplify/ui-react/internal\\";
+export declare type CustomTableProps = React.PropsWithChildren<{
+ overrides?: EscapeHatchProps | undefined | null;
+}>;
+export default function CustomTable(props: CustomTableProps): React.ReactElement;
+"
+`;
+
+exports[`amplify view renderer tests should render view with passed in predicate and sort 1`] = `
+"/* eslint-disable */
+import * as React from \\"react\\";
+import {
+ createDataStorePredicate,
+ useDataStoreBinding,
+} from \\"@aws-amplify/ui-react/internal\\";
+import { SortDirection } from \\"@aws-amplify/datastore\\";
+import {
+ Table,
+ TableBody,
+ TableCell,
+ TableHead,
+ TableRow,
+} from \\"@aws-amplify/ui-react\\";
+export default function MyPostTable(props) {
+ const {
+ items: itemsProps,
+ predicateOverride,
+ formatOverride,
+ highlightOnHover,
+ onRowClick,
+ disableHeaders,
+ ...rest
+ } = props;
+ const postFilter = {
+ and: [
+ { field: \\"username\\", operand: \\"username0\\", operator: \\"notContains\\" },
+ { field: \\"createdAt\\", operand: \\"2022\\", operator: \\"contains\\" },
+ ],
+ };
+ const postPredicate = createDataStorePredicate(postFilter);
+ const postPagination = { sort: (s) => s.username(SortDirection.ASCENDING) };
+ const MyPostTableDataStore = useDataStoreBinding({
+ type: \\"collection\\",
+ model: Post,
+ criteria: predicateOverride || postPredicate,
+ pagination: postPagination,
+ }).items;
+ const items = itemsProp !== undefined ? itemsProp : MyPostTableDataStore;
+ return (
+
+ {!disableHeaders && (
+
+
+ id
+ caption
+ username
+ post_url
+ profile_url
+ status
+ createdAt
+ updatedAt
+
+
+ )}
+
+ {items.map((item, index) => (
+ onRowClick(item, index) : null}>
+ {format?.id ? format.id(item?.id) : item?.id}
+
+ {format?.caption ? format.caption(item?.caption) : item?.caption}
+
+
+ {format?.username
+ ? format.username(item?.username)
+ : item?.username}
+
+
+ {format?.post_url
+ ? format.post_url(item?.post_url)
+ : item?.post_url}
+
+
+ {format?.profile_url
+ ? format.profile_url(item?.profile_url)
+ : item?.profile_url}
+
+
+ {format?.status ? format.status(item?.status) : item?.status}
+
+
+ {format?.createdAt
+ ? format.createdAt(item?.createdAt)
+ : item?.createdAt}
+
+
+ {format?.updatedAt
+ ? format.updatedAt(item?.updatedAt)
+ : item?.updatedAt}
+
+
+ ))}
+
+
+ );
+}
+"
+`;
+
+exports[`amplify view renderer tests should render view with passed in predicate and sort 2`] = `
+"import * as React from \\"react\\";
+import { EscapeHatchProps } from \\"@aws-amplify/ui-react/internal\\";
+export declare type MyPostTableProps = React.PropsWithChildren<{
+ overrides?: EscapeHatchProps | undefined | null;
+}>;
+export default function MyPostTable(props: MyPostTableProps): React.ReactElement;
+"
+`;
diff --git a/packages/codegen-ui-react/lib/__tests__/__utils__/amplify-renderer-generator.ts b/packages/codegen-ui-react/lib/__tests__/__utils__/amplify-renderer-generator.ts
index 77ce0128a..fb68856d9 100644
--- a/packages/codegen-ui-react/lib/__tests__/__utils__/amplify-renderer-generator.ts
+++ b/packages/codegen-ui-react/lib/__tests__/__utils__/amplify-renderer-generator.ts
@@ -70,6 +70,24 @@ export const generateWithAmplifyFormRenderer = (
return renderer.renderComponent();
};
+export const renderWithAmplifyViewRenderer = (
+ viewJsonFile: string,
+ dataSchemaJsonFile: string | undefined,
+ renderConfig: ReactRenderConfig = defaultCLIRenderConfig,
+): { componentText: string; declaration?: string } => {
+ let dataSchema: GenericDataSchema | undefined;
+ if (dataSchemaJsonFile) {
+ const dataStoreSchema = loadSchemaFromJSONFile(dataSchemaJsonFile);
+ dataSchema = getGenericFromDataStore(dataStoreSchema);
+ }
+ const rendererFactory = new StudioTemplateRendererFactory(
+ (view: StudioView) => new AmplifyViewRenderer(view, dataSchema, renderConfig),
+ );
+
+ const renderer = rendererFactory.buildRenderer(loadSchemaFromJSONFile(viewJsonFile));
+ return renderer.renderComponent();
+};
+
export const renderTableJsxElement = (
tableFilePath: string,
dataSchemaFilePath: string | undefined,
diff --git a/packages/codegen-ui-react/lib/__tests__/studio-ui-codegen-react-views.test.ts b/packages/codegen-ui-react/lib/__tests__/studio-ui-codegen-react-views.test.ts
index 7808eea87..ac36b8c38 100644
--- a/packages/codegen-ui-react/lib/__tests__/studio-ui-codegen-react-views.test.ts
+++ b/packages/codegen-ui-react/lib/__tests__/studio-ui-codegen-react-views.test.ts
@@ -13,9 +13,9 @@
See the License for the specific language governing permissions and
limitations under the License.
*/
-import { renderTableJsxElement } from './__utils__';
+import { renderTableJsxElement, renderWithAmplifyViewRenderer } from './__utils__';
-describe('amplify view renderer tests', () => {
+describe('amplify table renderer tests', () => {
test('should generate a table element', () => {
const tableElement = renderTableJsxElement('views/table-from-datastore', 'datastore/person', 'test-table.ts');
expect(tableElement).toMatchSnapshot();
@@ -26,3 +26,31 @@ describe('amplify view renderer tests', () => {
expect(tableElement).toMatchSnapshot();
});
});
+
+describe('amplify view renderer tests', () => {
+ test('should render view with passed in predicate and sort', () => {
+ const { componentText, declaration } = renderWithAmplifyViewRenderer(
+ 'views/post-table-datastore',
+ 'datastore/post-ds',
+ );
+ expect(componentText).toContain('useDataStoreBinding');
+ expect(componentText).toMatchSnapshot();
+ expect(declaration).toMatchSnapshot();
+ });
+ test('should render view with custom datastore', () => {
+ const { componentText, declaration } = renderWithAmplifyViewRenderer('views/table-from-custom-json', undefined);
+ expect(componentText).not.toContain('useDataStoreBinding');
+ expect(componentText).toMatchSnapshot();
+ expect(declaration).toMatchSnapshot();
+ });
+
+ test('should call util file if rendered', () => {
+ const { componentText, declaration } = renderWithAmplifyViewRenderer(
+ 'views/post-table-custom-format',
+ 'datastore/post-ds',
+ );
+ expect(componentText.replace(/\\/g, '')).toContain(`import { formatter } from "./utils"`);
+ expect(componentText).toMatchSnapshot();
+ expect(declaration).toMatchSnapshot();
+ });
+});
diff --git a/packages/codegen-ui-react/lib/amplify-ui-renderers/amplify-view-renderer.ts b/packages/codegen-ui-react/lib/amplify-ui-renderers/amplify-view-renderer.ts
index fe6f3dca9..eed37e3cf 100644
--- a/packages/codegen-ui-react/lib/amplify-ui-renderers/amplify-view-renderer.ts
+++ b/packages/codegen-ui-react/lib/amplify-ui-renderers/amplify-view-renderer.ts
@@ -25,7 +25,7 @@ export class AmplifyViewRenderer extends ReactViewTemplateRenderer {
case Primitive.Table:
return new ReactTableRenderer(
this.viewComponent,
- this.viewDefinition!,
+ this.viewDefinition,
this.viewMetadata,
this.importCollection,
).renderElement();
diff --git a/packages/codegen-ui-react/lib/imports/import-mapping.ts b/packages/codegen-ui-react/lib/imports/import-mapping.ts
index 712bf2e32..807a085e3 100644
--- a/packages/codegen-ui-react/lib/imports/import-mapping.ts
+++ b/packages/codegen-ui-react/lib/imports/import-mapping.ts
@@ -46,6 +46,7 @@ export enum ImportValue {
USE_EFFECT = 'useEffect',
VALIDATE_FIELD = 'validateField',
VALIDATE_FIELD_CODEGEN = 'validateField',
+ FORMATTER = 'formatter',
}
export const ImportMapping: Record = {
@@ -68,6 +69,7 @@ export const ImportMapping: Record = {
[ImportValue.USE_AUTH_SIGN_OUT_ACTION]: ImportSource.UI_REACT_INTERNAL,
[ImportValue.USE_STATE_MUTATION_ACTION]: ImportSource.UI_REACT_INTERNAL,
[ImportValue.USE_EFFECT]: ImportSource.REACT,
+ [ImportValue.FORMATTER]: ImportSource.UTILS,
[ImportValue.VALIDATE_FIELD]: ImportSource.UTILS,
[ImportValue.VALIDATE_FIELD_CODEGEN]: ImportSource.CODEGEN_UI_REACT,
};
diff --git a/packages/codegen-ui-react/lib/index.ts b/packages/codegen-ui-react/lib/index.ts
index 3a38bdcbb..c21e41a25 100644
--- a/packages/codegen-ui-react/lib/index.ts
+++ b/packages/codegen-ui-react/lib/index.ts
@@ -23,6 +23,7 @@ export * from './react-render-config';
export * from './react-output-manager';
export * from './amplify-ui-renderers/amplify-renderer';
export * from './amplify-ui-renderers/amplify-form-renderer';
+export * from './amplify-ui-renderers/amplify-view-renderer';
export * from './primitive';
export * from './react-index-studio-template-renderer';
export * from './react-utils-studio-template-renderer';
diff --git a/packages/codegen-ui-react/lib/react-studio-template-renderer-helper.ts b/packages/codegen-ui-react/lib/react-studio-template-renderer-helper.ts
index 986d88900..1f688cf9a 100644
--- a/packages/codegen-ui-react/lib/react-studio-template-renderer-helper.ts
+++ b/packages/codegen-ui-react/lib/react-studio-template-renderer-helper.ts
@@ -21,6 +21,7 @@ import {
InternalError,
InvalidInputError,
StudioComponentSlotBinding,
+ StudioComponentSort,
} from '@aws-amplify/codegen-ui';
import ts, {
createPrinter,
@@ -38,6 +39,9 @@ import ts, {
BindingName,
Expression,
PropertyAssignment,
+ ArrowFunction,
+ CallExpression,
+ Identifier,
} from 'typescript';
import { createDefaultMapFromNodeModules, createSystem, createVirtualCompilerHost } from '@typescript/vfs';
import path from 'path';
@@ -145,6 +149,50 @@ export function buildPrinter(fileName: string, renderConfig: ReactRenderConfig)
return { printer, file };
}
+/**
+ * (s: SortPredicate) => s.firstName('ASCENDING').lastName('DESCENDING')
+ */
+export const buildSortFunction = (model: string, sort: StudioComponentSort[]): ArrowFunction => {
+ const ascendingSortDirection = factory.createPropertyAccessExpression(
+ factory.createIdentifier('SortDirection'),
+ factory.createIdentifier('ASCENDING'),
+ );
+ const descendingSortDirection = factory.createPropertyAccessExpression(
+ factory.createIdentifier('SortDirection'),
+ factory.createIdentifier('DESCENDING'),
+ );
+
+ let expr: Identifier | CallExpression = factory.createIdentifier('s');
+ sort.forEach((sortPredicate) => {
+ expr = factory.createCallExpression(
+ factory.createPropertyAccessExpression(expr, factory.createIdentifier(sortPredicate.field)),
+ undefined,
+ [sortPredicate.direction === 'ASC' ? ascendingSortDirection : descendingSortDirection],
+ );
+ });
+
+ return factory.createArrowFunction(
+ undefined,
+ undefined,
+ [
+ factory.createParameterDeclaration(
+ undefined,
+ undefined,
+ undefined,
+ factory.createIdentifier('s'),
+ undefined,
+ factory.createTypeReferenceNode(factory.createIdentifier('SortPredicate'), [
+ factory.createTypeReferenceNode(factory.createIdentifier(model), undefined),
+ ]),
+ undefined,
+ ),
+ ],
+ undefined,
+ factory.createToken(ts.SyntaxKind.EqualsGreaterThanToken),
+ expr,
+ );
+};
+
export function getDeclarationFilename(filename: string): string {
return `${path.basename(filename, filename.includes('.tsx') ? '.tsx' : '.jsx')}.d.ts`;
}
diff --git a/packages/codegen-ui-react/lib/react-studio-template-renderer.ts b/packages/codegen-ui-react/lib/react-studio-template-renderer.ts
index 454527b01..5eca88ddd 100644
--- a/packages/codegen-ui-react/lib/react-studio-template-renderer.ts
+++ b/packages/codegen-ui-react/lib/react-studio-template-renderer.ts
@@ -52,8 +52,6 @@ import ts, {
Modifier,
ObjectLiteralExpression,
CallExpression,
- Identifier,
- ArrowFunction,
LiteralExpression,
BooleanLiteral,
addSyntheticLeadingComment,
@@ -77,6 +75,7 @@ import {
buildPropAssignmentWithFilter,
buildCollectionWithItemMap,
createHookStatement,
+ buildSortFunction,
} from './react-studio-template-renderer-helper';
import { Primitive, isPrimitive, PrimitiveTypeParameter, PrimitiveChildrenPropMapping } from './primitive';
import { RequiredKeys } from './utils/type-utils';
@@ -1056,12 +1055,7 @@ export abstract class ReactStudioTemplateRenderer extends StudioTemplateRenderer
factory.createObjectLiteralExpression(
([] as ts.PropertyAssignment[]).concat(
sort
- ? [
- factory.createPropertyAssignment(
- factory.createIdentifier('sort'),
- this.buildSortFunction(model, sort),
- ),
- ]
+ ? [factory.createPropertyAssignment(factory.createIdentifier('sort'), buildSortFunction(model, sort))]
: [],
),
),
@@ -1072,50 +1066,6 @@ export abstract class ReactStudioTemplateRenderer extends StudioTemplateRenderer
);
}
- /**
- * (s: SortPredicate) => s.firstName('ASCENDING').lastName('DESCENDING')
- */
- private buildSortFunction(model: string, sort: StudioComponentSort[]): ArrowFunction {
- const ascendingSortDirection = factory.createPropertyAccessExpression(
- factory.createIdentifier('SortDirection'),
- factory.createIdentifier('ASCENDING'),
- );
- const descendingSortDirection = factory.createPropertyAccessExpression(
- factory.createIdentifier('SortDirection'),
- factory.createIdentifier('DESCENDING'),
- );
-
- let expr: Identifier | CallExpression = factory.createIdentifier('s');
- sort.forEach((sortPredicate) => {
- expr = factory.createCallExpression(
- factory.createPropertyAccessExpression(expr, factory.createIdentifier(sortPredicate.field)),
- undefined,
- [sortPredicate.direction === 'ASC' ? ascendingSortDirection : descendingSortDirection],
- );
- });
-
- return factory.createArrowFunction(
- undefined,
- undefined,
- [
- factory.createParameterDeclaration(
- undefined,
- undefined,
- undefined,
- factory.createIdentifier('s'),
- undefined,
- factory.createTypeReferenceNode(factory.createIdentifier('SortPredicate'), [
- factory.createTypeReferenceNode(factory.createIdentifier(model), undefined),
- ]),
- undefined,
- ),
- ],
- undefined,
- factory.createToken(ts.SyntaxKind.EqualsGreaterThanToken),
- expr,
- );
- }
-
private buildCollectionBindingCall(
model: string,
modelVariableName: string,
diff --git a/packages/codegen-ui-react/lib/react-table-renderer-helper.ts b/packages/codegen-ui-react/lib/react-table-renderer-helper.ts
index 0e7f4d9c1..d306180c0 100644
--- a/packages/codegen-ui-react/lib/react-table-renderer-helper.ts
+++ b/packages/codegen-ui-react/lib/react-table-renderer-helper.ts
@@ -13,8 +13,76 @@
See the License for the specific language governing permissions and
limitations under the License.
*/
-import { StringFormat, ViewValueFormatting } from '@aws-amplify/codegen-ui/lib/types';
-import { factory, ObjectLiteralExpression } from 'typescript';
+import { StringFormat, TableConfiguration, ViewValueFormatting } from '@aws-amplify/codegen-ui/lib/types';
+import { CallExpression, factory, ObjectLiteralExpression, SyntaxKind } from 'typescript';
+
+export const getFilterName = (model: string) => `${model.toLowerCase()}Filter`;
+export const getPredicateName = (model: string) => `${model.toLowerCase()}Predicate`;
+export const getPaginationName = (model: string) => `${model.toLowerCase()}Pagination`;
+
+/*
+checks table to see if there is a formatter for stringFormat
+*/
+export const needsFormatter = (table: TableConfiguration): boolean => {
+ if (table.columns) {
+ return Object.values(table.columns).some((column) => column.valueFormatting?.stringFormat !== undefined);
+ }
+ return false;
+};
+
+/*
+ const dataStoreItems = useDataStoreBinding({
+ type: 'collection',
+ model: 'Post',
+ criteria: predicateOverrides ?? predicateApiSettings,
+ sort: sortApiSettings,
+ })
+ */
+export const buildDataStoreCollectionCall = (
+ model: string,
+ criteriaName?: string,
+ paginationName?: string,
+): CallExpression => {
+ const objectProperties = [
+ factory.createPropertyAssignment(factory.createIdentifier('type'), factory.createStringLiteral('collection')),
+ factory.createPropertyAssignment(factory.createIdentifier('model'), factory.createIdentifier(model)),
+ ]
+ .concat(
+ criteriaName
+ ? [
+ // criteria: predicateOverride || {criteriaName}
+ factory.createPropertyAssignment(
+ factory.createIdentifier('criteria'),
+ factory.createBinaryExpression(
+ factory.createIdentifier('predicateOverride'),
+ factory.createToken(SyntaxKind.BarBarToken),
+ factory.createIdentifier(criteriaName),
+ ),
+ ),
+ ]
+ : [
+ // criteria: predicateOverride
+ factory.createPropertyAssignment(
+ factory.createIdentifier('criteria'),
+ factory.createIdentifier('predicateOverride'),
+ ),
+ ],
+ )
+ .concat(
+ paginationName
+ ? [
+ factory.createPropertyAssignment(
+ factory.createIdentifier('pagination'),
+ factory.createIdentifier(paginationName),
+ ),
+ ]
+ : [],
+ );
+
+ return factory.createCallExpression(factory.createIdentifier('useDataStoreBinding'), undefined, [
+ factory.createObjectLiteralExpression(objectProperties, true),
+ ]);
+};
/* Helper to codegen objects
diff --git a/packages/codegen-ui-react/lib/react-table-renderer.ts b/packages/codegen-ui-react/lib/react-table-renderer.ts
index 2a1461a51..1c470fae4 100644
--- a/packages/codegen-ui-react/lib/react-table-renderer.ts
+++ b/packages/codegen-ui-react/lib/react-table-renderer.ts
@@ -50,11 +50,11 @@ export class ReactTableRenderer {
this.viewComponent = view;
this.viewDefinition = definition;
this.viewMetadata = metadata;
- this.viewMetadata.tableFieldFormatting = {};
+ this.viewMetadata.fieldFormatting = {};
this.viewDefinition.columns.forEach((column) => {
if (column.valueFormatting) {
- this.viewMetadata.tableFieldFormatting![column.header] = { ...column.valueFormatting };
+ this.viewMetadata.fieldFormatting[column.header] = { ...column.valueFormatting };
}
});
@@ -160,7 +160,7 @@ export class ReactTableRenderer {
}
generateFormatLiteralExpression(field: string): ObjectLiteralExpression | Identifier {
- const formatting = this.viewMetadata.tableFieldFormatting;
+ const formatting = this.viewMetadata.fieldFormatting;
if (formatting?.[field]) {
return objectToExpression(formatting[field].stringFormat);
@@ -186,7 +186,7 @@ export class ReactTableRenderer {
}
*/
createFormatArg(field: string) {
- const format = this.viewMetadata.tableFieldFormatting?.[field];
+ const format = this.viewMetadata.fieldFormatting?.[field];
const type: StringFormat['type'] | undefined = stringFormatToType(format);
@@ -203,7 +203,7 @@ export class ReactTableRenderer {
}
createFormatCallOrPropAccess(field: string) {
- const format = this.viewMetadata.tableFieldFormatting?.[field];
+ const format = this.viewMetadata.fieldFormatting?.[field];
return format
? factory.createCallExpression(factory.createIdentifier('formatter'), undefined, [
factory.createPropertyAccessChain(
diff --git a/packages/codegen-ui-react/lib/react-utils-studio-template-renderer.ts b/packages/codegen-ui-react/lib/react-utils-studio-template-renderer.ts
index 2a4a7f47b..76bec98cf 100644
--- a/packages/codegen-ui-react/lib/react-utils-studio-template-renderer.ts
+++ b/packages/codegen-ui-react/lib/react-utils-studio-template-renderer.ts
@@ -24,11 +24,11 @@ import { transpile, buildPrinter, defaultRenderConfig } from './react-studio-tem
import { generateValidationFunction } from './utils/forms/validation';
import { generateFormatUtil } from './utils/string-formatter';
-export type Util = string;
+export type UtilTemplateType = 'validation' | 'formatter';
export class ReactUtilsStudioTemplateRenderer extends StudioTemplateRenderer<
string,
- string[],
+ UtilTemplateType[],
ReactOutputManager,
{
componentText: string;
@@ -44,9 +44,9 @@ export class ReactUtilsStudioTemplateRenderer extends StudioTemplateRenderer<
/*
* list of util functions to generate
*/
- utils: Util[];
+ utils: UtilTemplateType[];
- constructor(utils: Util[], renderConfig: ReactRenderConfig) {
+ constructor(utils: UtilTemplateType[], renderConfig: ReactRenderConfig) {
super(utils, new ReactOutputManager(), renderConfig);
this.utils = utils;
this.renderConfig = {
diff --git a/packages/codegen-ui-react/lib/views/react-view-renderer.ts b/packages/codegen-ui-react/lib/views/react-view-renderer.ts
index 8e3245bba..7b1a482f0 100644
--- a/packages/codegen-ui-react/lib/views/react-view-renderer.ts
+++ b/packages/codegen-ui-react/lib/views/react-view-renderer.ts
@@ -24,6 +24,7 @@ import {
ViewMetadata,
handleCodegenErrors,
validateViewSchema,
+ StudioComponentPredicate,
} from '@aws-amplify/codegen-ui';
import {
addSyntheticLeadingComment,
@@ -36,6 +37,7 @@ import {
JsxSelfClosingElement,
Modifier,
NodeFlags,
+ ObjectLiteralExpression,
ScriptKind,
Statement,
SyntaxKind,
@@ -45,6 +47,7 @@ import { EOL } from 'os';
import {
buildBaseCollectionVariableStatement,
buildPrinter,
+ buildSortFunction,
defaultRenderConfig,
getDeclarationFilename,
transpile,
@@ -55,6 +58,13 @@ import { getComponentPropName } from '../react-component-render-helper';
import { ReactOutputManager } from '../react-output-manager';
import { ReactRenderConfig, scriptKindToFileExtension } from '../react-render-config';
import { RequiredKeys } from '../utils/type-utils';
+import {
+ buildDataStoreCollectionCall,
+ getFilterName,
+ getPaginationName,
+ getPredicateName,
+ needsFormatter,
+} from '../react-table-renderer-helper';
export abstract class ReactViewTemplateRenderer extends StudioTemplateRenderer<
string,
@@ -69,7 +79,7 @@ export abstract class ReactViewTemplateRenderer extends StudioTemplateRenderer<
protected renderConfig: RequiredKeys;
- protected viewDefinition: TableDefinition | undefined;
+ protected viewDefinition: TableDefinition;
protected viewComponent: StudioView;
@@ -93,14 +103,20 @@ export abstract class ReactViewTemplateRenderer extends StudioTemplateRenderer<
this.viewDefinition = generateTableDefinition(component, dataSchema);
break;
default:
- this.viewDefinition = undefined;
+ throw new Error(`Type: ${component.viewConfiguration.type} is not supported.`);
}
this.viewComponent = component;
+ // find if formatter is required
+ if (needsFormatter(component.viewConfiguration)) {
+ this.importCollection.addMappedImport(ImportValue.FORMATTER);
+ }
+
this.viewMetadata = {
id: component.id,
name: component.name,
+ fieldFormatting: {},
};
}
@@ -235,11 +251,28 @@ export abstract class ReactViewTemplateRenderer extends StudioTemplateRenderer<
buildVariableStatements() {
const statements: Statement[] = [];
const elements: BindingElement[] = [];
+ const { type, model, predicate, sort } = this.viewComponent.dataSource;
+ const isDataStoreEnabled = type === 'DataStore' && model;
+ if (isDataStoreEnabled) {
+ this.importCollection.addImport(ImportSource.LOCAL_MODELS, this.component.dataSource.type);
+ this.importCollection.addMappedImport(ImportValue.USE_DATA_STORE_BINDING);
+ elements.push(
+ factory.createBindingElement(
+ undefined,
+ factory.createIdentifier('items'),
+ factory.createIdentifier('itemsProps'),
+ undefined,
+ ),
+ factory.createBindingElement(undefined, undefined, factory.createIdentifier('predicateOverride'), undefined),
+ );
+ } else {
+ elements.push(factory.createBindingElement(undefined, undefined, factory.createIdentifier('items'), undefined));
+ }
+
+ // add base Props
// props
const props = [
- factory.createBindingElement(undefined, undefined, factory.createIdentifier('items'), undefined),
- factory.createBindingElement(undefined, undefined, factory.createIdentifier('predicateOverride'), undefined),
factory.createBindingElement(undefined, undefined, factory.createIdentifier('formatOverride'), undefined),
factory.createBindingElement(undefined, undefined, factory.createIdentifier('highlightOnHover'), undefined),
factory.createBindingElement(undefined, undefined, factory.createIdentifier('onRowClick'), undefined),
@@ -275,81 +308,138 @@ export abstract class ReactViewTemplateRenderer extends StudioTemplateRenderer<
),
);
- // add model import for datastore type
- if (this.component.dataSource.type === 'DataStore') {
- this.importCollection.addImport(ImportSource.LOCAL_MODELS, this.component.dataSource.type);
- }
-
- /*
- if datastore enabled
- const myViewDataStore = useDataStoreBinding({
- model: Model,
- type: 'Collection'
- }).items;
- const items = itemsProp !== undefined ? itemsProp : myViewDataStore;
-
- if custom enabled
- const myViewDataStore = [];
- const items = itemsProp !== undefined ? itemsProp : myViewDataStore;
- */
- statements.push(
- this.buildCollectionBindingCall(),
- factory.createVariableStatement(
- undefined,
- factory.createVariableDeclarationList(
- [
- factory.createVariableDeclaration(
- factory.createIdentifier('items'),
- undefined,
- undefined,
- factory.createConditionalExpression(
- factory.createBinaryExpression(
+ if (isDataStoreEnabled) {
+ /**
+ * builds predicate variable
+ */
+ if (predicate) {
+ this.importCollection.addMappedImport(ImportValue.CREATE_DATA_STORE_PREDICATE);
+ statements.push(
+ factory.createVariableStatement(
+ undefined,
+ factory.createVariableDeclarationList(
+ [
+ factory.createVariableDeclaration(
+ getFilterName(model),
+ undefined,
+ undefined,
+ this.predicateToObjectLiteralExpression(predicate),
+ ),
+ ],
+ NodeFlags.Const,
+ ),
+ ),
+ factory.createVariableStatement(
+ undefined,
+ factory.createVariableDeclarationList(
+ [
+ factory.createVariableDeclaration(
+ getPredicateName(model),
+ undefined,
+ undefined,
+ factory.createCallExpression(
+ factory.createIdentifier('createDataStorePredicate'),
+ [factory.createTypeReferenceNode(factory.createIdentifier(model), undefined)],
+ [factory.createIdentifier(getFilterName(model))],
+ ),
+ ),
+ ],
+ NodeFlags.Const,
+ ),
+ ),
+ );
+ }
+ /**
+ * builds sort function
+ */
+ if (sort) {
+ this.importCollection.addMappedImport(ImportValue.SORT_DIRECTION);
+ this.importCollection.addMappedImport(ImportValue.SORT_PREDICATE);
+ statements.push(
+ factory.createVariableStatement(
+ undefined,
+ factory.createVariableDeclarationList(
+ [
+ factory.createVariableDeclaration(
+ getPaginationName(model),
+ undefined,
+ undefined,
+ factory.createObjectLiteralExpression([
+ factory.createPropertyAssignment(factory.createIdentifier('sort'), buildSortFunction(model, sort)),
+ ]),
+ ),
+ ],
+ NodeFlags.Const,
+ ),
+ ),
+ );
+ }
+ /*
+ if datastore enabled
+ const myViewDataStore = useDataStoreBinding({
+ model: Model,
+ type: 'Collection'
+ }).items;
+ const items = itemsProp !== undefined ? itemsProp : myViewDataStore;
+
+ if custom enabled
+ uses regular items array for formatting
+ */
+ const dsItemsName = factory.createIdentifier(`${this.viewComponent.name}DataStore`);
+ statements.push(
+ buildBaseCollectionVariableStatement(
+ dsItemsName,
+ buildDataStoreCollectionCall(
+ model,
+ predicate ? getPredicateName(model) : undefined,
+ sort ? getPaginationName(model) : undefined,
+ ),
+ ),
+ // checks to see if an override was passed
+ factory.createVariableStatement(
+ undefined,
+ factory.createVariableDeclarationList(
+ [
+ factory.createVariableDeclaration(
+ factory.createIdentifier('items'),
+ undefined,
+ undefined,
+ factory.createConditionalExpression(
+ factory.createBinaryExpression(
+ factory.createIdentifier('itemsProp'),
+ factory.createToken(SyntaxKind.ExclamationEqualsEqualsToken),
+ factory.createIdentifier('undefined'),
+ ),
+ factory.createToken(SyntaxKind.QuestionToken),
factory.createIdentifier('itemsProp'),
- factory.createToken(SyntaxKind.ExclamationEqualsEqualsToken),
- factory.createIdentifier('undefined'),
+ factory.createToken(SyntaxKind.ColonToken),
+ dsItemsName,
),
- factory.createToken(SyntaxKind.QuestionToken),
- factory.createIdentifier('itemsProp'),
- factory.createToken(SyntaxKind.ColonToken),
- factory.createIdentifier('itemsDataStore'),
),
- ),
- ],
- NodeFlags.Const,
+ ],
+ NodeFlags.Const,
+ ),
),
- ),
- );
+ );
+ }
return statements;
}
- private buildCollectionBindingCall() {
- const { type, model } = this.viewComponent.dataSource;
- const itemsName = `${this.viewComponent.name}DataStore`;
- if (type === 'DataStore' && model) {
- this.importCollection.addMappedImport(ImportValue.USE_DATA_STORE_BINDING);
- const objectProperties = [
- factory.createPropertyAssignment(factory.createIdentifier('type'), factory.createStringLiteral('collection')),
- factory.createPropertyAssignment(factory.createIdentifier('model'), factory.createIdentifier(model)),
- ];
-
- const callExp = factory.createCallExpression(factory.createIdentifier('useDataStoreBinding'), undefined, [
- factory.createObjectLiteralExpression(objectProperties, true),
- ]);
- return buildBaseCollectionVariableStatement(factory.createIdentifier(itemsName), callExp);
- }
- return factory.createVariableStatement(
- undefined,
- factory.createVariableDeclarationList(
- [
- factory.createVariableDeclaration(
- factory.createIdentifier(itemsName),
- undefined,
- undefined,
- factory.createArrayLiteralExpression([], false),
- ),
- ],
- NodeFlags.Const,
- ),
+ private predicateToObjectLiteralExpression(predicate: StudioComponentPredicate): ObjectLiteralExpression {
+ return factory.createObjectLiteralExpression(
+ Object.entries(predicate).map(([key, value]) => {
+ return factory.createPropertyAssignment(
+ factory.createIdentifier(key),
+ key === 'and' || key === 'or'
+ ? factory.createArrayLiteralExpression(
+ (value as StudioComponentPredicate[]).map(
+ (pred: StudioComponentPredicate) => this.predicateToObjectLiteralExpression(pred),
+ false,
+ ),
+ )
+ : factory.createStringLiteral(value as string),
+ );
+ }, false),
);
}
diff --git a/packages/codegen-ui/example-schemas/datastore/post-ds.json b/packages/codegen-ui/example-schemas/datastore/post-ds.json
new file mode 100644
index 000000000..61e902db5
--- /dev/null
+++ b/packages/codegen-ui/example-schemas/datastore/post-ds.json
@@ -0,0 +1,106 @@
+{
+ "models": {
+ "Post": {
+ "name": "Post",
+ "fields": {
+ "id": {
+ "name": "id",
+ "isArray": false,
+ "type": "ID",
+ "isRequired": true,
+ "attributes": []
+ },
+ "caption": {
+ "name": "caption",
+ "isArray": false,
+ "type": "String",
+ "isRequired": false,
+ "attributes": []
+ },
+ "username": {
+ "name": "username",
+ "isArray": false,
+ "type": "String",
+ "isRequired": false,
+ "attributes": []
+ },
+ "post_url": {
+ "name": "post_url",
+ "isArray": false,
+ "type": "AWSURL",
+ "isRequired": false,
+ "attributes": []
+ },
+ "profile_url": {
+ "name": "profile_url",
+ "isArray": false,
+ "type": "AWSURL",
+ "isRequired": false,
+ "attributes": []
+ },
+ "status": {
+ "name": "status",
+ "isArray": false,
+ "type": {
+ "enum": "PostStatus"
+ },
+ "isRequired": false,
+ "attributes": []
+ },
+ "createdAt": {
+ "name": "createdAt",
+ "isArray": false,
+ "type": "AWSDateTime",
+ "isRequired": false,
+ "attributes": [],
+ "isReadOnly": true
+ },
+ "updatedAt": {
+ "name": "updatedAt",
+ "isArray": false,
+ "type": "AWSDateTime",
+ "isRequired": false,
+ "attributes": [],
+ "isReadOnly": true
+ }
+ },
+ "syncable": true,
+ "pluralName": "Posts",
+ "attributes": [
+ {
+ "type": "model",
+ "properties": {}
+ },
+ {
+ "type": "auth",
+ "properties": {
+ "rules": [
+ {
+ "allow": "private",
+ "provider": "iam",
+ "operations": [
+ "create",
+ "update",
+ "delete",
+ "read"
+ ]
+ }
+ ]
+ }
+ }
+ ]
+ }
+ },
+ "enums": {
+ "PostStatus": {
+ "name": "PostStatus",
+ "values": [
+ "PENDING",
+ "POSTED",
+ "IN_REVIEW"
+ ]
+ }
+ },
+ "nonModels": {},
+ "version": "00000"
+}
\ No newline at end of file
diff --git a/packages/codegen-ui/example-schemas/views/post-table-custom-format.json b/packages/codegen-ui/example-schemas/views/post-table-custom-format.json
new file mode 100644
index 000000000..851694a8c
--- /dev/null
+++ b/packages/codegen-ui/example-schemas/views/post-table-custom-format.json
@@ -0,0 +1,45 @@
+{
+ "dataSource": {
+ "model": "Post",
+ "predicate": {
+ "and": [
+ {
+ "field": "username",
+ "operand": "Guy",
+ "operator": "notContains"
+ },
+ {
+ "field": "createdAt",
+ "operand": "25",
+ "operator": "contains"
+ }
+ ]
+ },
+ "sort": [
+ {
+ "direction": "ASC",
+ "field": "username"
+ }
+ ],
+ "type": "DataStore"
+ },
+ "id": "v000001",
+ "name": "MyPostTable",
+ "schemaVersion": "1.0.0",
+ "style": {},
+ "viewConfiguration": {
+ "columns": {
+ "createdAt": {
+ "valueFormatting": {
+ "stringFormat": {
+ "dateTimeFormat": {
+ "dateFormat": "MM/DD/YYYY",
+ "timeFormat": "locale"
+ }
+ }
+ }
+ }
+ },
+ "type": "Table"
+ }
+}
\ No newline at end of file
diff --git a/packages/codegen-ui/example-schemas/views/post-table-datastore.json b/packages/codegen-ui/example-schemas/views/post-table-datastore.json
new file mode 100644
index 000000000..ff769eba4
--- /dev/null
+++ b/packages/codegen-ui/example-schemas/views/post-table-datastore.json
@@ -0,0 +1,31 @@
+{
+ "dataSource": {
+ "model": "Post",
+ "predicate": {
+ "and": [
+ {
+ "field": "username",
+ "operand": "username0",
+ "operator": "notContains"
+ },
+ {
+ "field": "createdAt",
+ "operand": "2022",
+ "operator": "contains"
+ }
+ ]
+ },
+ "sort": [
+ {
+ "direction": "ASC",
+ "field": "username"
+ }
+ ],
+ "type": "DataStore"
+ },
+ "id": "v-0001",
+ "name": "MyPostTable",
+ "schemaVersion": "1.0.0",
+ "style": {},
+ "viewConfiguration": { "type": "Table" }
+}
\ No newline at end of file
diff --git a/packages/codegen-ui/lib/types/view/view-metadata.ts b/packages/codegen-ui/lib/types/view/view-metadata.ts
index 674687845..e2ff96d2f 100644
--- a/packages/codegen-ui/lib/types/view/view-metadata.ts
+++ b/packages/codegen-ui/lib/types/view/view-metadata.ts
@@ -20,5 +20,5 @@ export type ViewMetadata = {
id?: string;
name: string;
// Stores the configured formatting for each field (table column)
- tableFieldFormatting?: { [fieldName: string]: ViewValueFormatting };
+ fieldFormatting: { [fieldName: string]: ViewValueFormatting };
};
diff --git a/packages/test-generator/lib/generators/BrowserTestGenerator.ts b/packages/test-generator/lib/generators/BrowserTestGenerator.ts
index 110b2e0e3..b5d83a67f 100644
--- a/packages/test-generator/lib/generators/BrowserTestGenerator.ts
+++ b/packages/test-generator/lib/generators/BrowserTestGenerator.ts
@@ -28,7 +28,7 @@ import {
ReactIndexStudioTemplateRenderer,
ReactUtilsStudioTemplateRenderer,
AmplifyFormRenderer,
- Util,
+ UtilTemplateType,
} from '@aws-amplify/codegen-ui-react';
import schema from '../models/schema';
import { TestGenerator } from './TestGenerator';
@@ -52,7 +52,7 @@ export class BrowserTestGenerator extends TestGenerator {
return new ReactIndexStudioTemplateRenderer(schemas, this.renderConfig).renderComponent();
}
- renderUtilsFile(utils: Util[]) {
+ renderUtilsFile(utils: UtilTemplateType[]) {
return new ReactUtilsStudioTemplateRenderer(utils, this.renderConfig).renderComponent();
}
diff --git a/packages/test-generator/lib/generators/NodeTestGenerator.ts b/packages/test-generator/lib/generators/NodeTestGenerator.ts
index 1f0620367..8c2e89d4d 100644
--- a/packages/test-generator/lib/generators/NodeTestGenerator.ts
+++ b/packages/test-generator/lib/generators/NodeTestGenerator.ts
@@ -32,7 +32,7 @@ import {
ReactIndexStudioTemplateRenderer,
ReactUtilsStudioTemplateRenderer,
AmplifyFormRenderer,
- Util,
+ UtilTemplateType,
} from '@aws-amplify/codegen-ui-react';
import schema from '../models/schema';
import { TestGenerator, TestGeneratorParams } from './TestGenerator';
@@ -74,7 +74,7 @@ export class NodeTestGenerator extends TestGenerator {
(schemas: StudioSchema[]) => new ReactIndexStudioTemplateRenderer(schemas, this.renderConfig),
);
this.utilsRendererFactory = new StudioTemplateRendererFactory(
- (utils: Util[]) => new ReactUtilsStudioTemplateRenderer(utils, this.renderConfig),
+ (utils: UtilTemplateType[]) => new ReactUtilsStudioTemplateRenderer(utils, this.renderConfig),
);
this.componentRendererManager = new StudioTemplateRendererManager(this.componentRendererFactory, this.outputConfig);
this.formRendererManager = new StudioTemplateRendererManager(this.formRendererFactory, this.outputConfig);