Skip to content

Commit 4bffdce

Browse files
Moumoulsdavimacedo
authored andcommitted
GraphQL: Inline Fragment on Array Fields (parse-community#5908)
* Inline Fragment Spec * Inline Fragment on Arrays * Fix Test * Only select the root field * Requested Changes * Lazy Loaded ArrayResult
1 parent 45dabbb commit 4bffdce

7 files changed

+247
-61
lines changed

spec/ParseGraphQLServer.spec.js

+115-1
Original file line numberDiff line numberDiff line change
@@ -539,6 +539,19 @@ describe('ParseGraphQLServer', () => {
539539
expect(dateType.kind).toEqual('SCALAR');
540540
});
541541

542+
it('should have ArrayResult type', async () => {
543+
const arrayResultType = (await apolloClient.query({
544+
query: gql`
545+
query ArrayResultType {
546+
__type(name: "ArrayResult") {
547+
kind
548+
}
549+
}
550+
`,
551+
})).data['__type'];
552+
expect(arrayResultType.kind).toEqual('UNION');
553+
});
554+
542555
it('should have File object type', async () => {
543556
const fileType = (await apolloClient.query({
544557
query: gql`
@@ -746,6 +759,25 @@ describe('ParseGraphQLServer', () => {
746759
).toBeTruthy(JSON.stringify(schemaTypes));
747760
});
748761

762+
it('should ArrayResult contains all types', async () => {
763+
const objectType = (await apolloClient.query({
764+
query: gql`
765+
query ObjectType {
766+
__type(name: "ArrayResult") {
767+
kind
768+
possibleTypes {
769+
name
770+
}
771+
}
772+
}
773+
`,
774+
})).data['__type'];
775+
const possibleTypes = objectType.possibleTypes.map(o => o.name);
776+
expect(possibleTypes).toContain('_UserClass');
777+
expect(possibleTypes).toContain('_RoleClass');
778+
expect(possibleTypes).toContain('Element');
779+
});
780+
749781
it('should update schema when it changes', async () => {
750782
const schemaController = await parseServer.config.databaseController.loadSchema();
751783
await schemaController.updateClass('_User', {
@@ -1661,6 +1693,84 @@ describe('ParseGraphQLServer', () => {
16611693
expect(new Date(result.updatedAt)).toEqual(obj.updatedAt);
16621694
});
16631695

1696+
it_only_db('mongo')(
1697+
'should return child objects in array fields',
1698+
async () => {
1699+
const obj1 = new Parse.Object('Customer');
1700+
const obj2 = new Parse.Object('SomeClass');
1701+
const obj3 = new Parse.Object('Customer');
1702+
1703+
obj1.set('someCustomerField', 'imCustomerOne');
1704+
const arrayField = [42.42, 42, 'string', true];
1705+
obj1.set('arrayField', arrayField);
1706+
await obj1.save();
1707+
1708+
obj2.set('someClassField', 'imSomeClassTwo');
1709+
await obj2.save();
1710+
1711+
//const obj3Relation = obj3.relation('manyRelations')
1712+
obj3.set('manyRelations', [obj1, obj2]);
1713+
await obj3.save();
1714+
1715+
await parseGraphQLServer.parseGraphQLSchema.databaseController.schemaCache.clear();
1716+
1717+
const result = (await apolloClient.query({
1718+
query: gql`
1719+
query GetCustomer($objectId: ID!) {
1720+
objects {
1721+
getCustomer(objectId: $objectId) {
1722+
objectId
1723+
manyRelations {
1724+
... on CustomerClass {
1725+
objectId
1726+
someCustomerField
1727+
arrayField {
1728+
... on Element {
1729+
value
1730+
}
1731+
}
1732+
}
1733+
... on SomeClassClass {
1734+
objectId
1735+
someClassField
1736+
}
1737+
}
1738+
createdAt
1739+
updatedAt
1740+
}
1741+
}
1742+
}
1743+
`,
1744+
variables: {
1745+
objectId: obj3.id,
1746+
},
1747+
})).data.objects.getCustomer;
1748+
1749+
expect(result.objectId).toEqual(obj3.id);
1750+
expect(result.manyRelations.length).toEqual(2);
1751+
1752+
const customerSubObject = result.manyRelations.find(
1753+
o => o.objectId === obj1.id
1754+
);
1755+
const someClassSubObject = result.manyRelations.find(
1756+
o => o.objectId === obj2.id
1757+
);
1758+
1759+
expect(customerSubObject).toBeDefined();
1760+
expect(someClassSubObject).toBeDefined();
1761+
expect(customerSubObject.someCustomerField).toEqual(
1762+
'imCustomerOne'
1763+
);
1764+
const formatedArrayField = customerSubObject.arrayField.map(
1765+
elem => elem.value
1766+
);
1767+
expect(formatedArrayField).toEqual(arrayField);
1768+
expect(someClassSubObject.someClassField).toEqual(
1769+
'imSomeClassTwo'
1770+
);
1771+
}
1772+
);
1773+
16641774
it('should respect level permissions', async () => {
16651775
await prepareData();
16661776

@@ -5609,7 +5719,11 @@ describe('ParseGraphQLServer', () => {
56095719
findSomeClass(where: { someField: { _exists: true } }) {
56105720
results {
56115721
objectId
5612-
someField
5722+
someField {
5723+
... on Element {
5724+
value
5725+
}
5726+
}
56135727
}
56145728
}
56155729
}

src/GraphQL/ParseGraphQLSchema.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -81,7 +81,7 @@ class ParseGraphQLSchema {
8181
parseClassMutations.load(this, parseClass, parseClassConfig);
8282
}
8383
);
84-
84+
defaultGraphQLTypes.loadArrayResult(this, parseClasses);
8585
defaultGraphQLQueries.load(this);
8686
defaultGraphQLMutations.load(this);
8787

src/GraphQL/loaders/defaultGraphQLTypes.js

+54
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import {
1212
GraphQLList,
1313
GraphQLInputObjectType,
1414
GraphQLBoolean,
15+
GraphQLUnionType,
1516
} from 'graphql';
1617
import { GraphQLUpload } from 'graphql-upload';
1718

@@ -1020,6 +1021,55 @@ const SIGN_UP_RESULT = new GraphQLObjectType({
10201021
},
10211022
});
10221023

1024+
const ELEMENT = new GraphQLObjectType({
1025+
name: 'Element',
1026+
description:
1027+
'The SignUpResult object type is used in the users sign up mutation to return the data of the recent created user.',
1028+
fields: {
1029+
value: {
1030+
description: 'Return the value of the element in the array',
1031+
type: new GraphQLNonNull(ANY),
1032+
},
1033+
},
1034+
});
1035+
1036+
// Default static union type, we update types and resolveType function later
1037+
let ARRAY_RESULT;
1038+
1039+
const loadArrayResult = (parseGraphQLSchema, parseClasses) => {
1040+
const classTypes = parseClasses
1041+
.filter(parseClass =>
1042+
parseGraphQLSchema.parseClassTypes[parseClass.className]
1043+
.classGraphQLOutputType
1044+
? true
1045+
: false
1046+
)
1047+
.map(
1048+
parseClass =>
1049+
parseGraphQLSchema.parseClassTypes[parseClass.className]
1050+
.classGraphQLOutputType
1051+
);
1052+
ARRAY_RESULT = new GraphQLUnionType({
1053+
name: 'ArrayResult',
1054+
description:
1055+
'Use Inline Fragment on Array to get results: https://graphql.org/learn/queries/#inline-fragments',
1056+
types: () => [ELEMENT, ...classTypes],
1057+
resolveType: value => {
1058+
if (value.__type === 'Object' && value.className && value.objectId) {
1059+
if (parseGraphQLSchema.parseClassTypes[value.className]) {
1060+
return parseGraphQLSchema.parseClassTypes[value.className]
1061+
.classGraphQLOutputType;
1062+
} else {
1063+
return ELEMENT;
1064+
}
1065+
} else {
1066+
return ELEMENT;
1067+
}
1068+
},
1069+
});
1070+
parseGraphQLSchema.graphQLTypes.push(ARRAY_RESULT);
1071+
};
1072+
10231073
const load = parseGraphQLSchema => {
10241074
parseGraphQLSchema.graphQLTypes.push(GraphQLUpload);
10251075
parseGraphQLSchema.graphQLTypes.push(ANY);
@@ -1056,6 +1106,7 @@ const load = parseGraphQLSchema => {
10561106
parseGraphQLSchema.graphQLTypes.push(POLYGON_CONSTRAINT);
10571107
parseGraphQLSchema.graphQLTypes.push(FIND_RESULT);
10581108
parseGraphQLSchema.graphQLTypes.push(SIGN_UP_RESULT);
1109+
parseGraphQLSchema.graphQLTypes.push(ELEMENT);
10591110
};
10601111

10611112
export {
@@ -1140,5 +1191,8 @@ export {
11401191
POLYGON_CONSTRAINT,
11411192
FIND_RESULT,
11421193
SIGN_UP_RESULT,
1194+
ARRAY_RESULT,
1195+
ELEMENT,
11431196
load,
1197+
loadArrayResult,
11441198
};

src/GraphQL/loaders/parseClassMutations.js

+4-10
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { GraphQLNonNull } from 'graphql';
22
import getFieldNames from 'graphql-list-fields';
33
import * as defaultGraphQLTypes from './defaultGraphQLTypes';
4-
import * as parseClassTypes from './parseClassTypes';
4+
import { extractKeysAndInclude } from '../parseGraphQLUtils';
55
import * as objectsMutations from './objectsMutations';
66
import * as objectsQueries from './objectsQueries';
77
import { ParseGraphQLClassConfig } from '../../Controllers/ParseGraphQLController';
@@ -119,9 +119,7 @@ const load = function(
119119
info
120120
);
121121
const selectedFields = getFieldNames(mutationInfo);
122-
const { keys, include } = parseClassTypes.extractKeysAndInclude(
123-
selectedFields
124-
);
122+
const { keys, include } = extractKeysAndInclude(selectedFields);
125123
const { keys: requiredKeys, needGet } = getOnlyRequiredFields(
126124
fields,
127125
keys,
@@ -180,9 +178,7 @@ const load = function(
180178
info
181179
);
182180
const selectedFields = getFieldNames(mutationInfo);
183-
const { keys, include } = parseClassTypes.extractKeysAndInclude(
184-
selectedFields
185-
);
181+
const { keys, include } = extractKeysAndInclude(selectedFields);
186182

187183
const { keys: requiredKeys, needGet } = getOnlyRequiredFields(
188184
fields,
@@ -225,9 +221,7 @@ const load = function(
225221
const { objectId } = args;
226222
const { config, auth, info } = context;
227223
const selectedFields = getFieldNames(mutationInfo);
228-
const { keys, include } = parseClassTypes.extractKeysAndInclude(
229-
selectedFields
230-
);
224+
const { keys, include } = extractKeysAndInclude(selectedFields);
231225

232226
let optimizedObject = {};
233227
const splitedKeys = keys.split(',');

src/GraphQL/loaders/parseClassQueries.js

+23-21
Original file line numberDiff line numberDiff line change
@@ -2,15 +2,35 @@ import { GraphQLNonNull } from 'graphql';
22
import getFieldNames from 'graphql-list-fields';
33
import * as defaultGraphQLTypes from './defaultGraphQLTypes';
44
import * as objectsQueries from './objectsQueries';
5-
import * as parseClassTypes from './parseClassTypes';
65
import { ParseGraphQLClassConfig } from '../../Controllers/ParseGraphQLController';
6+
import { extractKeysAndInclude } from '../parseGraphQLUtils';
77

88
const getParseClassQueryConfig = function(
99
parseClassConfig: ?ParseGraphQLClassConfig
1010
) {
1111
return (parseClassConfig && parseClassConfig.query) || {};
1212
};
1313

14+
const getQuery = async (className, _source, args, context, queryInfo) => {
15+
const { objectId, readPreference, includeReadPreference } = args;
16+
const { config, auth, info } = context;
17+
const selectedFields = getFieldNames(queryInfo);
18+
19+
const { keys, include } = extractKeysAndInclude(selectedFields);
20+
21+
return await objectsQueries.getObject(
22+
className,
23+
objectId,
24+
keys,
25+
include,
26+
readPreference,
27+
includeReadPreference,
28+
config,
29+
auth,
30+
info
31+
);
32+
};
33+
1434
const load = function(
1535
parseGraphQLSchema,
1636
parseClass,
@@ -40,25 +60,7 @@ const load = function(
4060
type: new GraphQLNonNull(classGraphQLOutputType),
4161
async resolve(_source, args, context, queryInfo) {
4262
try {
43-
const { objectId, readPreference, includeReadPreference } = args;
44-
const { config, auth, info } = context;
45-
const selectedFields = getFieldNames(queryInfo);
46-
47-
const { keys, include } = parseClassTypes.extractKeysAndInclude(
48-
selectedFields
49-
);
50-
51-
return await objectsQueries.getObject(
52-
className,
53-
objectId,
54-
keys,
55-
include,
56-
readPreference,
57-
includeReadPreference,
58-
config,
59-
auth,
60-
info
61-
);
63+
return await getQuery(className, _source, args, context, queryInfo);
6264
} catch (e) {
6365
parseGraphQLSchema.handleError(e);
6466
}
@@ -86,7 +88,7 @@ const load = function(
8688
const { config, auth, info } = context;
8789
const selectedFields = getFieldNames(queryInfo);
8890

89-
const { keys, include } = parseClassTypes.extractKeysAndInclude(
91+
const { keys, include } = extractKeysAndInclude(
9092
selectedFields
9193
.filter(field => field.includes('.'))
9294
.map(field => field.slice(field.indexOf('.') + 1))

0 commit comments

Comments
 (0)