Skip to content
This repository was archived by the owner on May 28, 2023. It is now read-only.

Commit dcdd29a

Browse files
authored
Merge pull request #101 from yuriboyko/feature/searchQuery-graphQl-Update-1
Add documentation to pull request #94 + bug fixes
2 parents b21316f + b8aef01 commit dcdd29a

File tree

7 files changed

+130
-8
lines changed

7 files changed

+130
-8
lines changed

config/default.json

+13-2
Original file line numberDiff line numberDiff line change
@@ -182,8 +182,19 @@
182182
},
183183
"product": {
184184
"excludeFields": [ "updated_at", "created_at", "attribute_set_id", "status", "visibility", "tier_prices", "options_container", "msrp_display_actual_price_type", "has_options", "stock.manage_stock", "stock.use_config_min_qty", "stock.use_config_notify_stock_qty", "stock.stock_id", "stock.use_config_backorders", "stock.use_config_enable_qty_inc", "stock.enable_qty_increments", "stock.use_config_manage_stock", "stock.use_config_min_sale_qty", "stock.notify_stock_qty", "stock.use_config_max_sale_qty", "stock.use_config_max_sale_qty", "stock.qty_increments", "small_image"],
185-
"includeFields": null
185+
"includeFields": null,
186+
"filterFieldMapping": {
187+
"category.name": "category.name.keyword"
188+
}
186189
}
187190
},
188-
"usePriceTiers": false
191+
"usePriceTiers": false,
192+
"boost": {
193+
"name": 3,
194+
"category.name": 1,
195+
"short_description": 1,
196+
"description": 1,
197+
"sku": 1,
198+
"configurable_children.sku": 1
199+
}
189200
}

doc/2. graphQl support.md

+89
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
# Introduction
2+
3+
Vue storefront now supports using graphQl as an alternative API endpoint to get data for products, categories, products, and taxerules.
4+
For now Graphql uses resolver what fetch data from Elasticsearch. But potentialy can have different resolvers working with different Search Engines / 3rd Party APIs
5+
6+
## GraphQl Schema
7+
8+
Graphql request to this API have to match query schema defined
9+
For product it is
10+
11+
```graphql
12+
type Query {
13+
products (
14+
search: String @doc(description: "Performs a full-text search using the specified key words."),
15+
filter: ProductFilterInput @doc(description: "Identifies which product attributes to search for and return."),
16+
pageSize: Int = 20 @doc(description: "Specifies the maximum number of results to return at once. This attribute is optional."),
17+
currentPage: Int = 1 @doc(description: "Specifies which page of results to return. The default value is 1."),
18+
sort: ProductSortInput @doc(description: "Specifies which attribute to sort on, and whether to return the results in ascending or descending order.")
19+
): Products
20+
}
21+
```
22+
23+
which has result of type Products
24+
25+
```graphql
26+
27+
type Products @doc(description: "The Products object is the top-level object returned in a product search") {
28+
items: JSON @doc(description: "An array of products that match the specified search criteria")
29+
page_info: SearchResultPageInfo @doc(description: "An object that includes the page_info and currentPage values specified in the query")
30+
total_count: Int @doc(description: "The number of products returned")
31+
aggregations: JSON @doc(description: "Layered navigation filters array as aggregations")
32+
sort_fields: SortFields @doc(description: "An object that includes the default sort field and all available sort fields")
33+
}
34+
```
35+
36+
and uses defined resolver
37+
```js
38+
const resolver = {
39+
Query: {
40+
products: (_, { search, filter, sort, currentPage, pageSize }, context, rootValue) =>
41+
list(filter, sort, currentPage, pageSize, search, context, rootValue)
42+
}
43+
};
44+
```
45+
46+
For other entity types you can check schemas and resolvers in the /src/graphql/elasticsearch correspond subfolder
47+
48+
49+
## Example request
50+
51+
Below is an example request for product
52+
53+
```graphql
54+
{
55+
products(search: "bag", filter: {
56+
status: {
57+
in: [0, 1], scope: "default"
58+
},
59+
stock: {
60+
is_in_stock: {eq: true, scope: "default"}
61+
},
62+
visibility: {
63+
in: [3, 4], scope: "default"}
64+
},
65+
sort: {
66+
updated_at: DESC
67+
}
68+
) {
69+
items
70+
total_count
71+
aggregations
72+
sort_fields {
73+
options {
74+
value
75+
}
76+
}
77+
page_info {
78+
page_size
79+
current_page
80+
}
81+
}
82+
}
83+
84+
85+
```
86+
87+
To see all available product filter options please check ProductFilterInput type in the graphQl product schema
88+
89+

