How to pass variables into a graphql query? - graphql

I am using the storefront API to create a headless e commerce site. I am having an issue passing variables into the query - where if I hard code the values it seems to work but as soon as I try and pass variables into the query it fails and says
Argument 'lineItems' on InputObject 'CheckoutCreateInput' has an invalid value ([[object, Object]]). Expected type '[CheckoutLineItemInput!]'
I am pretty sure the problem has to do with the passing of the variables becuase I have tried most things - and come to this conclusion.
Here is the query and action function
export const createCheckout = async (items: IProductVariant[]) => {
const query = `
mutation {
checkoutCreate(input:
lineItems: ${items}
}) {
checkout {
id
webUrl
lineItems(first: 5) {
edges {
node {
title
quantity
}
}
}
}
}
}
`;
export const action: ActionFunction = async ({request}) => {
// get the form data from the POST
const formData = await request.formData()
const id = Array.from(formData.getAll('id'))
const quantity = Array.from(formData.getAll('quantity'))
let items = id.map((item , idx) => {
const newObj: IProductVariant = {}
newObj["variantId"] = item as string
newObj["quantity"] = Number(quantity[idx]) as number
return newObj
})
const res = await createCheckout(items)
return {res}
}
I think i need to modify the query to take a variable ?

Hmm.. I'm not sure that's how you call the GraphQL end point.
https://shopify.dev/api/storefront/2022-01/mutations/checkoutCreate
import Shopify from '#shopify/shopify-api';
const client = new Shopify.Clients.Storefront('your-development-store.myshopify.com', storefrontAccessToken);
// define your mutation (note how you define the input type first, then call
// the mutation with the input variable
const query =
`mutation checkoutCreate($input: CheckoutCreateInput!) {
checkoutCreate(input: $input) {
checkout {
id
webUrl
lineItems(first: 5) {
edges {
node {
title
quantity
}
}
}
}
}
}`;
// define the input data (this is from the docs, pass what is needed here)
const input = {
{
"allowPartialAddresses": true,
"buyerIdentity": {
"countryCode": ""
},
"customAttributes": [
{
"key": "",
"value": ""
}
],
"email": "",
"lineItems": [
{
"customAttributes": {
"key": "",
"value": ""
},
"quantity": 1,
"variantId": ""
}
],
"note": "",
"presentmentCurrencyCode": "",
"shippingAddress": {
"address1": "",
"address2": "",
"city": "",
"company": "",
"country": "",
"firstName": "",
"lastName": "",
"phone": "",
"province": "",
"zip": ""
}
};
// this is where you call the API passing in the query and variables
const data = await client.query({
data: {
"query": query,
"variables": {
"input": input,
"queueToken": queueToken
},
},
});

Related

Apollo is not letting me edit an object field because it is readonly, but I cannot just make a copy of it

I am not sure how I should set cart.items to a new array, I have already made a copy of the original cache because I learned Apollo does not let you directly edit the cache, but I am still getting the following error
Error: Cannot assign to read only property 'items' of object '#'
Do I need to make a copy of the items array? And if so how do I go about changing the array on the current objects item field?
Here are my console.logs
You can ignore the typename fields as they are irrelevant to the problem
addItem
{
"__typename": "Cart",
"items": [
{
"__typename": "CartItem",
"name": "Item 3"
},
{
"__typename": "CartItem",
"name": "Item 4"
},
{
"__typename": "CartItem",
"name": "New Item!"
}
]
}
carts
{
"carts": [
{
"__typename": "Cart",
"id": "1",
"items": [
{
"__typename": "CartItem",
"id": "1",
"name": "Item 1"
},
{
"__typename": "CartItem",
"id": "2",
"name": "Item 2"
}
]
},
{
"__typename": "Cart",
"id": "2",
"items": [
{
"__typename": "CartItem",
"id": "3",
"name": "Item 3"
},
{
"__typename": "CartItem",
"id": "4",
"name": "Item 4"
}
]
}
]
}
So it seems you need to remake the items array as well since items is its own gql object type the easiest way to do this was to do it all at once with a map.
Please note the comment as that was an important detail I learned
// IMPORTANT NOTE when updating the cache of a query you must return the
// same fields as the original query even if you aren't using them in the code
const GET_CARTS = gql`
query {
carts{
id
items{
id
name
}}} `;
const MUTATION = gql`
mutation AddItem($input:MutationAddItemInput!) {
addItem(input: $input){
items{
id
name
}
}
}
`;
const { loading, error, data } = useQuery(GET_CARTS)
const [addItem] = useMutation(MUTATION, {
// refetchQueries: [{ query: GET_CARTS }]
update(cache, { data: { addItem } }) {
// addItem is the response of the query of add item function
console.log({ addItem });
// #ts-ignore
let { carts } = cache.readQuery({ query: GET_CARTS });
console.log({ carts })
// make a new array out of the carts array and add the new item to the array if the id of the cart is 2
let newCarts = carts.map((cart: Cart) => {
if (cart.id === "2") {
return { ...cart, items: [...addItem.items] }
} else {
return cart
}
})
console.log({ newCarts });
cache.writeQuery({
query: GET_CARTS,
data: { carts: newCarts }
// data: { carts: [{ id: "1", items: [{ id: "2", name: "an item" }] }] }
})
}
})
And lastly you will call the addItem function from the use mutation hook

Incrementing a value in nested attributes in AWS Lambda and DynamoDB

This is my query to add a new field or increment a nested attribute
const params = {
TableName: process.env.DYNAMODB_GAMES_TABLE,
Key: {
id: gameId
},
UpdateExpression: 'set players.#player.#score = players.#player.#score + :s',
ExpressionAttributeNames: {
'#player': playerId,
'#score': 'score'
},
ExpressionAttributeValues: {
':s': 1
},
ReturnValues: "ALL_NEW"
};
This is the error I get
{
"message": "The document path provided in the update expression is invalid for update",
"code": "ValidationException",
"time": "2020-05-21T03:03:14.328Z",
"requestId": "Q04QEP1G3E2LAM43I04ADLM4IRVV4KQNSO5AEMVJF66Q9ASUAAJG",
"statusCode": 400,
"retryable": false,
"retryDelay": 27.814212380235393
}
My object looks like
{
"id": "09e7a690",
"players": {
"M3EDJeHtoAMCLJg": [
{
"cardId": "1",
"cardTitle": "test",
"pun": "this is a pun"
}
]
}
}

How do I tell Alexa to prompt user for input

I need to tell alexa to prompt user for input then store that input in a variable to be used in my code.
InvocationName: send mail
Alexa: Tell me mail subject
User: Test email
Alexa: Okay, tell me message body.
User: This is just a sample test
Alexa, okay, tell me receiver email
User: test#gmail.com
Below is my intent schema:
{
"interactionModel": {
"languageModel": {
"invocationName": "send mail",
"intents": [
{
"name": "AMAZON.CancelIntent",
"samples": []
},
{
"name": "AMAZON.HelpIntent",
"samples": []
},
{
"name": "AMAZON.StopIntent",
"samples": []
},
{
"name": "AMAZON.FallbackIntent",
"samples": []
},
{
"name": "AMAZON.NavigateHomeIntent",
"samples": []
},
{
"name": "SendMailIntent",
"slots": [
{
"name": "ReceiverEmail",
"type": "AMAZON.SearchQuery"
}
],
"samples": [
"mail",
"send mail"
]
}
],
"types": []
},
"dialog": {
"intents": [
{
"name": "SendMailIntent",
"confirmationRequired": false,
"prompts": {},
"slots": [
{
"name": "ReceiverEmail",
"type": "AMAZON.SearchQuery",
"confirmationRequired": false,
"elicitationRequired": true,
"prompts": {
"elicitation": "Elicit.Slot.838288524310.965699312002"
}
}
]
}
],
"delegationStrategy": "ALWAYS"
},
"prompts": [
{
"id": "Elicit.Slot.838288524310.965699312002",
"variations": [
{
"type": "PlainText",
"value": "Enter subject"
}
]
}
]
}
}
and below is the code I have been able to come up with:
// sets up dependencies
const Alexa = require('ask-sdk-core');
const i18n = require('i18next');
const languageStrings = require('./languageStrings');
const SendMailHandler = {
canHandle(handlerInput) {
const request = handlerInput.requestEnvelope.request;
// var code = this.event.request.intent.slots.code.value;
//console.log(code)
// checks request type
return request.type === 'LaunchRequest'
|| (request.type === 'IntentRequest'
&& request.intent.name === 'SendMailIntent');
},
handle(handlerInput) {
const speechText = 'Ok. Tell me the mail subject'
const response = handlerInput.responseBuilder
.speak(speechText)
.reprompt(speechText) // <--- Here is our reprompt
.getResponse();
console.log(response)
return response;
},
};
// Omitted default Alexa handlers
const skillBuilder = Alexa.SkillBuilders.custom();
exports.handler = skillBuilder
.addRequestHandlers(
SendMailHandler,
)
.lambda();
You should use dialog management with 2 slots.
As I can see currently you only collect one slot (ReceiverEmail) with dialoge management.
But you need also to create a slot for the text you want to send.
Later in your code you need to check if the dialogue is in status COMPLETED.
See the example https://github.com/alexa/skill-sample-nodejs-petmatch/ or this video: https://www.youtube.com/watch?v=u99WMljnQXI

How to populate only with content of getter

I have some problem in mongoose project.
I try to populate and use getter but not all data
But now all virtuals appear in document.
I'm using mongoose.Schema and mongoose.Model
Here is example of my test code
const GroupsSchema = schema({
title: String,
users: [{
type: schema.Types.ObjectId,
ref: 'Users'
}]
});
const UsersSchema = schema({
name: String,
avatar: String
}, {
toJSON: {
virtuals: true
}
});
class Users extends Model {
get name() {
return {
name: this.name
};
}
get avatar() {
return {
avatar: this.avatar
};
}
}
Populating document
const groups = await Groups.find({}).populate('users').exec();
My current result:
[
{
"_id": "5c9bb51626924f0a08aa8c3d",
"title": "GroupName"
"users": [
{
"_id": "5c8e37169fc1f9000f8c333b",
"name": "Jack",
"avatar": "avatar.jpg",
"name": {
"name": "Jack",
},
"avatar": {
"avatar": "avatar.jpg"
}
}
]
}
]
How can I populate document with content of only name getter?
Desired result:
[
{
"_id": "5c9bb51626924f0a08aa8c3d",
"title": "GroupName"
"users": [
{
"name": "Jack"
}
]
}
]

GraphQL resolver with DataLoader and nested endpoints

Adapting the example of GraphQL best practices created by the Apollo Team (https://github.com/apollographql/GitHunt-API/tree/master/api), I'm having hard time to come up with a resolver that would result in a list of Person using DataLoaders.
Here's an example of the api (data from: https://github.com/steveluscher/zero-to-graphql/tree/master/zero-node)
Given the output of /people/ endpoint like:
{
"people": [
{
"username": "steveluscher",
"id": "1",
},
{
"username": "aholovaty",
"id": "2",
},
{
"username": "swillison",
"id": "3",
},
{
"username": "gvr",
"id": "4",
}
]
}
And a person from the endpoint /people/1/
{
"person": {
"last_name": "Luscher",
"username": "steveluscher",
"friends": [
"/people/2/",
"/people/3/"
],
"id": "1",
"email": "steveluscher#fb.com",
"first_name": "Steven"
}
I would like to have a resolver what would give me a list of Person like:
[
{
"person": {
"last_name": "Luscher",
"username": "steveluscher",
"friends": [
"/people/2/",
"/people/3/"
],
"id": "1",
"email": "steveluscher#fb.com",
"first_name": "Steven"
}
},
{
"person": {
"last_name": "Holovaty",
"username": "aholovaty",
"friends": [
"/people/1/",
"/people/4/"
],
"id": "2",
"email": "a.holovaty#django.com",
"first_name": "Adrian"
}
},
...
]
This is what I got so far:
server.js
import { ApiConnector } from './api/connector';
import { People } from './api/models';
import schema from './schema';
export function run() {
const PORT = 3000;
const app = express();
app.use(bodyParser.json());
app.use('/graphql', graphqlExpress((req) => {
const query = req.query.query || req.body.query;
if (query && query.length > 2000) {
throw new Error('Query too large.');
}
const apiConnector = new ApiConnector();
return {
schema,
context: {
People: new People({ connector: apiConnector }),
},
};
}));
app.use('/graphiql', graphiqlExpress({
endpointURL: '/graphql',
}));
const server = createServer(app);
server.listen(PORT, () => {
console.log(`API Server is now running on http://localhost:${PORT}`);
});
return server;
}
models.js
export class People {
constructor({ connector }) {
this.connector = connector;
}
getPeople() {
return this.connector.get(`/people/`);
}
getPerson(id) {
return this.connector.get(`/people/${id}/`);
}
}
connector.js
const API_ROOT = 'http://localhost:8080';
export class ApiConnector {
constructor() {
this.rp = rp;
this.loader = new DataLoader(this.fetch.bind(this));
}
fetch(urls) {
const options = {
json: true,
resolveWithFullResponse: true,
headers: {
'user-agent': 'Request-Promise',
},
};
return Promise.all(urls.map((url) => {
return new Promise((resolve) => {
this.rp({
uri: url,
...options,
}).then((response) => {
const body = response.body;
resolve(body);
}).catch((err) => {
console.error(err);
resolve(null);
});
});
}));
}
get(path) {
return this.loader.load(API_ROOT + path);
}
And the resolver in the schema would have something like:
const rootResolvers = {
Query: {
people(root, args, context) {
return context.People.getPeople();
},
person(root, { id }, context) {
return context.People.getPerson(id)
}
},
};
Until now I can get the first endpoint /people/ and a person from /people/id/. But how to change it to have a list of person? I'm not quite sure how/where should this code be.
Thanks a lot!
You could change your people resolver to something like the code bellow:
const rootResolvers = {
Query: {
people(root, args, context) {
const list = context.People.getPeople();
if (list && list.length > 0) {
return list.map(item => context.People.getPerson(item.id))
}
},
...
},
};
Ps: You said that you are using dataLoader, so i think your API calls is just being cached, but if it is not the case, you need to implement some cache to avoid calling same endpoints a lot times.

Resources