How not to expose duplicated (normalize?) nodes via GraphQL? - optimization

Given "user has many links" (what means a link was created by a user) DB entities relations, I want to develop API to fetch links along with users so that the returned data does not contain duplicated users.
In other words, instead of this request:
query {
links {
id
user {
id email
}
}
}
that returns the following data:
{
"data": {
"links": [
{
"id": 1,
"user": {
"id": 2,
"email": "user2#example.com"
}
},
{
"id": 2,
"user": {
"id": 2,
"email": "user2#example.com"
}
}
]
}
}
I want to make a request like this (note the "references" column):
query {
links {
id
userId
}
references {
users {
id
email
}
}
}
that returns associated users without duplicates:
{
"data": {
"links": [
{
"id": 1,
"userId": 2
},
{
"id": 2,
"userId": 2
},
],
"references": {
"users": [
{
"id": 2,
"email": "user2#example.com"
}
]
}
}
}
That should reduce amount of data transferred between client and server that adds a bit of speed boost.
Is there ready common implementation on any language of that idea? (Ideally, seeking for Ruby)

It's not a query or server role to normalize data.
there are no such possibilities in GraphQL specs;
server must return all asked fields within queried [response] structure;
... but you can implement some:
standarized (commonly used) pagination (relay style edges/nodes, nodes only or better both);
query [complexity] weights to promote this optimized querying style - separate problem;
reference dictionary field within queried type;
links {
egdes {
node {
id
title
url
authorId
# possible but limited usage with heavy weights
# author {
# id
# email
# }
}
}
pageInfo {
hasNextPages
}
referencedUsers {
id
email
}
}
where:
User has id and email props;
referencedUsers is [User!] type;
node.author is User type;
Normalizing Graphql client, like Apollo, can easily access cached user fields without making separate requests.
You can render (react?) some <User/> component (within <Link /> component) passing node.authorId as an argument like <User id={authorId} />. User component can useQuery hook with cache-only policy to read user props/fields.
See Apollo docs for details. You should implement this for yourself and document this to help/guide API users.

Related

Shopware 6: how to delete all products via admin api

How to delete all products via admin api?
To achieve the goal i try to use the Bulk Payloads | Deleting entities
The doc says:
[...] To delete entities, the payload of an operation contains the IDs. [...]
Questions:
to delete all products i have to read first all product.id's?
or is there a alternative way with a type of "wildcard"?
My current request body (using Postman) ...:
{
"delete-product": {
"entity": "product",
"action": "delete",
"payload": []
}
}
... response with (products remains in db):
{
"extensions": [],
"success": true,
"data": {
"delete-product": {
"extensions": [],
"result": []
}
},
"deleted": [],
"notFound": []
}
EDIT #1
With id's provided...:
...
const obj = {
"delete-products": {
"entity": "product",
"action": "delete",
"payload": [
{"id": "73af65014974440b95450f471b3afed8"},
{"id": "784f25a29e034fad9a416923f964ba8a"}
]
}
}
apiClient.request({
"url": "/_action/sync",
"method": "POST",
obj
})
...
... the request fails in class Symfony\\Component\\Serializer\\Encoder\\JsonDecode with message:
detail: "Syntax error"
Debugging the request, payload is missing (empty content):
What is wrong with the configuration of the /api/_action/sync call?
Indeed, what it means is that you will need a low impacting query to get all product id's, store it into a variable & delete them. Use includes:["id"] filter to just get the ID's.
Here is an example of me deleting some products in Postman.
Request body:
{
"delete-product": {
"entity": "product",
"action": "delete",
"payload": {{gen_dynamic_products}}
}
}
Pre-request script (you'll need to adjust this sightly to get your ID's):
const map = new Array(30).fill(0).map((val, index) => {
return { id: pm.environment.get('gen_product_list_sub_' + index) };
});
pm.variables.set('gen_dynamic_products', JSON.stringify(map));
to delete all products i have to read first all product.id's?
Yes, that is what you'll have to do. This is necessary to maintain the extendibility of the platform. The core or other plugins may react to the deletion of products by subscribing to an entity lifecycle event. This event includes the id of the deleted entity. Hence why it is necessary to explicitly provide the ids of the entities in the first place.

GraphQL query - Query by ID

