hotchocolate (GraphQL) filter by extended schema (stitching) - graphql

Is it possible to 'filter' (where) by an extended schema in stitching file in graphQL?
For example:
{
contractSKUCost(where: { contractSKU: { products: { productId: 1}} }) {
items {
id
contractSKU {
id
products {
productId
}
}
}
}
}
In this case, products is in a different database, that is why we need to define it in stitching file:
extend type ContractSKU {
products():[Product]
#delegate(
schema: "products",
path: "productsBySkuId(skuId: $fields:id)"
)
Essentially, what I want to do is "GET All ContractSKUCost that have ProductId = 1". But as you see, ContractSKUCost does not have direct relationship with Product, only its parent ContractSKU.
Edit:
Code above does not work; because ContractSkuCost schema technically does not know the 'product' extension yet.

Related

Contentful graphql one too many relationship

I am trying to achieve this: getArticleBySlugWithFilteredTags('tag1', 'tag2', 'tag3') using 1 query ( 1 request ) and avoid clientside filtering ( grab many and filter out with javascript ).
I have content type Article that has an entry type as list: Tag ( another custom content type ).
So there is a one too many relationship: an Article can have multiple Tags.
Now getting back to this: getArticleBySlugWithFilteredTags('tag1', 'tag2', 'tag3').
Attempt using custom content type: Tag
Query:
data: articleCollection(limit: 1, where: {
slug: "article-unique-1",
}) {
items {
title
tagsCollection(limit: 5) { // here it would be nice if I can use "where": {name: "tag1"}
items {
name
value
linkedFrom {
relatedArticles: articleCollection(limit: 7) { // other related articles that has the same tag as parent Article
items {
slug
title
category
}
}
}
}
}
}
}
}
The only thing that is missing here is the that I need to filter out the tagsCollection ( based on some property: name or value ).
I see that I am limited to use "where" on tagsCollection.
Attempt using contentfulMetadata tags
Query
{
data: articleCollection(where:
{
slug: "article-unique-1",
contentfulMetadata: {
tags_exists: true,
tags: {
id_contains_some: ["tag1", "tag2"]
}
}
}) {
items {
contentfulMetadata {
tags {
id
name
linkedFrom { // I can't use this here
relatedArticles: articleCollection(limit: 7) {
items {
slug
title
category
}
}
}
}
}
slug
title
publicationDate
}
}
}
With this approach I am not able to use the linkedFrom in order to get also other related articles that have the same contentfulMetadata tags. What should I do in other to achieve this making 1 query and no clientside filtering with javascript ?

Confused why returnPartialData works without a field policy in Apollo Client 3

In my application I am searching for products, then clicking into a product to see more detail about it.
I perform a GraphQL query on each page. The SEARCH query returns type [Product], and the PRODUCT query returns type Product.
// Search page
const SEARCH = gql`
query Search($query: String!) {
searchResults: search(query: $query) {
id
name
images
price
}
}
`
// ProductDetail page
const PRODUCT = gql`
query Product($id: Int!) {
product(id: $id) {
id
name
images
optionSetName
options {
id
images
name
}
price
}
}
`
I have enabled returnPartialData on the PRODUCT query, as some of the fields for that product already exist in the cache from the SEARCH query, and I would like to access them before the server request returns.
I thought I would also have to apply a field policy to reference the pre-existing Product, as I don't know how PRODUCT even knows what its return type is.
However, when I do the following:
const { loading, data: { product } = {} } = useQuery(
PRODUCT,
{ variables: { id: productId, isShallow }, returnPartialData: true }
)
console.log(product)
the following is logged to console (the first is from returnPartialData, the second from server):
Somehow the PRODUCT query has associated itself with the existing Product, without me explicitly writing a cache redirect.
I'm confused how this has occurred? It seems like Apollo must have a reference to the GraphQL schema, and has seen the return type of PRODUCT is Product, then automatically used the id arg to reference the existing product.
Using "#apollo/client": "^3.4.1"
Wow, turns out I had made a field policy ages ago and forgotten about it... xD
typePolicies: {
Query: {
fields: {
product: {
read (_, { args, toReference }) {
return toReference({
__typename: 'Product',
id: args.id
})
}
}
}
}
}

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)
}

Store error: the application attempted to write an object with no provided typename but the store already contains an object

