-
Notifications
You must be signed in to change notification settings - Fork 23
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
This auth token has then to be injected as a Bearer:AUTH_TOKEN in the "Authorization" 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.shoes-shop.com and www.glasses-shop.com, you'll want to create two different catalogs :
- Shoes Shop
- 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": "shoes_com", "name": "Shoes Shop", } |
Response | The created catalog, with IRI for later use |
GraphQl
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 Shoes Shop and 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 :
- Shoes Shop (Catalog) :
- Shoes Shop french (Localized Catalog)
- Shoes Shop english (Localized Catalog)
- Glasses Shop (Catalog) :
- Glasses Shop french (Localized Catalog)
- Glasses Shop 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": "Shoes Shop french", "code": "shoes_com_fr", "locale": "fr_FR", "currency": "EUR", "isDefault": true, "catalog": "shoes_com" } |
Response | The created localized catalog, with IRI for later use |
GraphQl
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 childrens", 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": "product" } |
Response | The created source field, with IRI for later use |
GraphQl
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 (Eg. 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": "shoes_com_fr", // The localized catalog to target "label": "Matière" // The translated label } |
Response | The created source field label, with IRI for later use |
GraphQl
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_cotton", // 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": "Cotton" // The default label of this option } |
Response | The created option, with IRI for later use |
GraphQl
If you are having several locales (Eg. 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/4", // 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 for later use |
GraphQl
Once your catalogs, localized catalogs, and source fields have been defined, you can send your data to Gally to get them indexed into Elasticsearch.
To do so, you first have to ask for creating a new index :
REST
URL | /indices |
Method | POST |
Body | { "entityType": "string", "localizedCatalog": "string" } |
GraphQl
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" ] } |
GraphQl
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 :
REST
URL | /indices/install/{name} |
Method | PUT |
GraphQl
And then, refresh it :
REST
URL | /indices/refresh/{name} |
Method | PUT |
GraphQl
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 | /indices |
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" },.... |
GraphQl
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.
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.
1. Getting started
2. Managing your content
3. Deploy on production