Unable to add remote file node to parent node Gatsby - graphql

I'm trying to add File nodes to a gatsby source that doesn't create them automatically (gatsby-source-tumblr in this case). Every node has an array of images associated that I want to download and later convert using Gatsby-Image.
Here's my code so far:
exports.onCreateNode = async ({
node,
actions: { createNode, createNodeField },
store,
cache,
getCache,
createNodeId,
}) => {
if (node.internal.type === `TumblrPost`) {
const pathValue = "/posts/" + node.slug
createNodeField({
node,
name: `path`,
value: pathValue,
})
}
if (
node.internal.type === `TumblrPost` &&
node.photos &&
node.photos.length
) {
await node.photos.map(async photo => {
let fileNode
try {
fileNode = await createRemoteFileNode({
url: photo.original_size.url,
parentNodeId: node.id,
getCache,
createNode,
cache,
createNodeId,
store,
})
// Adds a field `localFile` to the node
// ___NODE appendix tells Gatsby that this field will link to another node
if (fileNode) {
photo.localFile___NODE = fileNode.id
}
} catch (err) {
console.warn(err)
}
})
}
}
As suggested in multiple tutorials I've found, I'm using createRemoteFileNode to download the images and add them as file nodes. This part works, I can see the images being downloaded, and there's a number of items visible in GraphiQL under the allImageSharp category.
However I can't seem to get the association with the parent node to work. Doing something like photo.localFile___NODE = fileNode.id does not seem to have any effect. Experimenting with adding even one image to the parent node (post) doesn't seem to work: When looking at the post node in the GraphiQL inspector, there is no trace of the File node.
I have the feeling I might be missing something obvious, but I've been fiddling with this for hours now and can't seem to figure it out.
Any help would be really appreciated!