I have installed the strapi-starter-blog locally and I'm trying to understand how I can query article by ID (or slug). When I open the GraphQL Playground, I can get all the article using:
query Articles {
articles {
id
title
content
image {
url
}
category {
name
}
}
}
The response is:
{
"data": {
"articles": [
{
"id": "1",
"title": "Thanks for giving this Starter a try!",
"content": "\n# Thanks\n\nWe hope that this starter will make you want to discover Strapi in more details.\n\n## Features\n\n- 2 Content types: Article, Category\n- Permissions set to 'true' for article and category\n- 2 Created Articles\n- 3 Created categories\n- Responsive design using UIkit\n\n## Pages\n\n- \"/\" display every articles\n- \"/article/:id\" display one article\n- \"/category/:id\" display articles depending on the category",
"image": {
"url": "/uploads/blog_header_network_7858ad4701.jpg"
},
"category": {
"name": "news"
}
},
{
"id": "2",
"title": "Enjoy!",
"content": "Have fun!",
"image": {
"url": "/uploads/blog_header_balloon_32675098cf.jpg"
},
"category": {
"name": "trends"
}
}
]
}
}
But when I try to get the article using the ID with variable, like here github code in the GraphQL Playground with the following
Query:
query Articles($id: ID!) {
articles(id: $id) {
id
title
content
image {
url
}
category {
name
}
}
}
Variables:
{
"id": 1
}
I get an error:
...
"message": "Unknown argument \"id\" on field \"articles\" of type \"Query\"."
...
What is the difference and why can't I get the data like in the example of the Github repo.
Thanks for your help.
It's the difference between articles and article as the query. If you use the singular one you can use the ID as argument

how to select a single item and get it's relations in faunadb?

I have two collections which have the data in the following format
{
"ref": Ref(Collection("Leads"), "267824207030650373"),
"ts": 1591675917565000,
"data": {
"notes": "voicemail ",
"source": "key-name",
"name": "Glenn"
}
}
{
"ref": Ref(Collection("Sources"), "266777079541924357"),
"ts": 1590677298970000,
"data": {
"key": "key-name",
"value": "Google Ads"
}
}
I want to be able to query the Leads collection and be able to retrieve the corresponding Sources document in a single query
I came up with the following query to try and use an index but I couldn't get it to run
Let(
{
data: Get(Ref(Collection('Leads'), '267824207030650373'))
},
{
data: Select(['data'],Var('data')),
source: q.Lambda('data',
Match(Index('LeadSourceByKey'), Get(Select(['source'], Var('data') )) )
)
}
)
Is there an easy way to retrieve the Sources document ?
What you are looking for is the following query which I broke down for you in multiple steps:
Let(
{
// Get the Lead document
lead: Get(Ref(Collection("Leads"), "269038063157510661")),
// Get the source key out of the lead document
sourceKey: Select(["data", "source"], Var("lead")),
// use the index to get the values via match
sourceValues: Paginate(Match(Index("LeadSourceValuesByKey"), Var("sourceKey")))
},
{
lead: Var("lead"),
sourceValues: Var("sourceValues")
}
)
The result is:
{
lead: {
ref: Ref(Collection("Leads"), "269038063157510661"),
ts: 1592833540970000,
data: {
notes: "voicemail ",
source: "key-name",
name: "Glenn"
}
},
sourceValues: {
data: [["key-name", "Google Ads"]]
}
}
sourceValues is an array since you specified in your index that there will be two items returned, the key and the value and an index always returns the array. Since your Match could have returned multiple values in case it wasn't a one-to-one, this becomes an array of an array.
This is only one approach, you could also make the index return a reference and Map/Get to get the actual document as explained on the forum.
However, I assume you asked the same question here. Although I applaud asking questions on stackoverflow vs slack or even our own forum, please do not just post the same question everywhere without linking to the others. This makes many people spend a lot of time while the question is already answered elsewhere.
You might probably change the Leads document and put the Ref to Sources document in source:
{
"ref": Ref(Collection("Leads"), "267824207030650373"),
"ts": 1591675917565000,
"data": {
"notes": "voicemail ",
"source": Ref(Collection("Sources"), "266777079541924357"),
"name": "Glenn"
}
}
{
"ref": Ref(Collection("Sources"), "266777079541924357"),
"ts": 1590677298970000,
"data": {
"key": "key-name",
"value": "Google Ads"
}
}
And then query this way:
Let(
{
lead: Select(['data'],Get(Ref(Collection('Leads'), '267824207030650373'))),
source:Select(['source'],Var('lead'))
},
{
data: Var('lead'),
source: Select(['data'],Get(Var('source')))
}
)

express-graphql: How to remove external "data" object layer.

