react-admin graphql data provider: how to implement GET_MANY if GraphQL API only supports GET_ONE? - graphql

How to implement a GET_MANY data provider function in react-admin's ra-data-graphql package if the GraphQL API only supports the equivalent GET_ONE?
I understand how it can be done with REST by waiting on resolving all promises of the API call, but here the return from buildQuery is supposed to be an object with a key for the GraphQL query, and a key for a function parsing the response.
So how can this support multiple GraphQL queries, one for each ID we want to fetch, before we return the result from the data provider for GET_MANY?

#kurtko sorry for late reply. Just checking this now. Found a workaround that does not require changes to GQL schema (since that is an external API I cannot change).
You need to make changes to ra-data-graphql. Basically you need to allow for multiple GQL queries and then await until all promises resolve and return a combined result. By using either query or queries you can instruct ra-data-graphql to either run a single or multiple GQL queries. I modified index.js as follows:
// GRAPHQL QUERY
if (operation === 'query') {
if (!query.queries) {
// single GraphQL query
const apolloQuery = {
...query,
fetchPolicy: 'network-only',
...getOptions(otherOptions.query, aorFetchType, resource),
};
return client
.query(apolloQuery)
.then((response) => parseResponse(response))
.catch((error) => Promise.reject(error.message));
} else {
// multiple GraphQL queries
const prs = query.variables.map((variables) => {
const { queries, ...rest } = query;
const apolloQuery = {
...rest,
query: queries,
variables,
fetchPolicy: 'network-only',
...getOptions(otherOptions.query, aorFetchType, resource),
};
return client.query(apolloQuery);
});
return Promise.all(prs)
.then((responses) => parseResponse(responses))
.catch((error) => Promise.reject(error.message));
}
}

