-
Notifications
You must be signed in to change notification settings - Fork 21
Managing your content with Gally
Gally is tailored for E-Commerce and is able to deal out-of-the-box with the most commonly used concepts in the E-Commerce world : products and categories.
First of all, all the write operations will require you to be authenticated.
You should have created an admin user during the setup of your project, that's the account you can use to operate with the API.
To authenticate your API calls, you have to use the authentication_token API :
REST
URL | /authentication_token |
Method | POST |
Body | { "email": "[email protected]", "password": "apassword" } |
Response | The auth token |
GraphQl
URL | /graphql |
Method | POST |
Body | mutation { tokenAuthentication(input: { email: "[email protected]" password: "apassword" }) { authentication { token } } } |
Response | { "data": { "tokenAuthentication": { "authentication": { "token": "eyJ0eXAi...o3w" } } } } |
This auth token has then to be injected as a Bearer AUTH_TOKEN in the Authorization
HTTP header in all your subsequent API calls.
In Gally, a Catalog is the representation of a scope of your E-Commerce website.
If you're a retailer that is running several brands, like www.shiny-shoes-shop.com and www.classic-glasses-shop.com, you'll want to create two different catalogs :
- Shiny Shoes Shop
- Classic Glasses Shop
To create your catalog(s), you have to use the Catalog API. The catalog creation is required only once (or if later, you add a new catalog to your webshop).
REST
URL | /catalogs |
Method | POST |
Body | { "code": "shiny_shoes_com", "name": "Shiny Shoes Shop" } |
Response | The created catalog, with IRI (field `@id`) for later use
{ "@context": "/contexts/Catalog", "@id": "/catalogs/1", "@type": "Catalog", "id": 1, "code": "shiny_shoes_com", "name": "Shiny Shoes Shop", "localizedCatalogs": [] } |
GraphQl
URL | /graphql |
Method | POST |
Body |
mutation { createCatalog(input: { code: "shiny_shoes_com" name: "Shiny Shoes Shop" }) { catalog { id _id code name } } } |
Response | Here, the created catalog, with both :
- the IRI (field `id`) for later use in REST and GraphQL endpoints
- the internal ID (field `_id`)
{ "data": { "createCatalog": { "catalog": { "id": "/catalogs/1", "_id": 1, "code": "shiny_shoes_com" "name": "Shiny Shoes Shop" } } } } |
In Gally, a Localized Catalog is the combination of a Catalog and a locale. Under the hood, this will allow to have one Elasticsearch index per catalog and locale, to be able to deal with different languages.
So if your previously seen Shiny Shoes Shop and Classic Glasses Shop are available both in English and French, you'll have two localized catalogs for each of them.
The resulting structure will then be :
- Shiny Shoes Shop (Catalog) :
- Shiny Shoes French (Localized Catalog)
- Shiny Shoes English (Localized Catalog)
- Classic Glasses Shop (Catalog) :
- Classic Glasses French (Localized Catalog)
- Classic Glasses English (Localized Catalog)
To create your localized catalog(s), you have to use the LocalizedCatalog API. The localized catalog(s) creation is required only once (or if later, you add a new locale to your webshop).
REST
URL | /localizedcatalogs |
Method | POST |
Body | { "name": "Shiny Shoes French", "code": "shiny_shoes_com_fr", "locale": "fr_FR", "currency": "EUR", "isDefault": true, "catalog": "/catalogs/1" } |
Response | The created localized catalog, with IRI (field `@id`) for later use
{ "@context": "/contexts/LocalizedCatalog", "@id": "/localized_catalogs/1", "@type": "LocalizedCatalog", "id": 1, "name": "Shiny Shoes French", "code": "shiny_shoes_com_fr", "locale": "fr_FR", "currency": "EUR", "isDefault": true, "catalog": "/catalogs/1", "localName": "French (France)" } |
GraphQl
URL | /graphql |
Method | POST |
Body | mutation { createLocalizedCatalog(input: { name: "Shiny Shoes French" code: "shiny_shoes_com_fr" locale: "fr_FR" currency: "EUR" isDefault: true catalog: "/catalogs/1" }) { localizedCatalog { id _id code name } } } |
Response | The created localized catalog, with IRI (field `id`) for later use
{ "data": { "createLocalizedCatalog": { "localizedCatalog": { "id": "/localized_catalogs/1", "_id": 1, "code": "shiny_shoes_com_fr", "name": "Shiny Shoes French" } } } } |
That's as simple as that.
Now that you have your catalogs and localized catalogs, it's time to teach Gally how your catalog structure looks like.
That's where source fields appears. In Gally, each source field is the representation of a field of your entities.
Eg : If your products do have a name, a price and a brand, you need to tell Gally that you have those 3 source fields for the product entity type.
This step will help Gally to define properly the underlying mapping of the Elasticsearch indices that will be used for indexing and searching in content.
The good news is, you don't have to bother with Elasticsearch complicated field types or to understand how they work, Gally's logic is wrapping this inside a more comprehensible manner to define field types.
Type | Details | Example |
---|---|---|
text | Text fields | Product Name, description, etc... |
keyword | Non-analyzed text fields | String identifiers, etc... |
select | Fields that contains a closed list of values | Brand, color, etc... |
int | Integer fields | Unique identifiers, numerical values etc... |
boolean | True/false fields | "Eco label", "Suitable for children", etc... |
float | Floating numbers | Not "price" (see below), but rather "width", "length", etc... |
object | Complex objects containing several sub-fields | Structured content (tree, persons with name and surname, etc...) |
date | Date field |
Type | Details | Example |
---|---|---|
price | Price field | Product prices for different customer groups, including potential discount |
stock | Stock field | Product stock field, including stock status and quantity |
category | Category/Product relation field | Contains the list of categories a product belongs to |
reference | Unique identifier field | SKU |
image | Image field | URLs of the product images |
First, you have to create all your source fields. Source fields creation is required only once, or if the data type of your source field has changed in your ECommerce application.
To create source fields, you have to use the SourceFields API :
REST
URL | /sourcefields |
Method | POST |
Body | { "code": "name", "defaultLabel": "Product Name", "type": "text", "isFilterable": true, // Not mandatory "isSearchable": true, // Not mandatory "isSortable": true, // Not mandatory "isUsedForRules": true, // Not mandatory "weight": 1, // Not mandatory "isSpellchecked": true, // Not mandatory "metadata": "/metadata/1" } |
Response | The created source field, with IRI for later use |
GraphQl
URL | /graphql |
Method | POST |
Body |
mutation { createSourceField(input: { code: "alternative_name", defaultLabel: "Product Alternative Name", type: "text", isSystem: false, isFilterable: true, # Not mandatory isSearchable: true, # Not mandatory isSortable: true, # Not mandatory isUsedForRules: true, # Not mandatory weight: 1, # Not mandatory isSpellchecked: true, # Not mandatory metadata: "/metadata/1" }) { sourceField { id _id code } } } |
Response | The created source field , with IRI (field `id`) for later use
{ "data": { "createSourceField": { "sourceField": { "id": "/source_fields/1", "_id": 1, "code": "name" } } } } |
Here are the details of all the source fields parameters :
Parameter Name | Detail |
---|---|
code* | The code of the source field. Unique identifier. |
defaultLabel* | The label of the source field. Untranslated. |
type* | The type of the source field. As explained before. |
metadata* | The entity type this fields relates to ("product", "category", or something else. |
isFilterable | If this field is used for filtering. Can be changed in the back-office later. |
isSearchable | If this field is used for searching. Can be changed in the back-office later. |
isSortable | If this field is used for sorting. Can be changed in the back-office later. |
isUsedForRules | If this field is used for rules (virtual categories, boosts, etc...). Can be changed in the back-office later. |
weight | Weight of this field for searching. Can be changed in the back-office later. |
isSpellchecked | If this field is spellchecked. Can be changed in the back-office later |
If you are having several locales (e.g. fr_FR and en_US), you'll have to tell Gally how a source field label should be translated for a given catalog.
Eg : "Material" should be "Matière" when browsing a French localized catalog.
You have to do so with the SourceFieldLabel API.
REST
URL | /source_field_labels |
Method | POST |
Body | { "sourceField": "/source_fields/4", // The IRI of the "material" source field "localizedCatalog": "/localized_catalogs/1", // The localized catalog to target "label": "Matière" // The translated label } |
Response | The created source field label, with IRI (field `@id`) for later use
{ "@context": "/contexts/SourceFieldLabel", "@id": "/source_field_labels/137", "@type": "SourceFieldLabel", "id": 137, "sourceField": "/source_fields/4", "localizedCatalog": "/localized_catalogs/1", "label": "Matière" } |
GraphQl
URL | /graphql |
Method | POST |
Body |
mutation { createSourceFieldLabel(input: { sourceField: "/source_fields/4", localizedCatalog: "/localized_catalogs/1", label: "Matière" }) { sourceFieldLabel { id _id label } } } |
Response | The created source field , with IRI (field `id`) for later use
{ "data": { "createSourceFieldLabel": { "sourceFieldLabel": { "id": "/source_field_labels/137", "_id": 137, "label": "Matière" } } } } |
For your source fields that are of type "select", it means they are option-based. You then need to inform Gally what are the options of each field.
Eg : "Material" could have the following options : "Cotton", "Wool", "Leather"...
You have to do so with the SourceFieldOption API.
REST
URL | /source_field_options |
Method | POST |
Body | { "code": "material_leather", // Unique identifier of the option "sourceField": "/source_fields/4", // The IRI of the "material" source field "position": 0, // The position of this option within all the options "defaultLabel": "Leather" // The default label of this option } |
Response | The created option, with IRI (field `@id`) for later use
{ "@context": "/contexts/SourceFieldOption", "@id": "/source_field_options/1231", "@type": "SourceFieldOption", "id": 1231, "code": "material_leather", "sourceField": "/source_fields/4", "position": 0, "defaultLabel": "Leather", "labels": [] } |
GraphQl
URL | /graphql |
Method | POST |
Body |
mutation { createSourceFieldOption(input: { code: "material_leather", # Unique identifier of the option sourceField: "/source_fields/4", # The IRI of the "material" source field position: 0, # The position of this option within all the options defaultLabel: "Leather" # The default label of this option }) { sourceFieldOption { id code position defaultLabel } } } |
Response | The created source field , with IRI (field `id`) for later use
{ "data": { "createSourceFieldOption": { "sourceFieldOption": { "id": "/source_field_options/1231", "_id": 1231, "code": "material_leather", "position": 0, "defaultLabel": "Leather" } } } } |
If you are having several locales (e.g. fr_FR and en_US), you'll have to tell Gally how a source field option label should be translated for a given catalog.
Eg : "Leather" value of the "Material" Source Field should be "Cuir" when browsing a French localized catalog.
You have to do so with the SourceFieldOptionLabel API.
REST
URL | /source_field_options_labels |
Method | POST |
Body | { "sourceFieldOption": "/source_field_options/1231", // IRI of the option created previously "localizedCatalog": "/localized_catalogs/1", // IRI of the localized catalog the option belongs to "label": "Cuir" // The translated label } |
Response | The created option label, with IRI (field `@id`) for later use
{ "@context": "/contexts/SourceFieldOptionLabel", "@id": "/source_field_option_labels/1365", "@type": "SourceFieldOptionLabel", "id": 1365, "sourceFieldOption": { "@id": "/source_field_options/1231", "@type": "SourceFieldOption", "code": "material_leather" }, "localizedCatalog": "/localized_catalogs/1", "label": "Cuir" } |
GraphQl
URL | /graphql |
Method | POST |
Body |
mutation { createSourceFieldOptionLabel(input: { sourceFieldOption: "/source_field_options/1231", localizedCatalog: "/localized_catalogs/1", label: "Cuir" }) { sourceFieldOptionLabel { _id id label sourceFieldOption { id code } } } } |
Response | The created source field , with IRI (field `id`) for later use
{ "data": { "createSourceFieldOptionLabel": { "sourceFieldOptionLabel": { "_id": 1371, "id": "/source_field_option_labels/1371", "label": "Cuir", "sourceFieldOption": { "id": "/source_field_options/1231", "code": "material_leather" } } } } } |
Once your catalogs, localized catalogs, and source fields have been defined, you can send your data to Gally to get them indexed into Elasticsearch.
You will have to do this for at least 2 entity types : "product" and "category". That's the minimum requirement to have a functional back-office in Gally.
This means that you have to repeat the following process twice.
To do so, you first have to ask for creating a new index by providing an existing entity type and a localized catalog identifier (either the localized catalog code or its internal ID - not the IRI).
REST
URL | /indices |
Method | POST |
Body | { "entityType": "product", // existing metadata entity type (e.g. "product" or "category") "localizedCatalog": "shiny_shoes_com_fr" // the localized catalog code (e.g. "shiny_shoes_com_fr" or id e.g. "1") } |
Response | The created index with IRI (field `@id`) and name (`name`) for later use
{ "@context": "/contexts/Index", "@id": "/indices/gally_shiny_shoes_com_fr_product_20230123_150312", "@type": "Index", "name": "gally_shiny_shoes_com_fr_product_20230123_150312", "aliases": [ ".catalog_13", ".entity_product" ] } |
GraphQl
URL | /graphql |
Method | POST |
Body |
mutation { createIndex(input: { entityType: "product" # existing metadata entity type (e.g. "product" or "category") localizedCatalog: "shiny_shoes_com_fr" # the localized catalog code (e.g. "shiny_shoes_com_fr" or id e.g. "1") }) { index { id name } } } |
Response | The created index with IRI (field `id`) and name (`name`) for later use
{ "data": { "createIndex": { "index": { "id": "/indices/gally_shiny_shoes_com_fr_product_20230123_150605", "name": "gally_shiny_shoes_com_fr_product_20230123_150605", "aliases": [ ".catalog_13", ".entity_product" ] } } } } |
This method will give you the newly created index identifier.
Then, bulk your data into this newly created index :
REST
URL | /index_documents |
Method | POST |
Body | { "indexName": "string", "documents": [ "string" ] } |
The bulk Api is intended to be used with batches of items. You'll probably be willing to call it in a loop that is parsing your whole catalog and call it as much time as needed.
The Bulk API accepts jsonified version of the data you want to index. Your data has to be accurate with your source field types.
Here is an example table of how your data should be structured per each type :
Source Field Type | Data |
---|---|
text | name:"Nike red Shoes" |
keyword | my_identifier:"075d8b8" |
select | "fashion_size": [ { "value": 33, "label": "XL" }, { "value": 37, "label": "XS" }, { "value": 36, "label": "S" }, { "value": 35, "label": "M" }, { "value": 34, "label": "L" } ] |
int | "width":12 |
boolean | "has_video": { "value": true, "label": "Has Video" }, |
float | "length":127.04 |
object | "actor": { "firstname": "Hugh", "lastname": "Jackman" }, |
date | "created_at": "2022-09-07 14:57:19" |
price | "price": [ { "price": 88, "original_price": 88, "group_id": 0, "is_discounted": false }, { "price": 75, "original_price": 88, "group_id": 1, "is_discounted": true }, { "price": 88, "original_price": 88, "group_id": 2, "is_discounted": false } ], |
stock | "stock": { "qty": 0, "status": true }, |
category | "category": [ { "category_uid": "Mg==", "id": "cat_2" }, { "name": "Tops", "category_uid": "OA==", "id": "cat_8" }, { "is_parent": true, "name": "Blouses & Shirts", "category_uid": "OQ==", "id": "cat_9" } ] |
reference | "sku": "VT04" |
image | "image": "\/v\/t\/vt04-mt_main.jpg" |
Example of a full document being sent through a bulk
{
"entity_id": "1104",
"attribute_set_id": "11",
"type_id": "configurable",
"sku": "VT04",
"has_options": false,
"required_options": false,
"created_at": "2022-09-07 14:57:19",
"updated_at": "2022-09-07 14:57:19",
"visibility": {
"value": "4",
"label": "Catalog, Search"
},
"price": [{
"price": 88,
"original_price": 88,
"group_id": 0,
"is_discounted": false
}, {
"price": 88,
"original_price": 88,
"group_id": 1,
"is_discounted": false
}, {
"price": 88,
"original_price": 88,
"group_id": 2,
"is_discounted": false
}, {
"price": 88,
"original_price": 88,
"group_id": 3,
"is_discounted": false
}],
"indexed_attributes": ["price", "name", "url_key", "description", "fashion_material", "fashion_style", "status", "tax_class_id", "fashion_color", "fashion_size", "has_video"],
"category": [{
"is_virtual": "false",
"category_uid": "Mg==",
"id": "cat_2"
}, {
"is_virtual": "false",
"name": "Tops",
"category_uid": "OA==",
"id": "cat_8"
}, {
"is_parent": true,
"is_virtual": "false",
"name": "Blouses & Shirts",
"category_uid": "OQ==",
"id": "cat_9"
}],
"name": ["Anna Draped Top"],
"url_key": ["anna-draped-top"],
"description": ["<p>The Anna Draped Top hangs beautifully in the all the right places. The soft, sheer material offers graceful lines and seamless movement. Back lace detail and tie at the neckline complete the look. <\/p><p>Features:<\/p><ul><li>Draped neckline<\/li><li>Capped sleeves<\/li><li>Hits at the hips<\/li><li>Lace detail back & tie neckline<\/li><li>Hand wash, line dry<\/li><\/ul>"],
"fashion_material": [{
"value": "47",
"label": "Cotton"
}, {
"value": "56",
"label": "Viscose"
}],
"fashion_style": [{
"value": "62",
"label": "Short Sleeve"
}, {
"value": "72",
"label": "Crew"
}],
"status": [{
"value": 1,
"label": "Enabled"
}],
"tax_class_id": [{
"value": 2,
"label": "Taxable Goods"
}],
"fashion_color": [{
"value": 5,
"label": "Gold"
}, {
"value": 25,
"label": "Rain"
}, {
"value": 26,
"label": "Mint"
}, {
"value": 24,
"label": "Lilac"
}, {
"value": 28,
"label": "Latte"
}],
"fashion_size": [{
"value": 33,
"label": "XL"
}, {
"value": 37,
"label": "XS"
}, {
"value": 36,
"label": "S"
}, {
"value": 35,
"label": "M"
}, {
"value": 34,
"label": "L"
}],
"has_video": [{
"value": true,
"label": "Has Video"
}],
"children_ids": [144, 145, 146, 147, 148, 149, 150, 151, 152, 153, 154, 155, 156, 157, 158, 159],
"children_attributes": ["indexed_attributes", "url_key", "description", "fashion_material", "fashion_style", "fashion_color", "fashion_size", "has_video"],
"configurable_attributes": ["fashion_color", "fashion_size"],
"children.indexed_attributes": ["name", "url_key", "description", "fashion_material", "fashion_style", "status", "tax_class_id", "fashion_color", "fashion_size", "has_video"],
"children.url_key": ["anna-draped-top"],
"children.description": ["<p>The Anna Draped Top hangs beautifully in the all the right places. The soft, sheer material offers graceful lines and seamless movement. Back lace detail and tie at the neckline complete the look. <\/p><p>Features:<\/p><ul><li>Draped neckline<\/li><li>Capped sleeves<\/li><li>Hits at the hips<\/li><li>Lace detail back & tie neckline<\/li><li>Hand wash, line dry<\/li><\/ul>"],
"children.sku": ["VT04-RN-XS", "VT04-RN-S", "VT04-RN-M", "VT04-RN-L", "VT04-MT-XS", "VT04-MT-S", "VT04-MT-M", "VT04-MT-L", "VT04-LL-XS", "VT04-LL-S", "VT04-LL-M", "VT04-LL-L", "VT04-LA-XS", "VT04-LA-S", "VT04-LA-M", "VT04-LA-L"],
"id": 1104,
"stock": {
"qty": 0,
"status": true
},
"image": "\/v\/t\/vt04-mt_main.jpg"
}
When you are done with bulks, you can finish the installation of your index which will make sure that:
- from now on, this is the index that will be targeted when you use the API search endpoints for the given entity type and localized catalog ;
- this works because your index receives a special unique index alias of the form
[prefix]_[localized_catalog_code]_[entity_type]
which characterizes the live index for that localized catalog and entity type.
REST
URL | /indices/install/{name}
Example:/indices/install/gally_shiny_shoes_com_fr_product_20230123_150312 |
Method | PUT |
Response | The installed index with updated aliases and status. Note the extra alias which is similar to the index name but without the timestamp. { "@context": "/contexts/Index", "@id": "/indices/gally_shiny_shoes_com_fr_product_20230123_150605", "@type": "Index", "name": "gally_shiny_shoes_com_fr_product_20230123_150605", "aliases": [ ".catalog_1", ".entity_product" ], "docsCount": 0, "size": "226b", "entityType": "product", "localizedCatalog": "/localized_catalogs/1", "status": "indexing" } |
GraphQl
URL | /graphql |
Method | POST |
Body |
mutation { installIndex(input: { name: "gally_shiny_shoes_com_fr_product_20230123_150605" }) { index { name aliases status } } } |
Response | The installed index with updated aliases and status. Note the extra alias which is similar to the index name but without the timestamp. { "data": { "installIndex": { "index": { "name": "gally_shiny_shoes_com_fr_product_20230123_150605", "aliases": [ ".catalog_1", ".entity_product", "gally_shiny_shoes_com_fr_product" ], "status": "live" } } } } |
And then, refresh it :
REST
URL | /indices/refresh/{name} Example: /indices/refresh/gally_shiny_shoes_com_fr_product_20230123_150605 Note that since the index is installed, you can also use the unique alias to reference it: /indices/refresh/gally_shiny_shoes_com_fr_product |
Method | PUT |
GraphQl
URL | /graphql |
Method | POST |
Body |
mutation { refreshIndex(input: { name: "gally_shiny_shoes_com_fr_product_20230123_150605" # since the index is installed, you can also use the unique alias to reference it: # name: "gally_shiny_shoes_com_fr_product" }) { index { name aliases status } } } |
You may not want to re-create a new index and process all your content when something has changed by your side. That's where differential indexing will help you.
If you have some data that has changed recently (Eg : a product has been edited), here's how you want to proceed :
You can get the current live index by calling the indices API :
REST
URL | /index_documents |
Method | GET |
Response | "hydra:member": [ { "@id": "/indices/gally_en_fr_product_20230112_152027", "@type": "Index", "name": "gally_en_fr_product_20230112_152027", "aliases": [ ".catalog_11", ".entity_product", "gally_en_fr_product" ], "docsCount": 0, "size": "226b", "entityType": "product", "localizedCatalog": "/localized_catalogs/11", "status": "live" }, { "@id": "/indices/gally_fr_fr_product_20230112_152026", "@type": "Index", "name": "gally_fr_fr_product_20230112_152026", "aliases": [ ".catalog_9", ".entity_product", "gally_fr_fr_product" ], "docsCount": 1703, "size": "1mb", "entityType": "product", "localizedCatalog": "/localized_catalogs/9", "status": "live" },.... |
The index (or indices) that has status:live and entityType:product are the ones that you want to index your modified data into.
Just call the bulk API to those indices with the updated data.
To search on products, you are supposed to use the specialized GraphQl search api 'products()'. You can provide multiple arguments to precise your search :
Query arguments | Details |
---|---|
requestType | The identifier of the "request type" : in which context you are retrieving products. Could be product_catalog (catalog navigation) or product_search (fulltext search). |
localizedCatalog | The code or id of the localized catalog you're searching on. |
currentPage | Current page |
pageSize | Page size |
currentCategoryId | Indicates the current Category you are browsing. Mandatory for product_catalog requests. |
search | The text query to use for fulltext searching. Mandatory for product_search requests. |
sort | The field that should be used for sorting, with the selected direction (asc or desc ) |
filter | An array of filters to apply. The list of available filters is automatically generated according to the filterable source fields of the product metadata. You can find more details in the next section. |
This api can handle complexe filter with nested criteria. You can apply filter on a product field with this different operators:
-
eq
: The product field value match exactly the filter value -
in
: The product field value is one of the filter values -
match
: -
exist
: If the filter value is true, the product field value should be defined. If the filter value is false, the product field value should not be defined. -
gt
(gte
) : The product field value is greater (or equal) to the filter value -
lt
(lte
) : The product field value is lower (or equal) to the filter value
The filter operator depends on the type of the associated source field:
Source Field Type | Filter type | Available operator | Example |
---|---|---|---|
text, keyword, reference, image | text |
eq , in , match and exist
|
{ description: { match: "bags" } } { sku: { eq: "mysku1" }}
|
int, float, date | numeric |
eq , in , gt , gte , lt , lte and exist
|
{ cost: { lte: 120 }} |
boolean | boolean |
eq and exist
|
{ is_active: { eq: true }} |
select | select |
eq , in and exist
|
{ brand_value: { exist: true }} |
price | price |
eq , in , gt , gte , lt , lte and exist
|
{ price__price: { gt: 50 }} |
stock | stock |
eq and exist
|
{ stock__status: { eq: true }} |
category | category | eq |
{ category__id: { eq: 1254 }} |
object | Not managed yet |
You can use multiple filters at once, the result will contain the products that match all the provided filters:
filter: [
{ description: { match: "bags" } }
{ price__price: { gt: 50 }}
{ category__id: { eq: 1254 }}
]
If you want to create more complexe queries, you can use the boolFilter
. This a special filter that can combine multiple filter with boolean logic. Its behavior is based on elasticsearch boolean query. This filter has its own special operator:
-
_must
: Only the product matching the filters listed here will be in the result. -
_should
: The product matching this filters here will have a better score. -
_not
: The product matching the filters listed here will be excluded from the result.
There is some examples
- You want to list the blue products if their price is greater than 50€ but you want to exclude product from the category 10:
filter: [ { boolFilter: { _must: [ { color__value: { eq: "blue" } } { price__price: { gt: 50.0 } } ] _not: [ { category__id: { eq: 10 } } ] } } ]
- You want to list product that are in the category "Bags" (with the id 4) or which contains "Bags" in their name:
filter: [ { boolFilter: { _must: [ { boolFilter: { _should: [ { category__id: { eq: 4 } } { name: { match: "Bags" } } ] } } ] } } ]
There is the list of the data you can ask the products search api :
Query fields | Details |
---|---|
collection | The list of the products matching your search. |
collection.* | Source field of product to display in the response (eg: id , name , price.price ). |
paginationInfo | Usefull data to build pagination toolbar |
paginationInfo.itemsPerPage | Maximum number of products in the current page. |
paginationInfo.lastPage | Last page with products. |
paginationInfo.totalCount | Number of products matching your search. |
sortInfo | Information about the current sort used for the product in result |
sortInfo.current.field | Current field used for sorting result. |
sortInfo.current.direction | Current sort direction used in result. |
aggregations | List of aggregated data from the product in result |
aggregations.field | Source field code. |
aggregations.label | Source field label. |
aggregations.count | Number of products having a value for this source field. |
aggregations.type | Type of the aggregation, can be checkbox , slider , boolean or category . |
aggregations.hasMore | You can configure by source field a limit of the number of elements to display in the aggregation. The default value is '10'. The hasMore boolean indicate if the aggregation has more value than the one displayed |
aggregations.options.label | Label of the source field option. |
aggregations.options.value | Value of the source field option. |
aggregations.options.count | Number of products that match this source field option value. |
Example GraphQl
URL | /graphql |
Method | POST |
Body |
query getProducts( $requestType: ProductRequestTypeEnum!, $localizedCatalog: String!, $currentPage: Int, $currentCategoryId: String, $pageSize: Int, $search: String, $sort: ProductSortInput ) { products( requestType: $requestType, localizedCatalog: $localizedCatalog, currentPage: $currentPage, currentCategoryId: $currentCategoryId, pageSize: $pageSize, search: $search, sort: $sort, filter: [ { boolFilter: { _must: [ { boolFilter: { _should: [ { category__id: { eq: 4 } } { name: { match: "Bags" } } ] } } ] } } ] ) { collection { ...on Product { id sku name score image stock { status } price { price } } } paginationInfo { lastPage itemsPerPage totalCount } sortInfo { current { field direction } } aggregations { field label type options { count label value } hasMore } } } |
Variables | { "localizedCatalog": "1", "requestType": "product_search", "currentPage": 1, "pageSize": 10, "search": "scarf" } |
But Gally is more clever than just products and categories. You can also add any other content type (pages, blog posts, etc...) to the engine easily, and to be able to search on those contents.
To index additional content (pages, articles, blog posts, recipes, etc...) you need to create "metadata" associated to each of them.
REST
URL | /metadata |
Method | POST |
Body | { "entity": "blog_post" } |
Response | The created metadata, with IRI for later use |
GraphQl
URL | /graphql |
Method | POST |
Body |
mutation { createMetadata(input: { entity: "blog_post" }) { metadata { id _id entity } } } |
Response | Here, the created metadata, with both :
- the IRI (field `id`) for later use in REST and GraphQL endpoints
- the internal ID (field `_id`)
{ "data": { "createMetadata": { "metadata": { "id": "/metadata/4", "_id": 4, "entity": "blog_post" } } } } |
As you could do for Products or Categories, you will have to define the source fields of your custom content type.
This is done the same way as for "product" or "category"
Once you're done with the data structure of your additional content types, you can proceed to index them.
You can refer to the Sync your catalog data : this is exactly the same, except that you have to do this for your additional entity types.
So repeat the steps for them :
- create index
- bulk the data
- install the index
To search on additional content, you are supposed to use the standardized GraphQl search api 'documents()'. You can provide multiple arguments to precise your search :
Query arguments | Details |
---|---|
entityType | The identifier of the metadata you're searching on. |
localizedCatalog | The code or id of the localized catalog you're searching on. |
currentPage | Current page |
pageSize | Page size |
search | The text query to use for searching. Mandatory for fulltext requests. |
filter | A combination of filters on source fields that are filterables. You can find more details in the next section. |
sort | The field that should be used for sorting, with the selected direction (asc or desc ) |
This api can handle complexe filter with nested criteria. This is the different types of filter you can use:
Filter type | Available operator | Example |
---|---|---|
existFilter | None | { existFilter: { field: "description"}} |
equalFilter |
eq and in
|
{ equalFilter: { field: "sku", eq: "mysku1" }} |
matchFilter | match |
{ matchFilter: { field: "description", match: "bags" }} |
rangeFilter |
gt , gte , lt and lte
|
{ rangeFilter: { field: "cost", lte: 120 }} |
boolFilter |
_must , _should and _not
|
See next section. |
You can use multiple filters at once, the result will contain the documents that match all the provided filters:
filter: [
{ equalFilter: { field: "sku", eq: "mysku1" }}
{ matchFilter: { field: "description", match: "bags" }}
{ rangeFilter: { field: "cost", lte: 120 }}
]
If you want to create more complexe queries, you can use the boolFilter
. This a special filter that can combine multiple filter with boolean logic. Its behavior is based on elasticsearch boolean query. This filter has its own special operator:
-
_must
: Only the documents matching the filters listed here will be in the result. -
_should
: The documents matching this filters here will have a better score. -
_not
: The documents matching the filters listed here will be excluded from the result.
There is some examples
- You want to list the documents of color blue if their price is greater than 50€, but you want to exclude documents from the category 10:
filter: [ { boolFilter: { _must: [ { equalFilter: { field: "color__value", eq: "blue" }} { rangeFilter: { field: "price__price", gt: "50.0" }} ] _not: [ { equalFilter: { field: "category__id", eq: "10" }} ] } } ]
- You want to list documents that are in the category "Bags" (with the id 4) or which contains "Bags" in their title:
filter: [ { boolFilter: { _must: [ { boolFilter: { _should: [ { equalFilter: { field: "category__id", eq: "4" }} { equalFilter: { field: "title", eq: "Bags" }} ] } } ] } } ]
There is the list of the data you can ask the documents search api :
Query fields | Details |
---|---|
collection | The list of the documents matching your search. |
collection.id | Document id. |
collection.data | Document data (all source field values indexed in the elasticsearch document). |
collection.index | Name of the index where the document come from. |
collection.score | Score of the document for this query. |
collection.source | Document raw data as they were indexed in elasticsearch. |
paginationInfo | Usefull data to build pagination toolbar |
paginationInfo.itemsPerPage | Maximum number of documents in the current page. |
paginationInfo.lastPage | Last page with documents. |
paginationInfo.totalCount | Number of documents matching your search. |
sortInfo | Information about the current sort used for the documents in result |
sortInfo.current.field | Current field used for sorting result. |
sortInfo.current.direction | Current sort direction used in result. |
aggregations | List of aggregated data from the documents in result |
aggregations.field | Source field code. |
aggregations.label | Source field label. |
aggregations.count | Number of documents having a value for this source field. |
aggregations.type | Type of the aggregation, can be checkbox , slider , boolean or category . |
aggregations.hasMore | The limit of the number of elements to display in the aggregation is 10 by default. The hasMore boolean indicate if the aggregation has more value than the one displayed. |
aggregations.options.label | Label of the source field option. |
aggregations.options.value | Value of the source field option. |
aggregations.options.count | Number of documents that match this source field option value. |
Example GraphQl
URL | /graphql |
Method | POST |
Body |
query getDocuments( $entityType: String!, $localizedCatalog: String!, $currentPage: Int, $pageSize: Int, $search: String, $sort: SortInput ) { documents( entityType: $entityType, localizedCatalog: $localizedCatalog, currentPage: $currentPage, pageSize: $pageSize, search: $search, sort: $sort, filter: [ { boolFilter: { _must: [ { equalFilter: { field: "color.value", eq: "blue" }} { rangeFilter: { field: "price.price", gt: "50.0" }} ] _not: [ { equalFilter: { field: "category.id", eq: "10" }} ] } } ] ) { collection { ...on Document { id data } } paginationInfo { lastPage itemsPerPage totalCount } sortInfo { current { field direction } } aggregations { field label type options { count label value } hasMore } } } |
Variables | { "entityType": "product", "localizedCatalog": "1", "currentPage": 1, "pageSize": 10, "search": "scarf" } |
The autocomplete feature can be fully managed by the front, to get products the standardized GraphQl search api 'products()' can be called and to get any other entity the standardized GraphQl search api 'documents()' can be called.
Find below an example to get products and categories in the same GraphQl query for the term 'Dress':
query getAutoCompleteDocuments($requestType: ProductRequestTypeEnum!, $localizedCatalog: String!, $currentPage: Int, $currentCategoryId: String, $pageSize: Int, $search: String, $sort: ProductSortInput, $categoryEntityType: String!, $categoryLocalizedCatalog: String!, $categoryCurrentPage: Int, $categoryPageSize: Int, $categorySearch: String, $categorySort: SortInput) {
products: products(
requestType: $requestType
localizedCatalog: $localizedCatalog
currentPage: $currentPage
currentCategoryId: $currentCategoryId
pageSize: $pageSize
search: $search
sort: $sort
filter: null
) {
collection {
... on Product {
id
sku
name
score
image
stock {
status
}
price {
price
}
}
}
paginationInfo {
lastPage
itemsPerPage
totalCount
}
sortInfo {
current {
field
direction
}
}
}
categories: documents(
entityType: $categoryEntityType
localizedCatalog: $categoryLocalizedCatalog
currentPage: $categoryCurrentPage
pageSize: $categoryPageSize
search: $categorySearch
sort: $categorySort
filter: null
) {
collection {
... on Document {
id
score
source
}
}
paginationInfo {
lastPage
itemsPerPage
totalCount
}
sortInfo {
current {
field
direction
}
}
}
}
variables = {
"localizedCatalog": "com_en",
"requestType": "product_search",
"currentPage": 1,
"pageSize": 5,
"search": "Dress",
"categoryEntityType": "category",
"categoryLocalizedCatalog": "com_en",
"categoryCurrentPage": 1,
"categoryPageSize": 5,
"categorySearch": "Dress"
}
1. Getting started
2. Managing your content
3. Deploy on production