Skip to content
This repository has been archived by the owner on Sep 3, 2021. It is now read-only.

Commit

Permalink
Input object argument format for mutation API (#531)
Browse files Browse the repository at this point in the history
* makes test script more specific

* exports some filter argument builders

* experimental support for where / data mutation arguments

* Update node.js

* experimental support for where / data mutation arguments

* experimental support for where / data mutation arguments

* experimental support for where / data mutation arguments

* exports schema comparison helpers

now used by both the normal and experimental augmentation tests

* adds test for experimental augmented schema

* adds typeDefs for experimental augmentation test

* Update augmentSchemaTest.test.js

* augmentation test for experimental schema

* adds tests for experimental node and relationship mutation arguments

* removes unused arguments and branches on array emptiness

* fixed temporal ordering and filtering

#524: unified the translation of nested orderBy arguments for relationship fields into translateNestedOrderingArgument, fixed schemaType argument to be innerSchemaType, for call in relationFieldOnNodeType

#495: uses parentIsListArgument to buildNeo4jTypeTranslation, to appropriately translate temporal filters used within OR / AND list filters

* adds tests for fixing #495 and #524

* blocks empty string "" from passing

this results in letting the cypher error pass through, caused by datetime(""), if an empty string is provided for a .formatted argument

* removed now unused function argument

* Update input-values.js
  • Loading branch information
michaeldgraham authored Nov 6, 2020
1 parent c4ed242 commit 7246b50
Show file tree
Hide file tree
Showing 16 changed files with 2,793 additions and 555 deletions.
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
"build-with-sourcemaps": "babel src --presets @babel/preset-env --out-dir dist --source-maps",
"precommit": "lint-staged",
"prepare": "npm run build",
"test": "nyc --reporter=lcov ava test/unit/**.test.js --verbose",
"test": "nyc --reporter=lcov ava test/unit/augmentSchemaTest.test.js test/unit/configTest.test.js test/unit/assertSchema.test.js test/unit/cypherTest.test.js test/unit/filterTest.test.js test/unit/filterTests.test.js test/unit/experimental/augmentSchemaTest.test.js test/unit/experimental/cypherTest.test.js",
"parse-tck": "babel-node test/helpers/tck/parseTck.js",
"test-tck": "nyc ava --fail-fast test/unit/filterTests.test.js",
"report-coverage": "nyc report --reporter=text-lcov > coverage.lcov && codecov",
Expand Down
13 changes: 6 additions & 7 deletions src/augment/input-values.js
Original file line number Diff line number Diff line change
Expand Up @@ -333,7 +333,7 @@ const LogicalFilteringArgument = {
/**
* Builds the AST definitions for logical filtering arguments
*/
const buildLogicalFilterInputValues = ({ typeName = '' }) => {
export const buildLogicalFilterInputValues = ({ typeName = '' }) => {
return [
buildInputValue({
name: buildName({ name: LogicalFilteringArgument.AND }),
Expand Down Expand Up @@ -361,7 +361,7 @@ const buildLogicalFilterInputValues = ({ typeName = '' }) => {
/**
* Builds the AST definitions for filtering Neo4j property type fields
*/
const buildPropertyFilters = ({
export const buildPropertyFilters = ({
field,
fieldName = '',
outputType = '',
Expand Down Expand Up @@ -512,11 +512,10 @@ export const selectUnselectedOrderedFields = ({
);
const orderingArgumentFieldNames = Object.keys(orderedFieldNameMap);
orderingArgumentFieldNames.forEach(orderedFieldName => {
if (
!fieldSelectionSet.some(
field => field.name && field.name.value === orderedFieldName
)
) {
const orderedFieldAlreadySelected = fieldSelectionSet.some(
field => field.name && field.name.value === orderedFieldName
);
if (!orderedFieldAlreadySelected) {
// add the field so that its data can be used for ordering
// since as it is not actually selected, it will be removed
// by default GraphQL post-processing field resolvers
Expand Down
249 changes: 166 additions & 83 deletions src/augment/types/node/mutation.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,11 @@ import {
useAuthDirective,
isCypherField
} from '../../directives';
import { getPrimaryKey } from './selection';
import {
getPrimaryKey,
buildNodeSelectionInputType,
buildNodeSelectionInputTypes
} from './selection';
import { shouldAugmentType } from '../../augment';
import { OperationType } from '../../types/types';
import {
Expand Down Expand Up @@ -48,6 +52,7 @@ export const augmentNodeMutationAPI = ({
propertyInputValues,
generatedTypeMap,
operationTypeMap,
typeDefinitionMap,
typeExtensionDefinitionMap,
config
}) => {
Expand Down Expand Up @@ -78,89 +83,28 @@ export const augmentNodeMutationAPI = ({
});
});
}
return [operationTypeMap, generatedTypeMap];
};

/**
* Given the results of augmentNodeTypeFields, builds the AST
* definition for a Mutation operation field of a given
* NodeMutation name
*/
const buildNodeMutationField = ({
mutationType,
mutationAction = '',
primaryKey,
typeName,
propertyInputValues,
operationTypeMap,
typeExtensionDefinitionMap,
config
}) => {
const mutationFields = mutationType.fields;
const mutationName = `${mutationAction}${typeName}`;
const mutationTypeName = mutationType ? mutationType.name.value : '';
const mutationTypeExtensions = typeExtensionDefinitionMap[mutationTypeName];
if (
!getFieldDefinition({
fields: mutationFields,
name: mutationName
}) &&
!getTypeExtensionFieldDefinition({
typeExtensions: mutationTypeExtensions,
name: typeName
})
) {
const mutationConfig = {
name: buildName({ name: mutationName }),
args: buildNodeMutationArguments({
operationName: mutationAction,
primaryKey,
args: propertyInputValues
}),
type: buildNamedType({
name: typeName
}),
directives: buildNodeMutationDirectives({
mutationAction,
typeName,
config
})
};
let mutationField = undefined;
let mutationDescriptionUrl = '';
if (mutationAction === NodeMutation.CREATE) {
mutationField = mutationConfig;
mutationDescriptionUrl =
'[creating](https://neo4j.com/docs/cypher-manual/4.1/clauses/create/#create-nodes)';
} else if (mutationAction === NodeMutation.UPDATE) {
if (primaryKey && mutationConfig.args.length > 1) {
mutationField = mutationConfig;
mutationDescriptionUrl =
'[updating](https://neo4j.com/docs/cypher-manual/4.1/clauses/set/#set-update-a-property)';
}
} else if (mutationAction === NodeMutation.MERGE) {
if (primaryKey) {
mutationField = mutationConfig;
mutationDescriptionUrl =
'[merging](https://neo4j.com/docs/cypher-manual/4.1/clauses/merge/#query-merge-node-derived)';
}
} else if (mutationAction === NodeMutation.DELETE) {
if (primaryKey) {
mutationField = mutationConfig;
mutationDescriptionUrl =
'[deleting](https://neo4j.com/docs/cypher-manual/4.1/clauses/delete/#delete-delete-single-node)';
}
}
if (mutationField) {
mutationField.description = buildDescription({
value: `[Generated mutation](${GRANDSTACK_DOCS_SCHEMA_AUGMENTATION}/#${mutationAction.toLowerCase()}) for ${mutationDescriptionUrl} a ${typeName} node.`,
config
});
mutationFields.push(buildField(mutationField));
}
operationTypeMap[OperationType.MUTATION].fields = mutationFields;
if (config.experimental === true) {
generatedTypeMap = buildNodeSelectionInputTypes({
definition,
typeName,
propertyInputValues,
generatedTypeMap,
typeDefinitionMap,
typeExtensionDefinitionMap,
config
});
} else {
generatedTypeMap = buildNodeSelectionInputType({
definition,
typeName,
propertyInputValues,
generatedTypeMap,
typeDefinitionMap,
typeExtensionDefinitionMap,
config
});
}
return operationTypeMap;
return [operationTypeMap, generatedTypeMap];
};

/**
Expand Down Expand Up @@ -258,6 +202,145 @@ const buildNodeMutationArguments = ({
);
};

const buildNodeMutationObjectArguments = ({ typeName, operationName = '' }) => {
const args = [];
const nodeSelectionConfig = {
name: 'where',
type: {
name: `_${typeName}Where`,
wrappers: {
[TypeWrappers.NON_NULL_NAMED_TYPE]: true
}
}
};
const propertyInputConfig = {
name: 'data',
type: {
name: `_${typeName}Data`,
wrappers: {
[TypeWrappers.NON_NULL_NAMED_TYPE]: true
}
}
};
if (operationName === NodeMutation.CREATE) {
args.push(propertyInputConfig);
} else if (operationName === NodeMutation.UPDATE) {
args.push(nodeSelectionConfig);
args.push(propertyInputConfig);
} else if (operationName === NodeMutation.MERGE) {
const keySelectionInputConfig = {
name: 'where',
type: {
name: `_${typeName}Keys`,
wrappers: {
[TypeWrappers.NON_NULL_NAMED_TYPE]: true
}
}
};
args.push(keySelectionInputConfig);
args.push(propertyInputConfig);
} else if (operationName === NodeMutation.DELETE) {
args.push(nodeSelectionConfig);
}
return args.map(arg =>
buildInputValue({
name: buildName({ name: arg.name }),
type: buildNamedType(arg.type)
})
);
};

/**
* Given the results of augmentNodeTypeFields, builds the AST
* definition for a Mutation operation field of a given
* NodeMutation name
*/
const buildNodeMutationField = ({
mutationType,
mutationAction = '',
primaryKey,
typeName,
propertyInputValues,
operationTypeMap,
typeExtensionDefinitionMap,
config
}) => {
const mutationFields = mutationType.fields;
const mutationName = `${mutationAction}${typeName}`;
const mutationTypeName = mutationType ? mutationType.name.value : '';
const mutationTypeExtensions = typeExtensionDefinitionMap[mutationTypeName];
if (
!getFieldDefinition({
fields: mutationFields,
name: mutationName
}) &&
!getTypeExtensionFieldDefinition({
typeExtensions: mutationTypeExtensions,
name: typeName
})
) {
let mutationArgs = [];
if (config.experimental === true) {
mutationArgs = buildNodeMutationObjectArguments({
typeName,
operationName: mutationAction
});
} else {
mutationArgs = buildNodeMutationArguments({
operationName: mutationAction,
primaryKey,
args: propertyInputValues
});
}
const mutationConfig = {
name: buildName({ name: mutationName }),
args: mutationArgs,
type: buildNamedType({
name: typeName
}),
directives: buildNodeMutationDirectives({
mutationAction,
typeName,
config
})
};
let mutationField = undefined;
let mutationDescriptionUrl = '';
if (mutationAction === NodeMutation.CREATE) {
mutationField = mutationConfig;
mutationDescriptionUrl =
'[creating](https://neo4j.com/docs/cypher-manual/4.1/clauses/create/#create-nodes)';
} else if (mutationAction === NodeMutation.UPDATE) {
if (primaryKey && mutationConfig.args.length > 1) {
mutationField = mutationConfig;
mutationDescriptionUrl =
'[updating](https://neo4j.com/docs/cypher-manual/4.1/clauses/set/#set-update-a-property)';
}
} else if (mutationAction === NodeMutation.MERGE) {
if (primaryKey) {
mutationField = mutationConfig;
mutationDescriptionUrl =
'[merging](https://neo4j.com/docs/cypher-manual/4.1/clauses/merge/#query-merge-node-derived)';
}
} else if (mutationAction === NodeMutation.DELETE) {
if (primaryKey) {
mutationField = mutationConfig;
mutationDescriptionUrl =
'[deleting](https://neo4j.com/docs/cypher-manual/4.1/clauses/delete/#delete-delete-single-node)';
}
}
if (mutationField) {
mutationField.description = buildDescription({
value: `[Generated mutation](${GRANDSTACK_DOCS_SCHEMA_AUGMENTATION}/#${mutationAction.toLowerCase()}) for ${mutationDescriptionUrl} a ${typeName} node.`,
config
});
mutationFields.push(buildField(mutationField));
}
operationTypeMap[OperationType.MUTATION].fields = mutationFields;
}
return operationTypeMap;
};

/**
* Builds the AST definitions for directive instances used by
* generated node Mutation fields of NodeMutation names
Expand Down
Loading

0 comments on commit 7246b50

Please sign in to comment.