I am replacing an existing REST endpoint with GraphQL.
In our existing REST endpoint, we return a JSON array.
[{
"id": "ABC"
},
{
"id": "123"
},
{
"id": "xyz"
},
{
"id": "789"
}
]
GraphQL seems to be wrapping the array in two additional object layers. Is there any way to remove the "data" and "Client" layers?
Response data:
{
"data": {
"Client": [
{
"id": "ABC"
},
{
"id": "123"
},
{
"id": "xyz"
},
{
"id": "789"
}
]
}
}
My query:
{
Client(accountId: "5417727750494381532d735a") {
id
}
}
No. That was the whole purpose of GraphQL. To have a single endoint and allow users to fetch different type/granularity of data by specifying the input in a query format as opposed to REST APIs and then map them onto the returned JSON output.
'data' acts as a parent/root level container for different entities that you have queried. Without these keys in the returned JSON data, there won't be any way to segregate the corresponding data. e.g.
Your above query can be modified to include another entity like Owner,
{
Client(accountId: "5417727750494381532d735a") {
id
}
Owner {
id
}
}
In which case, the output will be something like
{
"data": {
"Client": [
...
],
"Owner": [
...
]
}
}
Without the 'Client' and 'Owner' keys in the JSON outout, there is no way to separate the corresponding array values.
In your case, you can get only the array by doing data.Client on the returned output.

Ember-data hasMany relationship not working (JSONAPIAdapter)

I'm using Ember-Data v.1.13.9 with Ember-CLI v.1.13.8. I'm using the JSONAPIAdapter adapter.
I have a problem with a hasMany relationship. I can see from the Ember inspector that both the main record and the related record are being loaded into the store. However, the relationship doesn't seem to be there since I cannot access the related records details in my template.
models/invoice.js
export default DS.Model.extend(
{
invNum : DS.attr('string'),
created : DS.attr('date', {defaultValue: function() { return new Date(); }}),
clientId : DS.attr('number'),
userId : DS.attr('number'),
details : DS.hasMany('invoice-detail', {async : true}),
});
models/invoice-detail.js
export default DS.Model.extend(
{
invoice : DS.belongsTo('invoice', {async : true}),
detail : DS.attr('string'),
amount : DS.attr('number'),
vat : DS.attr('number'),
});
my JSON data: (URL: /accounts/invoices/1)
{
"data": {
"id": 1,
"attributes": {
"inv-num": "A0011000001",
"created": "November, 01 2000 00:00:00",
"user-id": 2,
"client-id": 14,
"relationships": {
"details": {
"data": [
{
"id": 1,
"type": "invoice-detail"
}
]
}
}
},
"type": "invoice"
},
"included": [
{
"id": 1,
"attributes": {
"amount": 3000,
"detail": "Stage 1 delivery of 3Com Reseller Locator to\r\nFoundation Network LTD",
"vat": 525
},
"type": "invoice-detail"
}
]
}
I've tried accessing the details related array directly:
{{#each model.details as |detail index|}}
{{index}} : {{detail.detail}} £{{detail.amount}} (£{{detail.vat}} vat)
{{/each}}
And by using a controller: invoice/controller.js
export default Ember.Controller.extend({
invoiceDetails : function()
{
var invoice = this.get("model");
var details = invoice.get("details");
Ember.Logger.log("invoiceDetails",invoice,details);
return details;
}.property('model.details'),
});
and
{{#each invoiceDetails as |detail index|}}
{{index}} : {{detail.detail}} £{{detail.amount}} (£{{detail.vat}} vat)
{{/each}}
Neither is providing me with the data that I require.
What am I doing wrong?
A second related issue I am having is that I can't get it to reload data from the server. the {reload:true} makes no difference. Looking at the network traffic I can see that no call to the server is made for second and subsequent visits to this route.
invoice/route.js
export default Ember.Route.extend({
model: function(params) {
return this.store.findRecord('invoice', params.id, { reload: true });
}
})
What I actually want to do here is have one route which retrieves a list of invoices (without the details part - so it's quick to retrieve since I do not need the details on the list page). Then, when I drill down to a specific invoice, make a call to the server to get the full details for that invoice. My plan was to use the shouldReloadRecord function to check if I have details attached to this record or not. If so, use the copy from the store, if not, go to the server and then overwrite the limited "list" record I got initially. As a stepping stone in that direction I figured that just setting {reload:true} in the route would force all requests to go back to the server.
I guess I've misunderstood something somewhere...?