react query builder basic filters with multilevel nesting support
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
/**
* OpenSearch Query Builder for basic filters applied to variants.
*
* It supports the following cases:
*
* 1. **Top-level fields**
* Generates either a `term`, `terms` (for arrays), or a `range` query.
* Examples:
* { field: "variantCategory", value: "small" }
* { field: "variantCategory", value: ["cnv", "structural"] }
* { field: "start", value: { lte: "1" } }
*
* 2. **Indexed nested fields**
* These are fields stored as nested objects in OpenSearch.
* Example: { field: "genes.genePanels.source", value: ["NGTD", "AWSD"] }
*
* 3. **Nested fields that are not entirely indexed as nested**
* Allows overriding the nesting level through `nestedPath` in `queryConfig`.
* Example: { field: "reportEvents.vendorSpecificScores.rank", value: { lte: "1" }, queryConfig: { nestedPath: "reportEvents" } }
*
* 4. **Fields that imply additional underlying query terms**
* Uses `requiredTerm` in `queryConfig` to inject another QueryTerm.
* Example: { field: "reportEvents.score", value: { lte: "1" }, queryConfig: { requiredTerm: { field: "reportEvents.interpretationService", value: "Exomiser" } } }
*
* 5. **Special-case fields with ad-hoc render**
* Example: { field: "size", value: { lte: "1" } }
*/
type QueryTerm = {
field: string;
value: QueryTermValue;
queryConfig?: { nestedPath?: string | null; requiredTerm?: QueryTerm };
};
type QueryTermValue = string | string[] | RangeTerm | null;
// at least 1 key has to be present in a range
type RangeTerm = Partial<{ lt: number; lte: number; gt: number; gte: number; eq: number }> &
({ lt: number } | { lte: number } | { gt: number } | { gte: number } | { eq: number });
// intermediate type
type NestedFilter = {
fullPath: string;
value: QueryTermValue;
nestedPath?: string; // nested path override
};
// output types
type TermClause = { term: Record<string, string | null> };
type TermsClause = { terms:...