Skip to content

Commit

Permalink
fix(api-graphql): incorrect custom selection set for nested model.mod…
Browse files Browse the repository at this point in the history
…el.customType (#13216)
  • Loading branch information
HuiSF committed Apr 11, 2024
1 parent 2a7b3ac commit 390c159
Show file tree
Hide file tree
Showing 7 changed files with 199 additions and 44 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -1278,12 +1278,18 @@ const amplifyConfig = {
isRequired: true,
attributes: [],
},
warehouseId: {
name: 'warehouseId',
warehouse: {
name: 'warehouse',
isArray: false,
type: 'String',
isRequired: true,
type: {
model: 'Warehouse',
},
isRequired: false,
attributes: [],
association: {
connectionType: 'BELONGS_TO',
targetNames: ['warehouseProductsId'],
},
},
description: {
name: 'description',
Expand All @@ -1301,26 +1307,28 @@ const amplifyConfig = {
isRequired: false,
attributes: [],
},
owner: {
name: 'owner',
warehouseProductsId: {
name: 'warehouseProductsId',
isArray: false,
type: 'String',
type: 'ID',
isRequired: false,
attributes: [],
},
createdAt: {
name: 'createdAt',
isArray: false,
type: 'AWSDateTime',
isRequired: true,
isRequired: false,
attributes: [],
isReadOnly: true,
},
updatedAt: {
name: 'updatedAt',
isArray: false,
type: 'AWSDateTime',
isRequired: true,
isRequired: false,
attributes: [],
isReadOnly: true,
},
},
syncable: true,
Expand All @@ -1333,7 +1341,7 @@ const amplifyConfig = {
{
type: 'key',
properties: {
fields: ['sku', 'factoryId', 'warehouseId'],
fields: ['sku', 'factoryId'],
},
},
{
Expand All @@ -1358,7 +1366,87 @@ const amplifyConfig = {
primaryKeyInfo: {
isCustomPrimaryKey: true,
primaryKeyFieldName: 'sku',
sortKeyFieldNames: ['factoryId', 'warehouseId'],
sortKeyFieldNames: ['factoryId'],
},
},
Warehouse: {
name: 'Warehouse',
fields: {
id: {
name: 'id',
isArray: false,
type: 'ID',
isRequired: true,
attributes: [],
},
name: {
name: 'name',
isArray: false,
type: 'String',
isRequired: true,
attributes: [],
},
products: {
name: 'products',
isArray: true,
type: {
model: 'Product',
},
isRequired: false,
attributes: [],
isArrayNullable: true,
association: {
connectionType: 'HAS_MANY',
associatedWith: ['warehouseProductsId'],
},
},
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: 'Warehouses',
attributes: [
{
type: 'model',
properties: {},
},
{
type: 'auth',
properties: {
rules: [
{
provider: 'userPools',
ownerField: 'owner',
allow: 'owner',
identityClaim: 'cognito:username',
operations: ['create', 'update', 'delete', 'read'],
},
{
allow: 'public',
operations: ['read'],
},
],
},
},
],
primaryKeyInfo: {
isCustomPrimaryKey: false,
primaryKeyFieldName: 'id',
sortKeyFieldNames: [],
},
},
ImplicitOwner: {
Expand Down
10 changes: 7 additions & 3 deletions packages/api-graphql/__tests__/fixtures/modeled/schema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -91,15 +91,19 @@ const schema = a.schema({
.model({
sku: a.string().required(),
factoryId: a.string().required(),
warehouseId: a.string().required(),
description: a.string(),
warehouse: a.belongsTo("Warehouse"),
trackingMeta: a.customType({
productMeta: a.ref('ProductMeta'),
note: a.string(),
}),
})
.identifier(['sku', 'factoryId', 'warehouseId'])
.authorization([a.allow.public()]),
.identifier(['sku', 'factoryId'])
.authorization([a.allow.owner(), a.allow.public().to(["read"])]),
Warehouse: a.model({
name: a.string().required(),
products: a.hasMany("Product"),
}).authorization([a.allow.owner(), a.allow.public().to(["read"])]),
ProductMeta: a.customType({
releaseDate: a.date(),
status: a.enum(['in_production', 'discontinued']),
Expand Down
38 changes: 37 additions & 1 deletion packages/api-graphql/__tests__/internals/APIClient.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -415,6 +415,42 @@ describe('flattenItems', () => {

expect(selSet).toEqual(expected);
});

it('generates expected default selection set for nested model and custom type', () => {
const set = customSelectionSetToIR(modelIntroSchema, 'Warehouse', [
'id',
'name',
'products.*'
]);

const expected = {
id: '',
name: '',
products: {
items: {
createdAt: '',
updatedAt: '',
warehouseProductsId: '',
description: '',
factoryId: '',
owner: '',
sku: '',
trackingMeta: {
note: '',
productMeta: {
deepMeta: {
content: '',
},
releaseDate: '',
status: '',
}
}
}
}
}

expect(set).toEqual(expected);
});
});

describe('generateSelectionSet', () => {
Expand All @@ -431,7 +467,7 @@ describe('flattenItems', () => {
const generated = generateSelectionSet(modelIntroSchema, 'Product');

const expected =
'sku factoryId warehouseId description trackingMeta { productMeta { releaseDate status deepMeta { content } } note } owner createdAt updatedAt';
'sku factoryId description trackingMeta { productMeta { releaseDate status deepMeta { content } } note } warehouseProductsId createdAt updatedAt owner';

expect(generated).toEqual(expected);
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4786,7 +4786,6 @@ exports[`generateClient custom operations can query with returnType of a model t
echoModelHasNestedTypes(argumentContent: $argumentContent) {
sku
factoryId
warehouseId
description
trackingMeta {
productMeta {
Expand All @@ -4798,9 +4797,10 @@ exports[`generateClient custom operations can query with returnType of a model t
}
note
}
owner
warehouseProductsId
createdAt
updatedAt
owner
}
}
",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ describe('generateClient', () => {
'Post',
'Comment',
'Product',
'Warehouse',
'ImplicitOwner',
'CustomImplicitOwner',
'ModelGroupDefinedIn',
Expand Down Expand Up @@ -5330,7 +5331,6 @@ describe('generateClient', () => {
const mockReturnData = {
sku: 'sku',
factoryId: 'factoryId',
warehouseId: 'warehouseId',
description: 'description',
trackingMeta: {
productMeta: {
Expand All @@ -5357,7 +5357,10 @@ describe('generateClient', () => {
});

expect(normalizePostGraphqlCalls(spy)).toMatchSnapshot();
expect(result?.data).toEqual(mockReturnData);
expect(result?.data).toEqual({
...mockReturnData,
warehouse: expect.any(Function),
});
});

test('can query with returnType of string', async () => {
Expand Down
70 changes: 47 additions & 23 deletions packages/api-graphql/src/internals/APIClient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -318,10 +318,10 @@ export type ModelOperation = keyof typeof graphQLOperationsInfo;

const SELECTION_SET_WILDCARD = '*';

export function defaultSelectionSetForNonModelWithIR(
export const getDefaultSelectionSetForNonModelWithIR = (
nonModelDefinition: SchemaNonModel,
modelIntrospection: ModelIntrospectionSchema,
): Record<string, unknown> {
): Record<string, unknown> => {
const { fields } = nonModelDefinition;
const mappedFields = Object.values(fields)
.map(({ type, name }) => {
Expand All @@ -332,7 +332,7 @@ export function defaultSelectionSetForNonModelWithIR(
if (typeof (type as NonModelFieldType).nonModel === 'string') {
return [
name,
defaultSelectionSetForNonModelWithIR(
getDefaultSelectionSetForNonModelWithIR(
modelIntrospection.nonModels[(type as NonModelFieldType).nonModel],
modelIntrospection,
),
Expand All @@ -352,7 +352,47 @@ export function defaultSelectionSetForNonModelWithIR(
);

return Object.fromEntries(mappedFields);
}
};

const getDefaultSelectionSetForModelWithIR = (
modelDefinition: SchemaModel,
modelIntrospection: ModelIntrospectionSchema,
): Record<string, unknown> => {
const { fields } = modelDefinition;
const mappedFields = Object.values(fields)
.map(({ type, name }) => {
if (
typeof (type as { enum: string }).enum === 'string' ||
typeof type === 'string'
) {
return [name, FIELD_IR];
}

if (typeof (type as NonModelFieldType).nonModel === 'string') {
return [
name,
getDefaultSelectionSetForNonModelWithIR(
modelIntrospection.nonModels[(type as NonModelFieldType).nonModel],
modelIntrospection,
),
];
}

return undefined;
})
.filter(
(
pair: (string | Record<string, unknown>)[] | undefined,
): pair is (string | Record<string, unknown>)[] => pair !== undefined,
);

const ownerFields = resolveOwnerFields(modelDefinition).map(field => [
field,
FIELD_IR,
]);

return Object.fromEntries(mappedFields.concat(ownerFields));
};

function defaultSelectionSetForModel(modelDefinition: SchemaModel): string[] {
// fields that are explicitly part of the graphql schema; not
Expand Down Expand Up @@ -445,7 +485,7 @@ export function customSelectionSetToIR(

if (nested === SELECTION_SET_WILDCARD) {
result = {
[fieldName]: defaultSelectionSetForNonModelWithIR(
[fieldName]: getDefaultSelectionSetForNonModelWithIR(
relatedNonModelDefinition,
modelIntrospection,
),
Expand All @@ -471,8 +511,9 @@ export function customSelectionSetToIR(
modelIntrospection.models[relatedModel];

result = {
[fieldName]: modelsDefaultSelectionSetIR(
[fieldName]: getDefaultSelectionSetForModelWithIR(
nestedRelatedModelDefinition,
modelIntrospection,
),
};
} else {
Expand Down Expand Up @@ -529,23 +570,6 @@ export function customSelectionSetToIR(
);
}

const modelsDefaultSelectionSetIR = (relatedModelDefinition: SchemaModel) => {
const defaultSelectionSet = defaultSelectionSetForModel(
relatedModelDefinition,
);

const reduced = defaultSelectionSet.reduce(
(acc: Record<string, any>, curVal) => {
acc[curVal] = FIELD_IR;

return acc;
},
{},
);

return reduced;
};

/**
* Stringifies selection set IR
* * @example
Expand Down
Loading

0 comments on commit 390c159

Please sign in to comment.