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

fix: process input object, union and interface metadata in model introspection schema codegen #795

Merged
merged 14 commits into from
Apr 3, 2024
Merged
18 changes: 10 additions & 8 deletions .codebuild/e2e_workflow.yml
Original file line number Diff line number Diff line change
Expand Up @@ -145,24 +145,25 @@ batch:
depend-on:
- publish_to_local_registry
- identifier: >-
l_build_app_ts_uninitialized_project_codegen_js_uninitialized_project_modelgen_android_uninitialized_project_modelgen_flutter
l_build_app_ts_push_codegen_admin_modelgen_uninitialized_project_codegen_js_uninitialized_project_modelgen_android
buildspec: .codebuild/run_e2e_tests.yml
env:
compute-type: BUILD_GENERAL1_LARGE
variables:
TEST_SUITE: >-
src/__tests__/build-app-ts.test.ts|src/__tests__/uninitialized-project-codegen-js.test.ts|src/__tests__/uninitialized-project-modelgen-android.test.ts|src/__tests__/uninitialized-project-modelgen-flutter.test.ts
src/__tests__/build-app-ts.test.ts|src/__tests__/push-codegen-admin-modelgen.test.ts|src/__tests__/uninitialized-project-codegen-js.test.ts|src/__tests__/uninitialized-project-modelgen-android.test.ts
CLI_REGION: ap-southeast-1
DISABLE_ESLINT_PLUGIN: true
depend-on:
- publish_to_local_registry
- identifier: l_uninitialized_project_modelgen_ios_uninitialized_project_modelgen_js
- identifier: >-
l_uninitialized_project_modelgen_flutter_uninitialized_project_modelgen_ios_uninitialized_project_modelgen_js
buildspec: .codebuild/run_e2e_tests.yml
env:
compute-type: BUILD_GENERAL1_LARGE
variables:
TEST_SUITE: >-
src/__tests__/uninitialized-project-modelgen-ios.test.ts|src/__tests__/uninitialized-project-modelgen-js.test.ts
src/__tests__/uninitialized-project-modelgen-flutter.test.ts|src/__tests__/uninitialized-project-modelgen-ios.test.ts|src/__tests__/uninitialized-project-modelgen-js.test.ts
CLI_REGION: ap-southeast-2
depend-on:
- publish_to_local_registry
Expand Down Expand Up @@ -251,29 +252,30 @@ batch:
- publish_to_local_registry
- build_windows
- identifier: >-
w_build_app_ts_uninitialized_project_codegen_js_uninitialized_project_modelgen_android_uninitialized_project_modelgen_flutter
w_build_app_ts_push_codegen_admin_modelgen_uninitialized_project_codegen_js_uninitialized_project_modelgen_android
buildspec: .codebuild/run_e2e_tests_windows.yml
env:
compute-type: BUILD_GENERAL1_LARGE
image: $WINDOWS_IMAGE_2019
type: WINDOWS_SERVER_2019_CONTAINER
variables:
TEST_SUITE: >-
src/__tests__/build-app-ts.test.ts|src/__tests__/uninitialized-project-codegen-js.test.ts|src/__tests__/uninitialized-project-modelgen-android.test.ts|src/__tests__/uninitialized-project-modelgen-flutter.test.ts
src/__tests__/build-app-ts.test.ts|src/__tests__/push-codegen-admin-modelgen.test.ts|src/__tests__/uninitialized-project-codegen-js.test.ts|src/__tests__/uninitialized-project-modelgen-android.test.ts
CLI_REGION: us-east-1
DISABLE_ESLINT_PLUGIN: true
depend-on:
- publish_to_local_registry
- build_windows
- identifier: w_uninitialized_project_modelgen_ios_uninitialized_project_modelgen_js
- identifier: >-
w_uninitialized_project_modelgen_flutter_uninitialized_project_modelgen_ios_uninitialized_project_modelgen_js
buildspec: .codebuild/run_e2e_tests_windows.yml
env:
compute-type: BUILD_GENERAL1_LARGE
image: $WINDOWS_IMAGE_2019
type: WINDOWS_SERVER_2019_CONTAINER
variables:
TEST_SUITE: >-
src/__tests__/uninitialized-project-modelgen-ios.test.ts|src/__tests__/uninitialized-project-modelgen-js.test.ts
src/__tests__/uninitialized-project-modelgen-flutter.test.ts|src/__tests__/uninitialized-project-modelgen-ios.test.ts|src/__tests__/uninitialized-project-modelgen-js.test.ts
CLI_REGION: us-east-1
depend-on:
- publish_to_local_registry
Expand Down
14 changes: 14 additions & 0 deletions packages/amplify-codegen-e2e-core/src/utils/sdk-calls.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import {
AmplifyBackend,
} from 'aws-sdk';
import _ from 'lodash';
import { getProjectMeta } from './projectMeta';

