Add contains option to lucene query builder

This commit is contained in:
Peter Clement 2021-08-24 16:14:38 +01:00
parent dbd0d76613
commit d55218e813
10 changed files with 136 additions and 80 deletions

View File

@ -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"

View File

@ -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
}
</script>
@ -128,6 +142,14 @@
options={getFieldOptions(filter.field)}
bind:value={filter.value}
/>
{:else if filter.type === "array"}
<Multiselect
disabled={filter.noValue}
options={getFieldOptions(filter.field)}
bind:value={filter.value}
getOptionLabel={x => x}
getOptionValue={x => x}
/>
{:else if filter.type === "boolean"}
<Combobox
disabled={filter.noValue}

View File

@ -31,6 +31,11 @@ export const OperatorOptions = {
value: "rangeHigh",
label: "Less than",
},
Contains: {
value: "contains",
label: "Contains",
},
}
export const getValidOperatorsForType = type => {
@ -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") {

View File

@ -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: [],
}

View File

@ -99,7 +99,13 @@ exports.createAllSearchIndex = async appId => {
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") {

View File

@ -2036,11 +2036,7 @@
"type": "boolean",
"label": "Autocomplete",
"key": "autocomplete",
"defaultValue": false,
"dependsOn": {
"setting": "optionsType",
"value": "select"
}
"defaultValue": false
},
{
"type": "boolean",

View File

@ -1,7 +1,7 @@
<script>
import { Multiselect } from "@budibase/bbui"
import Field from "./Field.svelte"
import { getOptions } from "./optionsParser"
export let field
export let label
export let placeholder
@ -13,49 +13,20 @@
export let labelColumn
export let valueColumn
export let customOptions
console.log(defaultValue)
let fieldState
let fieldApi
let fieldSchema
$: flatOptions = optionsSource == null || optionsSource === "schema"
$: options = getOptions(
optionsSource,
fieldSchema,
dataProvider,
labelColumn,
valueColumn
valueColumn,
customOptions
)
const getOptions = (
optionsSource,
fieldSchema,
dataProvider,
labelColumn,
valueColumn
) => {
// Take options from schema
if (optionsSource == null || optionsSource === "schema") {
return fieldSchema?.constraints?.inclusion ?? []
}
// 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 []
}
</script>
<Field
@ -69,5 +40,10 @@
bind:fieldApi
bind:fieldSchema
>
<Multiselect {placeholder} options={options[0]} />
<Multiselect
getOptionLabel={flatOptions ? x => x : x => x.label}
getOptionValue={flatOptions ? x => x : x => x.value}
{placeholder}
{options}
/>
</Field>

View File

@ -1,7 +1,7 @@
<script>
import { CoreSelect, CoreRadioGroup } from "@budibase/bbui"
import Field from "./Field.svelte"
import { getOptions } from "./optionsParser"
export let field
export let label
export let placeholder
@ -26,41 +26,9 @@
fieldSchema,
dataProvider,
labelColumn,
valueColumn
valueColumn,
customOptions
)
const getOptions = (
optionsSource,
fieldSchema,
dataProvider,
labelColumn,
valueColumn
) => {
// Take options from schema
if (optionsSource == null || optionsSource === "schema") {
return fieldSchema?.constraints?.inclusion ?? []
}
// 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 []
}
</script>
<Field

View File

@ -0,0 +1,52 @@
export const getOptions = (
optionsSource,
fieldSchema,
dataProvider,
labelColumn,
valueColumn,
customOptions
) => {
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 []
}

View File

@ -11,6 +11,7 @@ export const buildLuceneQuery = filter => {
notEqual: {},
empty: {},
notEmpty: {},
contains: {}
}
if (Array.isArray(filter)) {
filter.forEach(expression => {