GraphQL DX: Relation/Pointer (#5946)

* Add a test on deep complex GraphQL Query

* Relation/Pointer new DX + deep nested mutations

* Fix lint

* Review

* Remove unnecessary code

* Fix objectId on update
This commit is contained in:
Antoine Cormouls
2019-08-21 23:55:34 +02:00
committed by Antonio Davi Macedo Coelho de Castro
parent 89e8868a85
commit 5b3a492965
7 changed files with 956 additions and 327 deletions

View File

@@ -395,14 +395,14 @@ const POLYGON_INPUT = new GraphQLList(new GraphQLNonNull(GEO_POINT_INPUT));
const POLYGON = new GraphQLList(new GraphQLNonNull(GEO_POINT));
const RELATION_OP = new GraphQLEnumType({
name: 'RelationOp',
description:
'The RelationOp enum type is used to specify which kind of operation should be executed to a relation.',
values: {
Batch: { value: 'Batch' },
AddRelation: { value: 'AddRelation' },
RemoveRelation: { value: 'RemoveRelation' },
const RELATION_INPUT = new GraphQLInputObjectType({
name: 'RelationInput',
description: 'Object involved into a relation',
fields: {
objectId: {
description: 'Id of the object involved.',
type: new GraphQLNonNull(GraphQLID),
},
},
});
@@ -491,6 +491,17 @@ const INCLUDE_ATT = {
type: GraphQLString,
};
const POINTER_INPUT = new GraphQLInputObjectType({
name: 'PointerInput',
description: 'Allow to link an object to another object',
fields: {
objectId: {
description: 'Id of the object involved.',
type: new GraphQLNonNull(GraphQLID),
},
},
});
const READ_PREFERENCE = new GraphQLEnumType({
name: 'ReadPreference',
description:
@@ -1080,7 +1091,6 @@ const load = parseGraphQLSchema => {
parseGraphQLSchema.addGraphQLType(FILE_INFO, true);
parseGraphQLSchema.addGraphQLType(GEO_POINT_INPUT, true);
parseGraphQLSchema.addGraphQLType(GEO_POINT, true);
parseGraphQLSchema.addGraphQLType(RELATION_OP, true);
parseGraphQLSchema.addGraphQLType(CREATE_RESULT, true);
parseGraphQLSchema.addGraphQLType(UPDATE_RESULT, true);
parseGraphQLSchema.addGraphQLType(CLASS, true);
@@ -1108,6 +1118,8 @@ const load = parseGraphQLSchema => {
parseGraphQLSchema.addGraphQLType(FIND_RESULT, true);
parseGraphQLSchema.addGraphQLType(SIGN_UP_RESULT, true);
parseGraphQLSchema.addGraphQLType(ELEMENT, true);
parseGraphQLSchema.addGraphQLType(RELATION_INPUT, true);
parseGraphQLSchema.addGraphQLType(POINTER_INPUT, true);
};
export {
@@ -1133,7 +1145,6 @@ export {
GEO_POINT,
POLYGON_INPUT,
POLYGON,
RELATION_OP,
CLASS_NAME_ATT,
FIELDS_ATT,
OBJECT_ID_ATT,
@@ -1195,6 +1206,8 @@ export {
SIGN_UP_RESULT,
ARRAY_RESULT,
ELEMENT,
POINTER_INPUT,
RELATION_INPUT,
load,
loadArrayResult,
};

View File

@@ -1,15 +1,12 @@
import { GraphQLNonNull, GraphQLBoolean } from 'graphql';
import * as defaultGraphQLTypes from './defaultGraphQLTypes';
import rest from '../../rest';
import { transformMutationInputToParse } from '../transformers/mutation';
const createObject = async (className, fields, config, auth, info) => {
if (!fields) {
fields = {};
}
transformMutationInputToParse(fields);
return (await rest.create(config, auth, className, fields, info.clientSDK))
.response;
};
@@ -26,8 +23,6 @@ const updateObject = async (
fields = {};
}
transformMutationInputToParse(fields);
return (await rest.update(
config,
auth,

View File

@@ -1,17 +1,15 @@
import { GraphQLNonNull } from 'graphql';
import getFieldNames from 'graphql-list-fields';
import * as defaultGraphQLTypes from './defaultGraphQLTypes';
import { extractKeysAndInclude } from '../parseGraphQLUtils';
import {
extractKeysAndInclude,
getParseClassMutationConfig,
} from '../parseGraphQLUtils';
import * as objectsMutations from './objectsMutations';
import * as objectsQueries from './objectsQueries';
import { ParseGraphQLClassConfig } from '../../Controllers/ParseGraphQLController';
import { transformClassNameToGraphQL } from '../transformers/className';
const getParseClassMutationConfig = function(
parseClassConfig: ?ParseGraphQLClassConfig
) {
return (parseClassConfig && parseClassConfig.mutation) || {};
};
import { transformTypes } from '../transformers/mutation';
const getOnlyRequiredFields = (
updatedFields,
@@ -55,43 +53,6 @@ const load = function(
classGraphQLOutputType,
} = parseGraphQLSchema.parseClassTypes[className];
const transformTypes = (inputType: 'create' | 'update', fields) => {
if (fields) {
const classGraphQLCreateTypeFields =
isCreateEnabled && classGraphQLCreateType
? classGraphQLCreateType.getFields()
: null;
const classGraphQLUpdateTypeFields =
isUpdateEnabled && classGraphQLUpdateType
? classGraphQLUpdateType.getFields()
: null;
Object.keys(fields).forEach(field => {
let inputTypeField;
if (inputType === 'create' && classGraphQLCreateTypeFields) {
inputTypeField = classGraphQLCreateTypeFields[field];
} else if (classGraphQLUpdateTypeFields) {
inputTypeField = classGraphQLUpdateTypeFields[field];
}
if (inputTypeField) {
switch (inputTypeField.type) {
case defaultGraphQLTypes.GEO_POINT_INPUT:
fields[field].__type = 'GeoPoint';
break;
case defaultGraphQLTypes.POLYGON_INPUT:
fields[field] = {
__type: 'Polygon',
coordinates: fields[field].map(geoPoint => [
geoPoint.latitude,
geoPoint.longitude,
]),
};
break;
}
}
});
}
};
if (isCreateEnabled) {
const createGraphQLMutationName = `create${graphQLClassName}`;
parseGraphQLSchema.addGraphQLMutation(createGraphQLMutationName, {
@@ -110,10 +71,16 @@ const load = function(
let { fields } = args;
if (!fields) fields = {};
const { config, auth, info } = context;
transformTypes('create', fields);
const parseFields = await transformTypes('create', fields, {
className,
parseGraphQLSchema,
req: { config, auth, info },
});
const createdObject = await objectsMutations.createObject(
className,
fields,
parseFields,
config,
auth,
info
@@ -172,12 +139,16 @@ const load = function(
const { objectId, fields } = args;
const { config, auth, info } = context;
transformTypes('update', fields);
const parseFields = await transformTypes('update', fields, {
className,
parseGraphQLSchema,
req: { config, auth, info },
});
const updatedObject = await objectsMutations.updateObject(
className,
objectId,
fields,
parseFields,
config,
auth,
info
@@ -205,7 +176,12 @@ const load = function(
info
);
}
return { ...updatedObject, ...fields, ...optimizedObject };
return {
objectId: objectId,
...updatedObject,
...fields,
...optimizedObject,
};
} catch (e) {
parseGraphQLSchema.handleError(e);
}

View File

@@ -15,7 +15,10 @@ import * as defaultGraphQLTypes from './defaultGraphQLTypes';
import * as objectsQueries from './objectsQueries';
import { ParseGraphQLClassConfig } from '../../Controllers/ParseGraphQLController';
import { transformClassNameToGraphQL } from '../transformers/className';
import { extractKeysAndInclude } from '../parseGraphQLUtils';
import {
extractKeysAndInclude,
getParseClassMutationConfig,
} from '../parseGraphQLUtils';
const mapInputType = (parseType, targetClass, parseClassTypes) => {
switch (parseType) {
@@ -34,18 +37,18 @@ const mapInputType = (parseType, targetClass, parseClassTypes) => {
case 'Pointer':
if (
parseClassTypes[targetClass] &&
parseClassTypes[targetClass].classGraphQLScalarType
parseClassTypes[targetClass].classGraphQLPointerType
) {
return parseClassTypes[targetClass].classGraphQLScalarType;
return parseClassTypes[targetClass].classGraphQLPointerType;
} else {
return defaultGraphQLTypes.OBJECT;
}
case 'Relation':
if (
parseClassTypes[targetClass] &&
parseClassTypes[targetClass].classGraphQLRelationOpType
parseClassTypes[targetClass].classGraphQLRelationType
) {
return parseClassTypes[targetClass].classGraphQLRelationOpType;
return parseClassTypes[targetClass].classGraphQLRelationType;
} else {
return defaultGraphQLTypes.OBJECT;
}
@@ -259,6 +262,11 @@ const load = (
classSortFields,
} = getInputFieldsAndConstraints(parseClass, parseClassConfig);
const {
create: isCreateEnabled = true,
update: isUpdateEnabled = true,
} = getParseClassMutationConfig(parseClassConfig);
const classGraphQLScalarTypeName = `${graphQLClassName}Pointer`;
const parseScalarValue = value => {
if (typeof value === 'string') {
@@ -339,31 +347,6 @@ const load = (
parseGraphQLSchema.addGraphQLType(classGraphQLScalarType) ||
defaultGraphQLTypes.OBJECT;
const classGraphQLRelationOpTypeName = `${graphQLClassName}RelationOpInput`;
let classGraphQLRelationOpType = new GraphQLInputObjectType({
name: classGraphQLRelationOpTypeName,
description: `The ${classGraphQLRelationOpTypeName} type is used in operations that involve relations with the ${graphQLClassName} class.`,
fields: () => ({
_op: {
description: 'This is the operation to be executed.',
type: new GraphQLNonNull(defaultGraphQLTypes.RELATION_OP),
},
ops: {
description:
'In the case of a Batch operation, this is the list of operations to be executed.',
type: new GraphQLList(new GraphQLNonNull(classGraphQLRelationOpType)),
},
objects: {
description:
'In the case of a AddRelation or RemoveRelation operation, this is the list of objects to be added/removed.',
type: new GraphQLList(new GraphQLNonNull(classGraphQLScalarType)),
},
}),
});
classGraphQLRelationOpType =
parseGraphQLSchema.addGraphQLType(classGraphQLRelationOpType) ||
defaultGraphQLTypes.OBJECT;
const classGraphQLCreateTypeName = `Create${graphQLClassName}FieldsInput`;
let classGraphQLCreateType = new GraphQLInputObjectType({
name: classGraphQLCreateTypeName,
@@ -430,6 +413,62 @@ const load = (
classGraphQLUpdateType
);
const classGraphQLPointerTypeName = `${graphQLClassName}PointerInput`;
let classGraphQLPointerType = new GraphQLInputObjectType({
name: classGraphQLPointerTypeName,
description: `Allow to link OR add and link an object of the ${graphQLClassName} class.`,
fields: () => {
const fields = {
link: {
description: `Link an existing object from ${graphQLClassName} class.`,
type: defaultGraphQLTypes.POINTER_INPUT,
},
};
if (isCreateEnabled) {
fields['createAndLink'] = {
description: `Create and link an object from ${graphQLClassName} class.`,
type: classGraphQLCreateType,
};
}
return fields;
},
});
classGraphQLPointerType =
parseGraphQLSchema.addGraphQLType(classGraphQLPointerType) ||
defaultGraphQLTypes.OBJECT;
const classGraphQLRelationTypeName = `${graphQLClassName}RelationInput`;
let classGraphQLRelationType = new GraphQLInputObjectType({
name: classGraphQLRelationTypeName,
description: `Allow to add, remove, createAndAdd objects of the ${graphQLClassName} class into a relation field.`,
fields: () => {
const fields = {
add: {
description: `Add an existing object from the ${graphQLClassName} class into the relation.`,
type: new GraphQLList(
new GraphQLNonNull(defaultGraphQLTypes.RELATION_INPUT)
),
},
remove: {
description: `Remove an existing object from the ${graphQLClassName} class out of the relation.`,
type: new GraphQLList(
new GraphQLNonNull(defaultGraphQLTypes.RELATION_INPUT)
),
},
};
if (isCreateEnabled) {
fields['createAndAdd'] = {
description: `Create and add an object of the ${graphQLClassName} class into the relation.`,
type: new GraphQLList(new GraphQLNonNull(classGraphQLCreateType)),
};
}
return fields;
},
});
classGraphQLRelationType =
parseGraphQLSchema.addGraphQLType(classGraphQLRelationType) ||
defaultGraphQLTypes.OBJECT;
const classGraphQLConstraintTypeName = `${graphQLClassName}PointerWhereInput`;
let classGraphQLConstraintType = new GraphQLInputObjectType({
name: classGraphQLConstraintTypeName,
@@ -700,8 +739,9 @@ const load = (
);
parseGraphQLSchema.parseClassTypes[className] = {
classGraphQLPointerType,
classGraphQLRelationType,
classGraphQLScalarType,
classGraphQLRelationOpType,
classGraphQLCreateType,
classGraphQLUpdateType,
classGraphQLConstraintType,
@@ -709,6 +749,11 @@ const load = (
classGraphQLFindArgs,
classGraphQLOutputType,
classGraphQLFindResultType,
config: {
parseClassConfig,
isCreateEnabled,
isUpdateEnabled,
},
};
if (className === '_User') {