Gatsby source plugin only showing last item in array in GraphQL - javascript

When I console log after I run build 6 objects show up in my drinks array. They also show up when I run develop. But when I query graphQL only the last object in my array is available. I am new to gatsby and graphQL so included image just in case my query was off.
Code is from my gatsby-node.js file:
exports.sourceNodes = async (
{ actions, createContentDigest, createNodeId,}
) => {
const NODE_TYPE = "CocktailRecipes";
try{
const response = await fetch(`https://www.thecocktaildb.com/api/json/v1/1/search.php?s=vodka`)
const data = await response.json();
const {drinks} = data;
console.log(drinks)
drinks.forEach((drink) => {
actions.createNode({
...drink,
id: createNodeId(`${NODE_TYPE }-${drink.id}`),
parent:null,
children:[],
internal:{
type:NODE_TYPE,
content:JSON.stringify(drink),
contentDigest: createContentDigest(drink)
}
})
})
}
catch(error){
console.log("ERROR")
console.log(error)
}
}
only one object showing in graphQL
If anyone could help it would be very much appreciated as I've been banging my head on a wall for awhile now. I've done a gatsby clean. I've tried map instead of forEach

I've faced exactly the same issue as you a few months ago and, in my case was that I needed to set a valid internal id for each element to allow GraphQL to create a proper schema for each node if not, the id is overridden in each element and it only takes the last one.
In your case, it seems that some field is wrong, making the following expression invalid:
id: createNodeId(`${NODE_TYPE }-${drink.id}`),
Try debugging more what's receiving and changing it to some hardcoded value. Something like:
id: drink.id,
Keep in mind that, if the ids are different for each node, you don't need to use createNodeId API for debugging purposes (but it's recommended).

Related

Query regarding looping a response data array using async await

I am working with an existing class components based React application and was dumbfounded when I spent almost 3 hours to figure out the root cause of the following code snippet.
In componentDidMount which is async btw, I am calling backend API designed with Node.js using axios like this
(Note state below is in Class Component which I skipped for obvious reasons)
state = {
grades = [];
}
try {
const res = await axios.get(
`/assignments/getStudentAssignment/${this.props.studentId}`
);
const assignments = res.data.assignment.assignments;
assignments.forEach((assignment) => {
this.setState({grades: [...this.state.grades, assignment.grade]})
}))
} catch (err) {
'some logic'
}
Where res.data.assignment.assignments is an array of each student assignments, and from each assignment I am pushing a new grade in grades array state. In console.log using Chrome browser I saw that the grades array had 8 elements but I couldn't view them in console.log, and the length of array was also set to 0.
I consoled assignment within forEach array and also set a counter in order to see that is forEach looping through the entire array or just first element. Console.log only printed first counter number and also first array element
I figured out that since the assignments is an array of promises that are yet to be resolved, I cannot loop through the entire array, and then I changed my code to this
await assignments.forEach((assignment) => {
this.setState({grades: [...this.state.grades, assignment.grade]})
}))
And viola, it worked like a charm but I am confused on a couple of things.
Isn't await supposed to resolve promise res.data.assignment.assignments,
meaning I can loop through resolved array data
Why did forEach work for only first element of
res.data.assignment.assignments if the array was yet to be resolved?
I tried on MDN, by creating a sample array in a promise and then looping through the contents of array in a then chain where each element was printed on console perfectly. Any help will be appreciated
EDIT
I have changed the original code, axios returns an assignment object which also contains an array of assignments
I worked with the following code once again without await, and its working fine. This is the second time, I had trouble with React setState, first time setState wasn't firing for some reason, and then after deleting node_modules and installing all required packages it worked, even though it wasted a day
state = {
grades = [];
}
try {
const res = await axios.get(
`/assignments/getStudentAssignment/${this.props.studentId}`
);
const assignments = res.data.assignment.assignments;
assignments.forEach((assignment) => {
this.setState({grades: [...this.state.grades, assignment.grade]})
}))
} catch (err) {
'some logic'
}

How to Sort/Filter Firebase Realtime Database with Query in Javascript

