Skip to content

Commit

Permalink
fix: enable authAttributes statement for component with auth action b…
Browse files Browse the repository at this point in the history
…inding (#395)
  • Loading branch information
alharris-at committed Feb 25, 2022
1 parent b427d09 commit 9190dd6
Show file tree
Hide file tree
Showing 6 changed files with 148 additions and 26 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -426,6 +426,50 @@ export default function Test(props: TestProps): React.ReactElement {
"
`;

exports[`amplify render tests bindings auth supports auth bindings in actions 1`] = `
"/* eslint-disable */
import React from \\"react\\";
import {
EscapeHatchProps,
getOverrideProps,
useAuth,
useDataStoreCreateAction,
} from \\"@aws-amplify/ui-react/internal\\";
import { Button, ButtonProps } from \\"@aws-amplify/ui-react\\";
import { Customer } from \\"../models\\";

export type ComponentWithAuthEventBindingProps = React.PropsWithChildren<
Partial<ButtonProps> & {
overrides?: EscapeHatchProps | undefined | null;
}
>;
export default function ComponentWithAuthEventBinding(
props: ComponentWithAuthEventBindingProps
): React.ReactElement {
const { overrides, ...rest } = props;
const authAttributes = useAuth().user?.attributes ?? {};
const componentWithAuthEventBindingClick = useDataStoreCreateAction({
model: Customer,
fields: {
userName: authAttributes[\\"username\\"],
favoriteIceCream: authAttributes[\\"custom:favorite_icecream\\"],
},
});
return (
/* @ts-ignore: TS2322 */
<Button
children=\\"Create\\"
onClick={() => {
componentWithAuthEventBindingClick();
}}
{...rest}
{...getOverrideProps(overrides, \\"ComponentWithAuthEventBinding\\")}
></Button>
);
}
"
`;

exports[`amplify render tests collection should render collection with data binding 1`] = `
"/* eslint-disable */
import React from \\"react\\";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -516,4 +516,14 @@ describe('amplify render tests', () => {
expect(generateWithAmplifyRenderer('iconBug').componentText).toMatchSnapshot();
});
});