I found this answer that helped me fix it. I think the problem was with how I was handling the async loops. I copied the code in which it sets product.images (in that other user's codebase) and adapted it to mine, it worked. I'm guessing the _ await Promise.all_ was the key.
Which is weird to me, because some point I brought in the fantastic async library (something I'm more familiar with than await etc.) to make sure there weren't any issues with the looping, and that didn't work. Ah well, such is life. I'm glad it works now.

Related

Howto add a custom link provider

In the latest release of vscode (1__49), there is a code snippet on creating a new link provider. https://code.visualstudio.com/updates/v1_49. I can't seem to find a reference on where to apply this code.
window.registerTerminalLinkProvider({
provideTerminalLinks: (context, token) => {
// Detect the first instance of the word "test" if it exists and linkify it
const startIndex = (context.line as string).indexOf('test');
if (startIndex === -1) {
return [];
}
// Return an array of link results, this example only returns a single link
return [
{
startIndex,
length: 'test'.length,
tooltip: 'Show a notification',
// You can return data in this object to access inside handleTerminalLink
data: 'Example data'
}
];
},
handleTerminalLink: (link: any) => {
vscode.window.showInformationMessage(`Link activated (data = ${link.data})`);
}
});
What is the process for getting the editor to utilize this feature?
You will need to create a vscode extension that includes your code.
As it so happens, I have just set up a fresh extension that will use the TerminalLinkProvider. You can take a look at how the sample code integrates into a sample extension on GitHub.
A good place to start with your first extension is the official guide.
After that, just add your code to the activate(...) function of your extension.
You can built your extension as a .vsix file and install it in any vscode instance you use, but if you think that your code might be of value to others, consider publishing it!

Go to next page until element found

I'm using Cypress for my automated tests. I'm trying to find a product on a page and click on it. If the product not displayed on the page, go to the next one until it is found.
I've been trying a lot of things already: while loop, each loop, simple cy.get but none of them work. Can anyone help me to solve this?
You'll need a recursive command, implementation of which will depend on your specific scenario. There's no one-size-fits-all solution, but generally it will look something like this:
function findElem ( targetSelector ) {
// first, we need to query a container (could be table, or a generic item
// container). The important thing is, this container must exist on every
// page you'll traverse.
cy.get('.someContainer').then( $container => {
// synchronously find the target element, however you want. In this
// example I use an attribute selector, but you can do whatever you
// want.
if ( $container.find(targetSelector).length ) {
return true;
} else {
return false;
}
}).then( found => {
if ( found ) {
return cy.log(`found elem "${targetSelector}"`);
} else {
// synchronously check if there's a next page/table
if ( Cypress.$('.nextPageButton').length ) {
// when know that there is a next page, click it using cypress
cy.get('.nextPageButton').click();
// here, assert that next page/table is loaded, possibly by
// asserting that current page/table is removed from DOM
// then, recurse
return findElem(targetSelector);
} else {
throw new Error(`Couldn't find elem "${targetSelector}" on any page`);
}
}
});
}
it('test', () => {
findElem(`[data-id="42"]`);
});
The crux of the solution is using a combination of commands to query a container, and synchronous inspection (using jQuery or whatever) to find the actual element. Learn more at Conditional Testing.
For a concrete implementation, you can refer to an older answer I gave to a similar question.

Deleting Apollo Client cache for a given query and every set of variables

I have a filtered list of items based on a getAllItems query, which takes a filter and an order by option as arguments.
After creating a new item, I want to delete the cache for this query, no matter what variables were passed. I don't know how to do this.
I don't think updating the cache is an option. Methods mentionned in Apollo Client documentation (Updating the cache after a mutation, refetchQueries and update) all seem to need a given set of variables, but since the filter is a complex object (with some text information), I would need to update the cache for every given set of variables that were previously submitted. I don't know how to do this. Plus, only the server does know how this new item impact pagination and ordering.
I don't think fetch-policy (for instance setting it to cache-and-network) is what I'm looking for, because if accessing the network is what I want after having created a new item, when I'm just filtering the list (typing in a string to search), I want to stay with the default behavior (cache-only).
client.resetStore would reset the store for all type of queries (not only the getAllItems query), so I don't think it's what I'm looking for either.
I'm pretty sure I'm missing something here.
There's no officially supported way of doing this in the current version of Apollo but there is a workaround.
In your update function, after creating an item, you can iterate through the cache and delete all nodes where the key starts with the typename you are trying to remove from the cache. e.g.
// Loop through all the data in our cache
// And delete any items where the key start with "Item"
// This empties the cache of all of our items and
// forces a refetch of the data only when it is next requested.
Object.keys(cache.data.data).forEach(key =>
key.match(/^Item/) && cache.data.delete(key)
)
This works for queries that exist a number of times in the cache with different variables, i.e. paginated queries.
I wrote an article on Medium that goes in to much more detail on how this works as well as an implementation example and alternative solution that is more complicated but works better in a small number of use cases. Since this article goes in to more detail on a concept I have already explained in this answer, I believe it is ok to share here: https://medium.com/#martinseanhunt/how-to-invalidate-cached-data-in-apollo-and-handle-updating-paginated-queries-379e4b9e4698
this worked for me (requires apollo 2 for cache eviction feature) - clears query matched by regexp from cache
after clearing cache query will be automatically refeteched without need to trigger refetch manually (if you are using angular: gql.watch().valueChanges will perform xhr request and emit new value)
export const deleteQueryFromCache = (cache: any, matcher: string | RegExp): void => {
const rootQuery = cache.data.data.ROOT_QUERY;
Object.keys(rootQuery).forEach(key => {
if (key.match(matcher)) {
cache.evict({ id: "ROOT_QUERY", fieldName: key })
}
});
}
ngrx like
resolvers = {
removeTask(
parent,
{ id },
{ cache, getCacheKey }: { cache: InMemoryCache | any; getCacheKey: any }
) {
const key = getCacheKey({ __typename: "Task", id });
const { [key]: deleted, ...data } = cache.data.data;
cache.data.data = { ...data };
return id;
}
}

Get room/rooms of client [duplicate]

I can get room's clients list with this code in socket.io 0.9.
io.sockets.clients(roomName)
How can I do this in socket.io 1.0?
Consider this rather more complete answer linked in a comment above on the question: https://stackoverflow.com/a/24425207/1449799
The clients in a room can be found at
io.nsps[yourNamespace].adapter.rooms[roomName]
This is an associative array with keys that are socket ids. In our case, we wanted to know the number of clients in a room, so we did Object.keys(io.nsps[yourNamespace].adapter.rooms[roomName]).length
In case you haven't seen/used namespaces (like this guy[me]), you can learn about them here http://socket.io/docs/rooms-and-namespaces/ (importantly: the default namespace is '/')
Updated (esp. for #Zettam):
checkout this repo to see this working: https://github.com/thegreatmichael/socket-io-clients
Using #ryan_Hdot link, I made a small temporary function in my code, which avoids maintaining a patch. Here it is :
function getClient(roomId) {
var res = [],
room = io.sockets.adapter.rooms[roomId];
if (room) {
for (var id in room) {
res.push(io.sockets.adapter.nsp.connected[id]);
}
}
return res;
}
If using a namespace :
function getClient (ns, id) {
return io.nsps[ns].adapter.rooms[id]
}
Which I use as a temporary fix for io.sockets.clients(roomId) which becomes findClientsSocketByRoomId(roomId).
EDIT :
Most of the time it is worth considering avoiding using this method if possible.
What I do now is that I usually put a client in it's own room (ie. in a room whose name is it's clientID). I found the code more readable that way, and I don't have to rely on this workaround anymore.
Also, I haven't tested this with a Redis adapter.
If you have to, also see this related question if you are using namespaces.
For those of you using namespaces I made a function too that can handle different namespaces. It's quite the same as the answer of nha.
function get_users_by_room(nsp, room) {
var users = []
for (var id in io.of(nsp).adapter.rooms[room]) {
users.push(io.of(nsp).adapter.nsp.connected[id]);
};
return users;
};
As of at least 1.4.5 nha’s method doesn’t work anymore either, and there is still no public api for getting clients in a room. Here is what works for me.
io.sockets.adapter.rooms[roomId] returns an object that has two properties, sockets, and length. The first is another object that has socketId’s for keys, and boolean’s as the values:
Room {
sockets:
{ '/#vQh0q0gVKgtLGIQGAAAB': true,
'/#p9Z7l6UeYwhBQkdoAAAD': true },
length: 2 }
So my code to get clients looks like this:
var sioRoom = io.sockets.adapter.rooms[roomId];
if( sioRoom ) {
Object.keys(sioRoom.sockets).forEach( function(socketId){
console.log("sioRoom client socket Id: " + socketId );
});
}
You can see this github pull request for discussion on the topic, however, it seems as though that functionality has been stripped from the 1.0 pre release candidate for SocketIO.

Passport and Passport Local req.isAuthenticated always returns false

I haven't been able to track this down, but for my set up, isAuthenticated always returns false even after a successful login. Here's the passport code:
req.isAuthenticated = function() {
var property = 'user';
if (this._passport && this._passport.instance._userProperty) {
property = this._passport.instance._userProperty;
}
return (this[property]) ? true : false;
};
But in a quick look around I don't see the _userProperty proeprty anywhere in the local strategy (sorry if I didn't look hard enough), so I suppose that might be why it's always returning false?
I'd leave a code sample of my application code, but I feel it's probably easier to have a quick look at the repo for my work in progress:
passport api token sessionless
Ultimately, my goal is to have logout work properly for that boilerplate project (which it currently it doesn't).
I guess you forgot to put: req.login(...) inside passport.authenticate('local', function(...){}).
See here (at the end of the page)
Apologies if my original question is not that useful in the first place, but...
I found that my combination of passport, passport-local, and passport-local-mongoose, a solution was to simply create an invalidation method on my mongoose Schema (that has the passportLocalMongoose "plugged in", and when my /logout route gets hit I essentially remove that user's token. Here's that method:
Account.statics.invalidateUserToken = function(email, cb) {
var self = this;
this.findOne({email: email}, function(err, usr) {
if(err || !usr) {
console.log('err');
}
usr.token = null;
usr.save(function(err, usr) {
if (err) {
cb(err, null);
} else {
cb(false, 'removed');
}
});
});
};
I presume it's more interesting to see this in context so again please feel free to refer to the repo listed in question...hope this helps someone.
Also, if a core from one of the aformentioned libs wants to suggest a better way I'd of course love to refactor my code to make it idiomatic; if not, this approach seemed to work.

Resources