Query unknown data structure in GraphQL - graphql

I just started to work with GraphQL and I am setting up a server with webonyx/graphql-php at the moment. Since a GraphQL query already has to contain the resulting data structure, I am not quite sure how to get dynamic data. Assumed that I query the content which consists different element types and my final structure should look like this:
{
"data": {
"dataset": {
"uuid": "abc...",
"insertDate": "2018-05-04T12:12:12Z",
// other metadata
"content": [
{
"type": "headline",
"text": "I am a headline"
},
{
"type": "image",
"src": "http://...",
"alt": "I am an image"
},
{
"type": "review",
"rating": 3,
"comment": "I am a review"
},
{
"type": "headline",
"text": "I am another headline"
}
// other content elements
]
}
}
}
How could I write a query for this example?
{
dataset {
uuid
insertDate
content {
????
}
}
}
And how would a type definition for the content section look like? There is a defined set of element types (headline, image, review, many more) but their order and number of elements is unknown and they have only one field, type, in common. While writing the query in my frontend, I don't know anything about the content structure. And what would the graphql-php type definition for the content section look like? I couldn't find any similar example online, so I am not sure if it is even possible to use GraphQL for this use case. As an extra information, I always want to query the whole content section, not a single element or field, always everything.

When you're returning an array of Object types, but each individual item could be one of any number of different Object types, you can use either an Interface or a Union. We can use an Interface here since all the implementing types share a field (type).
use GraphQL\Type\Definition\InterfaceType;
use GraphQL\Type\Definition\Type;
$content = new InterfaceType([
'name' => 'Content',
'description' => 'Available content',
'fields' => [
'type' => [
'type' => Type::nonNull(Type::string()),
'description' => 'The type of content',
]
],
'resolveType' => function ($value) {
if ($value->type === 'headline') {
return MyTypes::headline();
} elseif ($value->type === 'image') {
return MyTypes::image();
} # and so on
}
]);
Types that implement the Interface need to do so explicitly in their definition:
$headline = new ObjectType([
# other properties
'interfaces' => [
$content
]
]);
Now if you change the type of the content field to a List of content, you can query only fields specific to each implementing type by using inline fragments:
query GetDataset {
dataset {
uuid
insertDate
content {
type # this field is shared, so it doesn't need an inline fragment
... on Headline {
text
}
... on Image {
src
alt
}
# and so on
}
}
}
Please see the docs for more details.

Related

Match keys with sibling object JSONATA

I have an JSON object with the structure below. When looping over key_two I want to create a new object that I will return. The returned object should contain a title with the value from key_one's name where the id of key_one matches the current looped over node from key_two.
Both objects contain other keys that also will be included but the first step I can't figure out is how to grab data from a sibling object while looping and match it to the current value.
{
"key_one": [
{
"name": "some_cool_title",
"id": "value_one",
...
}
],
"key_two": [
{
"node": "value_one",
...
}
],
}
This is a good example of a 'join' operation (in SQL terms). JSONata supports this in a path expression. See https://docs.jsonata.org/path-operators#-context-variable-binding
So in your example, you could write:
key_one#$k1.key_two[node = $k1.id].{
"title": $k1.name
}
You can then add extra fields into the resulting object by referencing items from either of the original objects. E.g.:
key_one#$k1.key_two[node = $k1.id].{
"title": $k1.name,
"other_one": $k1.other_data,
"other_two": other_data
}
See https://try.jsonata.org/--2aRZvSL
I seem to have found a solution for this.
[key_two].$filter($$.key_one, function($v, $k){
$v.id = node
}).{"title": name ? name : id}
Gives:
[
{
"title": "value_one"
},
{
"title": "value_two"
},
{
"title": "value_three"
}
]
Leaving this here if someone have a similar issue in the future.

Is there any way to query products using dynamic filters in Magento 2.3 graphql endpoint?

I was looking at the new graphql endpoint exposed by Magento 2.3.1 and above at the query for listing products:
query products(
pageSize:6,
currentPage:1,
filter:{
category_id: { eq: "3" }
}
) {
filters {
name
request_var
filter_items_count
filter_items {
label value_string
}
}
items {
id
name
small_image {
url
}
# ...
}
# ...
}
The response body yields products in the items property just as expected and a bunch of custom filters in an array in the filters property which look like this:
"filters": [
{
"name": "Activity",
"request_var": "activity",
"filter_items_count": 12,
"filter_items": [
{
"label": "Outdoor",
"value_string": "5"
},
{
"label": "Yoga",
"value_string": "8"
},
{
"label": "Recreation",
"value_string": "9"
},
// rest of filter values
]
},
// rest of filters
]
Given the fact that those filters are dynamic and user defined is there a way of sending them back with a list products query in graphql? I would expect to have a property somewhere under the products query that could be an array of aforementioned filter objects but so far I haven't found anything neither in the schema nor in the official documentation.
Did anyone have any similar experience with this?
No. Theres no way of getting the filtered options sent back. There are options where you can return the additional filtering abilities (similar to when you are viewing a category page) to return list of ways to filter but nothing that returns the current active filters in place.
{
products(filter: {sku: {like: "%"} } pageSize: 500) {
filters{
request_var
name
filter_items{
label
value_string
}
}
items {
id
sku
name
...
}
}
}

GraphQL: Explore API without a wildcard (*)?

