Hi I am trying to learn GraphQL language. I have below snippet of code.
// Welcome to Launchpad!
// Log in to edit and save pads, run queries in GraphiQL on the right.
// Click "Download" above to get a zip with a standalone Node.js server.
// See docs and examples at https://github.com/apollographql/awesome-launchpad
// graphql-tools combines a schema string with resolvers.
import { makeExecutableSchema } from 'graphql-tools';
// Construct a schema, using GraphQL schema language
const typeDefs = `
type User {
name: String!
age: Int!
}
type Query {
me: User
}
`;
const user = { name: 'Williams', age: 26};
// Provide resolver functions for your schema fields
const resolvers = {
Query: {
me: (root, args, context) => {
return user;
},
},
};
// Required: Export the GraphQL.js schema object as "schema"
export const schema = makeExecutableSchema({
typeDefs,
resolvers,
});
// Optional: Export a function to get context from the request. It accepts two
// parameters - headers (lowercased http headers) and secrets (secrets defined
// in secrets section). It must return an object (or a promise resolving to it).
export function context(headers, secrets) {
return {
headers,
secrets,
};
};
// Optional: Export a root value to be passed during execution
// export const rootValue = {};
// Optional: Export a root function, that returns root to be passed
// during execution, accepting headers and secrets. It can return a
// promise. rootFunction takes precedence over rootValue.
// export function rootFunction(headers, secrets) {
// return {
// headers,
// secrets,
// };
// };
Request:
{
me
}
Response:
{
"errors": [
{
"message": "Field \"me\" of type \"User\" must have a selection of subfields. Did you mean \"me { ... }\"?",
"locations": [
{
"line": 4,
"column": 3
}
]
}
]
}
Does anyone know what I am doing wrong ? How to fix it ?
From the docs:
A GraphQL object type has a name and fields, but at some point those
fields have to resolve to some concrete data. That's where the scalar
types come in: they represent the leaves of the query.
GraphQL requires that you construct your queries in a way that only returns concrete data. Each field has to ultimately resolve to one or more scalars (or enums). That means you cannot just request a field that resolves to a type without also indicating which fields of that type you want to get back.
That's what the error message you received is telling you -- you requested a User type, but you didn't tell GraphQL at least one field to get back from that type.
To fix it, just change your request to include name like this:
{
me {
name
}
}
... or age. Or both. You cannot, however, request a specific type and expect GraphQL to provide all the fields for it -- you will always have to provide a selection (one or more) of fields for that type.
Related
I'm new in react-admin and I'm trying to create a new admin panel for my old API.
So when my data provider do API calls it causes me this error:
The response to 'getList' must be like { data : [...] }, but the received data is not an array. The dataProvider is probably wrong for 'getList'
The responses of my old API has various data fields like { 'posts': [] } or { 'users': [] }. How can I use these name of fields instead of { 'data': [] } ?
The 'data' in this case just refers to the type of information that should be retuned, not the name of the object.
Within your API, you can simply return a list in the following form:
const posts = [
{
"id":1,
"name":"post1"
},
{
"id":2,
"name":"post2"
},
];
return JSON.stringify(posts);
Then return that 'posts' object in your response and don't forget to set the expected ContentRange headers.
Not sure what language you are using, but the principle above should be easy enough to follow and apply in any language.
I am currently trying to write a GraphQL Query called recipe which can take a number of optional arguments based on a graphQL input called RecipeSearchInput, and uses the input to find a specific recipe matching the attributes passed.
I am struggling to write the frontend query to be able to be able to take the arguments as an object.
Here's my graphQl schema for graphql input RecipeSearchInput.
input RecipeSearchInput {
_id: ID
title: String
cookTime: Int
prepTime: Int
tools: [String!]
ingredients: [String!]
steps: [String!]
videoURL: String
tags: [String!]
country: String
}
And here's my query written in the frontend to access the my mongodb server through graphql:
// gql query that requests a recipe
export const findOne = obj => {
let requestBody = {
query: `
query {
recipe(recipeInput: ${obj}) {
_id
title
cookTime
prepTime
tools
ingredients
steps
videoURL
tags
country
}
}
`
};
return fetchEndpoint(requestBody);
};
When I wrote my frontend query with a simple object that I knew existed in my database:
const displayData = async () => {
const recipeData = await api.recipe.findOne({
title: "Greek Chicken Skewers"
});
console.log(recipeData);
};
This gives me the following error:
message: "Expected value of type "RecipeSearchInput", found [object, Object]."
The problem I'm seeing is that obj is not formatted properly (in String form) to be received by Graphql as an input. The Graphql query params look like this:
Here is what the working query looks like
What's the best approach for making a query that takes more than one argument? How should I package up the argument in the frontend to please GraphQL?
Thanks in advance, and please let me know if any of this was unclear!
Shawn
I am trying to update my chache after succesfully executing a mutation. Here is my query and mutation:
export const Dojo_QUERY = gql`
query Dojo($id: Int!){
dojo(id: $id){
id,
name,
logoUrl,
location {
id,
city,
country
},
members{
id
},
disziplines{
id,
name
}
}
}`;
export const addDiszipline_MUTATION = gql`
mutation createDisziplin($input:DisziplineInput!,$dojoId:Int!){
createDisziplin(input:$input,dojoId:$dojoId){
disziplin{
name,
id
}
}
}`;
and my mutation call:
const [createDisziplin] = useMutation(Constants.addDiszipline_MUTATION,
{
update(cache, { data: { createDisziplin } }) {
console.log(cache)
const { disziplines } = cache.readQuery({ query: Constants.Dojo_QUERY,variables: {id}});
console.log(disziplines)
cache.writeQuery({
...some update logic (craches in line above)
});
}
}
);
when i execute this mutation i get the error
Invariant Violation: "Can't find field dojo({"id":1}) on object {
"dojo({\"id\":\"1\"})": {
"type": "id",
"generated": false,
"id": "DojoType:1",
"typename": "DojoType"
}
}."
In my client cache i can see
data{data{DojoType {...WITH ALL DATA INSIDE APPART FROM THE NEW DISZIPLINE}}
and
data{data{DisziplineType {THE NEW OBJECT}}
There seems to be a lot of confusion around the client cache around the web. Somehow none of the posed solutions helped, or made any sense to me. Any help would be greatly appreciated.
EDIT 1:
Maybe this can help?
ROOT_QUERY: {…}
"dojo({\"id\":\"1\"})": {…}
generated: false
id: "DojoType:1"
type: "id"
typename: "DojoType"
<prototype>: Object { … }
<prototype>: Object { … }
Edit 2
I have taken Herku advice and started using fragment. however it still seems to not quite work.
My udated code:
const [createDisziplin] = useMutation(Constants.addDiszipline_MUTATION,
{
update(cache, { data: { createDisziplin } }) {
console.log(cache)
const { dojo } = cache.readFragment(
{ fragment: Constants.Diszilines_FRAGMENT,
id:"DojoType:"+id.toString()});
console.log(dojo)
}
}
);
with
export const Diszilines_FRAGMENT=gql`
fragment currentDojo on Dojo{
id,
name,
disziplines{
id,
name
}
}
`;
however the result from console.log(dojo) is still undefined.Any advice?
So I think your actual error is that you have to supply the ID as as a string: variables: {id: id.toString()}. You can see that these two lines are different:
dojo({\"id\":1})
dojo({\"id\":\"1\"})
But I would highly suggest to use readFragment instead of readQuery and update the dojo with the ID supplied. This should update the query as well and all other occurrences of the dojo in all your queries. You can find documentation on readFragment here.
Another trick is as well to simply return the whole dojo in the response of the mutation. I would say people should be less afraid of that and not do to much cache updates because cache updates are implicit behaviour of your API that is nowhere in your type system. That the new disziplin can be found in the disziplins field is now encoded in your frontend. Imagine you want to add another step here where new disziplins have to be approved first before they end up in there. If the mutation returns the whole dojo a simple backend change would do the job and your clients don't have to be aware of that behaviour.
I have a get route on my server-side that responds with two random records from MongoDB. I currently have a couple records hard-wired as excluded records that will never be returned to the client.
app.get("/api/matchups/:excludedrecords", (req, res) => {
const ObjectId = mongoose.Types.ObjectId;
Restaurant.aggregate([
{
$match: {
_id: { $nin: [ObjectId("5b6b5188ed2749054c277f95"), ObjectId("50mb5fie7v2749054c277f36")] }
}
},
{ $sample: { size: 2 } }
]).
This works, but I don't want to hard-wire the excluded records, I want to dynamically pass the ObjectIds from the client side. I want the user to be able to exclude multiple records from the random query. I have an action creator that pushes the ObjectId the user wishes to exclude through a reducer so that it becomes part of the store, and the store is an array that includes all the ObjectIds of the records the user wishes to exclude. Here's my action that fetches the random records, taking the excluded records from the store as an argument:
export function fetchRecords(excludedrecords) {
const excludedarray = JSON.stringify(excludedrecords); // Don't currently have this, but feel like I need to.
const request =
axios.get(`http://localhost:3000/api/matchups/${excludedarray}`);
return {
type: "FETCH_MATCHUP_DATA",
payload: request
};
}
I have the sense I need to stringify the array on the client side and parse it on the server side, but I'm not sure how. I've started something like:
app.get("/api/matchups/:excludedrecords", (req, res) => {
const excludedRecords = JSON.parse(req.params.excludedrecords);
const ObjectId = mongoose.Types.ObjectId;
Restaurant.aggregate([
{
$match: {
_id: { $nin: [excludedRecords] } //
}
},
But how do I get ObjectId() to wrap around each record number that is passed in params? I've tried inserting the number on the client side into a template string, like ObjectId('${excludedrecord}'), which results in me passing an array that looks like what I want, but when it gets stringified and parsed it doesn't quite work out.
Sorry if this question is a bit messy.
First of all you should pass the array of string as a body of the http request and not as a part of the url. You do not pass an array parameter as part of the url or in the query string.
Second, you must transcode the to $nin: [ObjectId(excludedRecord1), ObjectId(excludedRecord2)]
at the server side.
Hope it helps!
I'm in the process of building a community connector and am scratching my head; the documentation states:
getData()
Returns the tabular data for the given request.
Request
#param {Object} request A JavaScript object containing the data
request parameters.
The request parameter contains user provided values and additional
information that can be used to complete the data request. It has the
following structure:
{ "configParams": object, "scriptParams": {
"sampleExtraction": boolean,
"lastRefresh": string }, "dateRange": {
"startDate": string,
"endDate": string }, "fields": [
{
object(Field)
} ] }
I've correctly setup getConfig() (at least, my configurations are requested from the user), but my getData function is not being passed a configParams object. Here's my code.
function getConfig(request) {
var Harvest = HarvestService({
token: getHarvestAuthService().getAccessToken()
});
var accounts = Harvest.accounts.list();
var options = accounts.map(function(account) {
return {
label: account.name,
value: account.id
};
});
var config = {
configParams: [
{
type: 'SELECT_SINGLE',
name: 'harvestAccountId',
displayName: 'Harvest Account ID',
helpText: 'The ID of the Harvest Account to pull data from.',
options: options
}
],
dateRangeRequired: true
};
return config;
}
function getData(request) {
var startDate = request.dateRange.startDate;
var endDate = request.dateRange.endDate;
var accountId = request.configParams.harvestAccountId;
var harvestAuthService = getHarvestAuthService();
var Harvest = HarvestService({
token: harvestAuthService.getAccessToken(),
account: accountId
});
var fieldKeys = request.fields.map(function(field) { return field.name; });
var entries = Harvest.entries.list({
startDate: new Date(startDate),
endDate: new Date(endDate)
});
var rows = entries.map(entryToRow);
return {
schema: request.fields,
rows: rows,
cachedData: false
};
}
When I test/debug, I can select an Account at the config step, the schema is correctly returned, but I get the following exception when I try and add a widget to the report:
Script error message:
TypeError: Cannot read property "harvestAccountId" from undefined.
Script error cause: USER Script
error stacktrace: getData:244
Any advice greatly appreciated.
Found out the problem - the issue was that the value attribute of the option was a number, but it MUST be a string:
https://developers.google.com/datastudio/connector/reference#getconfig
Leaving this here in case anyone else gets stuck on this. Your config select options for your Data Studio Community Connector must have strings for both the label and the value, and nobody will coerce them for you. Fix was this:
var options = accounts.map(function(account) {
return {
label: account.name,
value: account.id + ''
};
});
Usually, request.configParams is undefined when there are no configuration values passed from the user config.
When testing the connector, are you selecting a value in the dropdown for harvestAccountId?
If you plan to share this connector with other users, it might be a good idea to have a default value for harvestAccountId in case the user does not select an option.
You can use Apps Script logging to see the response for getConfig() to ensure that right values are getting passed for options. Then you can also log the request for getData() to have a better understanding of what exactly is getting passed in the request.
Leaving this in case it helps anyone, note that the config params in the UI although they have a placeholder value need to be physically populated to appear in the request. Indeed, if none of these are filled in the configParams value does not appear in the request object.