Unable to filter custom data in siteMetaData in Gatsby using GraphQL in GraphiQL - graphql

I've created a basic Gatsby site with the default starter. I'm now trying to add some custom data (the people array) to gatsby-config.json like so:
module.exports = {
siteMetadata: {
title: `Gatsby Default Starter`,
description: `XXX`,
author: `#gatsbyjs`,
people : [
{ id : 1234, name : "Bill Smith", sales : 143, birthdate : "2233-03-22" },
{ id : 5678, name : "Roger Miller", sales : 281, birthdate : "2230-01-06" }
]
},
plugins: [
`gatsby-plugin-react-helmet`,
{ resolve: `gatsby-source-filesystem`,
options: { name: `images`, path: `${__dirname}/src/images`, }
},
`gatsby-transformer-sharp`,
`gatsby-plugin-sharp`,
{ resolve: `gatsby-plugin-manifest`,
options: {
name: `gatsby-starter-default`, short_name: `starter`, start_url: `/`,
background_color: `#663399`, theme_color: `#663399`, display: `minimal-ui`,
icon: `src/images/gatsby-icon.png`
}
}
]
}
Then, in GraphiQL, what I'm trying to do is a query to get a list of people, but limit it to just those with sales above 200, that's my end goal. So first, I did a basic test:
{
site {
siteMetadata {
people {
name
}
}
}
}
That works, I get all people back. Then, I tried:
{
site {
siteMetadata {
people(sales: { gt: 200 }) {
name
}
}
}
}
That gets me an error "Unknown argument sales on field people of type SiteSiteMetadata". That kinda seems to be telling me that Sift underneath Gatsby doesn't have any notion of my custom fields in its schema, which would kind of make sense to me. So, as a test, I try this:
{
site {
siteMetadata(author: { eq: "none" }) {
author
title
}
}
}
My expectation is the query runs successfully but returns an empty result set since the author element's value isn't "none". But instead, I get the same basic error but now telling me "Unknown argument author on field siteMetadata of type Site" and now I'm confused because it seems like it should know about THOSE fields even if it doesn't know about arbitrary ones I add. Then again, maybe that query won't ever work because there's only a single siteMetaData object versus trying to query an array. I'm not sure.
So then I do some research and I see some reference to 'filter', so I try this now:
{
site {
siteMetadata(filter: { eq: "none" }) {
author
title
}
}
}
That gets me "Unknown argument filter on field siteMetadata of type Site."
And now I'm kind of out of ideas.
I did find one post that seemed to possibly imply that you can't query custom data elements like this at all, but some replies seem to imply you, in fact, can (and clearly that first test worked, so the data is found, I just can't get the filtering to work). Maybe I'm using the wrong syntax, but if so then I can't seem to find what the correct syntax looks like (and what's worse is that in the Gatsby docs, the ONE example that MIGHT provide me an answer is error'ing out in the playground and I can't see the code).
It seems like such a simple thing, but I'm at a loss. Any help would be greatly appreciated. Thanks!
EDIT: After I wrote this, I tried putting the data in a separate file that get loaded with the gatsby-transformer-json plugin and tried to query that. The data gets loaded, but I still can't filter the query. I can do:
{
testData {
people {
name
sales
}
}
}
...and that works, returns my data fine. But if I try:
{
testData {
people(sales:{gt:200}) {
name
sales
}
}
}
...or...
{
testData {
people(filter:{sales:{gt:200}}) {
name
sales
}
}
}
...I get the same types of errors. So, I think that at least proves this isn't an issue of querying it from siteMetaData specifically, but I still don't know how to make it do what I want.
For anyone who wants to reproduce this, just add the file data.json in the root of the project with this content:
{
"people" : [
{ "id" : 1234, "name" : "Bill Smith", "sales" : 143, "birthdate" : "2233-03-22" },
{ "id" : 5678, "name" : "Roger Miller", "sales" : 281, "birthdate" : "2230-01-06" }
]
}
Then, add this to the plugins array in gatsby-config.json:
{
resolve: `gatsby-transformer-json`,
options: { typeName: `testData` }
},
{
resolve: `gatsby-source-filesystem`,
options: { name: `data`, path: `${__dirname}/data.json` }
}
No other changes from the initially-generated project are needed. Then, just hop into GraphiQL and try to execute the queries above.
Or, to make things easier, I've created a codesandbox instance that demonstrates this:
https://codesandbox.io/s/gatsby-graphql-querying-json-issue-47km4
EDIT2: I had the thought that maybe this is an issue with GraphiQL itself. So, I created a simple component:
import React from "react"
import { useStaticQuery, graphql } from "gatsby"
const Test = () => {
const testData = useStaticQuery(graphql`
query TestDateQuery {
testData(filter: {sales: {gte:200}}) {
people {
name
}
}
}
`)
console.log("testData", testData);
return (
<div />
)
}
export default Test
I then dropped that into my main Layout component. I get the same sort of error (about filter being an unknown argument) rather than seeing my data. If I remove the filter, I DO see ALL the data. So, again, I can't figure out why just filter isn't working, but that's what I've narrowed it down to.

Related

Apollo based server giving error code - BAD_USER_INPUT

I'm using Vendure headless e-commerce and trying to run a GraphQL mutation according to the specs - but I'm running into an error I cannot seem to decipher.
The error I'm getting is:
{
"errors": [{
"extensions": {
"code": "BAD_USER_INPUT"
},
"locations": [{
"line": 1,
"column": 28
}]
}]
}
This is in turn pointing to the $input variable.
query: "mutation AddPaymentToOrder($input: PaymentInput!) {\n ... etc ...
Usually when I've gotten this error code it means I've made a mistake in the mutation, but in those cases I get a clear message what is wrong. In this mutation I get nothing.
The Vendure docs on this mutation are quite simple: https://www.vendure.io/docs/graphql-api/shop/mutations/#addpaymenttoorder
The mutation I've written is quite simple as well for now:
export const AddPaymentToOrderQuery = gql`
mutation AddPaymentToOrder($input: PaymentInput!) {
addPaymentToOrder(input: $input) {
... on Order {
id
}
}
}
`;
The data I send looks like this:
type Variables = {
input: {
method: string;
}
};
Does anyone understand this error?
The problem was a misunderstanding - a missing input data element metadata that I had forgotten.
Input should look like:
type Variables = {
input: {
method: string;
metadata: Object;
}
};

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.

How to get Gatsby Plugin Contentful Optional Fields working?

Having an optional field on a content type in Contentful, I tried to get Gatsby-Plugin-Contentful-Optional-Fields working.
The optional field is an image, that I render with the new Gatsby-Image-Plugin. After configuring the plugin in my gatsby-config.js, I still get the following error:
There was an error in your GraphQL query:
Cannot query field "description" on type "Node".
Spent hours to solve it, with no success unfortunately. Im fairly new to developing, so for someone else, maybe just an obvious mistake. Any help is highly appreciated.
The plugin configuration in my gatsby-config.js:
{
resolve: "gatsby-plugin-contentful-optional-fields",
options: {
optionalFields: {
ContentfulNews: {
image: "Node",
},
},
},
},
My Query:
query {
allContentfulNews(sort: { fields: date, order: DESC }) {
edges {
node {
id
heading
date(formatString: "DD.MM.YYYY")
text {
childMarkdownRemark {
html
}
}
image {
gatsbyImageData(
layout: FULL_WIDTH
placeholder: BLURRED
formats: [AUTO, WEBP]
quality: 100
width: 1500
)
description
}
}
}
}
}

Gatsby's mapping between markdown files

I'm creating a multi-author site (using gatsby-plugin-mdx) and have the following file structure:
/posts
- /post-1/index.mdx
- /post-2/index.mdx
- ...
/members
- /member-a/index.mdx
- /member-b/index.mdx
- ...
In the frontmatter of the post page I have an array of authors like
authors: [Member A, Member B]
and I have the name of the author in the frontmatter of the author's markdown file.
I'd like to set the schema up so that when I query the post, I also get the details of the authors as well (name, email, etc.).
From reading this page it seems like I need to create a custom resolver... but all the examples I see have all the authors in one json file (so you have two collections, MarkdownRemark and AuthorJson... while I think for my case all my posts and members are in MarkdownRemark collection.
Thanks so much!
I end up doing something like this. Surely there's a cleaner way, but it works for me. It goes through all the Mdx and add a field called authors, which is queried, to all Mdx types.
One problem with this is that there's also authors under members, which is not ideal. A better approach is to define new types and change Mdx in the last resolver to your new post data type. Not sure how to get that to work though. At the end, I could query something like:
query MyQuery {
posts {
frontmatter {
title
subtitle
}
authors {
frontmatter {
name
email
}
}
}
}
exports.createResolvers = ({ createResolvers }) => {
const resolvers = {
Mdx: {
authors: {
type: ["Mdx"],
resolve(source, args, context, info) {
return context.nodeModel.runQuery({
query: {
filter: {
fields: {
collection: { eq: "members" }
},
frontmatter: {
memberid: { in: source.frontmatter.authors },
},
},
},
type: "Mdx",
firstOnly: false,
})
}
}
},
}
createResolvers(resolvers)
}

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

Resources