Issue with RANGE_ADD in Relay Mutations - graphql

I was going through the relay docs and came to following code in RANGE_ADD.
class IntroduceShipMutation extends Relay.Mutation {
// This mutation declares a dependency on the faction
// into which this ship is to be introduced.
static fragments = {
faction: () => Relay.QL`fragment on Faction { id }`,
};
// Introducing a ship will add it to a faction's fleet, so we
// specify the faction's ships connection as part of the fat query.
getFatQuery() {
return Relay.QL`
fragment on IntroduceShipPayload {
faction { ships },
newShipEdge,
}
`;
}
getConfigs() {
return [{
type: 'RANGE_ADD',
parentName: 'faction',
parentID: this.props.faction.id,
connectionName: 'ships',
edgeName: 'newShipEdge',
rangeBehaviors: {
// When the ships connection is not under the influence
// of any call, append the ship to the end of the connection
'': 'append',
// Prepend the ship, wherever the connection is sorted by age
'orderby(newest)': 'prepend',
},
}];
}
/* ... */
}
Now over here it is mentioned that edgeName is required for adding new node to the connection. Looks well and fine.
Now, I move further down the documentation and reached the GraphQL implementation of this mutation.
mutation AddBWingQuery($input: IntroduceShipInput!) {
introduceShip(input: $input) {
ship {
id
name
}
faction {
name
}
clientMutationId
}
}
Now according to docs this mutation gives me output as
{
"introduceShip": {
"ship": {
"id": "U2hpcDo5",
"name": "B-Wing"
},
"faction": {
"name": "Alliance to Restore the Republic"
},
"clientMutationId": "abcde"
}
}
I cannot see edgeName being present here.
I was using graphene for my project. Over there also I saw something similar only
class IntroduceShip(relay.ClientIDMutation):
class Input:
ship_name = graphene.String(required=True)
faction_id = graphene.String(required=True)
ship = graphene.Field(Ship)
faction = graphene.Field(Faction)
#classmethod
def mutate_and_get_payload(cls, input, context, info):
ship_name = input.get('ship_name')
faction_id = input.get('faction_id')
ship = create_ship(ship_name, faction_id)
faction = get_faction(faction_id)
return IntroduceShip(ship=ship, faction=faction)
Over here also I cannot see edgeName anywhere.
Any help please? I am working on mutations for the first so wanted to confirm a m I missing something or is something wrong here?

This example might be either simplified or a bit obsoloete, because in practice there is need to return edge and that's exactly what is fetched by relay (other fields in RANGE_ADD are more a kind of declaration and are not necessarily fetched).
Here is how you can do it in graphene:
# Import valid as of graphene==0.10.2 and graphql-relay=0.4.4
from graphql_relay.connection.arrayconnection import offset_to_cursor
class IntroduceShip(relay.ClientIDMutation):
class Input:
ship_name = graphene.String(required=True)
faction_id = graphene.String(required=True)
ship = graphene.Field(Ship)
faction = graphene.Field(Faction)
new_ship_edge = graphene.Field(Ship.get_edge_type().for_node(Ship))
#classmethod
def mutate_and_get_payload(cls, input, context, info):
ship_name = input.get('ship_name')
faction_id = input.get('faction_id')
ship = create_ship(ship_name, faction_id)
faction = get_faction(faction_id)
ship_edge_type = Ship.get_edge_type().for_node(Ship)
new_ship_edge = edge_type(
# Assuming get_ships_number supplied
cursor=offset_to_cursor(get_ships_number())
node=ship
)
return cls(ship=ship, faction=faction, new_ship_edge=new_ship_edge)

Related

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 to remove the nested input object in the Graphene Django mutation query (Relay)?

