Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: include all envs in gen 2 data codegen #14087

Open
wants to merge 7 commits into
base: migrations
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion packages/amplify-cli-core/API.md
Original file line number Diff line number Diff line change
Expand Up @@ -2204,7 +2204,7 @@ export function validateExportDirectoryPath(directoryPath: any, defaultPath: str
export class ViewResourceTableParams {
constructor(cliParams: CLIParams);
// (undocumented)
get categoryList(): string[] | [];
get categoryList(): [] | string[];
// (undocumented)
get command(): string;
// (undocumented)
Expand Down
4 changes: 2 additions & 2 deletions packages/amplify-gen1-codegen-data-adapter/API.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,11 @@

```ts

import { DataDefinition } from '@aws-amplify/amplify-gen2-codegen';
import { DataTableMapping } from '@aws-amplify/amplify-gen2-codegen';
import { Stack } from '@aws-sdk/client-cloudformation';

// @public (undocumented)
export const getDataDefinition: (dataStack: Stack) => DataDefinition;
export const getDataDefinition: (dataStack: Stack) => DataTableMapping;

// (No @packageDocumentation comment for this package)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,6 @@ describe('Data definition', () => {
],
} as Stack;
const result = getDataDefinition(stack);
assert.equal(result.tableMapping.hello, 'world');
assert.equal(result.hello, 'world');
});
});
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
import assert from 'node:assert';
import { DataDefinition } from '@aws-amplify/amplify-gen2-codegen';
import { DataTableMapping } from '@aws-amplify/amplify-gen2-codegen';
import { Stack } from '@aws-sdk/client-cloudformation';

export const tableMappingKey = 'DataSourceMappingOutput';

export const getDataDefinition = (dataStack: Stack): DataDefinition => {
export const getDataDefinition = (dataStack: Stack): DataTableMapping => {
const rawTableMapping = dataStack.Outputs?.find((o) => o.OutputKey === tableMappingKey)?.OutputValue;
assert(rawTableMapping);
const tableMapping = JSON.parse(rawTableMapping);
return { tableMapping };
return tableMapping;
};
5 changes: 4 additions & 1 deletion packages/amplify-gen2-codegen/API.md
Original file line number Diff line number Diff line change
Expand Up @@ -81,9 +81,12 @@ export type CustomAttributes = Partial<Record<`custom:${string}`, CustomAttribut

// @public (undocumented)
export type DataDefinition = {
tableMapping: Record<string, string>;
tableMappings: Record<string, DataTableMapping | undefined>;
};

// @public (undocumented)
export type DataTableMapping = Record<string, string>;

// @public (undocumented)
export type EmailOptions = {
emailVerificationBody: string;
Expand Down
22 changes: 9 additions & 13 deletions packages/amplify-gen2-codegen/src/data/source_builder.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,23 +16,19 @@ describe('Data Category code generation', () => {
});
describe('import map', () => {
it('is rendered', () => {
const tableMapping = { Todo: 'my-todo-mapping' };
const source = printNodeArray(generateDataSource({ tableMapping }));
assert.match(source, /importedAmplifyDynamoDBTableMap: \{\s+Todo: ['"]my-todo-mapping['"]/);
});
it('shows each key in the mapping table in the `importedModels` array', () => {
const tables = ['Todo', 'Foo', 'Bar'];
const tableMapping = tables.reduce((prev, curr) => ({ ...prev, [curr]: 'baz' }), {});
const source = printNodeArray(generateDataSource({ tableMapping }));
const array = source.match(/importedModels:\s+\[(.*?)\]/);
assert.deepEqual(tables, array?.[1].replaceAll('"', '').split(', '));
const tableMappings = { dev: { Todo: 'my-todo-mapping' } };
const source = printNodeArray(generateDataSource({ tableMappings }));
assert.match(
source,
/importedAmplifyDynamoDBTableMap: \{\s+\n\s+\/\/ replace the environment name \(dev\) with the corresponding branch name\n\s+dev: { Todo: ['"]my-todo-mapping['"] } }/,
);
});
it('has each each key in defineData', () => {
const tableMapping = { Todo: 'my-todo-mapping' };
const source = printNodeArray(generateDataSource({ tableMapping }));
const tableMappings = { dev: { Todo: 'my-todo-mapping' } };
const source = printNodeArray(generateDataSource({ tableMappings }));
assert.match(
source,
/defineData\({\n\s+importedAmplifyDynamoDBTableMap: \{\s+Todo: ['"]my-todo-mapping['"] },\n\s+importedModels:\s+\[.*?\],\n\s+schema: "TODO: Add your existing graphql schema here"\n}\)/,
/defineData\({\n\s+importedAmplifyDynamoDBTableMap: \{\s+\n\s+\/\/ replace the environment name \(dev\) with the corresponding branch name\n\s+dev: { Todo: ['"]my-todo-mapping['"] } },\n\s+schema: "TODO: Add your existing graphql schema here"\n}\)/,
);
});
});
Expand Down
50 changes: 35 additions & 15 deletions packages/amplify-gen2-codegen/src/data/source_builder.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
import ts, { ObjectLiteralElementLike } from 'typescript';
import { renderResourceTsFile } from '../resource/resource';
import { createTodoError } from '../todo_error';
import { table } from 'console';
Fixed Show fixed Hide fixed
const factory = ts.factory;

export type DataTableMapping = Record<string, string>;
export type DataDefinition = {
tableMapping: Record<string, string>;
tableMappings: Record<string, DataTableMapping | undefined>;
};

const importedAmplifyDynamoDBTableMapKeyName = 'importedAmplifyDynamoDBTableMap';
Expand All @@ -17,25 +19,43 @@
const namedImports: Record<string, Set<string>> = { '@aws-amplify/backend': new Set() };
namedImports['@aws-amplify/backend'].add('defineData');

if (dataDefinition?.tableMapping) {
const tableMappingProperties: ObjectLiteralElementLike[] = [];
for (const [tableName, tableId] of Object.entries(dataDefinition.tableMapping)) {
tableMappingProperties.push(
factory.createPropertyAssignment(factory.createIdentifier(tableName), factory.createStringLiteral(tableId)),
if (dataDefinition?.tableMappings) {
const tableMappingEnvironments: ObjectLiteralElementLike[] = [];
for (const [environmentName, tableMapping] of Object.entries(dataDefinition.tableMappings)) {
const tableMappingProperties: ObjectLiteralElementLike[] = [];
if (tableMapping) {
for (const [tableName, tableId] of Object.entries(tableMapping)) {
tableMappingProperties.push(
factory.createPropertyAssignment(factory.createIdentifier(tableName), factory.createStringLiteral(tableId)),
);
}
}

let tableMappingExpression = factory.createObjectLiteralExpression(tableMappingProperties);
if (tableMappingProperties.length === 0) {
tableMappingExpression = ts.addSyntheticLeadingComment(
ts.addSyntheticLeadingComment(tableMappingExpression, ts.SyntaxKind.SingleLineCommentTrivia, '', true),
ts.SyntaxKind.MultiLineCommentTrivia,
' Unable to find the table mapping for this environment.\n' +
' This could be due the enableGen2Migration feature flag not being set to true for this environment.\n' +
' Please enable the feature flag and push the backend resources.\n' +
' If you are not planning to migrate this environment, you can remove this key',
true,
);
}
tableMappingEnvironments.push(
ts.addSyntheticLeadingComment(
factory.createPropertyAssignment(factory.createIdentifier(environmentName), tableMappingExpression),
ts.SyntaxKind.SingleLineCommentTrivia,
` replace the environment name (${environmentName}) with the corresponding branch name`,
true,
),
);
}
dataRenderProperties.push(
factory.createPropertyAssignment(
importedAmplifyDynamoDBTableMapKeyName,
factory.createObjectLiteralExpression(tableMappingProperties),
),
);
dataRenderProperties.push(
factory.createPropertyAssignment(
importedModelsKey,
factory.createArrayLiteralExpression(
Object.keys(dataDefinition.tableMapping).map((tableName) => factory.createStringLiteral(tableName)),
),
factory.createObjectLiteralExpression(tableMappingEnvironments),
),
);
}
Expand Down
3 changes: 2 additions & 1 deletion packages/amplify-gen2-codegen/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ import {
ServerSideEncryptionConfiguration,
} from './storage/source_builder.js';

import { DataDefinition, generateDataSource } from './data/source_builder';
import { DataDefinition, DataTableMapping, generateDataSource } from './data/source_builder';

import { FunctionDefinition, renderFunctions } from './function/source_builder';

Expand Down Expand Up @@ -242,6 +242,7 @@ export {
AuthLambdaTriggers,
StorageTriggerEvent,
DataDefinition,
DataTableMapping,
SamlOptions,
OidcEndPoints,
MetadataOptions,
Expand Down
14 changes: 13 additions & 1 deletion packages/amplify-migration/src/backend_environment_selector.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import assert from 'node:assert';
import { AmplifyClient, BackendEnvironment, GetBackendEnvironmentCommand } from '@aws-sdk/client-amplify';
import { AmplifyClient, BackendEnvironment, GetBackendEnvironmentCommand, ListBackendEnvironmentsCommand } from '@aws-sdk/client-amplify';
import { getEnvInfo } from '@aws-amplify/cli-internal/lib/extensions/amplify-helpers/get-env-info';

export class BackendEnvironmentResolver {
Expand All @@ -19,4 +19,16 @@ export class BackendEnvironmentResolver {
this.selectedEnvironment = backendEnvironment;
return this.selectedEnvironment;
};

getAllBackendEnvironments = async (): Promise<BackendEnvironment[]> => {
const envInfo = getEnvInfo();
assert(envInfo);
const { backendEnvironments } = await this.amplifyClient.send(
new ListBackendEnvironmentsCommand({
appId: this.appId,
}),
);
assert(backendEnvironments, 'No backend environments found');
return backendEnvironments;
};
}
76 changes: 48 additions & 28 deletions packages/amplify-migration/src/data_definition_fetcher.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,13 @@ describe('DataDefinitionFetcher', () => {
it('maps cloudformation stack output to table mapping', async () => {
const mapping = { hello: 'world' };
const mockBackendEnvResolver: BackendEnvironmentResolver = {
selectBackendEnvironment: async () => {
return {
stackName: 'asdf',
} as BackendEnvironment;
getAllBackendEnvironments: async () => {
return [
{
environmentName: 'dev',
stackName: 'asdf',
},
] as BackendEnvironment[];
},
} as BackendEnvironmentResolver;
const mockAmplifyStackParser: AmplifyStackParser = {
Expand All @@ -32,14 +35,17 @@ describe('DataDefinitionFetcher', () => {
} as unknown as AmplifyStackParser;
const dataDefinitionFetcher = new DataDefinitionFetcher(mockBackendEnvResolver, mockAmplifyStackParser);
const results = await dataDefinitionFetcher.getDefinition();
assert(results?.tableMapping);
assert(results?.tableMappings);
});
it('throws an error if the json cannot be parsed', async () => {
it('return undefined for mapping if json cannot be parsed', async () => {
const mockBackendEnvResolver: BackendEnvironmentResolver = {
selectBackendEnvironment: async () => {
return {
stackName: 'asdf',
} as BackendEnvironment;
getAllBackendEnvironments: async () => {
return [
{
environmentName: 'dev',
stackName: 'asdf',
},
] as BackendEnvironment[];
},
} as BackendEnvironmentResolver;
const mockAmplifyStackParser: AmplifyStackParser = {
Expand All @@ -56,16 +62,21 @@ describe('DataDefinitionFetcher', () => {
} as AmplifyStacks),
} as unknown as AmplifyStackParser;
const dataDefinitionFetcher = new DataDefinitionFetcher(mockBackendEnvResolver, mockAmplifyStackParser);
await assert.rejects(() => dataDefinitionFetcher.getDefinition(), { message: 'Could not parse the Amplify Data table mapping' });
const results = await dataDefinitionFetcher.getDefinition();
assert(results?.tableMappings);
assert.equal(JSON.stringify(results?.tableMappings), JSON.stringify({ dev: undefined }));
});
});
describe('table mapping is not defined', () => {
it('reject with table mapping assertion', async () => {
it('return undefined for table mapping', async () => {
const mockBackendEnvResolver: BackendEnvironmentResolver = {
selectBackendEnvironment: async () => {
return {
stackName: 'asdf',
} as BackendEnvironment;
getAllBackendEnvironments: async () => {
return [
{
environmentName: 'dev',
stackName: 'asdf',
},
] as BackendEnvironment[];
},
} as BackendEnvironmentResolver;
const mockAmplifyStackParser: AmplifyStackParser = {
Expand All @@ -75,17 +86,22 @@ describe('DataDefinitionFetcher', () => {
} as AmplifyStacks),
} as unknown as AmplifyStackParser;
const dataDefinitionFetcher = new DataDefinitionFetcher(mockBackendEnvResolver, mockAmplifyStackParser);
await assert.rejects(dataDefinitionFetcher.getDefinition);
const results = await dataDefinitionFetcher.getDefinition();
assert(results?.tableMappings);
assert.equal(JSON.stringify(results?.tableMappings), JSON.stringify({ dev: undefined }));
});
});
});
describe('if data stack is undefined', () => {
it('does not reject with table mapping assertion', async () => {
const mockBackendEnvResolver: BackendEnvironmentResolver = {
selectBackendEnvironment: async () => {
return {
stackName: 'asdf',
} as BackendEnvironment;
getAllBackendEnvironments: async () => {
return [
{
environmentName: 'dev',
stackName: 'asdf',
},
] as BackendEnvironment[];
},
} as BackendEnvironmentResolver;
const mockAmplifyStackParser: AmplifyStackParser = {
Expand All @@ -97,12 +113,15 @@ describe('DataDefinitionFetcher', () => {
const dataDefinitionFetcher = new DataDefinitionFetcher(mockBackendEnvResolver, mockAmplifyStackParser);
await assert.doesNotReject(dataDefinitionFetcher.getDefinition);
});
it('returns undefined', async () => {
it('returns undefined for table mapping', async () => {
const mockBackendEnvResolver: BackendEnvironmentResolver = {
selectBackendEnvironment: async () => {
return {
stackName: 'asdf',
} as BackendEnvironment;
getAllBackendEnvironments: async () => {
return [
{
environmentName: 'dev',
stackName: 'asdf',
},
] as BackendEnvironment[];
},
} as BackendEnvironmentResolver;
const mockAmplifyStackParser: AmplifyStackParser = {
Expand All @@ -112,8 +131,9 @@ describe('DataDefinitionFetcher', () => {
} as AmplifyStacks),
} as unknown as AmplifyStackParser;
const dataDefinitionFetcher = new DataDefinitionFetcher(mockBackendEnvResolver, mockAmplifyStackParser);
const definition = await dataDefinitionFetcher.getDefinition();
assert.equal(definition, undefined);
const results = await dataDefinitionFetcher.getDefinition();
assert(results?.tableMappings);
assert.equal(JSON.stringify(results?.tableMappings), JSON.stringify({ dev: undefined }));
});
});
});
40 changes: 24 additions & 16 deletions packages/amplify-migration/src/data_definition_fetcher.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import assert from 'node:assert';
import { DataDefinition } from '@aws-amplify/amplify-gen2-codegen';
import { AmplifyStackParser } from './amplify_stack_parser.js';
import { BackendEnvironmentResolver } from './backend_environment_selector.js';
Expand All @@ -8,20 +7,29 @@ const dataSourceMappingOutputKey = 'DataSourceMappingOutput';
export class DataDefinitionFetcher {
constructor(private backendEnvironmentResolver: BackendEnvironmentResolver, private amplifyStackClient: AmplifyStackParser) {}
getDefinition = async (): Promise<DataDefinition | undefined> => {
const backendEnvironment = await this.backendEnvironmentResolver.selectBackendEnvironment();
assert(backendEnvironment?.stackName);
const amplifyStacks = await this.amplifyStackClient.getAmplifyStacks(backendEnvironment?.stackName);
if (amplifyStacks.dataStack) {
const tableMappingText = amplifyStacks.dataStack?.Outputs?.find((o) => o.OutputKey === dataSourceMappingOutputKey)?.OutputValue;
assert(tableMappingText, 'Amplify Data table mapping not found.');
try {
return {
tableMapping: JSON.parse(tableMappingText),
};
} catch (e) {
throw new Error('Could not parse the Amplify Data table mapping');
}
}
return undefined;
const backendEnvironments = await this.backendEnvironmentResolver.getAllBackendEnvironments();
const tableMappings = await Promise.all(
backendEnvironments.map(async (backendEnvironment) => {
if (!backendEnvironment?.stackName) {
return [backendEnvironment.environmentName, undefined];
}
const amplifyStacks = await this.amplifyStackClient.getAmplifyStacks(backendEnvironment?.stackName);
if (amplifyStacks.dataStack) {
const tableMappingText = amplifyStacks.dataStack?.Outputs?.find((o) => o.OutputKey === dataSourceMappingOutputKey)?.OutputValue;
if (!tableMappingText) {
return [backendEnvironment.environmentName, undefined];
}
try {
return [backendEnvironment.environmentName, JSON.parse(tableMappingText)];
} catch (e) {
return [backendEnvironment.environmentName, undefined];
}
}
return [backendEnvironment.environmentName, undefined];
}),
);
return {
tableMappings: Object.fromEntries(tableMappings),
};
};
}
Loading