After mutation when I am updating the cache, changes are reflected in UI but getting the below error
Invariant Violation: Store error: the application attempted to write an object with no provided typename but the store already contains an object with typename of ItemCodeConnection for the object of id $ROOT_QUERY.itemCodes({"filter":{"number":10000001}}). The selectionSet that was trying to be written is:
{"kind":"Field","name":{"kind":"Name","value":"itemCodes"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"filter"},"value":{"kind":"Variable","name":{"kind":"Name","value":"filter"}}}],"directives":[],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"itemCodes"},"arguments":[],"directives":[],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"itemCodeTile"},"directives":[]},{"kind":"Field","name":{"kind":"Name","value":"__typename"}}]}},{"kind":"Field","name":{"kind":"Name","value":"__typename"}}]}}
GraphQL query:
const CREATE_ITEM_CODE_SPEC = gql`
mutation createItemCodeSpec($input: createItemCodeSpecInput) {
createItemCodeSpecification(input: $input){
__typename
id
itemCode {
number
}
product
spec_class
grade
}
}
`
const GET_ITEM_CODE = gql`
query itemCode($filter: filterInput){
itemCodes(filter: $filter){
itemCodes {
number
type
description
group
item_code_spec {
id
itemCode {
number
}
product
spec_class
grade
}
created_on
created_by
changed_on
changed_by
}
}
}
`
Below is the mutation:
const [mutation, { data, loading, error}] = useMutation(
CREATE_ITEM_CODE_SPEC,
{
update(cache, { data: { createItemCodeSpecification } }){
const currentData = cache.readQuery({
query: GET_ITEM_CODE,
variables: { filter : {number:itemCode} }
})
cache.writeQuery({
query: GET_ITEM_CODE,
variables: { filter : {number:itemCode} },
data: {
...currentData,
itemCodes: {
itemCodes: currentData.itemCodes.itemCodes.map((itemCode, index) => {
return {
...itemCode,
item_code_spec: index === 0? [
...itemCode.item_code_spec,
createItemCodeSpecification
] : itemCode.item_code_spec
}
})
}
}
})
}
}
);
You simply need to add "id" for each subsection of your query. Adding "id" for "itemCodes" in your GET_ITEM_CODE query might solve your problem.
You have fields missing in your response mutation.
Basically, you should make your mutation results have all of the data necessary to update the queries previously fetched.
That’s also why is a best practice to use fragments to share fields among all queries and mutations that are related.
To make it work both query and mutation should have exactly the same fields.
Have a look here to see more in depth how cache updates work:
https://medium.com/free-code-camp/how-to-update-the-apollo-clients-cache-after-a-mutation-79a0df79b840

How can I insert records in AwsAppSync mutation with proper #connection values?

I have added a resources table to my schema, connecting to a Plants table:
type Resource #model
{
id: ID!
name: String!
Plants: [Plant] #connection(name: "ResourcePlant")
}
Ran amplify push, and all resources were created properly.
Now I wanted to add a Resource, and link it to all Plants properly.
Do you know how is the sintaxe I should use to run the recently created mutation createResource in order to add the items on Plant I want to include to that resource?
I tried to run like this:
mutation CreateResource {
createResource (input: {
name: "Plant",
Plants : {
items :
{ id: "f9a0468e-da74-41d5-8287-1cb6a76b25a5" }
}
}
) {
name,
Plants {
items {
id
}
nextToken
}
}
}
This was the error message:
Validation error of type WrongType: argument 'input' with value
'ObjectValue{objectFields=[ObjectField{name='name',
value=StringValue{value='Plant'}}, ObjectField{name='Plants',
value=ObjectValue{objectFields=[ObjectField{name='items', value=ObjectValue{objectFields=[ObjectField{name='id',
value=StringValue{value='f9a0468e-da74-41d5-8287-1cb6a76b25a5'}}]}}]}}]}'
contains a field not in 'CreateResourceInput': 'Plants' # 'createResource'
How did you define Plant?
And have you checked this example? https://aws-amplify.github.io/docs/cli-toolchain/graphql#connection
Ok, after some headache, I found what was missing in my model. For me so far it has proved to be the best way of doing this relationship...
I have added on my Plant type, on schema definition, a field named plantResourceId (other than the one used for the #connection directive). What I found out was that, by convention, when inserting/updating a record on "Plant" and adding the resource "id" field content of the resource I want to "connect" to that plant, it will automatically be retrieved when "Resources" is queried, for each item - what is better: Out-of-the-box from codegen.
Insert example
mutation CreatePlant {
createPlant(input:{
name: "MyPlant",
plantResourceId: "id-for-connected-resource"
}) {
name,
plantResourceId
}
}
Query example to retrieve items:
query listPlantsOnResource {
listResources(filter: {
name: {
contains: "myfilter"
}
}) {
items {
id
name
Plants
{
items {
id
name
description
}
}
}
}
}
It worked very well!
Thanks all who contributed!

Resources