Refetch Container with two independent data sets - relayjs

I have a component that has two 'independent' datasets, in that refetching argument a changes field A and refetching argument b changes field B.
export default createRefetchContainer(
Component,
{
a: graphql`
fragment Comp_a on Query #argumentDefinitions(
a: { type: "String!", defaultValue: "" },
) {
A(a: $a) {
name
}
}
`,
b: graphql`
fragment MainList_repo on Query #argumentDefinitions(
b: { type: "String!", defaultValue: "" }
) {
B(b: $b) {
name
}
}
`
},
{
a: graphql`
query CompRefetchQuery($a: String!) {
...Comp_a #arguments(a: $a)
}
`,
b: graphql`
query CompRefetchQuery($b: String!) {
...Comp_b #arguments(b: $b)
}
`
}
)
There is no such example in relay docs, and I just inferred this from fragment containers. The problem is calling
props.relay.refetch(vars => ({ data }), null)
results in a RelayModernGraphQLTag: Expected an operation, got{}. error (reporting refetch itself as the call site).
Is there a way to create two 'refetch' subsets like this in relay? Or do I need to create two refetch containers and nest them?

Related

Neo4j GraphQL how to check if node exists before connecting to it

B"H
I've seen this question and I want to do the same thing with the GraphQL driver for Neo4j. I think the answer lies somewhere in combining AND and OR operators found here, of some sort, but not sure how.
To illustrate the question, say I have this type:
type Comment {
id: ID #id
timeAdded: DateTime! #timestamp(
operations: [CREATE]
)
writer: User! #relationship(
type: "WRITTEN_BY",
direction: IN
)
content: String
}
type User {
id: ID #id
name: String
commentsWritten: [Comment!]! #relationship(
type: "WRITTEN_BY",
direction: OUT
)
}
type Mutation {
addComment(authorID: String)
}
and this resolver to add a new comment (where "Comment" is a reference to the OGM model of the Comment type):
addComment: async (
src,
args,
ctx
) => {
var authorID = args/*edit*/.authorID //let's say this is passed
//from request headers / JWT token or something
var adCom = await Comment.create({
input: [
{
content: args.content,
writer: {
connect: {
where: {
node: {
users: {
id: authorID
}
}
}
}
}
}
]
})
return adCom;
}
So it attempts to connect to that user with that ID. but if there is no user with that ID, I don't want to create anything [not only do I not want to connect it].
I could run another query to find a User with that ID, and test if it went through, but was wondering if it's possible to do everything in one call

Apollo server, GraphQL and Sequelize - how to put raw data into a GraphQL Schema Response

After scouring the internet for a specific example, I am throwing in the towel and asking for some help.
I am using Apollo server, GraphQL and Sequelize, and I am calling stored procedure that returns a record set created from two different tables. I am getting the data back, but I cannot figure out how to put the result into a GraphQL schema response.
Here is the code in my resolver:
async functionName(_, {input}, {user = null}) {
if (!user) {
throw new AuthenticationError('You must login to use this function');
}
const {record_id} = input;
const result = await DBService.query(
'Call sp_JoinTwoTables_Select(:id)',
{
model: FooModel,
mapToModel: true,
raw: true,
replacements: {id: record_id},
type: QueryTypes.SELECT
}
);
console.log('functionName.result');
console.log(result); // Getting results
return result;
}
Here is the code in my schema:
const {gql} = require('apollo-server-express');
module.exports = gql`
type Foo {
id: Int!
foo_name: String!
date_created: String!
date_modified: String!
}
extend type Mutation {
functionName(input: fooInput!): fooResponse!
}
input fooInput {
id: Int!
}
type fooResponse {
tree: [fooSchemaForBothTables!]
}
type fooSchemaForBothTables {
id: Int!
foo_name: String!
column_from_second_table: Int!
}
`;
Since there is no table in the database, I created a simple object. When that failed I tried a sequelized model object, but that also is failing. Here is this code:
module.exports = {FooModel: {
id: 0,
fooName: '',
column_from_second_table: 0
}};
The output I am getting is (not a 2d array as I thought):
Executing (default): Call sp_CommunityHierarchy_Select(9)
selectHierarchyTree.result
[
{
'0': {
community_id: 1,
community_name: 'Cars',
level_from_apex: null,
parent_id: null
},
'1': {
community_id: 8,
community_name: 'Chevy',
level_from_apex: 2,
parent_id: 1
},
'2': {
community_id: 9,
community_name: 'Suburban',
level_from_apex: 3,
parent_id: 8
},
meta: [ [ColumnDef], [ColumnDef], [ColumnDef], [ColumnDef] ]
},
{ affectedRows: 6, insertId: 0, warningStatus: 0 }
]
Your 'raw' DB result:
is an array;
1st element is an object with records/items encoded as index-named properties;
Your required mutation (why not a query type!?) response should tree: [fooSchemaForBothTables!] - object with tree named property (really required additional nesting level?) with an array of fooSchemaForBothTables-shaped objects as values:
{
tree: [
{
id: 1,
foo_name: 'Cars`,
column_from_second_table: 'whatever`,
},
{
id: 2,
foo_name: 'Chevy`,
column_from_second_table: 'whatever`,
}
]
}
Your job is to convert DB response into the required mutation result shape.
Hint: You can hardcode this DB result (some input const) in a side project (codesandbox) and write some conversion fn. When ready use it in this resolver.
You can also search for some more reliable sequelize (leave graphql alone for a moment) tutorials with 'more working' model mapping.
Next step?
If it is a tree then why not return this as a tree structure - nested nodes/types?