describe('bindings', () => {
describe('auth', () => {
it('supports auth bindings in actions', () => {
expect(
generateWithAmplifyRenderer('bindings/auth/componentWithAuthActionBinding').componentText,
).toMatchSnapshot();
});
});
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ import {
isStudioComponentWithBinding,
isSimplePropertyBinding,
isDataPropertyBinding,
isStudioComponentWithAuthProperty,
isStudioComponentWithAuthDependency,
isEventPropertyBinding,
isStudioComponentWithCollectionProperties,
isStudioComponentWithVariants,
Expand Down Expand Up @@ -620,7 +620,7 @@ export abstract class ReactStudioTemplateRenderer extends StudioTemplateRenderer
}

private buildUseAuthenticatedUserStatement(component: StudioComponent): Statement | undefined {
if (isStudioComponentWithAuthProperty(component)) {
if (isStudioComponentWithAuthDependency(component)) {
return factory.createVariableStatement(
undefined,
factory.createVariableDeclarationList(
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
{
"componentType": "Button",
"name": "ComponentWithAuthEventBinding",
"events": {
"click": {
"action": "Amplify.DataStoreCreateItemAction",
"parameters": {
"model": "Customer",
"fields": {
"userName": {
"userAttribute": "username"
},
"favoriteIceCream": {
"userAttribute": "custom:favorite_icecream"
}
}
}
}
},
"properties": {
"children": {
"value": "Create"
}
}
}

36 changes: 31 additions & 5 deletions packages/codegen-ui/lib/__tests__/renderer-helper.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ import {
isSimplePropertyBinding,
isStudioComponentWithCollectionProperties,
isStudioComponentWithVariants,
isStudioComponentWithAuthProperty,
isStudioComponentWithAuthDependency,
isEventPropertyBinding,
} from '../renderer-helper';

Expand Down Expand Up @@ -216,17 +216,20 @@ describe('render-helper', () => {
test('property not containing user attributes', () => {
expect(hasAuthProperty(componentWithoutAuthAttribute)).toBeFalsy();
});

test('property containing user attributes', () => {
expect(hasAuthProperty(componentWithAuthAttribute)).toBeTruthy();
});
});
describe('isStudioComponentWithAuthProperty', () => {
describe('isStudioComponentWithAuthDependency', () => {
test('parent not containing user attributes', () => {
expect(isStudioComponentWithAuthProperty(componentWithoutAuthAttribute)).toBeFalsy();
expect(isStudioComponentWithAuthDependency(componentWithoutAuthAttribute)).toBeFalsy();
});

test('parent containing user attributes', () => {
expect(isStudioComponentWithAuthProperty(componentWithAuthAttribute)).toBeTruthy();
expect(isStudioComponentWithAuthDependency(componentWithAuthAttribute)).toBeTruthy();
});

test('child containing user attributes', () => {
const testComponent: StudioComponent = {
id: '1234-5678-9010',
Expand Down Expand Up @@ -264,7 +267,30 @@ describe('render-helper', () => {
properties: {},
bindingProperties: {},
};
expect(isStudioComponentWithAuthProperty(testComponent)).toBeTruthy();
expect(isStudioComponentWithAuthDependency(testComponent)).toBeTruthy();
});

test('detects auth dependency for events', () => {
const testComponent: StudioComponent = {
componentType: 'Button',
name: 'ComponentWithAuthEventBinding',
events: {
click: {
action: 'Amplify.DataStoreCreateItemAction',
parameters: {
model: 'User',
fields: {
userName: {
userAttribute: 'username',
},
},
},
},
},
bindingProperties: {},
properties: {},
};
expect(isStudioComponentWithAuthDependency(testComponent)).toBeTruthy();
});
});
});
Expand Down
54 changes: 35 additions & 19 deletions packages/codegen-ui/lib/renderer-helper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,12 +14,6 @@
limitations under the License.
*/
import {
BoundStudioComponentProperty,
CollectionStudioComponentProperty,
ConcatenatedStudioComponentProperty,
ConditionalStudioComponentProperty,
FixedStudioComponentProperty,
StateStudioComponentProperty,
StudioComponent,
StudioComponentAuthProperty,
StudioComponentChild,
Expand All @@ -29,6 +23,8 @@ import {
StudioComponentSimplePropertyBinding,
StudioComponentStoragePropertyBinding,
StudioComponentPropertyBinding,
StudioComponentProperty,
ActionStudioComponentEvent,
} from './types';

export const StudioRendererConstants = {
Expand All @@ -41,31 +37,51 @@ export function isStudioComponentWithBinding(
return 'bindingProperties' in component;
}

export function hasAuthProperty(component: StudioComponent | StudioComponentChild): component is StudioComponent {
export function hasAuthProperty(component: StudioComponent | StudioComponentChild): boolean {
return Object.values(component.properties).some((val) => isAuthProperty(val));
}

export type ComponentPropertyValueTypes =
| ConcatenatedStudioComponentProperty
| ConditionalStudioComponentProperty
| FixedStudioComponentProperty
| BoundStudioComponentProperty
| CollectionStudioComponentProperty
| StateStudioComponentProperty
| StudioComponentAuthProperty;
function hasAuthAction(component: StudioComponent | StudioComponentChild): boolean {
const actions = component.events
? (Object.values(component.events).filter((event) => 'action' in event) as ActionStudioComponentEvent[])
: [];
return actions.some(doesActionHaveAuthBinding);
}

/**
* This should be written in a more generic way. Enumerating each case to get it out quickly.
*/
function doesActionHaveAuthBinding(action: ActionStudioComponentEvent): boolean {
switch (action.action) {
case 'Amplify.Navigation':
return Object.values(action.parameters).some(isAuthProperty);
case 'Amplify.AuthSignOut':
return Object.values(action.parameters).some(isAuthProperty);
case 'Amplify.DataStoreCreateItemAction':
return Object.values(action.parameters.fields).some(isAuthProperty);
case 'Amplify.DataStoreUpdateItemAction':
return isAuthProperty(action.parameters.id) || Object.values(action.parameters.fields).some(isAuthProperty);
case 'Amplify.DataStoreDeleteItemAction':
return isAuthProperty(action.parameters.id);
case 'Amplify.Mutation':
return action.parameters.state.set && isAuthProperty(action.parameters.state.set);
default:
throw new Error(`Action ${JSON.stringify(action)} could not be scanned for auth bindings.`);
}
}

export function isAuthProperty(prop: ComponentPropertyValueTypes): prop is StudioComponentAuthProperty {
export function isAuthProperty(prop: StudioComponentProperty): prop is StudioComponentAuthProperty {
return 'userAttribute' in prop;
}

export function isStudioComponentWithAuthProperty(
export function isStudioComponentWithAuthDependency(
component: StudioComponent | StudioComponentChild,
): component is StudioComponent {
if (hasAuthProperty(component)) {
if (hasAuthProperty(component) || hasAuthAction(component)) {
return true;
}
if (component.children) {
return component.children.some((child) => isStudioComponentWithAuthProperty(child));
return component.children.some(isStudioComponentWithAuthDependency);
}
return false;
}
Expand Down

0 comments on commit 9190dd6

Please sign in to comment.