I had the same problem and solved with schema like this: (look {ids: [ID]} in UserFilter it's the important thing)
type Query {
Post(id: ID!): Post
allPosts(page: Int, perPage: Int, sortField: String, sortOrder: String, filter: PostFilter): [Post]
_allPostsMeta(page: Int, perPage: Int, sortField: String, sortOrder: String, filter: PostFilter): ListMetadata
User(id: ID!): User
allUsers(page: Int, perPage: Int, sortField: String, sortOrder: String, filter: UserFilter): [User]
_allUsersMeta(page: Int, perPage: Int, sortField: String, sortOrder: String, filter: UserFilter): ListMetadata
}
type User {
id: ID!
name: String!
views: Int!
}
type Post {
id: ID!
title: String!
views: Int!
body: String
user_id: ID!
User: User
}
input PostFilter {
q: String
id: ID
title: String
views: Int
views_lt: Int
views_lte: Int
views_gt: Int
views_gte: Int
user_id: ID
}
input UserFilter {
q: String
ids: [ID]
title: String
views: Int
views_lt: Int
views_lte: Int
views_gt: Int
views_gte: Int
user_id: ID
}
type ListMetadata {
count: Int!
}

Related

How can I make GraphQL support int8 type in Supabase?

I'm creating a simple CRUD app to learn GraphQL and am using a Supabase postgres instance. All queries and mutations work fine except for one thing, I can't get the id field from my schemas because they are of type int8 on Supabase, and GraphQL only supports Int.
I'm getting this error when I try to get a row's id using the gql Int type in my type defs: GraphQLError: Int cannot represent non-integer value: 1
I know the solution involves creating a custom scalar type as in this example, but I'm not sure how to implement this type. Also, I cannot change this on Supabase's side, so I must find a way to handle this in gql. How can I handle this type in GraphQL?
TypeDefs:
export const typeDefs = `#graphql
type User {
id: Int!
name: String!
email: String!
age: Int!
verified: Boolean!
}
type Todo {
id: Int!
title: String!
description: String!
}
type Query {
# users queries
getAllUsers: [User]
getUser(email: String!): User
# todo queries
getAllTodos: [Todo]
getTodo(id: String!): Todo
}
type Mutation {
createUser(name: String!, email: String!, age: Int!): User
createTodo(title: String!, description: String!): Todo
}
`;
Resolvers:
import { GraphQLScalarType } from 'graphql';
import { prisma } from '../lib/db.js';
const BigInt = new GraphQLScalarType({
// how do I implement this type?
});
export const resolvers = {
BigInt,
Query: {
getAllUsers() {
return prisma.user.findMany();
},
getUser(parent, args) {
return prisma.user.findUnique({
where: {
email: args.email,
},
});
},
getAllTodos() {
return prisma.todo.findMany();
},
getTodo(parent, args) {
return prisma.todo.findUnique({
where: {
id: args.id,
},
});
},
},
// parent, arge are other arguments that get passes to resolvers automatically
Mutation: {
createUser(parent, args) {
return prisma.user.create({
data: args,
});
},
createTodo(parent, args) {
return prisma.todo.create({
data: args,
});
},
},
};
Solved this by using the graphql-type-ints package. You can just install it and then add the type you need to your schemas and resolvers. However, I don't quite understand why we need to do this. If someone could explain why Supabase uses int8 and that doesn't conform to graphql's Int I would appreciate it.

Can you apply sorting to a lists of models inside another model?

Using AWS Amplify, can we apply sorting to the messages in the Conversation model?
When fetching the conversation, it would be nice that the messages come sorted based on the generated createdAt date.
Currently these are the models used.
type Conversation #model {
id: ID!
messages: [Message!]! #hasMany
...
}
type Message #model {
id: ID!
authorId: String!
content: String!
conversation: Conversation #belongsTo
}
Ideally want to place sorting on the hasMany directive, but this is not possible.
type Conversation #model {
id: ID!
messages: [Message!]! #hasMany(sortKeys:['createdAt'])
...
}
Created a secondary index on the Message model with a sort field on createdAt.
type Message #model {
id: ID!
authorId: String! #index(name: "byAuthorId", queryField: "getMessagesByAuthorId", sortKeyFields: [ "createdAt" ])
content: String!
conversation: Conversation #belongsTo
}
Amplify created a new query to fetch the messages and apply sorting. Following example uses react-query to fetch the messages from an authorId with sorting.
export function useMessagesPerAuthorId({
id,
filter,
enabled = true,
}: {
id: string | undefined;
filter: any;
enabled?: boolean;
}) {
return useQuery(
['conversations', 'messages', id, filter],
() => fetchMessagesByAuthorId({ id: id!, filter }),
{ enabled: enabled && !!id }
);
}
async function fetchMessagesByAuthorId({ id, filter }: { id: string; filter: any }) {
const query: any = await API.graphql({
query: getMessagesByAuthorId,
variables: { authorId: id, sortDirection: 'DESC', filter },
});
const data: Message[] = query.data?.getMessagesByAuthorId.items;
return data;
}
Now we can call that hook in our view component and pass the filters.
const { isLoading, data: messages = [] } = useMessagesPerAuthorId({
id: profile?.id,
filter: {
and: [{ conversationMessagesId: { eq: conversationId } }, { deleted: { eq: false } }],
},
enabled: !!profile?.id,
});

Prisma 2 query to return records only that are associated with ALL of the provided tag IDs

