I want to create a local gatsby plugin to index graphql querys in elastic app search.
My problem is, how I can get the articles with a graphql query as an array.
I need a structure like this:
const documents = [
{
id: 'INscMGmhmX4',
url: 'https://www.youtube.com/watch?v=INscMGmhmX4',
title: 'The Original Grumpy Cat',
body: 'A wonderful video of a magnificent cat.'
},
{
id: 'JNDFojsd02',
url: 'https://www.youtube.com/watch?v=dQw4w9WgXcQ',
title: 'Another Grumpy Cat',
body: 'A great video of another cool cat.'
}
]
This is my query I want to index from Strapi:
const myQuery = `
{
allStrapiKbArticles {
edges {
node {
id
title
content
}
}
}
}
`
Here is my current code of the gatsby-node.js file:
const chunk = require('lodash.chunk');
const report = require('gatsby-cli/lib/reporter');
const AppSearchClient = require ('#elastic/app-search-node');
let activity = report.activityTimer(`Indexing to ElasticSearch`);
exports.onCreateDevServer = async function (
{ graphql },
{ baseUrlFn, apiKey, queries, chunkSize = 1000 }
) {
activity.start();
const client = new AppSearchClient(undefined, apiKey, baseUrlFn)
setStatus(activity, `${queries.length} queries to index`);
const jobs = queries.map(async function doQuery(
{ engineName: engineName, query, transformer = identity, indexConfig },
i
) {
if (!query) {
report.panic(
`failed to index to Elastic. You did not give "query" to this query`
);
}
setStatus(activity, `query ${i}: executing query`);
const result = await graphql((query)
).then(result => console.log(result))
activity.end();
})
}
Error: TypeError: graphql is not a function (const result = await graphql((query))
Related
I have recently migrated from Gatsby v3 to v4 and doing so caused images to not be generated correctly.
I have a simple data structure that has an imageUrl which I am trying to create a node for and then I am trying to create the image with createRemoteFileNode ('gatsby-source-filesystem').
Example Repo: https://github.com/stretch0/gatsby-sandbox
// source nodes
const testNodes = [
{
name: "My Node 1",
imageUrl: "https://images.unsplash.com/photo-1665081661649-8656335a6cbb?ixlib=rb-1.2.1&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=crop&w=1738&q=80"
}
]
const sourceNodes = async ({ actions, createNodeId, createContentDigest }, options) => {
testNodes.forEach((testNode) => {
const node = {
...testNode,
id: createNodeId(`myNode-${testNode.name}`),
}
actions.createNode({
...node,
internal: {
type: 'myNode',
contentDigest: createContentDigest(node),
},
});
})
};
// on create nodes
const { createRemoteFileNode } = require(`gatsby-source-filesystem`);
const onCreateNode = async ({ node, cache, store, actions: { createNode }, createNodeId }) => {
if( node.internal.type === 'myNode') {
const myNode = await createRemoteFileNode({
url: node.imageUrl,
parentNodeId: node.id,
createNode,
createNodeId,
cache,
store,
});
if (myNode) {
myNode.imageData___NODE = myNode.id;
}
}
};
I am defining my schema like so:
module.exports = ({ actions }) => {
const { createTypes } = actions;
const typeDefs = `
type myNode implements Node {
id: String!
name: String!
imageUrl: String!
imageData: File #link(from: "imageData___NODE")
}
`;
createTypes(typeDefs);
};
But when I try query the imageData via graphql it appears the image file hasn't been created. Am I doing something wrong / has something changed between v3 and v4?
My graphql queries work with the local DB file but while trying to connect to MongoDB it doesn't work as expected.
mongo.js
require("dotenv").config();
const mongoose = require("mongoose");
const MONGODB_URI = process.env.MONGODB_URI;
if (!MONGODB_URI) {
throw new Error(
"Please define the MONGODB_URI environment variable inside .env.local"
);
}
/**
* Global is used here to maintain a cached connection across hot reloads
* in development. This prevents connections from growing exponentially
* during API Route usage.
*/
let cached = global.mongoose;
if (!cached) {
cached = global.mongoose = { conn: null, promise: null };
}
async function dbConnect() {
if (cached.conn) {
return cached.conn;
}
if (!cached.promise) {
const opts = {
bufferCommands: false,
};
cached.promise = mongoose.connect(MONGODB_URI, opts).then((mongoose) => {
return mongoose;
});
}
cached.conn = await cached.promise;
return cached.conn;
}
module.exports = dbConnect;
MONGODB_URI=mongodb+srv://xxxx:xxxx#cluster0.yxbw7.mongodb.net/?retryWrites=true&w=majority
graphql query to get all the products:
exports.typeDefs = gql`
type Query {
products: [Product!]!
}
type Product {
id: ID!
name: String!
description: String!
price: Float!
image: String!
}
`;
Resolvers:
const { Product } = require("../models/Product");
exports.Query = {
products: async (parent, args, context) => {
let products = await Product.find({}).exec();
return products;
},
};
The DB has data in it but still making the query,
query{
products {
description
id
name
price
image
}
}
It returns an empty product array,
{
"data": {
"products": []
}
}
Something seems wrong with MongoDB to connect with graphql, the queries work with the local DB file which has a dummy object product data.
What i have set up for my firestore database is one collection called 'funkoPops'. That has documents that are genres of funkoPops, with an array of funkoData that holds all pops for that genre. it looks like this below
I should also note, that the collection funkoPops has hundreds of documents of 'genres' which is basically the funko pop series with the sub collections of funkoData that I web scraped and now need to be able to search through the array field of 'funkoData' to match the name field with the given search parameter.
collection: funkoPops => document: 2014 Funko Pop Marvel Thor Series => fields: funkoData: [
{
image: "string to hold image",
name: "Loki - with helmet",
number: "36"
},
{
image: "string to hold image",
name: "Black and White Loki with Helmet - hot topic exsclusive",
number: "36"
},
{
etc...
}
So how could i run a query in firestore to be able to search in collection('funkoPops'), search through the document fields for name.
I have the ability to search for genres like so, which gives the genre back and the document with the array of data below:
const getFunkoPopGenre = async (req, res, next) => {
try {
console.log(req.params);
const genre = req.params.genre;
const funkoPop = await firestore.collection("funkoPops").doc(genre);
const data = await funkoPop.get();
if (!data.exists) {
res.status(404).send("No Funko Pop found with that search parameter");
} else {
res.send(data.data());
}
} catch (error) {
res.status(400).send(error.message);
}
};
what i am trying to use to search by the field name is below and returns an empty obj:
const getFunkoPopName = async (req, res, next) => {
try {
const name = req.params.name;
console.log({ name });
const funkoPop = await firestore
.collection("funkoPops")
.whereEqualTo("genre", name);
const data = await funkoPop.get();
console.log(data);
res.send(data.data());
} catch (error) {
res.status(400).send(error);
}
};
Any help would be great, thanks!
So the way i went about answering this as it seems from top comment and researching a little more on firebase, you do you have to match a full string to search using firebase queries. Instead, I query all docs in the collection, add that to an array and then forEach() each funkoData. From there i then create a matchArray and go forEach() thru the new funkoData array i got from the first query. Then inside that forEach() I have a new variable in matches which is filter of the array of data, to match up the data field name with .inlcudes(search param) and then push all the matches into the matchArr and res.send(matchArr). Works for partial of the string as well as .includes() matches full and substring. Not sure if that is the best and most efficient way but I am able to query thru over probably 20k data in 1-2 seconds and find all the matches. Code looks like this
try {
const query = req.params.name.trim().toLowerCase();
console.log({ query });
const funkoPops = await firestore.collection("test");
const data = await funkoPops.get();
const funkoArray = [];
if (data.empty) {
res.status(404).send("No Funko Pop records found");
} else {
data.forEach((doc) => {
const funkoObj = new FunkoPop(doc.data().genre, doc.data().funkoData);
funkoArray.push(funkoObj);
});
const matchArr = [];
funkoArray.forEach((funko) => {
const genre = funko.genre;
const funkoData = funko.funkoData;
const matches = funkoData.filter((data) =>
data.name.toLowerCase().includes(query)
);
if (Object.keys(matches).length > 0) {
matchArr.push({
matches,
genre,
});
}
});
if (matchArr.length === 0) {
res.status(404).send(`No Funko Pops found for search: ${query}`);
} else {
res.send(matchArr);
}
}
} catch (error) {
res.status(400).send(error.message);
}
with a little bit of tweaking, i am able to search for any field in my database and match it with full string and substring as well.
update
ended up just combining genre, name, and number searches into one function so that whenver someone searches, the query param is used for all 3 searches at once and will give back data on all 3 searches as an object so that we can do whatever we like in front end:
const getFunkoPopQuery = async (req, res) => {
try {
console.log(req.params);
const query = req.params.query.trim().toLowerCase();
const funkoPops = await firestore.collection("test");
const data = await funkoPops.get();
const funkoArr = [];
if (data.empty) {
res.status(404).send("No Funko Pop records exsist");
} else {
data.forEach((doc) => {
const funkoObj = new FunkoPop(doc.data().genre, doc.data().funkoData);
funkoArr.push(funkoObj);
});
// genre matching if query is not a number
let genreMatches = [];
if (isNaN(query)) {
genreMatches = funkoArr.filter((funko) =>
funko.genre.toLowerCase().includes(query)
);
}
if (genreMatches.length === 0) {
genreMatches = `No funko pop genres with search: ${query}`;
}
// name & number matching
const objToSearch = {
notNullNameArr: [],
notNullNumbArr: [],
nameMatches: [],
numbMatches: [],
};
funkoArr.forEach((funko) => {
const genre = funko.genre;
if (funko.funkoData) {
const funkoDataArr = funko.funkoData;
funkoDataArr.forEach((data) => {
if (data.name) {
objToSearch.notNullNameArr.push({
funkoData: [data],
genre: genre,
});
}
if (data.number) {
objToSearch.notNullNumbArr.push({
funkoData: [data],
genre: genre,
});
}
});
}
});
// find name that includes query
objToSearch.notNullNameArr.forEach((funko) => {
const genre = funko.genre;
const name = funko.funkoData.filter((data) =>
data.name.toLowerCase().includes(query)
);
if (Object.keys(name).length > 0) {
objToSearch.nameMatches.push({
genre,
name,
});
}
});
// find number that matches query
objToSearch.notNullNumbArr.forEach((funko) => {
const genre = funko.genre;
const number = funko.funkoData.filter((data) => data.number === query);
if (Object.keys(number).length > 0) {
objToSearch.numbMatches.push({
genre,
number,
});
}
});
if (objToSearch.nameMatches.length === 0) {
objToSearch.nameMatches = `No funko pops found with search name: ${query}`;
}
if (objToSearch.numbMatches.length === 0) {
objToSearch.numbMatches = `No funko pop numbers found with search: ${query}`;
}
const searchFinds = {
genre: genreMatches,
name: objToSearch.nameMatches,
number: objToSearch.numbMatches,
};
res.send(searchFinds);
}
} catch (error) {
res.status(400).send(error.message);
}
};
If anyone is well suited in backend and knows more about firestore querying, please let me know!
I created the following gatsby node to query 1 record
const axios = require("axios");
exports.sourceNodes = async (
{ actions, createNodeId, createContentDigest },
configOptions
) => {
const { createNode } = actions;
// Gatsby adds a configOption that's not needed for this plugin, delete it
delete configOptions.plugins;
// Helper function that processes a post to match Gatsby's node structure
const processPost = post => {
const nodeId = createNodeId(`gutenberg-post-${post.id}`);
const nodeContent = JSON.stringify(post);
const nodeData = Object.assign({}, post, {
id: nodeId,
parent: null,
children: [],
internal: {
type: `GutenbergPost`,
content: nodeContent,
contentDigest: createContentDigest(post)
}
});
return nodeData;
};
const apiUrl = `http://wp.dev/wp-json/gutes-db/v1/${
configOptions.id || 1
}`;
// Gatsby expects sourceNodes to return a promise
return (
// Fetch a response from the apiUrl
axios
.get(apiUrl)
// Process the response data into a node
.then(res => {
// Process the post data to match the structure of a Gatsby node
const nodeData = processPost(res.data);
// Use Gatsby's createNode helper to create a node from the node data
createNode(nodeData);
})
);
};
My source is a rest API that has the following format:
http://wp.dev/wp-json/gutes-db/v1/{ID}
Currently the gatsby node default ID set is 1
I can query it in graphql by doing this:
{
allGutenbergPost {
edges {
node{
data
}
}
}
}
This will always return record 1
I wanted to add a custom parameter for ID so that I could do this
{
allGutenbergPost(id: 2) {
edges {
node{
data
}
}
}
}
What adjustments should I do with my existing code?
I assume you are creating page programmatically? If so, in the onCreatePage hook, when you do createPage, you can pass in a context object. Anything in there will be available as a query variable.
For example, if you have
createPage({
path,
component: blogPostTemplate,
context: {
foo: "bar",
},
})
Then you can do a page query like
export const pageQuery = graphql`
ExampleQuery($foo: String) {
post(name: { eq: $foo }) {
id
content
}
}
`
If you just want to filter by id, you can check out the docs on filter & comparison operators.
{
allGutenbergPost(filter: { id: { eq: 2 }}) {
edges {
node{
data
}
}
}
}
or
{
gutenbergPost(id: { eq: 2 }) {
data
}
}
Hope it helps!
I've created a function inside gatsby-node.js that creates links between users and locations and publishes them to the correct path based on the info from the markdown files.
Now, I have a local data.json with a survey data for every user that I need to integrate into this function.
I need to import data from the data.json and check - if the currently processed User page has a matching email address in data.json, then add the testimonials to the output data. I'm struggling to find a way of how to integrate this logic into the existing function.
Node.js
const path = require("path");
const { createFilePath } = require("gatsby-source-filesystem");
const data = require("src/data/data.json");
exports.createPages = ({ actions, graphql }) => {
const { createPage } = actions;
return graphql(`
{
allMarkdownRemark(limit: 1000) {
edges {
node {
id
fields {
slug
}
frontmatter {
templateKey
title
location
}
}
}
}
}
`).then(result => {
if (result.errors) {
result.errors.forEach(e => console.error(e.toString()));
return Promise.reject(result.errors);
}
const edges = result.data.allMarkdownRemark.edges;
edges.forEach(edge => {
const id = edge.node.id;
const title = edge.node.frontmatter.title;
const location = edge.node.frontmatter.location || null;
createPage({
path: edge.node.fields.slug,
component: path.resolve(
`src/templates/${String(
edge.node.frontmatter.templateKey,
)}.js`,
),
// query variables are passed in via the context
context: {
id,
title,
location,
},
});
});
});
};
exports.onCreateNode = ({ node, actions, getNode }) => {
const { createNodeField } = actions;
if (node.internal.type === `MarkdownRemark`) {
const value = createFilePath({ node, getNode });
createNodeField({
name: `slug`,
node,
value,
});
}
};
JSON
[
{
"agreed": true,
"email": "alex#test.com"
"testimonials": [
{
"name": "Alex",
"city": "Pedro",
"state": "WA",
"description": "Alex was very patient. He is one of the good guys!"
}
]
}
]
You need to include the email field in the graphql query, inside the frontmatter property from your markdown files. From there you can use data.find(u => u.email == edge.node.frontmatter.social_id) to find the right user, and pass the whole object in the comtext of createPage, or just the testimonials, your call.