I'm having a hard time understanding why this simple query is not giving me the expected results.
In my Firebase realtime database my structure looks like this:
Pretty simple, right? Next I have a cloud function that runs whenever I manually change one of the orbs values under any of the users. This is my function:
exports.testTopPlayers2 = functions.database.ref('/TestUsers/{user}/Statistics').onUpdate(_ => {
const allPlayersRef = admin.database().ref('/TestUsers');
const query = allPlayersRef.orderByChild('orbs').limitToLast(2)
return query.on('value', (snap, context) => {
return console.log(snap.val());
})
Also very straightforward I would think. In regular english, what I'm expecting that function to do is: "When any of the orbs values are changed, run this function to sort all of the users by their orbs value (lowest to highest) and take a new DataSnapshot of only the final 2 users' data (The users with the highest number of orbs)".
When I print to the console, I'm expecting to see the data from User3 & User6 because they have the highest number of orbs... but instead it seems to be sorting by the username (User5 & User6) or simply not sorting at all. Here is the log output:
Which clearly does not sort by what I'm defining in my query.
Maybe I'm missing something obvious but I've stared at it long enough... hoping someone can spot a mistake in my function regarding how it's sorting.
I appreciate the help!
You're looking for:
allPlayersRef.orderByChild('Statistics/orbs').limitToLast(2)
Note that you'll also want to use once() instead of on():
return query.once('value').then((snap) => {
return snap.val();
})
You'll also want to remove that console.log around snap.val(), since console.log() doesn't return anything.
Finally: when you call .val() on a snapshot, you're converting it to key-value pairs and lose the ordering information. If you want to maintain the ordering, you'll want to use forEach:
return query.once('value').then((snap) => {
let results = [];
snap.forEach((player) => {
results.push( {key: snap.key, ...snap.val() }
});
return results;
})
Thanks to Frank's thorough answer, I was able to understand what needs to happen and got it working perfectly with minor tweaks.
With the exact modifications suggested by Frank, my output in the Firebase console looked like this:
Which is a lot better than what I was able to produce until now, but it still wasn't showing the exact data I wanted to use.
I wasn't interested in the key of the snap (which is the top-level TestUsers node), but rather I wanted the key of the player which provides the username responsible for the high score.
Next, Instead of getting an [object] for the orb values, I needed the actual value of the orbs, so I had to dive into the children of each player object to get that value.
This is my tweaked cloud function in all it's glory lol:
exports.testTopPlayers2 = functions.database.ref('/TestUsers/{user}/Statistics').onUpdate(_ => {
const allPlayersRef = admin.database().ref('/TestUsers');
const query = allPlayersRef.orderByChild('/Statistics/orbs').limitToLast(2)
return query.once('value').then((snap) => {
let results = [];
snap.forEach((player) => {
let username = player.key
let orbsValue = player.child("Statistics").child("orbs").val()
results.push( {Player: username, Score: orbsValue } )
});
console.log(results)
return results;
})
})
And the result of the Firebase log is this:
This is exactly what I was after and can now proceed :D

TestCafé data driven tests with login

I had written some tests using for loops and recently discovered this handy doc page that describes how to write data-driven tests.
https://devexpress.github.io/testcafe/documentation/recipes/create-data-driven-tests.html
I'm now trying to refactor my tests but running into a problem. The purpose of the test is to log in as a bunch of different accounts, and then verify whether certain page elements exist. (I recognize that this is a heavy hammer to use, but our app has a huge number of permissions and the combinations often get forgotten when developing new features, so this seemed like the quickest way to get at the actual truth of what is on the screen for a real user).
My old tests look something like this:
test('Account manager', async (t) => {
const existingItems = [
[mediaSidePanel.customize, 'Customize'],
[mediaSidePanel.stats, 'Stats'],
[mediaSidePanel.download, 'Download'],
[mediaSidePanel.delete, 'Delete'],
];
const notExistingItems = [
[mediaSidePanel.adminTools, 'Admin Tools'],
];
await t
.useRole(advAccountManager)
.navigateTo(`https://${accountKey}.wistia.io/medias/${mediaHashedID}`);
await Promise.all(existingItems.map(async item => await t
.expect(item[0].exists).ok(`${item[1]} should exist for an Account Manager`)));
await Promise.all(notExistingItems.map(async item => await t
.expect(item[0].exists).notOk(`${item[1]} should not exist for an Account Manager`)));
});
The test works fine except for the obvious problems of having loops in tests: I need to have thorough diagnostic messages to ensure I know which element actually is failing, and worse, if something early in the loop fails, the test ends, and I have no way of knowing if there would have been subsequent failures.
I started trying to refactor this by pulling all of the existing/non-existing items into an array defined in a separate file and wrote this:
import * as dataSet from '../helpers/rolePermissions';
fixture `Advanced Account Manager`
.page `https://app.wistia.io/logout`
.beforeEach(async (t) => {
await t
.useRole(advAccountManager);
});
dataSet.advAccountManager.project.forEach(data => {
test.page `https://${accountKey}.wistia.io/projects/${projectHashedID}`(`Project - ${data.name}`, async t => {
if (data.present) {
await t
.expect(await data.selector.exists).ok(`${data.name} should exist for an Account Manager`);
}
else {
await t
.expect(await data.selector.exists).notOk(`${data.name} should not exist for an Account Manager`);
}
});
});
It works perfectly in that it gets rid of the biggest problem and keeps running the tests even if an earlier one fails. It introduces a much bigger problem, however. This is now considerably slower because it has to log in every single time it iterates through the test. I am already using Roles, as you can see, to try to speed things up, but it is still painfully slow comparatively. I don't want to continue down this refactoring path if it ultimately isn't going to pan out.
Is there a way to have the best of both worlds? Ideally, I would like to do the following:
log in once at the beginning of the fixture
stay on the page without reloading
iterate through all the relevant selectors
continue iterating even if an individual test fails
TestCafe reloads a page before every test to avoid indeterministic behavior caused by mutual influence of tests on one another. However, there is an experimental and undocumented feature that disables this mechanism. In your case, you can try using the fixture.disablePageReloads method as follows:
fixture `Advanced Account Manager`
.page `https://app.wistia.io/logout`
.disablePageReloads
.beforeEach(async (t) => {
await t
.useRole(advAccountManager);
});
Please, use it at your own risk.
Another way is to collect selectors of all non-existing elements into array and check its lenght.
For example:
import { Selector } from 'testcafe';
fixture `Log all requests`
.page`https://devexpress.github.io/testcafe/example/`;
test('Test 1', async t => {
const selectors = [
{
name: 'Remote testing',
selector: Selector('label').withText('Support for testing on remote devices')
},
{
name: 'Reuse JS code',
selector: Selector('label').withText('Re-using existing JavaScript code for testing')
},
{
name: 'Background parallel testing',
selector: Selector('label').withText('Running tests in background and/or in parallel')
}
];
const assertions = await Promise.all(selectors.map(async item => ({ name: item.name, exists: await item.selector.exists })));
const nonExistingItems = assertions.filter(item => !item.exists).map(item => item.name);
await t.expect(nonExistingItems.length).eql(0, `This items should exist: ${nonExistingItems.join(', ')}. `);
});

testcafe - how to assert text contains in html body

I am using testcafe for api testing however our api requires login. Below is my code. I can see json response fine. But i am not sure how to assert on the page.
import { Selector } from 'testcafe';
import Page from './page';
// Page model
const page = new Page();
const url = 'https://myexample.com';
const elementWithIdOrClassName = Selector(value => {
return document.getElementById(value) || document.getElementsByTagName(value);
});
fixture `Test`
.page(url + '/talent/career')
.beforeEach( async t => {
await t
.typeText(page.username, 'gvp50')
.typeText(page.password, 'password')
.click(page.login_button)
});
// Tests
test('Text typing basics', async t => {
await t
.navigateTo(url+'/api/learner/learning_items')
.expect(Selector('html')).contains('learning_items');
});
Testcafe just hangs after i run this code. I tried Selector('body') as well but it doesn't work.
You need to specify what element property (state) you'd like to obtain (verify).
After you selected the entire 'html' element (Selector('html')), specify what property (state) you'd like to access (attributes, childNodes, style, size, etc.). See the DOM Node State topic to learn more.
It looks like you wanted to access the text content as follows:
.expect(Selector('html').textContent).contains('learning_items');
However, such selector usage is unlikely to be the cause of the hang as TestCafe will properly display a message about invalid selector usage. You might want to simplify your test and/or debug it to find what causes the hang.
const cellcomparedata =await Selector('[role="gridcell"]').textContent;
console.log("cellcomparedata is",cellcomparedata);
either you can use this.

Does Gatsby need to rebuild my entire site every time a docpage is updated?

I am creating a Documentation site to hold DocPages for each of my products. Gatsby is building all of my sites as static pages. I have multiple tech writers who are constantly updating and creating new pages. How does Gatsby handle this? Do I have to rebuild my entire site with Gatsby each time something is updated?
gatsby-source-contentful plugin uses the sync API. If you keep the .cache and public folder around the plugin will be able to get only the changed entries and assets and update the Gatsby data. by keeping the .cache should build faster.
depend on you webhook Setup this build process may be triggered many times to workaround that maybe you can add a script that delays the builds and make sure that only one build process is running.
I know it is an old question, but I struggled with this today and it seems noone out there gave a decent post or answer about it, Gatsby v2 still rebuilds everything when you restart gatsby develop. This is a no-go if your build takes 20min on first run. My solution is below.
You could use the cache API to save a hash or updatedAt info of your data, and only call createPage action if the cache doesn't match (the data was updated)
This is how I do it in my project:
exports.createPages = async ({ actions: { createPage }, graphql, cache }) => {
const results = await graphql(`
query GetAllCars {
myNamespaceFromGraphqlSource {
allCars {
id
updatedAt
}
}
}
`);
const createDetailsPage = async (car) => {
const carCache = await cache.get(car.id);
if(carCache != car.updatedAt) {
createPage({
path: `/car_details/${car.id}/`,
component: require.resolve("./src/templates/details.js"),
context: {
id: car.id,
},
});
await cache.set(car.id, car.updatedAt);
}
}
for(let i=0; i < results.data.myNamespaceFromGraphqlSource.allCars.length; i++ ) {
const car = results.data.myNamespaceFromGraphqlSource.allCars[i];
await createDetailsPage(car)
}

Categories

Resources