Is it possible to return a random graphql element from a Graphcool backend? If so, how could this be done?
Alternatively, any tips on howto create custom queries for Graphcool backends?
The best way to do this is by using the API Gateway pattern.
The idea of that approach is to put a gateway server on top of Graphcool's CRUD API and thus customize the API. With this approach, you'd write an additional resolver function that retrieves the random element for you:
const extendTypeDefs = `
extend type Query {
randomItem: Item
}
`
const mergedSchemas = mergeSchemas({
schemas: [graphcoolSchema, extendTypeDefs],
resolvers: mergeInfo => ({
Query: {
randomItem: {
resolve: () => {
return request(endpoint, allItemsQuery).then(data => {
const { count } = data._allItemsMeta
const randomIndex = Math.floor((Math.random() * (count-1)) + 0)
const { id } = data.allItems[randomIndex]
return request(endpoint, singleItemQuery, { id }).then(data => data.Item)
})
},
}
},
}),
})
I created a full example of this here. Let me know if you have any additional questions :)
Related
I have records in strapi. I am using strapi content API. In my front-end, I need to display only 2 records randomly. For limiting, I have used limit query from content API. But random fetching what keyword I need to use. The official documentation doesn't provide any details regarding this - https://strapi.io/documentation/v3.x/content-api/parameters.html#available-operators
There's no official Strapi API parameter for random. You have to implement your own. Below is what I've done previously, using Strapi v3:
1 - Make a service function
File: api/mymodel/services/mymodel.js
This will contain our actual random query (SQL), and wrapping it in a service is handy because it can be used in many places (cron jobs, inside other models, etc).
module.exports = {
serviceGetRandom() {
return new Promise( (resolve, reject) => {
// There's a few ways to query data.
// This example uses Knex.
const knex = strapi.connections.default
let query = knex('mydatatable')
// Add more .select()'s if you want other fields
query.select('id')
// These rules enable us to get one random post
query.orderByRaw('RAND()')
query.limit(1)
// Initiate the query and do stuff
query
.then(record => {
console.log("getRandom() record: %O", record[0])
resolve(record[0])
})
.catch(error => {
reject(error)
})
})
}
}
2 - Use the service somewhere, like a controller:
File: api/mymodel/controllers/mymodel.js
module.exports = {
//(untested)
getRandom: async (ctx) => {
await strapi.services.mymodel.serviceGetRandom()
.then(output => {
console.log("getRandom output is %O", output.id)
ctx.send({
randomPost: output
}, 200)
})
.catch( () => {
ctx.send({
message: 'Oops! Some error message'
}, 204) // Place a proper error code here
})
}
}
3 - Create a route that points to this controller
File: api/mymodel/config/routes.json
...
{
"method": "GET",
"path": "/mymodelrandom",
"handler": "mymodel.getRandom",
"config": {
"policies": []
}
},
...
4 - In your front-end, access the route
(However you access your API)
e.g. ajax call to /api/mymodelrandom
There is no API parameter for getting a random result.
So: FrontEnd is the recommended solution for your question.
You need to create a random request range and then get some random item from this range.
function getRandomInt(max) {
return Math.floor(Math.random() * Math.floor(max));
}
const firstID = getRandomInt(restaurants.length);
const secondID = getRandomInt(3);
const query = qs.stringify({
id_in:[firstID,secondID ]
});
// request query should be something like GET /restaurants?id_in=3&id_in=6
One way you can do this reliably is by two steps:
Get the total number of records
Fetch the number of records using _start and _limit parameters
// Untested code but you get the idea
// Returns a random number between min (inclusive) and max (exclusive)
function getRandomArbitrary(min, max) {
return Math.random() * (max - min) + min;
}
const { data: totalNumberPosts } = await axios.get('/posts/count');
// Fetch 20 posts
const _limit = 20;
// We need to be sure that we are not fetching less than 20 posts
// e.g. we only have 40 posts. We generate a random number that is 30.
// then we would start on 30 and would only fetch 10 posts (because we only have 40)
const _start = getRandomArbitrary(0, totalNumberPosts - _limit);
const { data: randomPosts } = await axios.get('/posts', { params: { _limit, _start } })
The problem with this approach is that it requires two network requests but for my needs, this is not a problem.
This seem to work for me with Strapi v.4 REST API
Controller, Get 6 random entries
"use strict";
/**
* artwork controller
*/
const { createCoreController } = require("#strapi/strapi").factories;
module.exports = createCoreController("api::artwork.artwork", ({ strapi }) => {
const numberOfEntries = 6;
return {
async random(ctx) {
const entries = await strapi.entityService.findMany(
"api::artwork.artwork",
{
populate: ["image", "pageHeading", "seo", "socialMedia", "artist"],
}
);
const randomEntries = [...entries].sort(() => 0.5 - Math.random());
ctx.body = randomEntries.slice(0, numberOfEntries);
},
};
});
Route
random.js
"use strict";
module.exports = {
routes: [
{
method: "GET",
path: "/artwork/random",
handler: "artwork.random",
config: {
auth: false,
},
},
],
};
API
http://localhost:1337/api/artwork/random
To match default data structure of Strapi
"use strict";
/**
* artwork controller
*/
const { createCoreController } = require("#strapi/strapi").factories;
module.exports = createCoreController("api::artwork.artwork", ({ strapi }) => {
const numberOfEntries = 6;
return {
async random(ctx) {
const entries = await strapi.entityService.findMany(
"api::artwork.artwork",
{
populate: ["image", "pageHeading", "seo", "socialMedia", "artist"],
}
);
const randomEntries = [...entries]
.sort(() => 0.5 - Math.random())
.slice(0, numberOfEntries);
const structureRandomEntries = {
data: randomEntries.map((entry) => {
return {
id: entry.id,
attributes: entry,
};
}),
};
ctx.body = structureRandomEntries;
},
};
});
There is also a random sort plugin.
https://www.npmjs.com/package/strapi-plugin-random-sort
This seem to work for me with Strapi v4.3.8 and graphql
src/index.js
"use strict";
module.exports = {
register({ strapi }) {
const extensionService = strapi.service("plugin::graphql.extension");
const extension = ({ strapi }) => ({
typeDefs: `
type Query {
randomTestimonial: Testimonial
}
`,
resolvers: {
Query: {
randomTestimonial: async (parent, args) => {
const entries = await strapi.entityService.findMany(
"api::testimonial.testimonial"
);
const sanitizedRandomEntry =
entries[Math.floor(Math.random() * entries.length)];
return sanitizedRandomEntry;
},
},
},
resolversConfig: {
"Query.randomTestimonial": {
auth: false,
},
},
});
extensionService.use(extension);
},
bootstrap({ strapi }) {},
};
graphql query:
query GetRandomTestimonial {
randomTestimonial {
__typename
name
position
location
description
}
}
generate random testimonial on route change/refresh
https://jungspooner.com/biography
anyone here implemented Dialog flow fullfilment on graphql server? How do you handle it? Do you handle fulfillment as a mutation or you implement it as a separate rest endpoint?
I am able to expose my local server using ngrok but I am not sure how to go about setting up the fulfillment. I had separated my DF code from GraphQL code such that the DF module only exposes the methods that handle event and text queries to Dialog flow:
// df/index.js
module.exports={
text: ()=>{
self=module.exports
// ...
return self.getResult()
},
event: ()=>{
self=module.exports
// ...
return self.getResult()
},
getResult:()=>{
//...
return{
query,
response,
cards,
quickReply
}
}
}
Then this is passed through the graphQL context and exposed to the bot.resolver.js module where respective mutations for handling text and events are defined as shown
// schema/resolvers/bot.resolver.js
module.exports={
// Mutation
Mutation:{
sendText: (parent,args,context) => {
const {df}=context;
const response = df.text(args);
return response;
},
sendEvent: (parent,args,context) => {
const {df}=context;
const response = df.event(args);
return response;
},
},
};
The corresponding graphQL types are defined in bot.type.js as shown:
const { gql } = require('apollo-server-express');
module.exports=gql`
type Course {
id:ID
header:String!
image:String!
description:String
price:String!
}
type Option {
value:String!
payload:String
link:String
}
type QuickReply {
text:String!
options:[Option!]!
}
type Bot {
query:String!,
response:String!
cards:[Course!]
quickReply:QuickReply
}
type Mutation {
sendText(text: String!, userId:String!, parameters:String): Bot!
sendEvent(name: String!, userId:String!, parameters:String): Bot!
}
`;
Please advise where I can write the code below that sets up dialog flow fulfillment
dialogflow-fulfillment setup code
😊Surprisingly it was as simple as writing it as a middleware on my graphQl api.
// import the required dependencies
const express = require('express');
const bodyParser = require('body-parser')
const cors = require('cors');
const { ApolloServer, } = require('apollo-server-express');
// do not forget your graphQl schema definition
const schema = require('./schema');
// we shall also need to import the data source.
// I will assume an array for our data source defined as below
const models ={
Course:[
{id:1, name:'Chatbots',}
{id:2, name:'React',}
{id:3, name:'Express',}
{id:4, name:'GraphQl',}
],
Book:[
{id:1, title:'Fundermentals in Chatbots',courseId:1},
{id:2, title:'Express for Newbies',courseId:3},
{id:3, title:'Advanced AI on Bots',courseId:1},
{id:4, title:'GraphQl Simplified',courseId:4},
]
}
// Import the WebhookClient class
const { WebhookClient } = require('dialogflow-fulfillment');
// Do the graphQl gymnastics ... I find Apollo server 2 just on point.
const server = new ApolloServer(schema);
const path='/'
const port = process.env.PORT || 4000
const app = express(); // we will merge express with the Apollo server 2
// do the express gymnastics ...
app.use(path,cors(),bodyParser.json(),)
// **IT'S HERE THAT WE DEFINE DIALOG FLOW'S WEB-HOOK AS A MIDDLEWARE**
app.use('/webhook', async (request,response,next)=>{
const agent = new WebhookClient({ request, response });
const {parameters}=request.body.queryResult;
const course =parameters['course'];
// ... do the database logic here ...
// eg get the title of available text books for the particular course
// I will assume
const {id} = await models.Course.find(({name})=>name ===course)
const books = await model.Book.filter(({courseId})=>courseId===id)
const booksTitleArray = books.map(({title})=>title)
let titleList = booksTitle.Array.toString();
titleList.replace(',', ' , ') // put space btn comas
titleList.replace(/\,(?=[^,]*$)/, "and")
let intentMap = new Map();
const recommendBooks courses=>{
agent.add(`For ${course}, We use the following books: ${titleList}`);
};
intentMap.set('course.recommended.books',recommendBooks);
agent.handleRequest(intentMap);
next();
})
server.applyMiddleware({ app, path });
app.listen(port,()=>{
console.log( `Apollo Server Running on http://localhost:${port}`)
})
I feel like writing an article on this because I tried looking for help almost everywhere in vain. Incase I get the time to do so, I will provide it in the comments.😏😉🤔🤭
Guys, we should not forget the ngrok magic if we are testing from localhost 😁
In Gatsby how can I create a resolver that uses data loaded by the gatsby-source-graphql plugin. I'm having trouble figuring out to query the data inside a resolver. Can this be done? Any advice on what I'm missing would be helpful.
something like this in gatsby-node.js ...
exports.createResolvers = ({ createResolvers }) => {
createResolvers({
Query: {
getStructure: {
type: `Structure`,
async resolve(source, args, context, info) {
// assume gatsby-config.js is configured with gatsby-source-graphql and this node exists
const myGraphQlApiNode = await context.nodeModel.runQuery({
query: {
filter: {
fieldName: { eq: "myGqlApi" }
}
},
type: "GraphQLSource"
});
const someGqlApiData = // query all of type MyGqlApi_SomeTypeFromGqlApi loaded via gatsby-source-graphql
return toStructure(someGqlApiData)
}
}
}
});
};
It may be an unofficial solution
Because gatsby doesn’t provide graphql method in createResolvers but do it on createPages
you can do st like this
in gatsby-node.js
let apiHelperGraphql = null
exports.createPages = async ({ actions, graphql }) => {
// steal it from create Pages
apiHelperGraphql = graphql
}
exports.createResolvers = ({ createResolvers }) => {
// and call it here, do what ever you want
apiHelperGraphql(`same as grapql syntax`)
}
I'm having a real hard time understanding how to implement aor-realtime (trying to use it with firebase; reads only, no write).
The first place I get stuck: This library generates a saga, right? How do I connect that with a restClient/resource? I have a few custom sagas that alert me on errors, but there is a main restClient/resource backing those. Those sagas just handles some side-effects. In this case, I just don't understand what the role of the client is, and how it interacts with the generated saga (or visa-versa)
The other question is with persistence: Updates stream in and the initial set of records is not loaded in one go. Should I be calling observer.next() with each update? or cache the updated records and call next() with the entire collection to-date.
Here's my current attempt at doing the later, but I'm still lost with how to connect it to my Admin/Resource.
import realtimeSaga from 'aor-realtime';
import { client, getToken } from '../firebase';
import { union } from 'lodash'
let cachedToken
const observeRequest = path => (type, resource, params) => {
// Filtering so that only chats are updated in real time
if (resource !== 'chat') return;
let results = {}
let ids = []
return {
subscribe(observer) {
let databaseRef = client.database().ref(path).orderByChild('at')
let events = [ 'child_added', 'child_changed' ]
events.forEach(e => {
databaseRef.on(e, ({ key, val }) => {
results[key] = val()
ids = union([ key ], ids)
observer.next(ids.map(id => results[id]))
})
})
const subscription = {
unsubscribe() {
// Clean up after ourselves
databaseRef.off()
results = {}
ids = []
// Notify the saga that we cleaned up everything
observer.complete();
}
};
return subscription;
},
};
};
export default path => realtimeSaga(observeRequest(path));
How do I connect that with a restClient/resource?
Just add the created saga to the custom sagas of your Admin component.
About the restClient, if you need it in your observer, then pass it the function which return your observer as you did with path. That's actually how it's done in the readme.
Should I be calling observer.next() with each update? or cache the updated records and call next() with the entire collection to-date.
It depends on the type parameter which is one of the admin-on-rest fetch types:
CRUD_GET_LIST: you should return the entire collection, updated
CRUD_GET_ONE: you should return the resource specified in params (which should contains its id)
Here's the solution I came up with, with guidance by #gildas:
import realtimeSaga from "aor-realtime";
import { client } from "../../../clients/firebase";
import { union } from "lodash";
const observeRequest = path => {
return (type, resource, params) => {
// Filtering so that only chats are updated in real time
if (resource !== "chats") return;
let results = {}
let ids = []
const updateItem = res => {
results[res.key] = { ...res.val(), id: res.key }
ids = Object.keys(results).sort((a, b) => results[b].at - results[a].at)
}
return {
subscribe(observer) {
const { page, perPage } = params.pagination
const offset = perPage * (page - 1)
const databaseRef = client
.database()
.ref(path)
.orderByChild("at")
.limitToLast(offset + perPage)
const notify = () => observer.next({ data: ids.slice(offset, offset + perPage).map(e => results[e]), total: ids.length + 1 })
databaseRef.once('value', snapshot => {
snapshot.forEach(updateItem)
notify()
})
databaseRef.on('child_changed', res => {
updateItem(res)
notify()
})
const subscription = {
unsubscribe() {
// Clean up after ourselves
databaseRef.off();
// Notify the saga that we cleaned up everything
observer.complete();
}
};
return subscription;
}
};
}
};
export default path => realtimeSaga(observeRequest(path));
I might be facing a design limitation of Apollo GraphQL server and I'd like to ask if there is a workaround.
My schema contains type Thing, that has field flag. I'd like to be able to filter things by the value of flag, but there is appears to be impossible if this field is resolved separately. The same problem would arise if I wanted to sort things. Here’s an example:
type Thing {
id: String!
flag Boolean!
}
type Query {
things(onlyWhereFlagIsTrue: Boolean): [Thing!]!
}
const resolvers = {
Thing: {
flag: async ({id}) => {
const value = await getFlagForThing(id);
return value;
}
},
Query: {
async things(obj, {onlyWhereFlagIsTrue = false}) {
let result = await getThingsWithoutFlags();
if (onlyWhereFlagIsTrue) {
// ↓ this does not work, because flag is still undefined
result = _.filter(result, ['flag', true]);
}
return result;
}
}
}
Is there any way of filtering things after all the async fields are resolved? I know I can call getFlagForThing(id) inside things resolver, but won't that be just repeating myself? The logic behind resolving flag can be a bit more complex than just calling one function.
UPD: This is the best solution I could find so far. Pretty ugly and hard to scale to other fields:
const resolvers = {
Thing: {
flag: async ({id, flag}) => {
// need to check if flag has already been resolved
// to avoid calling getThingsWithoutFlags() twice
if (!_.isUndefined(flag)) {
return flag;
}
const value = await getFlagForThing(id);
return value;
}
},
Query: {
async things(obj, {onlyWhereFlagIsTrue = false}) {
let result = await getThingsWithoutFlags();
if (onlyWhereFlagIsTrue) {
// asynchroniously resolving flags when needed
const promises = _.map(result, ({id}) =>
getFlagForThing(id)
);
const flags = await Promise.all(promises);
for (let i = 0; i < flags.length; i += 1) {
result[i].flag = flags[i];
}
// ↓ this line works now
result = _.filter(result, ['flag', true]);
}
return result;
}
},
};
I think that the issue here is not really a limitation of Apollo server, and more to do with the fact that you have a primitive field with a resolver. Generally, it's best to use resolvers for fields only when that field is going to return a separate type:
Thing {
id: ID!
flag: Boolean!
otherThings: OtherThing
}
Query {
things(onlyWhereFlag: Boolean): [Thing!]!
}
In this example, it would be fine to have a separate resolver for otherThings, but if a field is a primitive, then I would just resolve that field along with Thing.
Using your original schema:
const filterByKeyValuePair = ([key, value]) => obj => obj[key] === value;
const resolvers = {
Query: {
async things(parent, { onlyWhereFlag }) {
const things = await Promise.all(
(await getThings()).map(
thing =>
new Promise(async resolve =>
resolve({
...thing,
flag: await getFlagForThing(thing)
})
)
)
);
if (onlyWhereFlag) {
return things.filter(filterByKeyValuePair(['flag', true]));
} else {
return things;
}
}
}
};
What if flag wasn't a primitive? Well, if you want to filter by it, then you would have a couple of different options. These options really depend on how you are fetching the "flag" data. I'd be happy to elaborate if you can provide more details about your schema and data models.