HotChocolate Stitching How to remove argument from a mutation? - graphql

I have an GraphQL api implemented using HotChocolate 11. The schema changed recently and I would like to use our stitching layer to keep it the same for clients.
I have an mutation that looked like this:
type Mutation {
generate(apertures: [ApertureInput!]! templates: [TemplateInput!]!): [GeneratedApertures!]!
}
I had to add one more argument, also we added a query to get the additional data
type Mutation {
generate(apertures: [ApertureInput!]! templates: [TemplateInput!]! algorithm: AlgorithmInput!): [GeneratedApertures!]!
}
type Query {
standardAlgorithm: StandardAlgorithm
}
type StandardAlgorithm {
shrink: Int!
}
What I'm trying to do is to use our stitching layer to add the old signature of the mutation and call a remote schema in the resolver to get the additional data and pass it to the new mutation.
I have renamed the new signature on the stitch and created a mutation that looks like the old one:
extend type Mutation {
product_generate(apertures: [ApertureInput!]! templates: [TemplateInput!]! algorithm: AlgorithmInput!): GeneratedApertures!
#source(name: "generate" schema: "productdefinition")
#delegate(schema: "productdefinition")
generate(apertures: [ApertureInput!]! templates: [TemplateInput!]!): GeneratedApertures!
}
But I am lost using the stitching context to build the sub queries.
public class GenerationMutationTypeExtension : ObjectTypeExtension
{
protected override void Configure(IObjectTypeDescriptor descriptor)
{
descriptor.Name("Mutation");
descriptor.Field("generate")
.Resolve(async ctx => {
var context = ctx.Service<IStitchingContext>();
var executor = context.GetRemoteRequestExecutor("productdefinition");
var request =
QueryRequestBuilder.New()
.SetQuery("{ standardAlgorithm { shrink } }")
.SetServices(executor.Services)
.Create();
var result = (QueryResult)await executor.ExecuteAsync(request);
var template = result.Data?["standardTemplate"];
if (template is NullValueNode || template is null) throw new GraphQLRequestException("Missing configuration!!!");
var shrink = (string)((IValueNode)((Dictionary<string, object>)template)["shrinkPercent"]).Value;
var apertures = ctx.ArgumentLiteral<IValueNode>("apertures");
var templates = ctx.ArgumentLiteral<IValueNode>("templates");
var selection = ctx.Selection.SyntaxNode.SelectionSet;
var query =
$"{{ product_generateDeposits(apertures: {apertures} templates: {templates} algorithm: {{ standardAlgorithm: {{ shrink: {shrink} }} }}) {selection} }}";
request =
QueryRequestBuilder.New()
.SetQuery(query)
.SetServices(executor.Services)
.Create();
result = (QueryResult)await executor.ExecuteAsync(request);
return result;
});
}
}
The first query for the shrink is working fine, I get the data. But I struggle with putting the other mutation together. Is there maybe a better way how to achieve this? Documentation is very vague when it comes to stitching.

Related

Different query for same __typename data returns undefined on Apollo Graphql from cache

I am fairly new to Apollo and Graphql and I quite don't know what I am doing wrong. I will try to explain my problem with some simplified code but if I am missing some information please let me know and I will update it.
I have an Apollo client instance consuming an external (Directus CMS) graphql API not modifiable by me.
The thing is I am querying a paginated (infinite scroll) of posts with offset and limit variables and the data returned is correct. I am using the following typePolicy in order to merge results arrays in my inMemoryCache. This code is from an example in the Apollo Docs:
posts {
const merged = existing ? existing.slice(0) : [];
const postIdToIndex = Object.create(null);
if (existing) {
existing.forEach((post, index) => {
postIdToIndex[readField("id", post)] = index;
});
}
incoming.forEach((post) => {
const id = readField("id", post);
const index = postIdToIndex[id];
if (typeof index === "number") {
// Merge the new post data with the existing post data.
merged[index] = mergeObjects(merged[index], post);
} else {
// First time we've seen this post in this array.
postIdToIndex[id] = merged.length;
merged.push(post);
}
});
return merged;
}
and this is a simplified version of my query:
query($offset: Int $limit: Int) {
posts(limit: $limit offset: $offset) {
id
name
}
}
With this merge function and query I get my pagination right and everything loads correctly. But when I try to query a single instance of post that queries also additional fields from it with the following query:
query($offset: Int $limit: Int $post_id: Int) {
posts(filter:{id:{_eq: $post_id}}) {
id
name
other_data
}
}
This returns undefined constantly when using useQuery() although i can see that is coming through my merge funtion and merging objects with the mergeObjects() function. If I use a fetchPolicy of no-cache in this query, the data returns correctly so it has something to do with the cache.
I hope someone can lead me in the right direction so I can fix this problem.
Thank you everyone in advance!!

