Add contains option to lucene query builder
This commit is contained in:
parent
dbd0d76613
commit
d55218e813
|
@ -168,6 +168,13 @@ export function makeDatasourceFormComponents(datasource) {
|
||||||
optionsSource: "schema",
|
optionsSource: "schema",
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
if (fieldType === "array") {
|
||||||
|
component.customProps({
|
||||||
|
placeholder: "Choose an option",
|
||||||
|
optionsSource: "schema",
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
if (fieldType === "link") {
|
if (fieldType === "link") {
|
||||||
let placeholder =
|
let placeholder =
|
||||||
fieldSchema.relationshipType === "one-to-many"
|
fieldSchema.relationshipType === "one-to-many"
|
||||||
|
|
|
@ -9,6 +9,7 @@
|
||||||
DrawerContent,
|
DrawerContent,
|
||||||
Layout,
|
Layout,
|
||||||
Body,
|
Body,
|
||||||
|
Multiselect,
|
||||||
} from "@budibase/bbui"
|
} from "@budibase/bbui"
|
||||||
import DrawerBindableInput from "components/common/bindings/DrawerBindableInput.svelte"
|
import DrawerBindableInput from "components/common/bindings/DrawerBindableInput.svelte"
|
||||||
import { generate } from "shortid"
|
import { generate } from "shortid"
|
||||||
|
@ -59,6 +60,14 @@
|
||||||
expression.operator = validOperators[0] ?? OperatorOptions.Equals.value
|
expression.operator = validOperators[0] ?? OperatorOptions.Equals.value
|
||||||
onOperatorChange(expression, expression.operator)
|
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) => {
|
const onOperatorChange = (expression, operator) => {
|
||||||
|
@ -74,7 +83,12 @@
|
||||||
|
|
||||||
const getFieldOptions = field => {
|
const getFieldOptions = field => {
|
||||||
const schema = schemaFields.find(x => x.name === 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>
|
</script>
|
||||||
|
|
||||||
|
@ -128,6 +142,14 @@
|
||||||
options={getFieldOptions(filter.field)}
|
options={getFieldOptions(filter.field)}
|
||||||
bind:value={filter.value}
|
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"}
|
{:else if filter.type === "boolean"}
|
||||||
<Combobox
|
<Combobox
|
||||||
disabled={filter.noValue}
|
disabled={filter.noValue}
|
||||||
|
|
|
@ -31,6 +31,11 @@ export const OperatorOptions = {
|
||||||
value: "rangeHigh",
|
value: "rangeHigh",
|
||||||
label: "Less than",
|
label: "Less than",
|
||||||
},
|
},
|
||||||
|
Contains: {
|
||||||
|
value: "contains",
|
||||||
|
label: "Contains",
|
||||||
|
},
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export const getValidOperatorsForType = type => {
|
export const getValidOperatorsForType = type => {
|
||||||
|
@ -55,6 +60,8 @@ export const getValidOperatorsForType = type => {
|
||||||
]
|
]
|
||||||
} else if (type === "options") {
|
} else if (type === "options") {
|
||||||
return [Op.Equals, Op.NotEquals, Op.Empty, Op.NotEmpty]
|
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") {
|
} else if (type === "boolean") {
|
||||||
return [Op.Equals, Op.NotEquals, Op.Empty, Op.NotEmpty]
|
return [Op.Equals, Op.NotEquals, Op.Empty, Op.NotEmpty]
|
||||||
} else if (type === "longform") {
|
} else if (type === "longform") {
|
||||||
|
|
|
@ -17,6 +17,7 @@ class QueryBuilder {
|
||||||
notEqual: {},
|
notEqual: {},
|
||||||
empty: {},
|
empty: {},
|
||||||
notEmpty: {},
|
notEmpty: {},
|
||||||
|
contains: {},
|
||||||
...base,
|
...base,
|
||||||
}
|
}
|
||||||
this.limit = 50
|
this.limit = 50
|
||||||
|
@ -104,6 +105,12 @@ class QueryBuilder {
|
||||||
return this
|
return this
|
||||||
}
|
}
|
||||||
|
|
||||||
|
addContains(key, value) {
|
||||||
|
this.query.contains[key] = value
|
||||||
|
return this
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Preprocesses a value before going into a lucene search.
|
* Preprocesses a value before going into a lucene search.
|
||||||
* Transforms strings to lowercase and wraps strings and bools in quotes.
|
* Transforms strings to lowercase and wraps strings and bools in quotes.
|
||||||
|
@ -121,7 +128,7 @@ class QueryBuilder {
|
||||||
}
|
}
|
||||||
// Escape characters
|
// Escape characters
|
||||||
if (escape && originalType === "string") {
|
if (escape && originalType === "string") {
|
||||||
value = `${value}`.replace(/[ #+\-&|!(){}\]^"~*?:\\]/g, "\\$&")
|
value = `${value}`.replace(/[ #+\-&|!{}\]^"~*?:\\]/g, "\\$&")
|
||||||
}
|
}
|
||||||
// Wrap in quotes
|
// Wrap in quotes
|
||||||
if (hasVersion && wrap) {
|
if (hasVersion && wrap) {
|
||||||
|
@ -212,6 +219,19 @@ class QueryBuilder {
|
||||||
build(this.query.notEmpty, key => `${key}:["" TO *]`)
|
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
|
return query
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -253,6 +273,7 @@ const runQuery = async (url, body) => {
|
||||||
method: "POST",
|
method: "POST",
|
||||||
})
|
})
|
||||||
const json = await response.json()
|
const json = await response.json()
|
||||||
|
console.log(json)
|
||||||
let output = {
|
let output = {
|
||||||
rows: [],
|
rows: [],
|
||||||
}
|
}
|
||||||
|
|
|
@ -99,7 +99,13 @@ exports.createAllSearchIndex = async appId => {
|
||||||
for (let key of Object.keys(input)) {
|
for (let key of Object.keys(input)) {
|
||||||
let idxKey = prev != null ? `${prev}.${key}` : key
|
let idxKey = prev != null ? `${prev}.${key}` : key
|
||||||
idxKey = idxKey.replace(/ /, "_")
|
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
|
continue
|
||||||
}
|
}
|
||||||
if (typeof input[key] === "string") {
|
if (typeof input[key] === "string") {
|
||||||
|
|
|
@ -2036,11 +2036,7 @@
|
||||||
"type": "boolean",
|
"type": "boolean",
|
||||||
"label": "Autocomplete",
|
"label": "Autocomplete",
|
||||||
"key": "autocomplete",
|
"key": "autocomplete",
|
||||||
"defaultValue": false,
|
"defaultValue": false
|
||||||
"dependsOn": {
|
|
||||||
"setting": "optionsType",
|
|
||||||
"value": "select"
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "boolean",
|
"type": "boolean",
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
<script>
|
<script>
|
||||||
import { Multiselect } from "@budibase/bbui"
|
import { Multiselect } from "@budibase/bbui"
|
||||||
import Field from "./Field.svelte"
|
import Field from "./Field.svelte"
|
||||||
|
import { getOptions } from "./optionsParser"
|
||||||
export let field
|
export let field
|
||||||
export let label
|
export let label
|
||||||
export let placeholder
|
export let placeholder
|
||||||
|
@ -13,49 +13,20 @@
|
||||||
export let labelColumn
|
export let labelColumn
|
||||||
export let valueColumn
|
export let valueColumn
|
||||||
export let customOptions
|
export let customOptions
|
||||||
console.log(defaultValue)
|
|
||||||
let fieldState
|
let fieldState
|
||||||
let fieldApi
|
let fieldApi
|
||||||
let fieldSchema
|
let fieldSchema
|
||||||
|
|
||||||
|
$: flatOptions = optionsSource == null || optionsSource === "schema"
|
||||||
$: options = getOptions(
|
$: options = getOptions(
|
||||||
optionsSource,
|
optionsSource,
|
||||||
fieldSchema,
|
fieldSchema,
|
||||||
dataProvider,
|
dataProvider,
|
||||||
labelColumn,
|
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>
|
</script>
|
||||||
|
|
||||||
<Field
|
<Field
|
||||||
|
@ -69,5 +40,10 @@
|
||||||
bind:fieldApi
|
bind:fieldApi
|
||||||
bind:fieldSchema
|
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>
|
</Field>
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
<script>
|
<script>
|
||||||
import { CoreSelect, CoreRadioGroup } from "@budibase/bbui"
|
import { CoreSelect, CoreRadioGroup } from "@budibase/bbui"
|
||||||
import Field from "./Field.svelte"
|
import Field from "./Field.svelte"
|
||||||
|
import { getOptions } from "./optionsParser"
|
||||||
export let field
|
export let field
|
||||||
export let label
|
export let label
|
||||||
export let placeholder
|
export let placeholder
|
||||||
|
@ -26,41 +26,9 @@
|
||||||
fieldSchema,
|
fieldSchema,
|
||||||
dataProvider,
|
dataProvider,
|
||||||
labelColumn,
|
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>
|
</script>
|
||||||
|
|
||||||
<Field
|
<Field
|
||||||
|
|
|
@ -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 []
|
||||||
|
}
|
|
@ -11,6 +11,7 @@ export const buildLuceneQuery = filter => {
|
||||||
notEqual: {},
|
notEqual: {},
|
||||||
empty: {},
|
empty: {},
|
||||||
notEmpty: {},
|
notEmpty: {},
|
||||||
|
contains: {}
|
||||||
}
|
}
|
||||||
if (Array.isArray(filter)) {
|
if (Array.isArray(filter)) {
|
||||||
filter.forEach(expression => {
|
filter.forEach(expression => {
|
||||||
|
|
Loading…
Reference in New Issue