How can i use logical operators with strapi's graphql filtering - graphql

I'm passing graphql variables like this:
variables: {
where: {
league: {
name_contains: "some league name"
},
teams: {
name_contains: "some teams name"
}
}
}
I want either the league's name or teams name or both to exist.
but my current configuration is set to have both league's name and team's name to exist
the query looks like this:
const SEARCH_QUERY = gql`
query SEARCH_QUERY($where: JSON) {
games(where: $where) {
id
teams {
id
name
}
league {
id
name
}
}
}

I guess that you are looking some thing like in the howtographql documentation, I can't see on the moment some similar solution in the strapi documentation but I have some another workaround solution...
teams.name && league.name
You need just these arguments
where:{teams: {name_contains: "some league name"}, league: {name_contains: "some league name"}}
teams.name || league.name
In this case you need merge separate queries for each name parent like this:
where:{teams: {name_contains: "some league name"}}
where:{league: {name_contains: "some league name"}}
Body transform function:
const gameMap = [...teamsGames, ...leagueGames].reduce((games, game) => {
games[game.id] = games[game.id] ? games[game.id] : { id: game.id, teams: [], league: [] };
games[game.id].teams = [...games[game.id].teams, ...game.teams];
games[game.id].league = [...games[game.id].league, ...game.league];
return games;
}, {});
//flat to array
Object.keys(gameMap).map(key => gameMap[key]);

Related

GraphQL query how to pass variables

Hi all I have a query where I am trying to get messages from a user with a specific uuid or a role that matches the users role. I am unsure of how to properly use the _ilike or % in this instance. I've tried a myriad of combinations yet the role messages never get returned. My query as it sits now and the hook used in the component are below.
I appreciate any feedback.
Here is my query
query getUserMessages($userId: String!) {
messageReceivers(
where: { _or: [{ userId: { _eq: $userId } }, { message: { roles: { _ilike: "%" } } }] }
) {
messageId
userId
message {
id
audioLink
body
videoLink
user {
firstName
lastName
photo
title
specialty
profession
location
}
}
}
}
Using the lazyquery hook in component
const [getUserMessages, { error, called, loading, data }] = useGetUserMessagesLazyQuery()
const userRole = `%${user.role}%`
useEffect(() => {
getUserMessages({
variables: { userId: user?.id, message: { roles: { _ilike: userRole } } },
})
}, [user])
You are incorrectly passing userRole to the query. To fix it, apply userId's pattern to userRole.
In the query definition, add $userRole in the operation signature (You are currently hardcoding _ilike to % in the query, but you want set it dynamically as $userRole).
In the calling function, send the variables correctly variables: { userId: user?.id, userRole: userRole}.
The GraphQL Variable docs neatly describe how this fits together.
Thanks #fedonev! Though I didn't see your solution you were absolutely correct. I was able to work it out a little differently and I hope this helps someone who's run into the same issue.
By creating the variable $role in the query I was able to use the same syntax as was being used by the userId variable. If anyone has this issue please feel free to comment I will happily help if I can.
Query
query getUserMessages($userId: String!, $role: String = "%") {
messages(
where: {
_or: [{ roles: { _ilike: $role } }, { messageReceivers: { userId: { _eq: $userId } } }]
}
order_by: { createdAt: desc }
) {
createdAt
id
body
audioLink
videoLink
roles
}
Call from in component
useEffect(() => {
getUserMessages({
variables: { userId: user?.id, role: user?.role },
})
}, [user])

Dynamically create pages with Gatsby based on many Contentful references

I am currently using Gatsby's collection routes API to create pages for a simple blog with data coming from Contentful.
For example, creating a page for each blogpost category :
-- src/pages/categories/{contentfulBlogPost.category}.js
export const query = graphql`
query categoriesQuery($category: String = "") {
allContentfulBlogPost(filter: { category: { eq: $category } }) {
edges {
node {
title
category
description {
description
}
...
}
}
}
}
...
[React component mapping all blogposts from each category in a list]
...
This is working fine.
But now I would like to have multiple categories per blogpost, so I switched to Contentful's references, many content-type, which allows to have multiple entries for a field :
Now the result of my graphQL query on field category2 is an array of different categories for each blogpost :
Query :
query categoriesQuery {
allContentfulBlogPost {
edges {
node {
category2 {
id
name
slug
}
}
}
}
}
Output :
{
"data": {
"allContentfulBlogPost": {
"edges": [
{
"node": {
"category2": [
{
"id": "75b89e48-a8c9-54fd-9742-cdf70c416b0e",
"name": "Test",
"slug": "test"
},
{
"id": "568r9e48-t1i8-sx4t8-9742-cdf70c4ed789vtu",
"name": "Test2",
"slug": "test-2"
}
]
}
},
{
"node": {
"category2": [
{
"id": "75b89e48-a8c9-54fd-9742-cdf70c416b0e",
"name": "Test",
"slug": "test"
}
]
}
},
...
Now that categories are inside an array, I don't know how to :
write a query variable to filter categories names ;
use the slug field as a route to dynamically create the page.
For blogposts authors I was doing :
query authorsQuery($author__slug: String = "") {
allContentfulBlogPost(filter: { author: { slug: { eq: $author__slug } } }) {
edges {
node {
id
author {
slug
name
}
...
}
...
}
And creating pages with src/pages/authors/{contentfulBlogPost.author__slug}.js
I guess I'll have to use the createPages API instead.
You can achieve the result using the Filesystem API, something like this may work:
src/pages/category/{contentfulBlogPost.category2__name}.js
In this case, it seems that this approach may lead to some caveats, since you may potentially create duplicated pages with the same URL (slug) because the posts can contain multiple and repeated categories.
However, I think it's more succinct to use the createPages API as you said, keeping in mind that you will need to treat the categories to avoid duplicities because they are in a one-to-many relationship.
exports.createPages = async ({ graphql, actions }) => {
const { createPage } = actions
const result = await graphql(`
query {
allContentfulBlogPost {
edges {
node {
category2 {
id
name
slug
}
}
}
}
}
`)
let categories= { slugs: [], names: [] };
result.data.allContentfulBlogPost.edges.map(({node}))=> {
let { name, slug } = node.category2;
// make some checks if needed here
categories.slugs.push(slug);
categories.names.push(name);
return new Set(categories.slugs) && new Set(categories.names);
});
categories.slugs.forEach((category, index) => {
let name = categories.names[index];
createPage({
path: `category/${category}`,
component: path.resolve(`./src/templates/your-category-template.js`),
context: {
name
}
});
});
}
The code's quite self-explanatory. Basically you are defining an empty object (categories) that contains two arrays, slugs and names:
let categories= { slugs: [], names: [] };
After that, you only need to loop through the result of the query (result) and push the field values (name, slug, and others if needed) to the previous array, making the needed checks if you want (to avoid pushing empty values, or that matches some regular expression, etc) and return a new Set to remove the duplicates.
Then, you only need to loop through the slugs to create pages using createPage API and pass the needed data via context:
context: {
name
}
Because of redundancy, this is the same than doing:
context: {
name: name
}
So, in your template, you will get the name in pageContext props. Replace it with the slug if needed, depending on your situation and your use case, the approach is exactly the same.

Deep merging fragments

I'm using a GraphQL API (which I do not own) to access data. I make extensive use of fragments and one problem I've run into more than once is that fragments don't seem to deep-merge. Consider the following query:
fragment BasicInfo on Person {
id
name
address {
city
country
}
}
query ProfileCard($id: ID) {
personById(id: $id) {
...BasicInfo
id
age
address {
streetName
streetNumber
}
}
}
Here we run a basic query to get some information from a profile card including the person's age and some of their address (street name and number). Another component used by the profile card also wants some info which can be found in the BasicInfo fragment. This includes their name as well as their city and country.
Running this query returns an object that contains the following fields: id, name, age, address.streetName and address.streetNumber.
address.city and address.country are both missing - it appears that the query did not deep-merge the fragment in and only inserted it at a shallow level.
Is it possible to force my fragments to deep-merge? Is this even the expected behavior? Do I have to get in contact with the API owners to correct this?
I've had trouble finding documentation that says it should be one way or the other.
I have just run into a similar issue using #apollo/client, and funny enough it's also related to an address model. My second fragment seems to be completely disregarded and not merged. I wrote up a foobar code sample below:
type Request = {
id: string
stops: Array<Stop>
}
type Stop = {
id: string;
address: Address;
}
type Address = {
id: string;
address1: string;
name: string;
}
const ROOT_FRAGMENT = gql`
fragment foo_Request on Request {
id
stops {
...bar_Stop
...qux_Stop
}
${STOP_FRAGMENT_1}
${STOP_FRAGMENT_2}
}
`;
const STOP_FRAGMENT_1 = gql`
fragment bar_Stop on Stop {
id
address {
id
address1
}
}
}
`;
const STOP_FRAGMENT_2 = gql`
fragment qux_Stop on Stop {
id
address {
id
name
}
}
}
`;
/*
expected:
{
id: "request-1"
stops: [
{
id: "stop-1",
address: {
id: "address-1",
address1: "123 my way",
name: "Home",
},
},
],
}
actual:
{
id: "request-1"
stops: [
{
id: "stop-1",
address: {
id: "address-1",
address1: "123 my way",
},
},
],
}
*/
Try using alias instead. Something like
fragment BasicInfo on Person {
id
name
cityCountryAddress: address {
city
country
}
}

Strapi GraphQL search by multiple attributes

I've got a very simple Nuxt app with Strapi GraphQL backend that I'm trying to use and learn more about GraphQL in the process.
One of my last features is to implement a search feature where a user enters a search query, and Strapi/GraphQL performs that search based on attributes such as image name and tag names that are associated with that image. I've been reading the Strapi documentation and there's a segment about performing a search.
So in my schema.graphql, I've added this line:
type Query {
...other generated queries
searchImages(searchQuery: String): [Image
}
Then in the /api/image/config/schema.graphql.js file, I've added this:
module.exports = {
query: `
searchImages(searchQuery: String): [Image]
`,
resolver: {
Query: {
searchImages: {
resolverOf: 'Image.find',
async resolver(_, { searchQuery }) {
if (searchQuery) {
const params = {
name_contains: searchQuery,
// tags_contains: searchQuery,
// location_contains: searchQuery,
}
const searchResults = await strapi.services.image.search(params);
console.log('searchResults: ', searchResults);
return searchResults;
}
}
}
},
},
};
At this point I'm just trying to return results in the GraphQL playground, however when I run something simple in the Playground like:
query($searchQuery: String!) {
searchImages(searchQuery:$searchQuery) {
id
name
}
}
I get the error: "TypeError: Cannot read property 'split' of undefined".
Any ideas what might be going on here?
UPDATE:
For now, I'm using deep filtering instead of the search like so:
query($searchQuery: String) {
images(
where: {
tags: { title_contains: $searchQuery }
name_contains: $searchQuery
}
) {
id
name
slug
src {
url
formats
}
}
}
This is not ideal because it's not an OR/WHERE operator, meaning it's not searching by tag title or image name. It seems to only hit the first where. Ideally I would like to use Strapi's search service.
I actually ran into this problem not to recently and took a different solution.
the where condition can be combined with using either _and or _or. as seen below.
_or
articles(where: {
_or: [
{ content_contains: $dataContains },
{ description_contains: $dataContains }
]})
_and
(where: {
_and: [
{slug_contains: $categoriesContains}
]})
Additionally, these operators can be combined given that where in this instance is an object.
For your solution I would presume you want an or condition in your where filter predicate like below
images(where: {
_or: [
{ title_contains: $searchQuery },
{ name_contains: $searchQuery }
]})
Lastly, you can perform a query that filters by a predicate by creating an event schema and adding the #search directive as seen here

How should I query and match data from the same response in GraphQL with Apollo Client and Link Rest?

I have the following query:
const getPage = gql`
query Page($path: String!) {
page(path: $path) #rest(type: "Page", path: "{args.path}") {
blocks #type(name: Block) {
name
posts #type(name: Post) {
body
author
}
}
authors #type(name: Author) {
name
}
}
}
In blocks.posts.author there's only an AuthorId. The authors object is containing all the available authors.
I'd like to replace/match the AuthorId with it's corresponding object. Is it possible to do this within one query?
I also wouldn't mind to have a separate query for Author only (fetch will be cached, no new request would be made), but I still don't know how would I match it through 2 queries.
Example API response
{
blocks: [
{
posts: [
{
id: 1,
title: 'My post',
author: 12,
}
]
}
],
authors: [
{
id: 12,
name: 'John Doe'
}
]
}
What I want with 1 query that author inside a post becomes the full author object.
Great question. With GraphQL, you have the power to expand any field and select the exact subfields you want from it, so if you were using GraphQL on your backend as well this would be a non-issue. There are some workarounds you can do here:
If all of the Author objects are in your Apollo cache and you have access to each Author's id, you could use ApolloClient.readFragment to access other properties, like this:
const authorId = ...; // the id of the author
const authorInfo = client.readFragment({
id: authorId,
fragment: gql`
fragment AuthorInfo on Author {
id
name
# anything else you want here
}
`,
});
Although it's worth noting that with your original query in the question, if you have all of the Author objects as a property of the query, you could just use Javascript operations to go from Author id to object.
const authorId = ...; // the id of the author
data.page.authors.find(author => author.id === authorId);
The following should work.
First, capture the author id as a variable using the #export directive. Then add a new field with some name other than author and decorate it with the #rest, using the exported variable inside the path.
So the query would look something like this:
query Page($path: String!) {
page(path: $path) #rest(type: "Page", path: "{args.path}") {
blocks #type(name: Block) {
name
posts #type(name: Post) {
body
author #export(as: "authorId")
authorFull #rest(
path: '/authors/{exportVariables.authorId}'
type: 'Author'
) {
name
}
}
}
authors #type(name: Author) {
name
}
}
}
You can use the fieldNameNormalizer option to rename the author property in the response to a field with a different name (for example, authorId). Ideally, that should still work with the above so you can avoid having a weird field name like authorFull but apollo-link-rest is a bit wonky so no promises.

Resources