Closed freiksenet closed 4 years ago
@stefanprobst I understand your point, but I think if the goal is to do less inferred schema and more declared schema, Markdown frontmatter should be in scope. Gatsby with Markdown files is a popular combo. I
Having different types/schema in Markdown would be easier if gatsby-transformer-remark
didn't make the surface area of forking so big. There's a lot going on in the arrow function that does the node type assignment.
Let's close out this conversation. I'm likely in a minority of those that want different content types in Markdown. Thanks a bunch for the sample code, that's the direction I'll take for now.
Regarding runQuery
and its query
parameter...I have filter
working correctly but I can't get an example of sort
working. The tests that use sort seem to use runQuery
from a template string. When I try sort: { order: 'DESC', fields: ['frontmatter___date'] }
(or with 'desc' as the string) the resolve bails out.
@pauleveritt Does it work with an Array on order
?
@stefanprobst Alas, no. This:
sort: { order: ['DESC'], fields: ['frontmatter___date'] }
...leads to:
error gatsby-node.js returned an error
TypeError: Cannot read property 'resolve' of undefined
- prepare-nodes.js:34 awaitSiftField
[gatsby-theme-bulmaio]/[gatsby]/dist/redux/prepare-nodes.js:34:13
- prepare-nodes.js:52
[gatsby-theme-bulmaio]/[gatsby]/dist/redux/prepare-nodes.js:52:69
- Array.map
- prepare-nodes.js:52 resolveRecursive
[gatsby-theme-bulmaio]/[gatsby]/dist/redux/prepare-nodes.js:52:44
...
@pauleveritt unfortunately I was not able to reproduce with this basic setup -- would you be able to provide a small repro for this issue to look into? This would be very helpful, thanks!
@stefanprobst Found the cause: it's 2.8.x -- want me to file a new ticket?
Imagine your gatsby-blog
for YAML repo. Add this to gatsby-node.js
:
exports.createResolvers = ({ createResolvers, schema }) => {
createResolvers({
Query: {
allResourcesByType: {
type: ['MarkdownRemark'],
args: {
resourceType: 'String'
},
resolve(source, args, context, info) {
return context.nodeModel.runQuery({
query: {
filter: {
frontmatter: {}
},
sort: { fields: ["frontmatter___title"], order: ["ASC"] },
},
type: `MarkdownRemark`
})
}
}
}
})
}
In gatsby 2.7.6 you can query for allResourcesByType
in the explorer. In 2.8.0-2.8.2 you get a resolve error.
@stefanprobst I'm trying to convert tags that I specify as an array of strings in the frontmatter of my posts into a type Tag
with fields title
and slug
where title
is just the string I wrote and slug = _.kebabCase(title)
. I threw together this snippet
exports.sourceNodes = ({ actions, schema }) => {
actions.createTypes([
`type MarkdownRemark implements Node {
frontmatter: MarkdownRemarkFrontmatter
}`,
`type Tag { title: String!, slug: String! }`,
schema.buildObjectType({
name: `MarkdownRemarkFrontmatter`,
fields: {
tags: {
type: `[Tag!]`,
resolve(source) {
if (!source.tags) return null
return source.tags.map(tag => ({
title: tag,
slug: kebabCase(tag),
}))
},
},
},
}),
])
}
which works for adding the Tag
type to MarkdownRemarkFrontmatter.tags
. But of course, when I try to group all tags with
tags: allMarkdownRemark {
group(field: frontmatter___tags) {
title: fieldValue
count: totalCount
}
}
I get only the string I wrote into the frontmatter. I tried writing a resolver for allTags
like so
exports.createResolvers = ({ createResolvers }) => {
const resolvers = {
Query: {
allTags: {
type: [`Tag`],
resolve(source, args, context) {
return context.nodeModel.getAllNodes({
type: `MarkdownRemarkFrontmatterTags`,
})
},
},
},
}
createResolvers(resolvers)
}
but can't get it to work. Any advice?
@janosh
frontmatter.tags
from [String]
to [Tag]
, the field
argument of group
would also have to be adjusted to either frontmatter___tags___title
or frontmatter___tags___slug
. UNFORTUNATELY this does not yet work correctly, because we don't call field resolvers for group
fields, only for filter
and sort
fields (see #11368).
{
allMarkdownRemark(filter: {frontmatter: {tags: {elemMatch: {title: {ne: null}}}}}) {
group(field: frontmatter___tags___title) {
fieldValue
totalCount
nodes {
frontmatter {
title
}
}
}
}
}
Fixing this is on my todo list -- i'll get on it asap
* The issue with your `createResolvers` snippet is this: `getAllNodes` will retrieve nodes by type, where "node" means objects with a unique ID created by source or transformer plugins (with the `createNode` action). So you would have to retrieve `MarkdownRemark` nodes, and then further manipulate the results in the resolver.
* Depending on your usecase, you might not even need to use `createTypes`, but could simply add a custom root query field to group posts by tag, and include a slug field:
```js
// gatsby-node.js
const { kebabCase } = require(`lodash`)
exports.createResolvers = ({ createResolvers }) => {
createResolvers({
Query: {
allMarkdownRemarkGroupedByTag: {
type: [
`type MarkdownRemarkGroup {
nodes: [MarkdownRemark]
count: Int
tag: String
slug: String
}`,
],
resolve(source, args, context, info) {
const allMarkdownRemarkNodes = context.nodeModel.getAllNodes({
type: `MarkdownRemark`,
})
const groupedByTag = allMarkdownRemarkNodes.reduce((acc, node) => {
const { tags } = node.frontmatter
tags.forEach(tag => {
acc[tag] = (acc[tag] || []).concat(node)
})
return acc
}, {})
return Object.entries(groupedByTag).map(([tag, nodes]) => {
return {
nodes,
count: nodes.length,
tag,
slug: kebabCase(tag),
}
})
},
},
},
})
}
@stefanprobst Thanks for the quick reply!
So you would have to retrieve MarkdownRemark nodes, and then further manipulate the results in the resolver.
I thought about doing that but suspected that I was overlooking something and hence approaching this from the wrong angle. Thanks for clearing this up and for planning to add support for field resolvers for group
fields!
Maybe another solution would be to let Tag
implement Node
such that Gatsby creates an allTag
query automatically.
type Tag implements Node { title: String!, slug: String! }
Then all I would have to do is to actually create nodes of type Tag
every time I come across a new one in the call to schema.buildObjectType
for MarkdownRemarkFrontmatter
. Is it possible to apply side effects like this from within resolvers? I.e. in this case check if Tag
with title MyTag
exists and if not createNode({type: `Tag`, title: `MyTag, slug: `my-tag` })
.
@pauleveritt sorry for the late reply!
In your example, sorting should work with this:
exports.createResolvers = ({ createResolvers }) => {
createResolvers({
Query: {
allResourcesByType: {
type: ["MarkdownRemark"],
args: {
resourceType: "String",
},
resolve(source, args, context, info) {
return context.nodeModel.runQuery({
query: {
sort: { fields: ["frontmatter.post_title"], order: ["DESC"] },
},
type: "MarkdownRemark",
})
},
},
},
})
}
Note that both sort fields
and order
are arrays, and fields
uses the dot-notation instead of triple underscore to separate fields. (We have to document this! EDIT: #14681)
There are two reasons why this is different in runQuery
than it is when using a query string (i.e. in the Graph_i_ql explorer):
DESC
). we currently don't do that with runQuery
(we probably should)fields
and order
are GraphQLEnum
fields. In Graph_i_ql you would use the enum keys, while runQuery
needs the internal enum values. E.g. the fields
enum separates fields by triple underscore because graphql does not allow dots, but internally this translates to fields in dot notation.@janosh grouping on resolved fields should now work with gatsby@2.8.7
@stefanprobst Wow, that was fast! Thanks for the update.
Is there a way to get both the title and slug of a tag in a single grouping? I can only figure out how to group by either and then only have access to one of them.
{
tags: allMarkdownRemark {
group(field: frontmatter___tags___(slug|title)) {
(slug|title): fieldValue
count: totalCount
}
}
}
If I try to group by frontmatter___tags
instead,
{
tags: allMarkdownRemark {
group(field: frontmatter___tags) {
tag: fieldValue
count: totalCount
}
}
}
I get only a single result
{
"data": {
"tags": {
"group": [
{
"tag": "[object Object]",
"count": 52
}
]
}
}
}
@janosh you can only group by one field (hierarchical sub-groupings are not possible). if you need the values of both title
and slug
, you can group by one, and get the value of the other from the results (maybe I'm misunderstanding?):
{
allMarkdownRemark {
group(field: frontmatter___tags___title) {
fieldValue
totalCount
nodes {
frontmatter {
tags {
slug
title
}
}
}
}
}
}
@stefanprobst That doesn't fit my use case but it's only a minor inconvenience. Thanks for all the awesome work you're putting into schema customization!
I've been having an issue since 2.2.0 which seems to be OS-related. I would be grateful to get another Windows 10 user's eyes on it for a test so I can either confirm or rule it out: https://github.com/escaladesports/gatsby-plugin-netlify-cms-paths/issues/10
Thanks!
@laradevitt I don't currently have access to a windows machine, and I'm also not familiar with gatsby-plugin-netlify-cms-paths
-- but does it work if you replace the path on frontmatter.image
to a relative path, i.e. ../static/media/gatsby-astronaut.png
?
@stefanprobst - Thanks for your reply! Yes, it does, but the point of the plugin is that you should not have to use relative paths:
A gatsby plugin to change file paths in your markdown files to Gatsby-friendly paths when using Netlify CMS to edit them.
I've created a pull request with a fix that makes the returned relative path platform agnostic.
I still don't know why it broke with 2.2.0. 🤷♀
@laradevitt ah, sorry, should have checked the readme.
I still don't know why it broke with 2.2.0.
This is very probably a regression in Gatsby, as with 2.2.0 type inference changed a bit -- but normalizing paths in gatsby-plugin-netlify-cms-paths
definitely seems more correct :+1:
@stefanprobst
I just created a new ticket related to schema customization. If it is better for me to close that and put it in this umbrella thread just let me know.
https://github.com/gatsbyjs/gatsby/issues/16099
Thanks!
Arguments in a graphQL query don't work when using schema customization.
After watching the learn with Jason stream on advanced GraphQL(broken up in multiple parts due to technical difficulties) I incorporated a lot of what was talked about into my theme. However using graphQL arguments (like filter and sort) that rely on values from the customized schema doesn't work.
run this branch of my theme locally. Fire up /___graphql and query allBlogPosts. Now try to query allBlogPosts again with a sort by descending date. RESULT: no change in order (ASC, or DESC)
Query for all posts and show their titles.
query MyQuery {
allBlogPost {
nodes {
title
}
}
}
RESULT: works
now query for all posts that are have a tag with slug "lorem-ipsum"
query MyQuery {
allBlogPost(filter: {tags: {elemMatch: {slug: {eq: "lorem-ipsum"}}}}) {
nodes {
title
}
}
}
RESULT: empty nodes array.
Try to query a single BlogPost (picks first one by default if no arguments are given).
Now try to query blogPost(slug: {eq: "herp-derpsum"})
.
This works, I think because I added the slug to the MdxBlogPost node.
https://github.com/NickyMeuleman/gatsby-theme-nicky-blog/blob/d29c966e639f4733caf9ee43e9f5755df42db71d/theme/gatsby-node.js#L209-L210
It seems like the graphql arguments use data from the MdxBlogPost node rather than the eventual result after it runs its resolvers. Is my suspicion close?
System: OS: Linux 4.19 Ubuntu 18.04.2 LTS (Bionic Beaver) CPU: (4) x64 Intel(R) Core(TM) i5-2500K CPU @ 3.30GHz Shell: 4.4.19 - /bin/bash Binaries: Node: 12.4.0 - /tmp/yarn--1565124765846-0.45931526383359134/node Yarn: 1.16.0 - /tmp/yarn--1565124765846-0.45931526383359134/yarn npm: 6.9.0 - ~/.nvm/versions/node/v12.4.0/bin/npm Languages: Python: 2.7.15+ - /usr/bin/python
Relevant parts of code are in gatsby-node.js
in the theme directory.
specifically the createSchemaCustomization
and onCreateNode
lifecycles.
I tried to heavily comment to show my though process.
@NickyMeuleman sorry haven't had time yet to look closely but it seems you are missing a link extension on the BlogPost interface.
You define your MdxBlogPost.tags
field to link by name:
tags: {
type: "[Tag]",
extensions: {
link: { by: "name" },
},
},
in the typedefs for the BlogPost interface you have:
interface BlogPost @nodeInterface {
tags: [Tag]
}
Does it work with
interface BlogPost @nodeInterface {
tags: [Tag] @link(by: "name")
}
Related: #16466
After adding your suggestion, querying allBlogPost
with a filter
based on a tag did work! 🎉 Thanks!
n the future, will I be able to remove the @link
from the interface, since Tags might be linked differently per type that implements the BlogPost interface? (same question for Author, filtering there doesn't work, even with the @link
moved up to the interface level.
@NickyMeuleman
thanks for experimenting!
after looking at this i think it's something we need to fix in core: both issues have to with the fact that we use the interfaces's resolver when manually preparing nodes so we can run a filter on them. instead, we should run the resolver of the type we get from the interface's resolveType
, which is also what happens when graphql processes the selection set.
After https://github.com/gatsbyjs/gatsby/pull/17284 got merged I tried removing the logic in my theme to work around this.
Used Gatsby version: 2.15.14
It doesn't appear to be working.
https://github.com/NickyMeuleman/gatsby-theme-nicky-blog/blob/bbc782332e6938daaa2fca1b25d6df7e78f19c6c/theme/gatsby-node.js#L278-L282
When you comment out the lines linked above, filtering in GraphQL on these fields is no longer possible.
LekoArts suggested I ask this question more publicly than in the discord, since it might be a good topic for documentation.
How do I describe Many to Many relationships with createSchemaCustomization?
The use cases I have are the following:
@Everspace Try this for 1. add to gatsby-node.js
. I've not tried this but I am doing something similar linking from a custom type (using the node interface) to an image, and it works. It does rely on the link extension so you will need to use IDs assigned by Gatsby. I guess you could map whatever IDs you are using in your datasource to Gatsby IDs during createPages
?
exports.createSchemaCustomization = ({ actions }) => {
const { createTypes } = actions
const typeDefs = [
schema.buildObjectType({
name: 'Store',
fields: {
products: {
type: "[Product]",
extensions: {
link: {},
},
},
},
interfaces: ['Node'],
}),
schema.buildObjectType({
name: 'Product',
fields: {
stores: {
type: "[Store]",
extensions: {
link: {},
},
},
},
interfaces: ['Node'],
}),
]
createTypes(typeDefs)
}
@NickyMeuleman Do you mean you don't get results or that you don't see fields in input object?
When I comment out the date
key in that link, sorting by date no longer has any effect.
query MyQuery {
allBlogPost(sort: {fields: date, order: DESC}) {
nodes {
date(fromNow: true)
}
}
}
has the same (non-empty) result as the same query with the order set to ASC
.
Was hoping that wouldn't be necessary since there is a custom date resolver
Are there any examples out there for how to handle schema customization for an optional image coming from Contentful?
Been a while since Schema Customisation has been released and things are stable. I think it's time to close this issue 🙂
Incredible work @freiksenet and @stefanoverna ❤️
Folks, if you're having trouble related to Schema Customisation, let's open independent issues. Thanks!
This is a meta issue for all the issues with 2.2.0, that were introduced by the schema refactoring.
What?
See the blog post for the details about why we did the refactoring and what's it all about.
See the release blog post for release notes and final updates.
How?
Install latest version of Gatsby and try running your site. Hopefully it will all just work. If you want, you could also try the two new APIs (
createTypes
andcreateResolvers
).Changelog
gatsby@2.5.0
gatsby@2.5.0-rc.1
gatsby@2.4.0-alpha.2
gatsby@2.4.0-alpha.1
gatsby@2.2.0
gatsby@2.2.0-rc.2
gatsby@2.2.0-rc.1
gatsby@2.2.0-alpha.6
gatsby@2.2.0-alpha.5
gatsby@2.2.0-alpha.4
gatsby@2.2.0-alpha.3
gatsby@2.2.0-alpha.2
graphql-compose@5.11.0
FilterInput
types are now not prefixed by output type, reducing proliferation of typesgatsby@2.2.0-alpha.1
@dontInfer(noDefaultResolvers: false)
actually workscreateResolvers
resolversinfo.originalResolver
is available even if there is no resolver on original fieldgatsby@2.2.0-alpha.0
gatsby@2.1.20-alpha.0