export const getDDBTable = async (tableName: string, region: string) => {
const service = new DynamoDB({ region });
Expand Down Expand Up @@ -42,6 +43,19 @@ export const bucketNotExists = async (bucket: string) => {
}
};

export const getDeploymentBucketObject = async (projectRoot: string, objectKey: string) => {
const meta = getProjectMeta(projectRoot);
const deploymentBucket = meta.providers.awscloudformation.DeploymentBucketName;
const s3 = new S3();
const result = await s3
.getObject({
Bucket: deploymentBucket,
Key: objectKey,
})
.promise();
return result.Body.toLocaleString();
};

export const deleteS3Bucket = async (bucket: string, providedS3Client: S3 | undefined = undefined) => {
const s3 = providedS3Client ? providedS3Client : new S3();
let continuationToken: Required<Pick<S3.ListObjectVersionsOutput, 'KeyMarker' | 'VersionIdMarker'>> = undefined;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
input AMPLIFY { globalAuthRule: AuthRule = { allow: public } } # FOR TESTING ONLY!

type Todo @model {
id: ID!
name: String!
description: String
phone: Phone
}
type Phone {
number: String
}
enum BillingSource {
CLIENT
PROJECT
}
input CustomInput {
customField1: String!
customField2: BillingSource
customField3: NestedInput!
}
input NestedInput {
content: String! = "hello"
}
interface ICustom {
firstName: String!
lastName: String
birthdays: [INestedCustom!]!
}
interface INestedCustom {
birthDay: AWSDate!
}
# The member types of a Union type must all be Object base types.
union CustomUnion = Todo | Phone

type Query {
getAllTodo(msg: String, input: CustomInput): String @function(name: "echofunction-${env}")
echo(msg: String!): String
echo2(todoId: ID!): Todo
echo3: [Todo!]!
echo4(number: String): Phone
echo5: [CustomUnion!]!
echo6(customInput: CustomInput): String!
echo7: [ICustom]!
echo8(msg: [Float], msg2: [Int!], enumType: BillingSource, enumList: [BillingSource], inputType: [CustomInput]): [String]
echo9(msg: [Float]!, msg2: [Int!]!, enumType: BillingSource!, enumList: [BillingSource!]!, inputType: [CustomInput!]!): [String!]!

}
type Mutation {
mutate(msg: [String!]!): Todo
}
type Subscription {
onMutate(msg: String): [Todo!]
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import { DEFAULT_JS_CONFIG, createNewProjectDir } from "@aws-amplify/amplify-codegen-e2e-core";
import { deleteAmplifyProject, testPushAdminModelgen, testPushCodegen } from "../codegen-tests-base";

const schema = 'admin-modelgen.graphql';

describe('Amplify push with codegen tests - admin modelgen', () => {
let projectRoot: string;
beforeEach(async () => {
projectRoot = await createNewProjectDir('pushCodegenAdminModelgen');
});

afterEach(async () => {
await deleteAmplifyProject(projectRoot);
});

it(`should not throw error for executing the admin modelgen step required by studio CMS usage post push given the schema with input, union and interface types`, async () => {
await testPushAdminModelgen(DEFAULT_JS_CONFIG, projectRoot, schema);
});
});
57 changes: 48 additions & 9 deletions packages/amplify-codegen-e2e-tests/src/cleanup-e2e-resources.ts
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This change responds to the opt-in regions used for e2e tests, which is similar to the related cleanup script change in API category

Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ type AmplifyAppInfo = {

type S3BucketInfo = {
name: string;
region: string;
jobId?: string;
cbInfo?: CodeBuild.Build;
};
Expand Down Expand Up @@ -118,7 +119,16 @@ const getOrphanS3TestBuckets = async (account: AWSAccountInfo): Promise<S3Bucket
const s3Client = new aws.S3(getAWSConfig(account));
const listBucketResponse = await s3Client.listBuckets().promise();
const staleBuckets = listBucketResponse.Buckets.filter(testBucketStalenessFilter);
return staleBuckets.map(it => ({ name: it.Name }));
const bucketInfos = await Promise.all(
staleBuckets.map(async (staleBucket): Promise<S3BucketInfo> => {
const region = await getBucketRegion(account, staleBucket.Name);
return {
name: staleBucket.Name,
region,
};
}),
);
return bucketInfos;
};

/**
Expand Down Expand Up @@ -276,27 +286,52 @@ const getJobCodeBuildDetails = async (jobIds: string[]): Promise<CodeBuild.Build
}
};

const getBucketRegion = async (account: AWSAccountInfo, bucketName: string): Promise<string> => {
const awsConfig = getAWSConfig(account);
const s3Client = new aws.S3(awsConfig);
const location = await s3Client.getBucketLocation({ Bucket: bucketName }).promise();
const region = location.LocationConstraint ?? 'us-east-1';
return region;
};

const getS3Buckets = async (account: AWSAccountInfo): Promise<S3BucketInfo[]> => {
const s3Client = new aws.S3(getAWSConfig(account));
const awsConfig = getAWSConfig(account);
const s3Client = new aws.S3(awsConfig);
const buckets = await s3Client.listBuckets().promise();
const result: S3BucketInfo[] = [];
for (const bucket of buckets.Buckets) {
let region: string | undefined;
try {
const bucketDetails = await s3Client.getBucketTagging({ Bucket: bucket.Name }).promise();
region = await getBucketRegion(account, bucket.Name);
// Operations on buckets created in opt-in regions appear to require region-specific clients
const regionalizedClient = new aws.S3({
region,
...(awsConfig as object),
});
const bucketDetails = await regionalizedClient.getBucketTagging({ Bucket: bucket.Name }).promise();
const jobId = getJobId(bucketDetails.TagSet);
if (jobId) {
result.push({
name: bucket.Name,
region,
jobId
});
}
} catch (e) {
if (e.code !== 'NoSuchTagSet' && e.code !== 'NoSuchBucket') {
// TODO: Why do we process the bucket even with these particular errors?
if (e.code === 'NoSuchTagSet' || e.code === 'NoSuchBucket') {
result.push({
name: bucket.Name,
region: region ?? 'us-east-1',
});
} else if (e.code === 'InvalidToken') {
// We see some buckets in some accounts that were somehow created in an opt-in region different from the one to which the account is
// actually opted in. We don't quite know how this happened, but for now, we'll make a note of the inconsistency and continue
// processing the rest of the buckets.
console.error(`Skipping processing ${account.accountId}, bucket ${bucket.Name}`, e);
} else {
throw e;
}
result.push({
name: bucket.Name,
});
}
}
return result;
Expand Down Expand Up @@ -516,8 +551,12 @@ const deleteBucket = async (account: AWSAccountInfo, accountIndex: number, bucke
const { name } = bucket;
try {
console.log(`${generateAccountInfo(account, accountIndex)} Deleting S3 Bucket ${name}`);
const s3 = new aws.S3(getAWSConfig(account));
await deleteS3Bucket(name, s3);
const awsConfig = getAWSConfig(account);
const regionalizedS3Client = new aws.S3({
region: bucket.region,
...(awsConfig as object),
});
await deleteS3Bucket(name, regionalizedS3Client);
} catch (e) {
console.log(`${generateAccountInfo(account, accountIndex)} Deleting bucket ${name} failed with error ${e.message}`);
if (e.code === 'ExpiredTokenException') {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,10 @@ import {
amplifyPushWithCodegenAdd,
AmplifyFrontendConfig,
amplifyPushWithCodegenUpdate,
updateAPIWithResolutionStrategyWithModels
updateAPIWithResolutionStrategyWithModels,
getProjectMeta,
getDeploymentBucketObject,
amplifyPush
} from "@aws-amplify/amplify-codegen-e2e-core";
import { existsSync } from "fs";
import path from 'path';
Expand Down Expand Up @@ -38,3 +41,38 @@ export async function testPushCodegen(config: AmplifyFrontendConfig, projectRoot
expect(existsSync(userSourceCodePath)).toBe(true);
expect(isNotEmptyDir(path.join(projectRoot, config.modelgenDir))).toBe(true);
}

export async function testPushAdminModelgen(config: AmplifyFrontendConfig, projectRoot: string, schema: string) {
// init project and add API category
await initProjectWithProfile(projectRoot, { ...config, disableAmplifyAppCreation: false, });
const {
DeploymentBucketName: bucketName,
Region: region,
AmplifyAppId: appId,
} = getProjectMeta(projectRoot).providers.awscloudformation;

expect(bucketName).toBeDefined()
expect(region).toBeDefined();
expect(appId).toBeDefined();

const projectName = createRandomName();
await addApiWithoutSchema(projectRoot, { apiName: projectName });
await updateApiSchema(projectRoot, projectName, schema);
// add codegen succeeds
await amplifyPush(projectRoot);

/**
* Source code from
* https://github.com/aws-amplify/amplify-cli/blob/1da5de70c57b15a76f02c92364af4889d1585229/packages/amplify-provider-awscloudformation/src/admin-modelgen.ts#L85-L93
*/
const s3ApiModelsPrefix = `models/${projectName}/`;
const cmsArtifactLocalToS3Keys = [
`${s3ApiModelsPrefix}schema.graphql`,
`${s3ApiModelsPrefix}schema.js`,
`${s3ApiModelsPrefix}modelIntrospection.json`,
];
// expect CMS assets to be present in S3
cmsArtifactLocalToS3Keys.forEach(async (key) => {
await expect(getDeploymentBucketObject(projectRoot, key)).resolves.not.toThrow();
});
}
24 changes: 22 additions & 2 deletions packages/appsync-modelgen-plugin/API.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ export interface AppSyncModelPluginConfig extends RawDocumentsConfig {
// @public (undocumented)
export type Argument = {
name: string;
type: FieldType;
type: InputFieldType;
isArray: boolean;
isRequired: boolean;
isArrayNullable?: boolean;
Expand Down Expand Up @@ -94,14 +94,27 @@ export type FieldAttribute = ModelAttribute;
export type Fields = Record<string, Field>;

// @public (undocumented)
export type FieldType = 'ID' | 'String' | 'Int' | 'Float' | 'AWSDate' | 'AWSTime' | 'AWSDateTime' | 'AWSTimestamp' | 'AWSEmail' | 'AWSURL' | 'AWSIPAddress' | 'Boolean' | 'AWSJSON' | 'AWSPhone' | {
export type FieldType = ScalarType | {
enum: string;
} | {
model: string;
} | {
nonModel: string;
};

// @public (undocumented)
export type Input = {
name: string;
attributes: Arguments;
};

// @public (undocumented)
export type InputFieldType = ScalarType | {
enum: string;
} | {
input: string;
};

// @public (undocumented)
export type ModelAttribute = {
type: string;
Expand All @@ -119,6 +132,7 @@ export type ModelIntrospectionSchema = {
queries?: SchemaQueries;
mutations?: SchemaMutations;
subscriptions?: SchemaSubscriptions;
inputs?: SchemaInputs;
};

// Warning: (ae-forgotten-export) The symbol "RawAppSyncModelConfig" needs to be exported by the entry point index.d.ts
Expand All @@ -136,6 +150,9 @@ export type PrimaryKeyInfo = {
sortKeyFieldNames: string[];
};

// @public (undocumented)
export type ScalarType = 'ID' | 'String' | 'Int' | 'Float' | 'AWSDate' | 'AWSTime' | 'AWSDateTime' | 'AWSTimestamp' | 'AWSEmail' | 'AWSURL' | 'AWSIPAddress' | 'Boolean' | 'AWSJSON' | 'AWSPhone';

// @public (undocumented)
export type SchemaEnum = {
name: string;
Expand All @@ -145,6 +162,9 @@ export type SchemaEnum = {
// @public (undocumented)
export type SchemaEnums = Record<string, SchemaEnum>;

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

// @public (undocumented)
export type SchemaModel = {
name: string;
Expand Down
Loading
Loading