I have tables Principles and Tags. And there is a many-to-many relation between them (joined implicitly).
Without using prisma.raw, how can I run the following query?
SELECT p.id, p.title, p.description, p.createdAt, p.modifiedAt
FROM principle p
WHERE EXISTS (SELECT NULL
FROM _PrincipleToTag pt
WHERE pt.B IN (${tagIds.join(',')})
AND pt.A = p.id
GROUP BY pt.A
HAVING COUNT(DISTINCT pt.B) = ${tagIds.length})
How can I update this Prisma 2 query such that the principles returned are only principles that are associated with ALL of the provided tagIds?
export const principles = ({ tagIds }) => {
const payload = {
where: {
//TODO filter based on tagIds
},
}
return db.principle.findMany(payload)
}
The docs mention contains and in and every, but I can't find examples of what I'm trying to do.
I'm using RedwoodJs, Prisma 2, Apollo, GraphQL.
Update in response to comment: here is the SDL:
input CreatePrincipleInput {
title: String!
description: String
}
input CreatePrincipleWithTagsInput {
title: String!
description: String
tagIdsJson: String
}
input CreateTagInput {
title: String!
description: String
}
# A date string, such as 2007-12-03, compliant with the `full-date` format
# outlined in section 5.6 of the RFC 3339 profile of the ISO 8601 standard for
# representation of dates and times using the Gregorian calendar.
scalar Date
# A date-time string at UTC, such as 2007-12-03T10:15:30Z, compliant with the
# `date-time` format outlined in section 5.6 of the RFC 3339 profile of the ISO
# 8601 standard for representation of dates and times using the Gregorian calendar.
scalar DateTime
type Mutation {
createPrinciple(input: CreatePrincipleInput!): Principle
createPrincipleWithTags(input: CreatePrincipleWithTagsInput!): Principle
updatePrinciple(id: Int!, input: UpdatePrincipleInput!): Principle!
deletePrinciple(id: Int!): Principle!
createTag(input: CreateTagInput!): Tag!
updateTag(id: Int!, input: UpdateTagInput!): Tag!
deleteTag(id: Int!): Tag!
}
type Principle {
id: Int!
title: String!
description: String!
tags: [Tag]
createdAt: DateTime!
modifiedAt: DateTime!
}
type Query {
redwood: Redwood
principles(searchQuery: String, tagIds: [Int]): [Principle!]!
tags: [Tag!]!
tagsByLabel(searchTerm: String): [TagCount!]!
tag(id: Int!): Tag!
}
type Redwood {
version: String
}
type Tag {
id: Int!
title: String!
principles: [Principle]
description: String
createdAt: DateTime!
modifiedAt: DateTime!
}
type TagCount {
id: Int!
title: String!
count: Int!
principles: [Principle]
description: String
createdAt: DateTime!
modifiedAt: DateTime!
}
# A time string at UTC, such as 10:15:30Z, compliant with the `full-time` format
# outlined in section 5.6 of the RFC 3339profile of the ISO 8601 standard for
# representation of dates and times using the Gregorian calendar.
scalar Time
input UpdatePrincipleInput {
title: String
description: String
}
input UpdateTagInput {
title: String
description: String
}
It doesn't look like you are using prisma 2. Prisma 2 uses models (not types) and has arrays classified like Principles[] vs [Principles]. Maybe Redwood does the conversion(Never used it).
I created your model in Prisma 2 and used the following command to get a single principle that has the two tags associated with it. Keep in mind the IDs in there are from my test dataset. Hopefully, you can modify this to your code. If not, please create a sandbox/playground with minimal code for us to test.
export const principles = async ({ searchQuery, tagIds }) => {
const payload = {
where: {
OR: [
{ title: { contains: searchQuery } },
{ description: { contains: searchQuery } },
],
userId: userIdFromSession,
},
}
if (tagIds.length) {
const whereAnd = []
tagIds.forEach((tagId) => {
whereAnd.push({
tags: { some: { id: tagId } },
})
})
payload.where.AND = whereAnd
}
const result = await db.principle.findMany(payload)
return result
}
You could try something like this
export const principles = ({ searchQuery, tagIds }) => {
const payload = {
where: {
OR: [
{ title: { contains: searchQuery } },
{ description: { contains: searchQuery } },
],
// using the `in` operator like this
tagId: { in: tagIds },
userId: userIdFromSession,
},
}
console.log('db.principle.findMany(payload)', payload)
return db.principle.findMany(payload)
}
That should do the trick!
I had to resort to using AND for something similar - hope this helps!
const tagIds = [9,6];
where: {
// ...
AND: tagIds.map(tagId => ({
tags: {
some: {
id: {
equals: tagId,
},
},
},
})),
}

graphql trouble accessing items in object

