This repository has been archived by the owner on Mar 8, 2023. It is now read-only.
/
queryBuilder.js
186 lines (167 loc) · 6.57 KB
/
queryBuilder.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
import bodybuilder from 'bodybuilder';
import getBoosts from 'src/lib/boost'
import map from 'lodash/map';
import getMapping from './mapping'
import config from 'config'
function processNestedFieldFilter (attribute, value) {
let processedFilter = {
'attribute': attribute,
'value': value
};
let filterAttributeKeys = Object.keys(value);
for (let filterAttributeKey of filterAttributeKeys) {
if (value[filterAttributeKey] && !Array.isArray(value[filterAttributeKey]) && typeof value[filterAttributeKey] === 'object') {
processedFilter = processNestedFieldFilter(attribute + '.' + filterAttributeKey, value[filterAttributeKey]);
}
}
return processedFilter;
}
/**
*
* @param {Object} object
* @param {String} scope
* @returns {boolean}
*/
function checkIfObjectHasScope ({ object, scope }) {
return object.scope === scope || (Array.isArray(object.scope) && object.scope.find(scrope => scrope === scope));
}
function applyFilters (filter, query, type) {
if (filter.length === 0) {
return query
}
const rangeOperators = ['gt', 'lt', 'gte', 'lte', 'moreq', 'from', 'to']
const optionsPrefix = '_options'
const appliedFilters = [];
if (filter) {
for (var attribute in filter) {
let processedFilter = processNestedFieldFilter(attribute, filter[attribute])
let appliedAttributeValue = processedFilter['value']
const scope = appliedAttributeValue.scope || 'default';
delete appliedAttributeValue.scope;
appliedFilters.push({
attribute: processedFilter['attribute'],
value: appliedAttributeValue,
scope: scope
});
}
}
// process applied filters
if (appliedFilters.length > 0) {
let hasCatalogFilters = false;
// apply default filters
appliedFilters.forEach((filter) => {
if (checkIfObjectHasScope({ object: filter, scope: 'default' }) && Object.keys(filter.value).length) {
if (rangeOperators.every(rangeOperator => Object.prototype.hasOwnProperty.call(filter.value, rangeOperator))) {
// process range filters
query = query.filter('range', filter.attribute, filter.value);
} else {
// process terms filters
filter.value = filter.value[Object.keys(filter.value)[0]];
if (!Array.isArray(filter.value)) {
filter.value = [filter.value];
}
query = query.filter('terms', getMapping(filter.attribute), filter.value)
}
} else if (filter.scope === 'catalog') {
hasCatalogFilters = true;
}
})
// apply catalog scope filters
let attrFilterBuilder = (filterQr, attrPostfix = '') => {
appliedFilters.forEach((catalogfilter) => {
const valueKeys = Object.keys(catalogfilter.value);
if (checkIfObjectHasScope({ object: catalogfilter, scope: 'catalog' }) && valueKeys.length) {
const isRange = valueKeys.filter(value => rangeOperators.indexOf(value) !== -1)
if (isRange.length) {
let rangeAttribute = catalogfilter.attribute
if (rangeAttribute === 'price') {
rangeAttribute = 'final_price'
}
// process range filters
filterQr = filterQr.andFilter('range', rangeAttribute, catalogfilter.value);
} else {
// process terms filters
let newValue = catalogfilter.value[Object.keys(catalogfilter.value)[0]]
if (!Array.isArray(newValue)) {
newValue = [newValue];
}
if (attrPostfix === '') {
filterQr = filterQr.andFilter('terms', getMapping(catalogfilter.attribute), newValue)
} else {
filterQr = filterQr.andFilter('terms', catalogfilter.attribute + attrPostfix, newValue)
}
}
}
})
return filterQr
}
if (hasCatalogFilters) {
query = query.filterMinimumShouldMatch(1).orFilter('bool', (b) => attrFilterBuilder(b))
.orFilter('bool', (b) => attrFilterBuilder(b, optionsPrefix).filter('match', 'type_id', 'configurable')); // the queries can vary based on the product type
}
// Add aggregations for filters
if (appliedFilters.length > 0 && type === 'product') {
for (let attrToFilter of appliedFilters) {
if (attrToFilter.scope === 'catalog') {
if (attrToFilter.attribute !== 'price') {
query = query.aggregation('terms', getMapping(attrToFilter.attribute))
query = query.aggregation('terms', attrToFilter.attribute + optionsPrefix)
} else {
query = query.aggregation('terms', attrToFilter.attribute)
query.aggregation('range', 'price', {
ranges: [
{ from: 0, to: 50 },
{ from: 50, to: 100 },
{ from: 100, to: 150 },
{ from: 150 }
]
})
}
}
}
}
}
return query;
}
function applySearchQuery (search, query) { // TODO: as search is avaialble for other entities than product we should modify this query part to apply to any entity
if (search !== '') {
query = query.andQuery('bool', b => b.orQuery('match_phrase_prefix', 'name', { query: search, boost: getBoosts('name'), slop: 2 })
.orQuery('match_phrase', 'category.name', { query: search, boost: getBoosts('category.name') })
.orQuery('match_phrase', 'short_description', { query: search, boost: getBoosts('short_description') })
.orQuery('match_phrase', 'description', { query: search, boost: getBoosts('description') })
.orQuery('match_phrase', 'detail', { query: search, boost: getBoosts('description') }) // reviews
.orQuery('bool', b => b.orQuery('terms', 'sku', search.split('-'))
.orQuery('terms', 'configurable_children.sku', search.split('-'))
.orQuery('match_phrase', 'sku', { query: search, boost: getBoosts('sku') })
.orQuery('match_phrase', 'configurable_children.sku', { query: search, boost: getBoosts('configurable_children.sku') }))
);
}
return query;
}
function applySort (sort, query) {
if (sort) {
map(sort, (value, key) => {
query.sort(key, value);
});
}
return query;
}
export function buildQuery ({
filter = [],
sort = '',
currentPage = 1,
pageSize = 10,
search = '',
type = 'product'
}) {
let query = bodybuilder();
query = applySearchQuery(search, query);
query = applyFilters(filter, query, type);
query = applySort(sort, query);
query = query.from((currentPage - 1) * pageSize).size(pageSize);
let builtQuery = query.build()
if (search !== '') {
builtQuery['min_score'] = config.elasticsearch.min_score
}
return builtQuery;
}