Apollo Server / GraphQL - Properties of Nested Array Returning Null - graphql

Bear with me, I will explain this the best I can. Please let me know if more information is needed, I am trying to keep this as brief as possible.
I am using Apollo Server and the 'apollo-datasource-rest' plugin to access a REST API. When attempting to get the property values from a nested array of objects I get a null response for each field/property. In addition, the array being queried is only showing a single iteration when multiple are available.
The field in question is the 'cores' field within the Rocket type, i.e., launch.rocket.firstStage.cores
I have attempted various ways of mapping through 'cores' (thinking this was what it wanted) with no success.
To keep things short and simple I'm only including the code for the specific issue. All other parts of the query are operating as expected.
You can view the API response I am hitting here: https://api.spacexdata.com/v3/launches/77
schema.js
const { gql } = require('apollo-server');
const typeDefs = gql`
type Query {
singleLaunch(flightNumber: Int!): Launch
}
type Launch {
flightNumber: Int!
rocket: Rocket
}
type Rocket {
firstStage: Cores
}
type Cores {
cores: [CoreFields]
}
type CoreFields {
flight: Int
gridfins: Boolean
legs: Boolean
reused: Boolean
landingType: String
landingVehicle: String
landingSuccess: Boolean
}
`;
module.exports = typeDefs;
Data Source - launch.js
const { RESTDataSource } = require('apollo-datasource-rest');
class LaunchAPI extends RESTDataSource {
constructor() {
super();
this.baseURL = 'https://api.spacexdata.com/v3/';
}
async getLaunchById({ launchId }) {
const res = await this.get('launches', {
flight_number: launchId,
});
return this.launchReducer(res[0]);
}
launchReducer(launch) {
return {
flightNumber: launch.flight_number || 0,
rocket: {
firstStage: {
cores: [
{
flight: launch.rocket.first_stage.cores.flight,
gridfins: launch.rocket.first_stage.cores.gridfins,
legs: launch.rocket.first_stage.cores.legs,
landingType: launch.rocket.first_stage.cores.landing_type,
landingVehicle: launch.rocket.first_stage.cores.landing_vehicle,
landingSuccess: launch.rocket.first_stage.cores.landing_success,
},
],
},
};
}
}
module.exports = LaunchAPI;
resolvers.js
module.exports = {
Query: {
singleLaunch: (_, { flightNumber }, { dataSources }) =>
dataSources.launchAPI.getLaunchById({ launchId: flightNumber }),
},
};
Query
query GetLaunchById($flightNumber: Int!) {
singleLaunch(flightNumber: $flightNumber) {
flightNumber
rocket {
firstStage {
cores {
flight
gridfins
legs
reused
landingType
landingVehicle
landingSuccess
}
}
}
}
}
Expected Result
{
"data": {
"singleLaunch": {
"flightNumber": 77,
"rocket": {
"firstStage": {
"cores": [
{
"flight": 1,
"gridfins": true,
"legs": true,
"reused": true,
"landingType": "ASDS",
"landingVehicle": "OCISLY",
"landSuccess": true,
},
{
"flight": 1,
"gridfins": true,
"legs": true,
"reused": false,
"landingType": "RTLS",
"landingVehicle": "LZ-1",
"landSuccess": true
},
{
"flight": 1,
"gridfins": true,
"legs": true,
"reused": false,
"landingType": "RTLS",
"landingVehicle": "LZ-2",
"landSuccess": true
},
]
}
},
}
}
}
Actual Result (Through GraphQL Playground)
{
"data": {
"singleLaunch": {
"flightNumber": 77,
"rocket": {
"firstStage": {
"cores": [
{
"flight": null,
"gridfins": null,
"legs": null,
"reused": null,
"landingType": null,
"landingVehicle": null,
"landingSuccess": null
}
]
}
},
}
}
}
Any suggestions as to what I am doing wrong here would be greatly appreciated. Again, let me know if more information is needed.
Thank you!

Missing base url
There should be
await this.get( this.baseURL + 'launches'
IMHO there should be a map used within launchReducer to return an array, sth like:
launchReducer(launch) {
return {
flightNumber: launch.flight_number || 0,
rocket: {
firstStage: {
cores: launch.rocket.first_stage.cores.map(core => ({
flight: core.flight,
gridfins: core.gridfins,
legs: core.legs,
landingType: core.landing_type,
landingVehicle: core.landing_vehicle,
landSuccess: core.land_success,
})),
},
},
};
}
.map(core => ({ is for returning object [literal], the same as/shorter version of .map(core => { return {

Related

Cannot pass custom result from resolver to Graphql

I am trying to fetch data with sequelize with an attribute and pass it to graphql.
The result is fine in console but the graphql query is returning null for the attribute field.
my resolver
getUnpayedLessons: async (_, args, { models }) => {
const { Attendance, Student } = models;
return await Attendance.findAll({
include: {
model: Student,
},
where: {
fk_lessonsSerieId: { [Op.is]: null },
},
attributes: ["id", [sequelize.fn("count", sequelize.col("absenceFlag")), "unpayedLessons"]],
group: ["student.id"],
});
},
query
getUnpayedLessons {
id
unpayedLessons
student {
id
firstName
lastName
}
}
schema
type UnpayedLessons {
id: Int
unpayedLessons: Int
student: Student
}
extend type Query {
getUnpayedLessons: [UnpayedLessons]
}
and this is the console.log of the resolver when I run the query
[
attendance {
dataValues: { id: 2, unpayedLessons: 8, student: [student] },
_previousDataValues: { id: 2, unpayedLessons: 8, student: [student] },
_changed: Set {},
_options: {
isNewRecord: false,
_schema: null,
_schemaDelimiter: '',
include: [Array],
includeNames: [Array],
includeMap: [Object],
includeValidated: true,
attributes: [Array],
raw: true
},
]
and from graphql
{
"data": {
"getUnpayedLessons": [
{
"id": 2,
"unpayedLessons": null,
"student": {
"id": 2,
"__typename": "Student"
},
"__typename": "UnpayedLessons"
},
]
}
}
Any idea how I can have unpayedLessons passed to graphql?
To debug this you need to check what is returned from DB, the shape:
const values = await Attendance.findAll({...
console.log( values );
// adapt structure to match query requirements
// finally return
return values;

How to query multiple images in Gatsby from Strapi using Graphql

I have set up a multiple media(images) field called pictures on my project content type on Strapi and I have added 2 projects with pictures containing 4 images each.
I want to query these images in Gatsby using Graphql.
This is my plugins array in gatsby-config.js
plugins: [
`gatsby-plugin-react-helmet`,
{
resolve: `gatsby-source-filesystem`,
options: {
name: `images`,
path: `${__dirname}/src/images`,
},
},
`gatsby-plugin-sharp`,
`gatsby-transformer-sharp`,
{
resolve: `gatsby-plugin-manifest`,
options: {
name: `gatsby-starter-default`,
short_name: `starter`,
start_url: `/`,
background_color: `#663399`,
theme_color: `#663399`,
display: `minimal-ui`,
icon: `src/images/gatsby-icon.png`,
},
},
{
resolve: `gatsby-source-strapi`,
options: {
apiURL: `http://localhost:1337`,
queryLimit: 1000,
contentTypes: [`project`],
},
}]
This is my graphql query on localhost:8000/___graphql
query MyQuery {
allStrapiProject {
nodes {
pictures {
formats {
thumbnail {
childImageSharp {
fluid {
src
}
}
}
}
}
}
}
}
This is the result I am getting
{
"data": {
"allStrapiProject": {
"nodes": [
{
"pictures": [
{
"formats": {
"thumbnail": null
}
},
{
"formats": {
"thumbnail": {
"childImageSharp": {
"fluid": {
"src": "/static/eb8a7ee6108ecc0e6185aced82c3316b/b4216/167f320a448c2d6ff65acf179ee627e2.jpg"
}
}
}
}
},
{
"formats": {
"thumbnail": null
}
},
{
"formats": {
"thumbnail": null
}
}
]
},
{
"pictures": [
{
"formats": {
"thumbnail": null
}
},
{
"formats": {
"thumbnail": null
}
},
{
"formats": {
"thumbnail": null
}
},
{
"formats": {
"thumbnail": null
}
}
]
}
]
}
}
}
All of the thumbnails contain null except for one.
I have tried running 'gatsby clean' and sometimes get the query output to have same image urls in multiple places even though i don't have repeating images on Strapi.
As of now, there is no "official" way to make it happen. But there is a workaround which creates a custom node in the build process. For a graphql query like below
query MyQuery {
allStrapiPortfolio {
edges {
node {
category {
images {
localFile {
childImageSharp {
fluid {
base64
tracedSVG
srcWebp
srcSetWebp
originalImg
originalName
}
}
}
}
}
}
}
}
}
The code given below creates the localFile node after images. The code should go in gatsby-node.js.
const { createRemoteFileNode } = require(`gatsby-source-filesystem`);
exports.onCreateNode = async ({ node, actions, store, cache }) => {
const { createNode, createNodeField } = actions;
if (node.internal.type !== null && node.internal.type === "StrapiPortfolio") {
for (const category of node.category) {
for (const image of category.images) {
console.log(image);
const fileNode = await createRemoteFileNode({
url: "http://localhost:1337" + image.url,
store,
cache,
createNode,
createNodeId: (id) => image.id.toString(),
});
if (fileNode) {
image.localFile___NODE = fileNode.id;
}
}
}
}
};
Please note that you will have to customize the code depending on your needs. In my solution, I used two for loops because of my data structure. If you're unsure or just want to check if your custom code works, you can simply add a console.log(node) before the first if statement and a console.log(image) after the second for loop(in my case). That should give you an indication about your data structure and in which way you should proceed.
You need to create a localFile___NODE.
First, you need to edit gatsby-node.js file.
const { createRemoteFileNode } = require(`gatsby-source-filesystem`)
exports.onCreateNode = async ({
node,
actions,
store,
cache,
createNodeId,
}) => {
const { createNode } = actions
// replace ".sliderHome" for the name of multiple media in Strapi CMS
let sliderImages = node.sliderHome
// replace “StrapiHome” for your node type
if (node.internal.type === "StrapiHome") {
if (sliderImages.length > 0) {
// sliderImages.forEach(el => console.log(el))
const images = await Promise.all(
sliderImages.map(el =>
createRemoteFileNode({
url: `http://localhost:1337${el.url}`,
parentNodeId: node.id,
store,
cache,
createNode,
createNodeId,
})
)
)
sliderImages.forEach((image, i) => {
image.localFile___NODE = images[i].id
})
}
}
}
later restart Gatsby and now this is your query
query MyQuery {
allStrapiProject {
nodes {
pictures {
localFile{
childImageSharp{
fluid(maxWidth: 1200){
// or for gatsby use ...GatsbyImageSharpFluid_withWebp
src
}
}
}
}
}
}
}
this has worked for me to bring multiple images with a good quality I hope it works for you
Try below, replace the value you need to display:
Here I am the example for the user avatar
query MyQuery {
allStrapiUser {
edges {
node {
id
avatar {
publicURL
childImageSharp {
fluid {
src
aspectRatio
}
}
}
}
}
}
}
and:
const poster = data.allStrapiUser.edges[0].node
<Img fluid={{aspectRatio: 1.6, src: poster.avatar.publicURL}}/>

How to create a new key and add or append an array of objects on AWS DynamoDB

I have this object in my dynamodb table which looks like
{
"id": "b31de483"
}
I want to add another object that looks like
{
"id": "b31de483",
"players": [{"playerId": "1234"}]
}
This is my code
const addPlayerToGame = async (gameId, playerId) => {
const params = {
TableName: process.env.DYNAMODB_GAMES_TABLE,
Key: {
id: gameId
},
UpdateExpression: 'set players = list_append(if_not_exists(players, :players)',
ExpressionAttributeValues: {
':players': {
"L": [
{ "S": playerId }
]
}
}
};
return await documentClient.update(params);
}
This throws an error but I cannot understand how to fix it. I am looking at documentation here
Figured it out
const addPlayerToGame = async (gameId, playerId) => {
const params = {
TableName: process.env.DYNAMODB_GAMES_TABLE,
Key: {
id: gameId
},
UpdateExpression: 'set players = list_append(players, :players)',
ExpressionAttributeValues: {
':players': [{
"playerId": playerId
}]
}
};
return await documentClient.update(params).promise();
}

Is there a way to filter json data format field in strapi?

Hi Guys I'm trying to filter post with data json format field?
"categoryList": ["cat", "cat1"]
For anyone still looking for a solution, this is what I have done for a json type field called tags of a collection type called Articles.
I have two articles in the database with one article having the following values set:
title: "lorem ipsum 1",
tags: [
"test",
"rest"
]
The other article has the following values set:
title: "lorem ipsum 2",
tags: [
"test",
"graphql"
]
My graphql query looks like this:
query {
articlesByTag(limit: 2, where: {tags_include: ["test", "rest"]}, start: 0, sort: "title:asc") {
title,
tags
}
}
While my rest query looks like this:
http://localhost:1337/articlesByTag?limit=2&tags_include[]=test&tags_include[]=rest
This is my articles.js service file:
const { convertRestQueryParams, buildQuery } = require('strapi-utils');
const _ = require('lodash');
const { convertToParams, convertToQuery } = require('../../../node_modules/strapi-plugin-graphql/services/utils');
module.exports = {
async findByTag(ctx) {
let tags_include;
if (ctx.where && ctx.where.tags_include && ctx.where.tags_include.length > 0) {
tags_include = ctx.where.tags_include;
delete ctx.where.tags_include;
} else if (ctx.query && ctx.query.tags_include && ctx.query.tags_include.length > 0) {
tags_include = ctx.query.tags_include;
delete ctx.query.tags_include;
}
if (!Array.isArray(tags_include)) {
tags_include = [tags_include];
}
let filters = null;
if (ctx.query) {
filters = convertRestQueryParams({
...convertToParams(ctx.query)
});
} else {
filters = convertRestQueryParams({
...convertToParams(_.pick(ctx, ['limit', 'start', 'sort'])),
...convertToQuery(ctx.where),
});
}
const entities = await strapi.query('articles').model.query(qb => {
buildQuery({ model: strapi.query('articles').model, filters: filters })(qb);
if (tags_include.length > 0) {
tags_include.forEach((tag) => {
if (tag && tag.length > 0) {
const likeStr = `%"${tag}"%`;
qb.andWhere('tags', 'like', likeStr);
}
});
}
}).fetchAll();
return entities;
},
};
This is the entry needed in routes.js
{
"method": "GET",
"path": "/articlesByTag",
"handler": "articles.findByTag",
"config": {
"policies": []
}
}
This is the controller articles.js
const { sanitizeEntity } = require('strapi-utils');
module.exports = {
async findByTag(ctx) {
const entities = await strapi.services.articles.findByTag(ctx);
return entities.map(entity => sanitizeEntity(entity, { model: strapi.models.articles }));
},
};
And finally this is the schema.graphql.js
module.exports = {
query: `
articlesByTag(sort: String, limit: Int, start: Int, where: JSON): [Articles]
`,
resolver: {
Query: {
articlesByTag: {
description: 'Return articles filtered by tag',
resolverOf: 'application::articles.articles.findByTag',
resolver: async (obj, options, ctx) => {
return await strapi.api.articles.controllers.articles.findByTag(options);
},
},
},
},
};
There is not currently a way to filter the JSON fields yet as of beta.17.8 (latest)
Probably something like that?
strapi.query('cool_model').find({ categoryList: { $all: [ "cat" , "cat1" ] } })

How to concat array of observables into one. throw error Property 'pipe' does not exist on type 'Observable<Message>[]'

angular v 6.1.10
typescript v 2.9.2
rxjs v 6.3.3
ng2-stmompjs v 7.0.0
I am using ng2-stomp library for web sockets which create observable of will initiate a subscription which is observable. In my requirements, I am creating multiple channel subscriptions based on application id and now want to subscribe all these channels all in once or we can say higher order observable so tried to use the various rxjs operator merge, mergeAll, concat but nothing works so far. Here is what I have done so far.
Right now this one is working
appList = [{appID: '123'}, {appID: '345'}];
const appList$ = appList.map((appID: string, idx: number) => {
const headers = Object.assign({}, this.headers, { id: `app_${idx}` });
const watcher = this.rxStompService.watch(`/topic/${appID}`, headers);
console.log({ watcher }); // This is observable
return watcher;
});
appList$.forEach((app$) => {
app$.subscribe((message: Message) => {
const notification: Notification = JSON.parse(message.body);
this.totalNotificationCount++;
if (Object.keys(notification).length) {
this.notificationMessages.push(notification);
}
});
});
{
"watcher": { "_isScalar": false, "source": { "source": { "_isScalar": false } }, "operator": { "connectable": { "source": { "_isScalar": false } } } }
}
BUT I think we can concat all observables in one and can subscribe all. Note that I am unable to use ForkJoin because appList is dynamic and so the number of WebSocket. followings are my trail to convert multiple observable into once.
Trial 1: using concat and map operator
const batch = appList.map((appID, idx) => {
console.log({ appID, idx });
const headers = Object.assign({}, this.headers, { id: `app_${idx}` });
const watcher = this.rxStompService.watch(`/topic/${appID}`, headers);
return watcher;
});
concat(...batch).pipe( map (i => i)).subscribe({ });
this gives error:
Property 'pipe' does not exist on type 'MonoTypeOperatorFunction'.
trial 2: use subscribe all after concat
concat(...batch).subscribe({
next: (v: any) => console.log(v),
complete: () => console.log('Complete')
});
Error: Property 'subscribe' does not exist on type 'MonoTypeOperatorFunction'.
Trail 3: using pipe
const appList$ = appList.map((appID: string, idx: number) => {
const headers = Object.assign({}, this.headers, { id: `app_${idx}` });
const watcher = this.rxStompService.watch(`/topic/${appID}`, headers);
return watcher;
});
console.log({ appList$ });
appList$.pipe(
takeUntil(this.ngUnsubscribe),
tap((i) => {
console.log('tapping', i);
})
);
console.log({appList$}) return this
{
"appList$": [
{
"_isScalar": false,
"source": {
"source": {
"_isScalar": false
}
},
"operator": {
"connectable": {
"source": {
"_isScalar": false
}
}
}
},
{
"_isScalar": false,
"source": {
"source": {
"_isScalar": false
}
},
"operator": {
"connectable": {
"source": {
"_isScalar": false
}
}
}
},
{
"_isScalar": false,
"source": {
"source": {
"_isScalar": false
}
},
"operator": {
"connectable": {
"source": {
"_isScalar": false
}
}
}
},
{
"_isScalar": false,
"source": {
"source": {
"_isScalar": false
}
},
"operator": {
"connectable": {
"source": {
"_isScalar": false
}
}
}
},
{
"_isScalar": false,
"source": {
"source": {
"_isScalar": false
}
},
"operator": {
"connectable": {
"source": {
"_isScalar": false
}
}
}
},
{
"_isScalar": false,
"source": {
"source": {
"_isScalar": false
}
},
"operator": {
"connectable": {
"source": {
"_isScalar": false
}
}
}
}
]
}
Error: Property 'pipe' does not exist on type 'Observable[]'
So my question is how to merge all observable into once and subscribe in once
This is amazing; whenever I write the question here and try again and I found the solution myself.
I have solved this way using from and mergeMap and thanks to this angular in depth article
private watchApplications(appList: string[]) {
const appList$ = from(appList).pipe(
mergeMap((appID, idx) => {
const headers = Object.assign({}, this.headers, { id: `app_${idx}` });
const watcher = this.rxStompService.watch(`/topic/${appID}`, headers);
return watcher;
})
);
appList$
.pipe(
takeUntil(this.ngUnsubscribe),
tap((f: Frame) => {
console.log('tapping Frame', f);
})
)
.subscribe((message: Message) => {
const notification: Notification = JSON.parse(message.body);
console.log({ notification });
this.totalNotificationCount++;
if (Object.keys(notification).length) {
this.notificationMessages.push(notification);
}
});
}

Resources