How to update connection metadata in client-side store?

I'm attempting to learn Relay by implementing TodoMVC from scratch.
I can query my data like this which is working well:
query {
allTodos(first: 100) {
totalCount
completedCount
edges {
node {
id
text
completed
}
}
}
}
I got the idea to add the totalCount and completedCount metadata to the connection from here: http://graphql.org/learn/pagination/#end-of-list-counts-and-connections
It's similar in this example: https://github.com/graphql/swapi-graphql/blob/master/src/schema/index.js#L78
Now I am writing a mutation to change the completed field of a Todo given its id.
I gather I will need to return the new completedCount in the mutation payload, but I'm not sure how to implement getConfigs() to update this in the client-side store. I don't have an id for the connection, right? Is there is a flaw in my schema design? Thanks!
Assuming your mutation returns a viewer, you'll need to add the viewer to your fatQuery and getConfigs. I think this tutorial might be helpful. Here's the excerpt relevant to your task:
Adding a Todo is more complex. The reason for this is that we need to
update not only the state of a Todo object that we will create, but
also a connection where it is stored - the count of Todos will change,
as well as the listing of Todo nodes in edges.
import Relay from 'react-relay';
export default class AddTodoMutation extends Relay.Mutation {
static fragments = {
viewer: () => Relay.QL`fragment on ReindexViewer {
id
allTodos {
count,
}
}`
};
getMutation() {
return Relay.QL`mutation{ createTodo }`;
}
getVariables() {
return {
text: this.props.text,
complete: false,
};
}
getFatQuery() {
return Relay.QL`
fragment on _TodoPayload {
changedTodoEdge,
viewer {
id,
allTodos {
count
}
}
}
`;
}
getConfigs() {
return [{
type: 'RANGE_ADD',
parentID: this.props.viewer.id,
connectionName: 'allTodos',
edgeName: 'changedTodoEdge',
rangeBehaviors: {
'': 'prepend',
},
}, {
type: 'FIELDS_CHANGE',
fieldIDs: {
viewer: this.props.viewer.id,
},
}];
}
getOptimisticResponse() {
return {
changedTodoEdge: {
node: {
text: this.props.text,
complete: false,
},
},
viewer: {
id: this.props.viewer.id,
allTodos: {
count: this.props.viewer.allTodos.count + 1,
},
},
};
}
}
In order to perform this mutation, we need some data that might not be
available to the component - the id of viewer object and count of
allTodos connection. Therefore we need to specify fragments for the
mutation same way as we specify them for containers.
Our configs are more complex this time too - we need to add our new
Todo to a connection, so we use RANGE_ADD mutation config. Relay
expects an edge to be passed in payload, not just a Todo, Reindex
provides changedTodoEdge for this. Lastly we need to fetch updated
connection count from the server and for this viewer field is
available for every payload.
In our optimistic update we increment the count of allTodos, so that
we change our “total” display without any delay.

Client-side mutation with RANGE_ADD type doesn't include edge inside request payload