I want to create a Mutation in Relay. I'm using InputObjectType pattern to separate the input and make it reusable.
In mutation class I'm using Input class and there I'm passing the InputObjectType
In general it works but the final query at the client side is very ugly.
I need to pass arguments in this way
query( input : { input : { ...arguments } } )
and to be honest I don't like it. I think it looks ugly.
So the question is: Is it possible to avoid to use a lot of these input objects?
It's ok to use 1 input object, but the nested one is redundant and I'd like to avoid to use it.
Thanks for any help!
Here is the example
class FuelTypeInput(graphene.InputObjectType):
id = graphene.Int()
label = graphene.String()
class FuelSubtypeInput(graphene.InputObjectType):
id = graphene.ID()
label = graphene.String()
fuel_type = graphene.Field(FuelTypeInput)
class CreateFuelSubType(relay.ClientIDMutation):
class Input:
input = FuelSubtypeInput(required=True)
fuel_subtype = Field(FuelSubTypeNode)
ok = graphene.Boolean()
def mutate_and_get_payload(root, info, input):
label = input.label
fuel_type = FuelType.objects.get(pk=input.fuel_type.id)
fuel_subtype = FuelSubType(label=label, fuel_type=fuel_type)
ok = True
return CreateFuelSubType(fuel_subtype=fuel_subtype, ok=ok)
The mutation query is:
mutation MyMutations {
createFuelSubtype( input: { input : { label: "Mutation Subtype", fuelType: {
id: 3
}} } ) {
fuelSubtype {
label
}
ok
}
}
It works fine, here is the result. But I'd like to remove the nested input things
{
"data": {
"createFuelSubtype": {
"fuelSubtype": {
"label": "Mutation Subtype"
},
"ok": true
}
}
}
you can fix with this:
class FuelTypeInput(graphene.AbstractType):
id = graphene.Int()
label = graphene.String()
class CreateFuelSubType(relay.ClientIDMutation):
Input = FuelSubtypeInput
fuel_subtype = Field(FuelSubTypeNode)
ok = graphene.Boolean()
# Other Code ...

Emit deprecation warnings with Apollo client

Background
We are working on a fairly large Apollo project. A very simplified version of our api looks like this:
type Operation {
foo: String
activity: Activity
}
type Activity {
bar: String
# Lots of fields here ...
}
We've realised splitting Operation and Activity does no benefit and adds complexity. We'd like to merge them. But there's a lot of queries that assume this structure in the code base. In order to make the transition gradual we add #deprecated directives:
type Operation {
foo: String
bar: String
activity: Activity #deprecated
}
type Activity {
bar: String #deprecated(reason: "Use Operation.bar instead")
# Lots of fields here ...
}
Actual question
Is there some way to highlight those deprecations going forward? Preferably by printing a warning in the browser console when (in the test environment) running a query that uses a deprecated field?
So coming back to GraphQL two years later I just found out that schema directives can be customized (nowadays?). So here's a solution:
import { SchemaDirectiveVisitor } from "graphql-tools"
import { defaultFieldResolver } from "graphql"
import { ApolloServer } from "apollo-server"
class DeprecatedDirective extends SchemaDirectiveVisitor {
public visitFieldDefinition(field ) {
field.isDeprecated = true
field.deprecationReason = this.args.reason
const { resolve = defaultFieldResolver, } = field
field.resolve = async function (...args) {
const [_,__,___,info,] = args
const { operation, } = info
const queryName = operation.name.value
// eslint-disable-next-line no-console
console.warn(
`Deprecation Warning:
Query [${queryName}] used field [${field.name}]
Deprecation reason: [${field.deprecationReason}]`)
return resolve.apply(this, args)
}
}
public visitEnumValue(value) {
value.isDeprecated = true
value.deprecationReason = this.args.reason
}
}
new ApolloServer({
typeDefs,
resolvers,
schemaDirectives: {
deprecated: DeprecatedDirective,
},
}).listen().then(({ url, }) => {
console.log(`🚀 Server ready at ${url}`)
})
This works on the server instead of the client. It should print all the info needed to track down the faulty query on the client though. And having it in the server logs seem preferable from a maintenance perspective.

Relay mutation fragments intersection

I don't use Relay container, because I'd like to have more control over components. Instead of it I use HOC + Relay.Store.forceFetch, that fetches any given query with variables. So I have the following query:
query {
root {
search(filter: $filter) {
selectors {
_id,
data {
title,
status
}
},
selectorGroups {
_id,
data {
title,
}
}
}
}
}
Then I have to do some mutation on selector type.
export default class ChangeStatusMutation extends Relay.Mutation {
getMutation() {
return Relay.QL`mutation {selectors_status_mutation}`;
}
getVariables() {
return {
id: this.props.id,
status: this.props.status
};
}
getFatQuery() {
return Relay.QL`
fragment on selectors_status_mutationPayload{
result {
data {
status
}
}
}
`;
}
static fragments = {
result: () => Relay.QL`
fragment on selector {
_id,
data {
title,
status
}
}`,
};
getOptimisticResponse() {
return {
result: {
_id: this.props.id,
data: {
status: this.props.status
}
}
};
}
getConfigs() {
return [{
type: 'FIELDS_CHANGE',
fieldIDs: {
result: this.props.id
},
}];
}
}
Call mutation in component:
const mutation = new ChangeStatusMutation({id, status, result: selector});
Relay.Store.commitUpdate(mutation);
After mutation commitment selector in Relay storage is not changed. I guess that's because of empty Tracked Fragment Query and mutation performs without any fields:
ChangeStatusMutation($input_0:selectors_statusInput!) {
selectors_status_mutation(input:$input_0) {
clientMutationId
}
}
But the modifying selector was already fetched by Relay, and I pass it to the mutation with props. So Relay knows the type, that should be changed, how to find the item and which fields should be replaced. But can not intersect. What's wrong?
So, you're definitely a bit "off the ranch" here by avoiding Relay container, but I think this should still work...
Relay performs the query intersection by looking up the node indicated by your FIELDS_CHANGE config. In this case, your fieldIDs points it at the result node with ID this.props.id.
Are you sure you have a node with that ID in your store? I'm noticing that in your forceFetch query you fetch some kind of alternative _id but not actually fetching id. Relay requires an id field to be present on anything that you later want to refetch or use the declarative mutation API on...
I'd start by checking the query you're sending to fetch whatever this result type is. I don't see you fetching that anywhere in your question description, so I'm just assuming that maybe you aren't fetching that right now?