check for missing resolvers

How would you scan schema for missing resolver for queries and non-scalar fields ?
I'm trying to work with a dynamic schema so I need to be able to test this programmatically. I've been browsing graphql tools for few hours to find a way to do this, but I'm getting nowhere...
checkForResolveTypeResolver - this only apply to interface and union resolveType resolver
I can't find a way to know when a defaultFieldResolver is applied
I tried working with custom directives to add #requiredResolver, to help identify those fields, but custom resolver are far from being fully supported:
introspection & directives
no graphql-js directives handler (can workaround this with graphql-tools tho)
any help is appreciated !
Given an instance of GraphQLSchema (i.e. what's returned by makeExecutableSchema) and your resolvers object, you can just check it yourself. Something like this should work:
const { isObjectType, isWrappingType, isLeafType } = require('graphql')
assertAllResolversDefined (schema, resolvers) {
// Loop through all the types in the schema
const typeMap = schema.getTypeMap()
for (const typeName in typeMap) {
const type = schema.getType(typeName)
// We only care about ObjectTypes
// Note: this will include Query, Mutation and Subscription
if (isObjectType(type) && !typeName.startsWith('__')) {
// Now loop through all the fields in the object
const fieldMap = type.getFields()
for (const fieldName in fieldMap) {
const field = fieldMap[fieldName]
let fieldType = field.type
// "Unwrap" the type in case it's a list or non-null
while (isWrappingType(fieldType)) {
fieldType = fieldType.ofType
}
// Only check fields that don't return scalars or enums
// If you want to check *only* non-scalars, use isScalarType
if (!isLeafType(fieldType)) {
if (!resolvers[typeName]) {
throw new Error(
`Type ${typeName} in schema but not in resolvers map.`
)
}
if (!resolvers[typeName][fieldName]) {
throw new Error(
`Field ${fieldName} of type ${typeName} in schema but not in resolvers map.`
)
}
}
}
}
}
}

GraphQL queries for Gatsby - Contentful setup with a flexible content model

I have a gatsby site with the contentful plugin and graphql queries (setup is working).
[EDIT]
My gatsby setup pulls data dynamically using the pageCreate feature. And populates my template component, the root graphql query of which I've shared below. I can create multiple pages using the setup if the pages on contentful follow the structure given in the below query.
[/EDIT]
My question is about a limitation I seemed to have come across or just don't know enough grpahql to understand this yet.
My high level content model 'BasicPageLayout' consists of references to other content types through the field 'Section'. So, it's flexible in terms of which content types are contained in the 'BasicPageLayout' and the order in which they are added.
Root page query
export const pageQuery = graphql`
query basicPageQuery {
contentfulBasicPageLayout(pageName: {eq: "Home"}) {
heroSection {
parent {
id
}
...HeroFields
}
section1 {
parent {
id
}
...ContentText
}
section2 {
parent {
id
}
...ContentTextOverMedia
}
section3 {
parent {
id
}
...ContentTextAndImage
}
section4 {
parent {
id
}
...ContentText
}
}
}
The content type fragments all live in the respecitve UI components.
The above query and setup are working.
Now, I have "Home" Hard coded because I'm having trouble creating a flexible reusable query. I'm taking advantage of contentful's flexible nature when creating the models, but haven't found a way to create that flexibility in the graphql query for it.
What I do know:
Graphql query is resolved at run time, so everything that needs to be fetched should be in that query. It can't be 'dynamic'.
Issue: The 'Section' fields in the basicPageLayout can link to any content type. So we can mix and match the granular level content types. How do I add the content type fragment (like ContentTextAndImage vs ContentText) so it is appropriate for that section instance ('Section' field in the query)?
In other words
I'd like the root query to get 'Home' data which might have 4 sections, all of type - ContentTextOverMedia
as well as 'About ' data that might have also have 4 sections but with alternating types - ContentText and ContentTextAndImage
This is the goal because I want to create content (Pages) by mix-matching content types on contentful, without needing to update the code each time a new Page is created. Which is why Contentful is useful and was picked in the first place.
My ideas so far:
A. Run two queries, in series. One fetches the parent.id on each section and that holds the content type info. Second fetches the data using the appropriate fragment.
B. Fetch the JSON file of the basicPageLayouts content instance (such as 'Home') separately through Contentful API, and using that JSON file create the graphql string to be used in each instance (So, different layout for Home, About, and so on)
This needs more experimentation, not sure if it's viable, could also be more complex then it needs to be.
So, please share thoughts on the above paths that I'm exploring or another solution that I haven't considered using graphql or gatsby's features.
This is my first question on SO btw, I've spent some time on refining it and trying to follow the guidelines but please do give me feedback in comments so I can improve even if you don't have an answer to my question.
Thanks in advance.
If I understood correctly you want to create pages dynamically from the data coming from Contentful.
You can achieve this using the Gatsbyjs Node API specifically createPage.
In your gatsby-node.js file you can have something like this
const fs = require('fs-extra')
const path = require('path')
exports.createPages = ({graphql, boundActionCreators}) => {
const {createPage} = boundActionCreators
return new Promise((resolve, reject) => {
const landingPageTemplate = path.resolve('src/templates/landing-page.js')
resolve(
graphql(`
{
allContentfulBesicPageLayout {
edges {
node {
pageName
}
}
}
}
`).then((result) => {
if (result.errors) {
reject(result.errors)
}
result.data.allContentfulBesicPageLayout.edges.forEach((edge) => {
createPage ({
path: `${edge.node.pageName}`,
component: landingPageTemplate,
context: {
slug: edge.node.pageName // this will passed to each page gatsby create
}
})
})
return
})
)
})
}
Now in your src/templates/landing-page.js
import React, { Component } from 'react'
const LandingPage = ({data}) => {
return (<div>Add you html here</div>)
}
export const pageQuery = graphql`
query basicPageQuery($pageName: String!) {
contentfulBasicPageLayout(pageName: {eq: $pageName}) {
heroSection {
parent {
id
}
...HeroFields
}
section1 {
parent {
id
}
...ContentText
}
section2 {
parent {
id
}
...ContentTextOverMedia
}
section3 {
parent {
id
}
...ContentTextAndImage
}
section4 {
parent {
id
}
...ContentText
}
}
}
note the $pageName param that's what was passed to the component context when creating a page.
This way you will end up creating as many pages as you want.
Please note: the react part of the code was not tested but I hope you get the idea.
Update:
To have a flexible query you instead of having your content Types as single ref field, you can have one field called sections and you can add the section you want there in the order you desire.
Your query will look like this
export const pageQuery = graphql`
query basicPageQuery($pageName: String!) {
contentfulBasicPageLayout(pageName: {eq: $pageName}) {
sections {
... on ContentfulHeroFields {
internal {
type
}
}
}
}
Khaled

RethinkDB Thinky - rows within 1 hour

r.db('dbname').table('urls').filter(function(url) {
return url("expires_at").date().eq(r.now().date())
.and(url("expires_at").hours().eq(r.now().hours().sub(1)))
});
I am trying to write the equivalent query using thinky ORM for node.js
I've never worked with Thinky, but according to docs, you should create model and make query on it.
1) Create model. I don't know what documents you are storing in Rethink. But something like this:
var thinky = require('thinky')();
var type = thinky.type;
// Create a model
var Urls = thinky.createModel("urls", {
id: String,
expires_at: Date
// another fields if needed
});
2) Query:
Don't know actual syntaxes for filter in Thinky, but somehting like this:
Urls.filter(function(url) {
return url("expires_at").date().eq(r.now().date())
.and(url("expires_at").hours().eq(r.now().hours().sub(1)))
}).then(function(result) {
// result is an array of instances of `Urls `
});

CouchDb - Access replication filters in view

is it possible to use the replication filter feature of couchdb (http://wiki.apache.org/couchdb/Replication#Filtered_Replication) by requesting a view, f.e.:
.../_view/candidates?filter=hrtool/myfilter
This would be nice to filter the documents based on the usersession or userrole
Thanks in advance
fadh
That is possible with a _list function.
List functions are Javascript code which pre-process the view output before sending it to the client. You can modify the view output in any way, such as by filtering some rows.
function(head, req) {
// lists.filtered: filter view output by using a replication filter.
var ddoc = this; // A common trick to explicitly identify the design document.
function error(reason) {
start({"code":400, "headers":{"content-type":"application/json"}});
send(JSON.stringify({"error":reason}));
}
var filter_name = req.query.filter;
if(!filter_name)
return error("Need filter_name parameter");
var filter_src = ddoc.filters[filter_name];
if(!filter_src)
return error("Invalid filter_name: " + filter_name);
// Not 100% sure on this, you could also use new Function(args, src);
// In the worst-case, the couchapp tool has the !code tool to copy code.
var filter = eval(filter_src); // Not 100% sure on this
var row;
start({"headers":{"content-type":"application/json"}});
send('{"rows":[\r\n');
var first = true;
while(row = getRow()) {
if(filter(row)) { // Or perhaps use include_docs=true and filter(row.doc)
if(! first)
send(",\r\n");
first = false;
send(JSON.stringify(row));
}
}
send("]}\r\n");
}
Use this list "filter" like any filter function:
GET /db/_design/example/_list/filtered/candidates?filter=myfilter&include_docs=true

Resources