How to use Promise.all in graphql resolver for neo4j? - neo4j

I have multiple user nodes (around 45 users) and I want to return the total users count as well as the User's details too in a single query. (Similar to How to design the following resolver for GraphQL server?)
My schema:
type User {
ID: Int
name: String
}
type Query {
users: [User]
}
And after running the resolver for users query, I want to pull the total count as well as the users details too like below:
{
"data": {
"users": {
"total": 45
"users": [
{
"ID": 1,
"name": "User A"
},
{
"ID": 2,
"name": "User B"
},
...
]
}
But I am confused how to use Promise.all in neo4j. I tried to look how the promise works in neo4j but I did not find any desired info.
So, could you please let me know how should I write my resolver for this case ? Any help would be appreciable !!

Using Promise.all is not different in neo4j. Promise.all is from javascript. To write resolver you can do following:
let countQuery = "MATCH(n:User) RETURN count(n) as count;";
let userQuery = "MATCH(u:User) RETURN u;";
return Promise.all([
dbSession().run(countQuery, params),
dbSession().run(userQuery, params)
]).then((data) => {
return {
total: data[0].records.map(record => {return record.get('count')}
users: data[1].records.map(record => {return record.get('u')}
}
})
In your schema type you can change it to following:
type User {
ID: Int
name: String
}
type PagedData {
total: Int,
users: [User]
}
type Query {
users: PagedData
}

Related

Auto-generated Mutation Does Not Create Relationship

I want to test auto-generated CRUD mutations created by calling makeAugmentedSchema from 'neo4j-graphql-js'. There is no problem with creating nodes but creating relationship does not work for me. Please advise on what I am doing wrong here.
Schema:
type Bio{
id: ID!
description: String
}
type Person{
id: ID!
name: String
dob: Date
gender: String
bioRelation: [Bio] #relation(name: "HAS_BIO", direction: "OUT")
}
Mutation:
I am following the Interface Mutations guidance https://grandstack.io/docs/graphql-interface-union-types to create mutation.
mutation {
p: CreatePerson(
name: "Anton",
gender: "Male") {
name
gender
id
}
b: CreateBio(
description: "I am a developer") {
description
id
}
r: AddPersonBioRelation(
from: {id: "p"},
to:{id: "b"}
){
from{
name
}
to{
description
}
}
}
It create Person and Bio nodes but no any relationship gets created between the two:
{
"data": {
"p": {
"name": "Anton",
"gender": "Male",
"id": "586b63fd-f9a5-4274-890f-26ba567c065c"
},
"b": {
"description": "I am a developer",
"id": "a46b4c22-d23b-4630-ac84-9d6248bdda89"
},
"r": null
}
}
This is how AddPersonBioRelation looks like:
Thank you.
I am new to GRANDstack, and I have also been struggling with these types of issues myself. I have typically broken this out separate mutations (in javascript) and used the return value for each as values for the next mutation. for example:
await createIncident({
variables: {
brief: values.brief,
date,
description: values.description,
recordable: values.recordable,
title: values.title
}
}).then((res) => {
addIncidentUser({
variables: {
from: user.id,
to: res.data.CreateIncident.id
}
});
});
the problem that i see in the example you've provided is that you are specifying a string value for from and to as "p" and "b" respectively and NOT the p.id and b.id return values from the parent mutations.
it's fine of me to point that out but what i can't for the LIFE of me figure out is how to properly reference p.id and b.id in the mutation itself. in other words you are trying to send
from: { id: "586b63fd-f9a5-4274-890f-26ba567c065c"}
to: { id: "a46b4c22-d23b-4630-ac84-9d6248bdda89" }
but in reality you are sending
from: { id: "p"}
to: { id: "b" }
which aren't actually references in neo4j so it fails.
if we can figure out how to properly reference p.id and b.id we should get this working.
Thank you, #dryhurst. It appears that there is no way to reference id of newly created nodes, but I found a solution by introducing temp id property. Please see the discussion of this matter and final solution on:
https://community.neo4j.com/t/auto-generated-mutation-does-not-create-relationship/21412/16.

Group a Searchkick result?

I have a basic Searchkick system set-up. I want to take the results and then group them by an attribute to sum a another attribute etc.
This question is close to my issue:
Elasticsearch + searckick
and the only answer was to use aggregations. I could do that but then I would be building an active record call for each of the agg keys returned.
Here is what I have so far:
BudgetItem.all.search("*", body_options: { aggs: { cbs_item_id: { terms: { field: "cbs_item_id" }, aggs: { "total": { "sum": { "field": "total" } } } } } } )
which results in:
"aggregations"=>{"cbs_item_id"=>{"doc_count_error_upper_bound"=>0, "sum_other_doc_count"=>0, "buckets"=>[{"key"=>5, "doc_count"=>2, "total"=>{"value"=>2956.0}}, {"key"=>6, "doc_count"=>2, "total"=>{"value"=>7734.0}}]}}}>
in my search_data I have a term 'cbs' which is a text value that relates to the 'cbs_item_id'. I am looking for this result:
"aggregations"=>
{"cbs_item_id"=>
{"doc_count_error_upper_bound"=>0, "sum_other_doc_count"=>0, "buckets"=>
[{"key"=>5, "doc_count"=>2, "total"=>{"value"=>2956.0}, "cbs"=>{"value"=>"MY CBS Related Field" }},
{"key"=>6, "doc_count"=>2, "total"=>{"value"=>7734.0}, "cbs"=>{"value"=>"MY OTHER CBS Related Field" }}]}}}
This of this where you have in inventory of cars and a separate table of car_colors ( [id = 1, color = red], [id = 3, color = blue ]. I want to search for the cars of a given color then group them and sum etc.
I am sure I am perhaps missing something simple here.
UPDATE
Getting close:
BudgetItem.all.search("*", body_options: { aggs: { cbs_item_id: { terms: { field: "cbs_item_id" }, aggs: { cbs: { terms: { field: "cbs" } }, "total": { "sum": { "field": "total" } } } } } } )
which results:
"buckets"=>
[{"key"=>5, "doc_count"=>2, "total"=>{"value"=>2956.0}, "cbs"=>{"doc_count_error_upper_bound"=>0, "sum_other_doc_count"=>0, "buckets"=>[{"key"=>"001", "doc_count"=>2}]}},
{"key"=>6, "doc_count"=>2, "total"=>{"value"=>7734.0}, "cbs"=>{"doc_count_error_upper_bound"=>0, "sum_other_doc_count"=>0, "buckets"=>[{"key"=>"002", "doc_count"=>2}]}}]}}
The second "key"s 001 and 002 are the data I am looking for.

Combining results of two tables in mongoid/mongo

Hi guys what would be the best way to combine results of two mongoid queries.
My issue is that I would like to know active users, A user can send a letter and a notification, both are separate table and a user if he sends either the letter or the notification is considered active. What I want to know is how many active users were there per month.
right now what I can think of is doing this
Letter.collection.aggregate([
{ '$match': {}.merge(opts) },
{ '$sort': { 'created_at': 1 } },
{
'$group': {
_id: '$customer_id',
first_notif_sent: {
'$first': {
'day': { '$dayOfMonth': '$created_at' },
'month': { '$month': '$created_at' },
'year': { '$year': '$created_at' }
}
}
}
}])
Notification.collection.aggregate([
{ '$match': {}.merge(opts) },
{ '$sort': { 'created_at': 1 } },
{
'$group': {
_id: '$customer_id',
first_notif_sent: {
'$first': {
'day': { '$dayOfMonth': '$created_at' },
'month': { '$month': '$created_at' },
'year': { '$year': '$created_at' }
}
}
}
}])
What I am looking for is to get the minimum of the dates and then combine the results and get the count. Right now I can get the results and loop over each of them and create a new list. But I wanted to know if there is a way to do it in mongo directly.
EDIT
For letters
def self.get_active(tenant_id)
map = %{
function() {
emit(this.customer_id, new Date(this.created_at))
}
}
reduce = %{
function(key, values) {
return new Date(Math.min.apply(null, values))
}
}
where(tenant_id: tenant_id).map_reduce(map, reduce).out(reduce: "#{tenant_id}_letter_notification")
end
Notifications
def self.get_active(tenant_id)
map = %{
function() {
emit(this.customer_id, new Date(this.updated_at))
}
}
reduce = %{
function(key, values) {
return new Date(Math.min.apply(null, values))
}
}
where(tenant_id: tenant_id, transferred: true).map_reduce(map, reduce).out(reduce: "#{tenant_id}_outgoing_letter_standing_order_balance")
end
This is what I am thinking of going with, one of the reason is that, lookup does not work with my version of mongo.
the customer created a new notification, or a new letter, and I would like to get the first created at of either.
Let's address this first as a foundation. Given examples of document schema as below:
Document schema in Letter collection:
{ _id: <ObjectId>,
customer_id: <integer>,
created_at: <date> }
And, document schema in Notification collection:
{ _id: <ObjectId>,
customer_id: <integer>,
created_at: <date> }
You can utilise aggregation pipeline $lookup to join the two collections. For example using mongo shell :
db.letter.aggregate([
{"$group":{"_id":"$customer_id", tmp1:{"$max":"$created_at"}}},
{"$lookup":{from:"notification",
localField:"_id",
foreignField:"customer_id",
as:"notifications"}},
{"$project":{customer_id:"$_id",
_id:0,
latest_letter:"$tmp1",
latest_notification: {"$max":"$notifications.created_at"}}},
{"$addFields":{"latest":
{"$cond":[{"$gt":["$latest_letter", "$latest_notification"]},
"$latest_letter",
"$latest_notification"]}}},
{"$sort":{latest:-1}}
], {cursor:{batchSize:100}})
The output of the above aggregation pipeline is a list of customers in sorted order of created_at field from either Letter or Notification. Example output documents:
{
"customer_id": 0,
"latest_letter": ISODate("2017-12-19T07:00:08.818Z"),
"latest_notification": ISODate("2018-01-26T13:43:56.353Z"),
"latest": ISODate("2018-01-26T13:43:56.353Z")
},
{
"customer_id": 4,
"latest_letter": ISODate("2018-01-04T18:55:26.264Z"),
"latest_notification": ISODate("2018-01-25T02:05:19.035Z"),
"latest": ISODate("2018-01-25T02:05:19.035Z")
}, ...
What I want to know is how many active users were there per month
To achieve this, you can just replace the last stage ($sort) of the above aggregation pipeline with $group. For example:
db.letter.aggregate([
{"$group":{"_id":"$customer_id", tmp1:{$max:"$created_at"}}},
{"$lookup":{from:"notification",
localField:"_id",
foreignField:"customer_id",
as:"notifications"}},
{"$project":{customer_id:"$_id",
_id:0,
latest_letter:"$tmp1",
latest_notification: {"$max":"$notifications.created_at"}}},
{"$addFields":{"latest":
{"$cond":[{"$gt":["$latest_letter", "$latest_notification"]},
"$latest_letter",
"$latest_notification"]}}},
{"$group":{_id:{month:{"$month": "$latest"},
year:{"$year": "$latest"}},
active_users: {"$sum": "$customer_id"}
}
}
],{cursor:{batchSize:10}})
Where the example output would be as below:
{
"_id": {
"month": 10,
"year": 2017
},
"active_users": 9
},
{
"_id": {
"month": 1,
"year": 2018
},
"active_users": 18
},

How to add sorting for field object of graphql type which refers to different graphql type?

I am using Neo4j dB and using pattern comprehension to return the values. I have 2 types Person and Friend:
(p:Person)-[:FRIEND_WITH]->(f:Friend)
Type Person{
id: String
name: String
friends: [Friend]
}
Type Friend{
id: String
name: String
}
type Query {
persons( limit:Int = 10): [Person]
friends( limit:Int = 10): [Friend]
}
What i want to do is to pull the array list of field friends (present in Person Type) in ascending order when the "persons" query executes. For e.g.
{
"data": {
"persons": {
"id": "1",
"name": "Timothy",
"friends": [
{
"id": "c3ef473",
"name": "Adam",
},
{
"id": "ef4e373",
"name": "Bryan",
},
(
"id": "e373ln45",
"name": "Craig",
},
How should I do it ? I researched regarding the sorting, but I did not find anything specific on the array object's sorting when we are using pattern comprehension in neo4j. Any suggestions would be really helpful !
I used the sortBy function of lodash to return the result into an ascending order.
And here is the graphql resolver query:
persons(_, params) {
let query = `MATCH (p:Person)
RETURN p{
.id,
.name,
friends: [(p)-[:FRIEND_WITH]->(f:Friend)) | f{.*}]
}
LIMIT $limit;`;
return dbSession().run(query, params)
.then(result => {
return result.records.map(record => {
let item = record.get("p");
item.friends = sortBy(item.friends, [function(i) {
return i.name;
}]);
return item;
})
})
}

graphql-ruby: Int isn't a defined input type (on $first)

I’ve got a question I can’t seemingly resolve on my own.
Together with basic Query, Mutation and so on types I’ve made the following type definition:
module Types
UserType = GraphQL::ObjectType.define do
name 'User'
description 'A user'
implements GraphQL::Relay::Node.interface
global_id_field :id
field :email, !types.String, 'Email address'
connection :docs, DocType.connection_type, 'Available docs'
end
end
And I then try to query it with:
query FileListQuery(
$after: String
$first: Int
) {
viewer {
currentUser {
docs(first: $first, after: $after) {
edges {
node {
id
name
__typename
}
cursor
}
pageInfo {
endCursor
hasNextPage
hasPreviousPage
startCursor
}
}
id
}
id
}
}
And I pass the following as query variables:
{
"first": 1,
"after": null
}
The problem is it bails out with the following:
{
"errors": [
{
"message": "Int isn't a defined input type (on $first)",
"locations": [
{
"line": 3,
"column": 3
}
],
"fields": [
"query FileListQuery"
]
}
]
}
I honestly have no clue why it complains about the Int type…
If I get rid of the problematic $first query variable in the request, it works fine.
This:
query FileListQuery(
$after: String
) {
viewer {
currentUser {
docs(first: 10, after: $after) {
edges {
node {
id
name
__typename
}
cursor
}
pageInfo {
endCursor
hasNextPage
hasPreviousPage
startCursor
}
}
id
}
id
}
}
Produces this:
{
"data": {
"viewer": {
"currentUser": {
"docs": {
"edges": [
{
"node": {
"id": "1",
"name": "First Doc",
"__typename": "Doc"
},
"cursor": "MQ=="
}
],
"pageInfo": {
"endCursor": "MQ==",
"hasNextPage": false,
"hasPreviousPage": false,
"startCursor": "MQ=="
}
},
"id": "1"
},
"id": "VIEWER"
}
}
}
Any hints, ideas on how to fix this? I use the graphql gem v1.6.3.
Currently, there seems to be a bug in graphql-ruby that prevents types not explicitly used in a schema from being propagated. Check out this issue on GitHub: https://github.com/rmosolgo/graphql-ruby/issues/788#issuecomment-308996229
To fix the error one has to include an Int field somewhere in the schema. Turns out I haven't had one. Yikes.
This fixed it for me:
# Make sure Int is included in the schema:
field :testInt, types.Int

Resources