GraphQL Relay Mutation Config RANGE_ADD's parentName for connections

I have a page that uses GraphQL Relay connection which fetches drafts.
query {
connections {
drafts(first: 10) {
edges {
node {
... on Draft {
id
}
}
}
}
}
}
In this page, I also create draft through CreateDraftMutation.
mutation {
createDraft(input: {
clientMutationId: "1"
content: "content"
}) {
draft {
id
content
}
}
}
After this mutation, I want Relay to add the created draft into its store. The best candidate for mutation config is RANGE_ADD, which is documented as following:
https://facebook.github.io/relay/docs/guides-mutations.html
RANGE_ADD
Given a parent, a connection, and the name of the newly created edge in the response payload Relay will add the node to the store and attach it to the connection according to the range behavior specified.
Arguments
parentName: string
The field name in the response that represents the parent of the connection
parentID: string
The DataID of the parent node that contains the connection
connectionName: string
The field name in the response that represents the connection
edgeName: string
The field name in the response that represents the newly created edge
rangeBehaviors: {[call: string]: GraphQLMutatorConstants.RANGE_OPERATIONS}
A map between printed, dot-separated GraphQL calls in alphabetical order, and the behavior we want Relay to exhibit when adding the new edge to connections under the influence of those calls. Behaviors can be one of 'append', 'ignore', 'prepend', 'refetch', or 'remove'.
The example from the documentation goes as the following:
class IntroduceShipMutation extends Relay.Mutation {
// This mutation declares a dependency on the faction
// into which this ship is to be introduced.
static fragments = {
faction: () => Relay.QL`fragment on Faction { id }`,
};
// Introducing a ship will add it to a faction's fleet, so we
// specify the faction's ships connection as part of the fat query.
getFatQuery() {
return Relay.QL`
fragment on IntroduceShipPayload {
faction { ships },
newShipEdge,
}
`;
}
getConfigs() {
return [{
type: 'RANGE_ADD',
parentName: 'faction',
parentID: this.props.faction.id,
connectionName: 'ships',
edgeName: 'newShipEdge',
rangeBehaviors: {
// When the ships connection is not under the influence
// of any call, append the ship to the end of the connection
'': 'append',
// Prepend the ship, wherever the connection is sorted by age
'orderby(newest)': 'prepend',
},
}];
}
/* ... */
}
If the parent is as obvious as faction, this is a piece of cake, but I've been having hard time identifying parentName and parentID if it came directly from query connections.
How do I do this?
Edit:
This is how query was exported.
export default new GraphQLObjectType({
name: 'Query',
fields: () => ({
node: nodeField,
viewer: {
type: viewerType,
resolve: () => ({}),
},
connections: {
type: new GraphQLObjectType({
name: 'Connections',
which in return is used in the relay container
export default Relay.createContainer(MakeRequestPage, {
fragments: {
connections: () => Relay.QL`
fragment on Connections {
I've been having hard time identifying parentName and parentID if it
came directly from query connections.
faction and ships in the example from Relay documentation are exactly the same as connections and drafts in your case. Each GraphQLObject has an ID,so does your connections object. Therefore, for your mutation, parentName is connctions and parentID is the ID of connections.
query {
connections {
id
drafts(first: 10) {
edges {
node {
... on Draft {
id
}
}
}
}
}
}
By the way, I guessconnections and drafts are terms from your application domain. Otherwise, connections confuses with GraphQL connection type.

Resources