I'm trying to create new object using client-side mutation described below:
import Relay from 'react-relay'
export default class CreateThemeMutation extends Relay.Mutation {
static fragments = {
admin: () => Relay.QL`fragment on Admin { id }`,
};
getMutation() {
return Relay.QL`mutation { createTheme }`
}
getFatQuery() {
return Relay.QL`
fragment on CreateThemePayload {
admin { themes }
themeEdge
}
`
}
getVariables() {
return {
name: this.props.name,
}
}
getConfigs() {
return [{
type: 'RANGE_ADD',
parentName: 'admin',
parentID: this.props.admin.id,
connectionName: 'themes',
edgeName: 'themeEdge',
rangeBehaviors: {
'': 'append',
},
}]
}
}
Root query field admin is quite similar to viewer so this shouldn't be a problem. The problem is I haven't found themeEdge (which I believe should present) within the request payload (admin { themes } is there though):
query: "mutation CreateThemeMutation($input_0:CreateThemeInput!){createTheme(input:$input_0){clientMutationId,...F3}} fragment F0 on Admin{id} fragment F1 on Admin{id,...F0} fragment F2 on Admin{_themes2gcwoM:themes(first:20,query:""){count,pageInfo{hasNextPage,hasPreviousPage,startCursor,endCursor},edges{node{id,name,createdAt},cursor}},id,...F1} fragment F3 on CreateThemePayload{admin{id,...F0,id,...F2}}"
variables: {input_0: {name: "test", clientMutationId: "0"}}
As a result outputFields.themeEdge.resolve inside the server-side mutation never get called and I see this message:
Warning: writeRelayUpdatePayload(): Expected response payload to include the newly created edge `themeEdge` and its `node` field. Did you forget to update the `RANGE_ADD` mutation config?
I've seen similar issue on github. However REQUIRED_CHILDREN isn't my case because the application has requested themes connection already. Am I missing something obvious? Should I paste more info? Thanks.
react-relay version: 0.6.1
I ran into the same issue and eventually solved it by making sure that my equivalent of themeEdge actually existed as an edge in my schema. If you grep your schema for themeEdge, does an object exist?
For reference, here's my edge definition tailored for you:
{
"name":"themeEdge",
"description":null,
"args":[],
"type":{
"kind":"NON_NULL",
"name":null,
"ofType":{
"kind":"OBJECT",
"name":"ThemeEdge",
"ofType":null
}
},
"isDeprecated":false,
"deprecationReason":null
}
and
{
"kind":"OBJECT",
"name":"ThemeEdge",
"description":"An edge in a connection.",
"fields":[{
"name":"node",
"description":"The item at the end of the edge.",
"args":[],
"type":{
"kind":"NON_NULL",
"name":null,
"ofType":{
"kind":"OBJECT",
"name":"Theme",
"ofType":null
}
},
"isDeprecated":false,
"deprecationReason":null
}
Also note that your rangeBehaviors must exactly match the query you use to retrieve your parent object. You can specify multiple queries as follows, which also shows the syntax for when your query contains multiple variables:
{
type: 'RANGE_ADD',
parentName: 'admin',
parentID: this.props.admin.id,
connectionName: 'themes',
edgeName: 'themeEdge',
rangeBehaviors: {
'': 'append',
'first(1).id($adminId)': 'append',
},
}

Rally SDK 2.0 - Multi-type artifact filtering

I'm trying to get a combined record set from User Stories and Defects. I have filters for each that work (e.g. Defect State != Closed and User Story Direct Child Count = 0) but I'm unable to have a combined query or custom query that will work. For example, the following code brings back User Stories but inherently filters out all defects.
I'm sure there are multiple ways of doing this but how do you get a combined result set of multiple types with filters specific for each type? Thanks.
_getData: function(name) {
var deferred = Ext.create('Deft.Deferred');
Ext.create('Rally.data.wsapi.artifact.Store', {
models: ['UserStory', 'Defect'],
pageSize: 2000,
fetch: ['c_MyCustomField', 'ScheduleState', 'PlanEstimate', 'Name'],
filters: [
{ property: 'ScheduleState', operator: '!=', value: 'Accepted' },
function(item){
var dirChildCountIsGood = false;
try
{
if (item.DirectChildrenCount > 0)
dirChildCountIsGood = false;
}
catch(ex) {}
return false;
},
/* or */{ property: 'DirectChildrenCount', operator: '=', value: '0' }
//{ property: 'State', operator: '!=', value: 'Closed' }
],
sorters: [
{ property: 'c_MyCustomField', direction: 'ASC'} // Same field for both User Stories and Defects
],
autoLoad: true,
listeners: {
scope: this,
load: this._onRecordsLoaded
}
});
console.log('Call to WSAPI store complete.');
return deferred;
}
This is an unfortunate weirdness with the artifact endpoint. You can work around it by using a special hidden TypeDefOid attribute in your query to get the various clauses to only apply to the correct types. Longer term we hope to make enhance the WSAPI query language to better support this type of scenario.
Build two filters like so:
var nonClosedDefectsFilter = Rally.data.wsapi.Filter.and([
{
property: 'TypeDefOid',
value: 12345 //replace with your defect typedef oid
},
{
property: 'State',
operator: '!='
value: 'Closed'
}
]);
var leafStoryFilter = Rally.data.wsapi.Filter.and([
{
property: 'TypeDefOid',
value: 23456 //replace with your story typedef oid
},
{
property: 'DirectChildrenCount',
value: 0
}
]);
And then or them together when you pass them to your store at creation time:
Ext.create('Rally.data.wsapi.artifact.Store', {
//other config from above omitted for brevity
filters: [nonClosedDefectsFilter.or(leafStoryFilter)]
});

Resources