src/graphql/elasticsearch/catalog/resolver.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ async function list(filter, sort, currentPage, pageSize, search, context, rootVa
2525
const parseURL = context.req.url.replace(/^\/+|\/+$/g, '');
2626
let urlParts = parseURL.split('/');
2727
let esIndex = config.elasticsearch.indices[0]
28-
if (urlParts.length >= 1 && urlParts[0] != '') {
28+
if (urlParts.length >= 1 && urlParts[0] != '' && urlParts[0] != '?') {
2929
esIndex = config.storeViews[urlParts[0]].elasticsearch.index
3030
}
3131

src/graphql/elasticsearch/catalog/schema.graphqls

+2-1
Original file line numberDiff line numberDiff line change
@@ -142,11 +142,12 @@ input ProductFilterInput @doc(description: "ProductFilterInput defines the filte
142142
size: FilterTypeInput @doc(description: "The product size. Customers use this size to identify the product.")
143143
color: FilterTypeInput @doc(description: "The product color. Customers use this color to identify the product.")
144144
or: ProductFilterInput @doc(description: "The keyword required to perform a logical OR comparison")
145-
category: ProductFilterInput @doc(description: "The product color. Customers use this color to identify the product.")
145+
category: JSON @doc(description: "The product color. Customers use this color to identify the product.")
146146
category_id: FilterTypeInput @doc(description: "Category ID the product belongs to")
147147
configurable_children: ProductFilterInput @doc(description: "Configurable product childrens")
148148
stock: ProductFilterInput @doc(description: "The product stock. Customers use this stock to identify the product.")
149149
is_in_stock: FilterTypeInput @doc(description: "Is product in stock")
150+
keyword: FilterTypeInput @doc(description: "keyword filter input")
150151
}
151152

152153
type LayerFilter {

src/graphql/elasticsearch/mapping.js

+16
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
import config from 'config'
2+
3+
export default function getMapping (attribute, entityType = 'product') {
4+
let mapping = [
5+
]
6+
7+
if (typeof config.entities[entityType].filterFieldMapping !== 'undefined') {
8+
mapping = config.entities[entityType].filterFieldMapping
9+
}
10+
11+
if (typeof mapping[attribute] !== 'undefined') {
12+
return mapping[attribute]
13+
}
14+
15+
return attribute
16+
}

src/graphql/elasticsearch/queryBuilder.js

+8-3
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import bodybuilder from 'bodybuilder';
22
import getBoosts from '../../lib/boost'
33
import map from 'lodash/map';
4+
import getMapping from './mapping'
45

56
function processNestedFieldFilter(attribute, value) {
67
let processedFilter = {
@@ -55,7 +56,7 @@ function applyFilters(filter, query, type) {
5556
if (!Array.isArray(filter.value)) {
5657
filter.value = [filter.value];
5758
}
58-
query = query.filter('terms', filter.attribute, filter.value);
59+
query = query.filter('terms', getMapping(filter.attribute), filter.value)
5960
}
6061
} else if (filter.scope == 'catalog') {
6162
hasCatalogFilters = true;
@@ -81,7 +82,11 @@ function applyFilters(filter, query, type) {
8182
if (!Array.isArray(newValue)) {
8283
newValue = [newValue];
8384
}
84-
filterQr = filterQr.andFilter('terms', catalogfilter.attribute + attrPostfix, newValue);
85+
if (attrPostfix === '') {
86+
filterQr = filterQr.andFilter('terms', getMapping(catalogfilter.attribute), newValue)
87+
} else {
88+
filterQr = filterQr.andFilter('terms', catalogfilter.attribute + attrPostfix, newValue)
89+
}
8590
}
8691
}
8792
})
@@ -98,7 +103,7 @@ function applyFilters(filter, query, type) {
98103
for (let attrToFilter of appliedFilters) {
99104
if (attrToFilter.scope == 'catalog') {
100105
if (attrToFilter.attribute != 'price') {
101-
query = query.aggregation('terms', attrToFilter.attribute)
106+
query = query.aggregation('terms', getMapping(attrToFilter.attribute))
102107
query = query.aggregation('terms', attrToFilter.attribute + optionsPrfeix)
103108
} else {
104109
query = query.aggregation('terms', attrToFilter.attribute)

src/lib/boost.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ export default function getBoosts (attribute = '') {
99
}
1010

1111
if (boosts.hasOwnProperty(attribute)) {
12-
return boosts.attribute
12+
return boosts[attribute]
1313
}
1414

1515
return 1

0 commit comments

Comments
 (0)