I am new to GraphQL and I wonder how I can explore an API without a possible wildcard (*) (https://github.com/graphql/graphql-spec/issues/127).
I am currently setting up a headless Craft CMS with GraphQL and I don't really know how my data is nested.
Event with the REST API I have no chance of just getting all the data, because I have to setup all the endpoints and therefore I have to know all field names as well.
So how could I easily explore my CraftCMS data structure?
Thanks for any hints on this.
Cheers
merc
------ Edit -------
If I use #simonpedro s suggestion:
{
__schema {
types {
name
kind
fields {
name
}
}
}
}
I can see a lot of types (?)/fields (?)...
For example I see:
{
"name": "FlexibleContentTeaser",
"kind": "OBJECT",
"fields": [
{
"name": "id"
},
{
"name": "enabled"
},
{
"name": "teaserTitle"
},
{
"name": "text"
},
{
"name": "teaserLink"
},
{
"name": "teaserLinkConnection"
}
]
But now I would like to know how a teaserLink ist structured.
I somehow found out that the teaserLink (it is a field with the type Entries, where I can link to another page) has the properties url & title.
But how would I set up query to explore the properties available within teaserLink?
I tried all sorts of queries, but I am always confrontend with messages like this:
I would be really glad if somebody could give me another pointer how I can find out which properties I can actually query...
Thank you
As far as I'm concerned currently there is no graphql implementation with that capability. However, if what you want to do is to explore the "data structure", i.e, the schema, you should use schema instrospection, which was thought for that (explore the graphql schema). For example, a simple graphql instrospection query would be like this:
{
__schema {
types {
name
kind
fields {
name
}
}
}
}
References:
- https://graphql.org/learn/introspection/
UPDATE for edit:
What you want to do I think is the following:
Make a query like this
{
__schema {
types {
name
kind
fields {
name
type {
fields {
name
}
}
}
}
}
}
And then find the wished type field to grab more information (the fields) from it. Something like this (I don't know if this works, just an idea):
const typeFlexibleContentTeaser = data.__schema.types.find(t => t === "FlexibleContentTeaser")
const teaserLinkField = typeFlexibleContentTeaser.fields.find(f => f.name === "teaserLink")
const teaserLinkField = teaserLinkField.type.fields;
i.e, you have to transverse recursively through the type field.

Send multiple variable to laravel Api resources

I want to different type of collection..One containing users acros the city and another one containing near by users. I want these from single api hit. is it possible ? If yes then please suggest how to do that.
Waht I did
return ServiceProviderCollection::collection($near_by);
Output:
"data": [
{
"username": "??",
"email": "??",
"rating": 0,
"role_id": 2,
"wallet": "0"
}
],
I want
return ServiceProviderCollection::collection($near_by,$across_city);
expected output:
{
"across_city": {
"data": [
{
"username": "??",
"email": "??",
}
],
},
"near_by": {
"data": [
{
"username": "??",
"email": "??",
}
],
}
}
No, you can't pass 2 objects in Resource. You can do it like this
return [
'across_city' => ServiceProviderCollection::collection($across_city),
'near_by' => ServiceProviderCollection::collection($near_by)
];
Edit: After comment
If you want to show pagination information then you have to create separate controller action and then return ServiceProviderCollection::collection then you will get result with pagination meta information.
create these action in your controller ex. (UserController)
public function acrossCity(){
$acrossCity = User::where('city','test')->paginate(10); //example
return ServiceProviderCollection::collection($acrossCity);
}
public function nearBy(){
$nearBy = User::where('near','1')->paginate(10); //example
return ServiceProviderCollection::collection($nearBy);
}
create routes for these
Route::get('user/acrossCity','UserController#acrossCity');
Route::get('user/nearBy','UserController#nearBy');
Check document https://laravel.com/docs/5.6/eloquent-resources#pagination
Note: when using resource class then name it without Collection. For your case you should name resource as ServiceProviderResource and then when you call its collection then ServiceProviderResource::collection($object) but when returning single object then new ServiceProviderResource($object).
I am currently using Laravel 7 and in my controller I am passing an array of collection objects to resource class
$data = ['quotation' => Quotation::first()];
return new QuotationResource($data);
and in my resource class I can access the data using
public function toArray($request)
{
return [
'quotation' => $this->resource['quotation']
];
}

How can I add heterogeneous data to Elasticsearch?

I am trying to add heterogenous data (i.e. of different "types") to Elasticsearch. Each (top-level) object contains a user's settings for an application. A simplified example is:
{
'name':'test',
'settings': [
{
'key':'color',
'value':'blue'
},
{
'key':'isTestingMode',
'value':true
},
{
'visibleColumns',
'value': [
'column1',
'column3',
'column4',
]
},
...
...
}
When I try to add this, the POST fails with an MapperParsingException. Searching around, it seems like this is because the 'value' field has different types.
Is there any way to just store arbitrary data like this?
This is not possible.
Mapping is per field and mapping is not array aware.
This means that you can keep settings.value as string or array but not both.
An easy tweak would be to define all value as array -
{
'name':'test',
'settings': [
{
'key':'color',
'value': [ 'blue' ]
},
{
'key':'isTestingMode',
'value': [ true ]
},
{
'visibleColumns',
'value': [
'column1',
'column3',
'column4',
]
},
...
...
}
If that is not acceptable , then another idea would be to apply source transform which will do this normalization to the settings.value field before it is indexed. This way , the source is kept as it is AND you will get what you want.

Resources