react query builder basic filters
by alfredopacino
CSS
body {
background: #20262E;
padding: 20px;
font-family: Helvetica;
}
#app {
background: #fff;
border-radius: 4px;
padding: 20px;
transition: all 0.2s;
text-align: center;
}
TypeScript
function buildQuery(filters) {
const must = [];
const nonNested = [];
const nestedGroups = {};
const isRangeTerm = v => typeof v === 'object' && v !== null && ['lt', 'lte', 'gt', 'gte'].some(key => key in v);
filters.forEach(({field, value}) => {
const fieldParts = field.split(".");
if (fieldParts.length === 1) {
// Top-level field (not nested)
if (Array.isArray(value)) {
must.push({terms: { [field]: value }});
} else {
if (isRangeTerm(value)) {
must.push( { range: { [field]: value} } )
} else {
must.push({term: { [field]: value }});
}
}
} else {
// Group by nested path
const nestedPath = fieldParts.slice(0, -1).join(".");
if (!nestedGroups[nestedPath]) {
nestedGroups[nestedPath] = [];
}
nestedGroups[nestedPath].push({
fullPath: field,
value
});
}
});
Object.entries(nestedGroups).forEach(([nestedPath, groupFilters]) => {
console.log("nestedPath, groupFilters", nestedPath, groupFilters)
const mustInner = groupFilters.map(({fullPath, value}) => (
Array.isArray(value)
? {terms: { [fullPath]: value }}
: isRangeTerm(value)
? { range: { [fullPath]: value} }
: {term: { [fullPath]: value }}
));
const nestedLevels = nestedPath.split(".");
let nestedQuery = { bool: { must: mustInner } };
// loops over multilevels and nesting backwards
for (let i = nestedLevels.length - 1; i >= 0; i--) {
console.log("levels", nestedLevels.slice(0, i + 1).join("."))
nestedQuery = {
nested: {
path: nestedLevels.slice(0, i + 1).join("."),
query: nestedQuery
}
};
}
must.push(nestedQuery);
});
return { query: { bool: { must } } };
}
const filters = [
//{ field: "variantCategory", value: "small" },
//{ field:...