I am trying to create a relationship between to nodes in Neo4j using GraphQL. What should the mutation look like?
Schema shows it should look like this.
AddPersonRoll(
from: _PersonInput!
to: _RollInput!
): _AddPersonRollPayload
I tryed
mutation {
AddPersonRoll(
from: {
id: "be91aaca-944d-49f7-b3fd-c89ad454d5ab"
}
to: {
id: "8726255f-b6b6-4299-ba01-95f6d4ef2be7"
}
) {
from {
id
name
}
to {
id
name
}
}
}
And it worked. But when I tried putting var into the query I got
{
"error": "Failed to execute 'fetch' on 'Window': Invalid name"
}
the code is
mutation AddPersonRoll($PersonInput: ID!, $RollInput: ID!){
AddPersonRoll(
from: {
id: $PersonInput
}
to: {
id: $RollInput
}
) {
from {
id
name
}
to {
id
name
}
}
}
{
"PersonInput": "3cc70aca-9e07-4bbd-acb2-92670b4d1b0d",
"RollInput": "8726255f-b6b6-4299-ba01-95f6d4ef2be7"
}
Found this earlier today when I ran into a similar problem. Here is my working mutation:
const ADD_INCIDENT_USER = gql`
mutation AddIncidentUser($from: ID!, $to: ID!) {
AddIncidentUser(from: { id: $from }, to: { id: $to }) {
from {
id
}
to {
id
}
}
}
`;
so in your example you would want to change
mutation AddPersonRoll($PersonInput: ID!, $RollInput: ID!){
to
mutation AddPersonRoll($from: ID!, $to: ID!){
Related
Suppose that we have this mutation:
const [addCryptoAddress] =
useMutation(graphql`
mutation useCrypto_AddCryptoWalletAddressMutation(
$input: AddCryptoAddressInput!
) {
addCryptoAddress(input: $input) {
userAccount {
cryptoCurrencyAddresses {
count
edges {
node {
id
address
}
}
}
}
errors {
message
}
}
}
`);
If successful, a new CryptoCurrencyAddresses will be available under UserAccount.
Now suppose that somewhere else in the code we have a lazyLoadQuery that fetches these addresses, e.g.
const { visitor } = useLazyLoadQuery<ManualPayoutMachineContextQuery>(
graphql`
query ManualPayoutMachineContextQuery {
visitor {
userAccount {
cryptoCurrencyAddresses {
edges {
node {
id
address
canDelete
currency
isDefault
label
}
}
}
}
}
}
`,
{}
);
However, note that this query references additional fields that are not mentioned in the mutation. The result is that all unmentioned fields are undefined immediately after the mutation, i.e.
visitor.userAccount?.cryptoCurrencyAddresses.edges
.map(({ node }) => {
return {
address: node.address,
canDelete: node.canDelete,
currency: node.currency,
id: node.id,
isDefault: node.isDefault,
label: node.label,
};
});
Produces:
[
{
address: '0xc0ffee254729296a45a3885639AC7E10F9d54979',
canDelete: undefined,
currency: undefined,
id: 'WyJDcnlwdG9DdXJyZW5jeUFkZHJlc3MiLDIwMjE3NF0=',
isDefault: undefined,
label: undefined,
}
]
Apart from listing every overlapping field in the mutation, is there a way to force all queries that depend on this data to reload when they detect new data?
You can set fetchPolicy as store-and-network, like this:
const { visitor } = useLazyLoadQuery<ManualPayoutMachineContextQuery>(
graphql`
query ManualPayoutMachineContextQuery {
visitor {
userAccount {
cryptoCurrencyAddresses {
edges {
node {
id
address
canDelete
currency
isDefault
label
}
}
}
}
}
}
`,
{},
{
fetchPolicy: 'store-and-network',
}
);
The store-and-network will use the data of the store and always do a network request without making regardless.
I am running a GraphQl mutation via a resolver. Its against a Neo4j database. The query is working fine but I am not able to structure the results in a way that they are displayed back in the result. I've tried several different combinations of results and record mapping but I can't get the properties of the returned result set to show in the outputs.
Specifically, you will see in the below examples that the name field in the response (via GraphiQL) is null when I expect it to be set to the value returned.
Resolver:
AddIdiomHasChildIdiom(object, params, ctx, resolveInfo) {
//************************************************************************************************************************************
//** Author: MOS
//** Date: 22/07/2019
//** Description: Add a child Idiom
//************************************************************************************************************************************
//Build the cypher query across multiple lines so that we can adjust the query depending on which parameters are passed
let query = new StringBuilder();
query.appendLine("MATCH (p:entity:Idiom {id:'" + params.from.id + "'}), (c:entity:Idiom {id:'" + params.to.id + "'})")
query.appendLine("MERGE (p)-[r:HAS_CHILD_IDIOM]->(c)")
query.appendLine("RETURN p,c")
console.log(query)
//Execute the query and send the results back to the user
return ctx.driver.session().run(query.toString(), {props:params})
.then(result => {
return {
from: result.records.map(record => { return record.get("p").properties}),
to: result.records.map(record => { return record.get("c").properties})
}
})
.catch(error => {
//ToDo: Error handling code need to go here
})
}
GraphQL Query
mutation{
AddIdiomHasChildIdiom(from:{id:"d94676b0-ac6c-11e9-a7a1-edf120d553ac"},to:{id:"e730a720-ac74-11e9-a45f-df629a6df5e1"})
{
from{
name
}
to{
name
}
}
}
Output:
{
"data": {
"AddIdiomHasChildIdiom": {
"from": {
"name": null
},
"to": {
"name": null
}
}
}
}
Relevant Schema Parts
type Mutation{
AddIdiomHasChildIdiom(
from: _IdiomInput!
to: _IdiomInput!
): _AddIdiomHasChildIdiomPayload
}
type _AddIdiomHasChildIdiomPayload {
from: Idiom
to: Idiom
}
input _IdiomInput {
id: ID!
}
type _AddIdiomHasChildIdiomPayload {
from: Idiom
to: Idiom
}
type Idiom {
id: ID
name: String
type: IdiomType
description: String
lifecycle: IdiomLifecycle
quality: Float
numStates: Int
numChildIdioms: Int
hasChildIdiom: [Idiom]
first: Int
offset: Int
orderBy: [_IdiomOrdering]
}
The problem in your code isthat you have mistmatch type between your graphql schema and the implementation of your resolver.
Your mutation query should return this :
type _AddIdiomHasChildIdiomPayload {
from: Idiom
to: Idiom
}
And your resolver returns this :
return {
from: result.records.map(record => { return record.get("p").properties}),
to: result.records.map(record => { return record.get("c").properties})
}
Here you are using a map function, and its result is an array.
So you are returning something like that :
{
from : [ { id:'', name:'', ... } ]
to : [ { id:'', name:'', ... } ]
}
And [ { id:'', name:'', ... } ] doesn't validate the Idiom type of your schema, it's an array : Idiom[].
So you need to change your resolver to return just one element for from & to.
And moreover, your implementation is weird : you are doing two loop against the result of Neo4j, due to the use of two map and you don't close the session.
You should try something like that :
const session = ctx.driver.session();
return session.run(query.toString(), {props:params})
.then(result => {
return result.records.map(record => {
return {
from:record.get("p").properties
to:record.get("c").properties
}
})[0]
})
.catch(error => {
//ToDo: Error handling code need to go here
})
.finally(() => {
session.close();
})
Moreover
EDIT: after some research I found out that ReactRelayPaginationContainer does only support one #connection for now (relay release 1.0.0).
I am not sure how to deal with multiple Connections in a PaginationContainer using Relay Modern as the only examples I saw so far only included one Connection. To illustrate my concern, there is some code below where I am having troubles
export default createPaginationContainer(MyComponent,
{
item: graphql`fragment mycomponent_item on Item {
foos(first: $count_foo, after: $cursor_foo) #connection(key: item_foos) {
pageInfo {
hasNextPage
endCursor
}
edges {
node {
id
...foo_foo
}
}
}
bars(first: $count_bar, cursor: $cursor_bar) #connection(key: item_bars) {
pageInfo {
hasNextPage
endCursor
}
edges {
node {
id
...bar_bar
}
}
}
}
}`
},
{
direction: 'forward',
getConnectionFromProps(props) {
return props.item.foos && props.item.bars;
},
getFragmentVariables(prevVars, totalCount) {
return {
...prevVars,
count: totalCount,
};
},
getVariables(props, {count, cursor}, fragmentVariables) {
return {
count,
cursor,
};
},
query: graphql`
query itemPaginationQuery(
$count_foo: Int!
$cursor_foo: String
$count_bar: Int!
$cursor_bar: String
) {
Item {
... mycomponent_item
}
}
`
});
The problem is in the ConnectionConfig of PaginationContainer of Relay Modern:
export type ConnectionConfig = {
direction?: 'backward' | 'forward',
getConnectionFromProps?: (props: Object) => ?ConnectionData,
getFragmentVariables?: FragmentVariablesGetter,
getVariables: (
props: Object,
paginationInfo: {count: number, cursor: ?string},
fragmentVariables: Variables,
) => Variables,
query: GraphQLTaggedNode,
};
It is clearly made just for one Connection in the PaginationContainer. And createPaginationContainer only accepts one of such ConnectionConfigs in it's constructor.
How am I supposed to deal with multiple Connections in one PaginationContainer?
According to this answer it should be possible to specify edges with arguments in fat query, but I can't make it work. Here are is my mutation config and fat query:
getFatQuery() {
return Relay.QL`
fragment on AddCommentPayload {
commentEdge(sort:[{createdAt: ASC}])
customer {
id
comments
}
}
`;
}
getConfigs() {
return [{
type: 'RANGE_ADD',
parentName: 'customer',
parentID: this.props.customer.id,
connectionName: 'comments',
edgeName: 'commentEdge',
rangeBehaviors() {
return 'append';
},
}];
}
Problem is that resulting query doesn't contain any arguments on edge field:
mutation AddComment($input_0:AddCommentInput!) {
addComment(input:$input_0) {
clientMutationId,
...F1
}
}
fragment F0 on Customer {
id
}
fragment F1 on AddCommentPayload {
commentEdge {
cursor,
__typename,
node {
id,
createdAt,
commentText
}
},
customer {
id,
...F0
}
}
I am getting the error:
bundle.js:28169 Warning: RelayMutation: Expected prop `group` supplied to `AddBlock` to be data fetched by Relay. This is likely an error unless you are purposely passing in mock data that conforms to the shape of this mutation's fragment.
It might seem similar to the problem described in this question, but the answer (of making sure the mutation is added to the initial query) is not working as a solution for me. I already have the mutation in the original query.
Here is my relevant code:
export class AddBlock extends Relay.Mutation {
getMutation() {
return Relay.QL`mutation { addBlock }`;
}
getVariables() {
return {
body: this.props.body
};
}
getFatQuery() {
return Relay.QL`
fragment on AddBlock {
newBlockEdge,
group {
blocks
}
}
`;
}
getConfigs() {
return [{
type: 'RANGE_ADD',
parentName: 'group',
parentID: this.props.group.id,
connectionName: 'blocks',
edgeName: 'newBlockEdge',
rangeBehaviors: {
'': 'append',
},
}];
}
getOptimisticResponse() {
return {
newBlockEdge: {
node: {
body: this.props.body
}
},
group: {
id: this.props.group.id
}
}
}
static get fragments() {
return {
group: () => Relay.QL`
fragment on GroupNode {
id
}
`,
}
}
}
class Designer extends React.Component {
...
addToBlocks(blocks) {
// Create a mutation to save to the blocks.
Relay.Store.commitUpdate(
new AddBlock({
body: blocks[0].block,
group: this.props.group
})
);
}
...
}
Designer = Relay.createContainer(Designer, {
fragments: {
group: (Component) => Relay.QL`
fragment on GroupNode {
title
visibility
blocks(first: 20) {
edges {
node {
${Block.getFragment('block')}
${UpdateBlockBodyMutation.getFragment('block')}
position
title
}
}
}
${AddBlock.getFragment('group')}
}
`,
}
});
What could I be doing wrong here?
I suspect your mutation fragment isn't actually being used - you should run a test to see that if you add other fields to the AddBlock fragment, you'll find that they aren't being requested...? I'm not 100% sure why (likely something about static get fragments), but not quite sure.
Once you get your mutation fragment actually being used, Relay won't complain anymore since it'll be getting data in the correct way :D