From f000d44e39732a6bdc3c8cfe09593c370bffe761 Mon Sep 17 00:00:00 2001 From: Peter Clement Date: Tue, 24 Aug 2021 16:14:38 +0100 Subject: [PATCH] Add contains option to lucene query builder --- .../screenTemplates/utils/commonComponents.js | 7 +++ .../FilterEditor/FilterDrawer.svelte | 24 ++++++++- packages/builder/src/helpers/lucene.js | 7 +++ .../src/api/controllers/row/internalSearch.js | 23 +++++++- packages/server/src/db/views/staticViews.js | 10 +++- packages/standard-components/manifest.json | 6 +-- .../src/forms/MultiFieldSelect.svelte | 48 +++++------------ .../src/forms/OptionsField.svelte | 38 ++------------ .../src/forms/optionsParser.js | 52 +++++++++++++++++++ packages/standard-components/src/lucene.js | 1 + 10 files changed, 136 insertions(+), 80 deletions(-) create mode 100644 packages/standard-components/src/forms/optionsParser.js diff --git a/packages/builder/src/builderStore/store/screenTemplates/utils/commonComponents.js b/packages/builder/src/builderStore/store/screenTemplates/utils/commonComponents.js index 7d40995925..5b3bc041ff 100644 --- a/packages/builder/src/builderStore/store/screenTemplates/utils/commonComponents.js +++ b/packages/builder/src/builderStore/store/screenTemplates/utils/commonComponents.js @@ -168,6 +168,13 @@ export function makeDatasourceFormComponents(datasource) { optionsSource: "schema", }) } + if (fieldType === "array") { + component.customProps({ + placeholder: "Choose an option", + optionsSource: "schema", + }) + } + if (fieldType === "link") { let placeholder = fieldSchema.relationshipType === "one-to-many" diff --git a/packages/builder/src/components/design/PropertiesPanel/PropertyControls/FilterEditor/FilterDrawer.svelte b/packages/builder/src/components/design/PropertiesPanel/PropertyControls/FilterEditor/FilterDrawer.svelte index d41d28e4e3..cd5cc1661c 100644 --- a/packages/builder/src/components/design/PropertiesPanel/PropertyControls/FilterEditor/FilterDrawer.svelte +++ b/packages/builder/src/components/design/PropertiesPanel/PropertyControls/FilterEditor/FilterDrawer.svelte @@ -9,6 +9,7 @@ DrawerContent, Layout, Body, + Multiselect, } from "@budibase/bbui" import DrawerBindableInput from "components/common/bindings/DrawerBindableInput.svelte" import { generate } from "shortid" @@ -59,6 +60,14 @@ expression.operator = validOperators[0] ?? OperatorOptions.Equals.value onOperatorChange(expression, expression.operator) } + + // if changed to an array, change default value to empty array + const idx = filters.findIndex(x => (x.field = field)) + if (expression.type === "array") { + filters[idx].value = [] + } else { + filters[idx].value = null + } } const onOperatorChange = (expression, operator) => { @@ -74,7 +83,12 @@ const getFieldOptions = field => { const schema = schemaFields.find(x => x.name === field) - return schema?.constraints?.inclusion || [] + const opt = + schema.type == "array" + ? schema?.constraints?.inclusion[0] + : schema?.constraints?.inclusion || [] + + return opt } @@ -128,6 +142,14 @@ options={getFieldOptions(filter.field)} bind:value={filter.value} /> + {:else if filter.type === "array"} + x} + getOptionValue={x => x} + /> {:else if filter.type === "boolean"} { @@ -55,6 +60,8 @@ export const getValidOperatorsForType = type => { ] } else if (type === "options") { return [Op.Equals, Op.NotEquals, Op.Empty, Op.NotEmpty] + } else if (type === "array") { + return [Op.Equals, Op.NotEquals, Op.Empty, Op.NotEmpty, Op.Contains] } else if (type === "boolean") { return [Op.Equals, Op.NotEquals, Op.Empty, Op.NotEmpty] } else if (type === "longform") { diff --git a/packages/server/src/api/controllers/row/internalSearch.js b/packages/server/src/api/controllers/row/internalSearch.js index cc35da40ec..e2e7beee27 100644 --- a/packages/server/src/api/controllers/row/internalSearch.js +++ b/packages/server/src/api/controllers/row/internalSearch.js @@ -17,6 +17,7 @@ class QueryBuilder { notEqual: {}, empty: {}, notEmpty: {}, + contains: {}, ...base, } this.limit = 50 @@ -104,6 +105,12 @@ class QueryBuilder { return this } + addContains(key, value) { + this.query.contains[key] = value + return this + } + + /** * Preprocesses a value before going into a lucene search. * Transforms strings to lowercase and wraps strings and bools in quotes. @@ -121,7 +128,7 @@ class QueryBuilder { } // Escape characters if (escape && originalType === "string") { - value = `${value}`.replace(/[ #+\-&|!(){}\]^"~*?:\\]/g, "\\$&") + value = `${value}`.replace(/[ #+\-&|!{}\]^"~*?:\\]/g, "\\$&") } // Wrap in quotes if (hasVersion && wrap) { @@ -212,6 +219,19 @@ class QueryBuilder { build(this.query.notEmpty, key => `${key}:["" TO *]`) } + if (this.query.contains) { + build(this.query.contains, (key, value) => { + if (!value) { + return null + } + let opts = [] + value.forEach(val => opts.push(`${key}.${val}:${builder.preprocess(val, allPreProcessingOpts)}`)) + const joined = opts.join(' AND ') + return joined + }) + + } + return query } @@ -253,6 +273,7 @@ const runQuery = async (url, body) => { method: "POST", }) const json = await response.json() + console.log(json) let output = { rows: [], } diff --git a/packages/server/src/db/views/staticViews.js b/packages/server/src/db/views/staticViews.js index 23f320d7eb..35ccbd21b2 100644 --- a/packages/server/src/db/views/staticViews.js +++ b/packages/server/src/db/views/staticViews.js @@ -94,12 +94,18 @@ exports.createAllSearchIndex = async appId => { await searchIndex( appId, SearchIndexes.ROWS, - function (doc) { + function (doc) { function idx(input, prev) { for (let key of Object.keys(input)) { let idxKey = prev != null ? `${prev}.${key}` : key idxKey = idxKey.replace(/ /, "_") - if (key === "_id" || key === "_rev" || input[key] == null) { + + + if (Array.isArray(input[key])) { + for (val in input[key]) { + index(`${idxKey}.${input[key][v]}`, input[key][v], { store: true }); + } + } else if (key === "_id" || key === "_rev" || input[key] == null) { continue } if (typeof input[key] === "string") { diff --git a/packages/standard-components/manifest.json b/packages/standard-components/manifest.json index 8b2bb863a5..948ebc91d8 100644 --- a/packages/standard-components/manifest.json +++ b/packages/standard-components/manifest.json @@ -2036,11 +2036,7 @@ "type": "boolean", "label": "Autocomplete", "key": "autocomplete", - "defaultValue": false, - "dependsOn": { - "setting": "optionsType", - "value": "select" - } + "defaultValue": false }, { "type": "boolean", diff --git a/packages/standard-components/src/forms/MultiFieldSelect.svelte b/packages/standard-components/src/forms/MultiFieldSelect.svelte index d44f471304..4e6777d96c 100644 --- a/packages/standard-components/src/forms/MultiFieldSelect.svelte +++ b/packages/standard-components/src/forms/MultiFieldSelect.svelte @@ -1,7 +1,7 @@ - + x : x => x.label} + getOptionValue={flatOptions ? x => x : x => x.value} + {placeholder} + {options} + /> diff --git a/packages/standard-components/src/forms/OptionsField.svelte b/packages/standard-components/src/forms/OptionsField.svelte index b43ddb9f36..e240c8f23d 100644 --- a/packages/standard-components/src/forms/OptionsField.svelte +++ b/packages/standard-components/src/forms/OptionsField.svelte @@ -1,7 +1,7 @@ { + const isArray = fieldSchema?.type === "array" + // Take options from schema + if (optionsSource == null || optionsSource === "schema") { + if (isArray) { + return fieldSchema?.constraints?.inclusion[0] ?? [] + } + return fieldSchema?.constraints?.inclusion ?? [] + } + + if (optionsSource === "provider" && isArray) { + let optionsSet = {} + + dataProvider?.rows?.forEach(row => { + const value = row?.[valueColumn] + if (value) { + const label = row[labelColumn] || value + optionsSet[value] = { value, label } + } + }) + return Object.values(optionsSet) + } + + + + // Extract options from data provider + if (optionsSource === "provider" && valueColumn) { + let optionsSet = {} + dataProvider?.rows?.forEach(row => { + const value = row?.[valueColumn] + if (value) { + const label = row[labelColumn] || value + optionsSet[value] = { value, label } + } + }) + return Object.values(optionsSet) + } + + // Extract custom options + if (optionsSource === "custom" && customOptions) { + return customOptions + } + + return [] + } diff --git a/packages/standard-components/src/lucene.js b/packages/standard-components/src/lucene.js index 50aae1f32c..f132086aa2 100644 --- a/packages/standard-components/src/lucene.js +++ b/packages/standard-components/src/lucene.js @@ -11,6 +11,7 @@ export const buildLuceneQuery = filter => { notEqual: {}, empty: {}, notEmpty: {}, + contains: {} } if (Array.isArray(filter)) { filter.forEach(expression => {