I am still trying to learn graphql and I am having trouble accessing items that are within an object in the database. In my client side code the data for id and createdAt shows up just fine it is just when I add the object that I get the error:
Expected Iterable, but did not find one for field Users.profile
I am not sure what my code is missing:
resolver:
Query: {
getUser(root, args, { userId }) {
const {id } = args;
const user = User.findOne({
id
});
return user;
}
},
schema
const User = `
type User{
id: String!
createdAt: Date
profile: [Profile]
}
type Profile {
name: String!
email: String!
}
extend type Query {
getUser(
id: String!
): User
}
How I am calling it in my client code:
const getUser = gql`
query getUser($id: String!) {
getUser(id: $id) {
id
createdAt
profile {
name
email
}
}
}
`;
This is how it looks in the MongoDB database:
user{
_id: "22222"
createdAt: 11/22/2018
profile:{
name: "Chris"
email: "chris#emample.com"
}
} `
In case it helps someone in future I had to set my objects to JSON to get it to work.
const User = `
type User{
id: String!
createdAt: Date
profile: JSON
}
extend type Query {
getUser(
id: String!
): User
}

How to pass params to child property in GraphQL

i am pretty new to GraphQL, getting to become a huge fan :)
But, something is not clear to me. I am using Prisma with and GraphQL-Yoga with Prisma bindings.
I do not know how to pass params from my graphQL server to sub properties. Don't know if this is clear, but i will show it with code, thats hopefully easier :)
These are my types
type User {
id: ID! #unique
name: String!
posts: [Post!]!
}
type Post {
id: ID! #unique
title: String!
content: String!
published: Boolean! #default(value: "false")
author: User!
}
My schema.graphql
type Query {
hello: String
posts(searchString: String): [Post]
users(searchString: String, searchPostsTitle: String): [User]
me(id: ID): User
}
and my users resolver:
import { Context } from "../../utils";
export const user = {
hello: () => "world",
users: (parent, args, ctx: Context, info) => {
return ctx.db.query.users(
{
where: {
OR: [
{
name_contains: args.searchString
},
{
posts_some: { title_contains: args.searchPostsTitle }
}
]
}
},
info
);
},
me: (parent, args, ctx: Context, info) => {
console.log("parent", parent);
console.log("args", args);
console.log("info", info);
console.log("end_________________");
return ctx.db.query.user({ where: { id: args.id } }, info);
}
};
and my posts resolver
import { Context } from "../../utils";
export const post = {
posts: (parent, args, ctx: Context, info) => {
return ctx.db.query.posts(
{
where: {
OR: [
{
title_contains: args.searchString
},
{
content_contains: args.searchString
}
]
}
},
info
);
}
};
so, now :)
I am able to do the following when i am in the GraphQL playground on my prisma service:
{
user(where: {id: "cjhrx5kaplbu50b751a3at99d"}) {
id
name
posts(first: 1, after: "cjhweuosv5nsq0b75yc18wb2v") {
id
title
content
}
}
}
but i cant do it on the server, if i do something like that.. i am getting the error:
"error": "Response not successful: Received status code 400"
this is what i am trying:
{
me(id: "cjhrx5kaplbu50b751a3at99d") {
id
name
posts(first:1) {
id
title
content
}
}
}
does somebody know how i could do that?
since i have a custom type of user, posts does not have params like the generated one. Either i am using the the generated one, or modifying it to look like this:
type User {
id: ID!
name: String!
posts(where: PostWhereInput, orderBy: PostOrderByInput, skip: Int, after: String, before: String, first: Int, last: Int): [Post!]
}
EDIT 2018 June 4th
# import Post from './generated/prisma.graphql'
type Query {
hello: String
posts(searchString: String): [Post]
users(searchString: String, where: UserWhereInput, orderBy: UserOrderByInput, skip: Int, after: String, before: String, first: Int, last: Int): [User]
me(id: ID): User
}
type Mutation {
createUser(name: String!): User
createPost(
title: String!
content: String!
published: Boolean!
userId: ID!
): Post
}
I copied the params over from prisma.graphql manually.

Resources