diff --git a/packages/builder/src/pages/builder/app/[application]/design/[screenId]/components/[componentId]/_components/navigation/ComponentListPanel.svelte b/packages/builder/src/pages/builder/app/[application]/design/[screenId]/components/[componentId]/_components/navigation/ComponentListPanel.svelte index 5b86d4da29..1bb4e3d9cd 100644 --- a/packages/builder/src/pages/builder/app/[application]/design/[screenId]/components/[componentId]/_components/navigation/ComponentListPanel.svelte +++ b/packages/builder/src/pages/builder/app/[application]/design/[screenId]/components/[componentId]/_components/navigation/ComponentListPanel.svelte @@ -9,6 +9,7 @@ import { setContext } from "svelte" import DNDPositionIndicator from "./DNDPositionIndicator.svelte" import { DropPosition } from "./dndStore" + import { notifications } from "@budibase/bbui" let scrollRef @@ -55,6 +56,15 @@ }) } + const onDrop = async () => { + try { + await dndStore.actions.drop() + } catch (error) { + console.error(error) + notifications.error("Error saving component") + } + } + // Set scroll context so components can invoke scrolling when selected setContext("scroll", { scrollTo, @@ -83,6 +93,7 @@ opened scrollable icon="WebPage" + on:drop={onDrop} > diff --git a/packages/client/src/components/app/blocks/CardsBlock.svelte b/packages/client/src/components/app/blocks/CardsBlock.svelte index f6aed54760..a13364833a 100644 --- a/packages/client/src/components/app/blocks/CardsBlock.svelte +++ b/packages/client/src/components/app/blocks/CardsBlock.svelte @@ -4,6 +4,7 @@ import BlockComponent from "components/BlockComponent.svelte" import { Heading } from "@budibase/bbui" import { makePropSafe as safe } from "@budibase/string-templates" + import { enrichSearchColumns, enrichFilter } from "utils/blocks.js" export let title export let dataSource @@ -33,14 +34,6 @@ const { fetchDatasourceSchema, styleable } = getContext("sdk") const context = getContext("context") const component = getContext("component") - const schemaComponentMap = { - string: "stringfield", - options: "optionsfield", - number: "numberfield", - datetime: "datetimefield", - boolean: "booleanfield", - formula: "stringfield", - } let formId let dataProviderId @@ -68,39 +61,6 @@ }, ] - // Enrich the default filter with the specified search fields - const enrichFilter = (filter, columns, formId) => { - let enrichedFilter = [...(filter || [])] - columns?.forEach(column => { - const safePath = column.name.split(".").map(safe).join(".") - enrichedFilter.push({ - field: column.name, - operator: column.type === "string" ? "string" : "equal", - type: column.type, - valueType: "Binding", - value: `{{ ${safe(formId)}.${safePath} }}`, - }) - }) - return enrichedFilter - } - - // Determine data types for search fields and only use those that are valid - const enrichSearchColumns = (searchColumns, schema) => { - let enrichedColumns = [] - searchColumns?.forEach(column => { - const schemaType = schema?.[column]?.type - const componentType = schemaComponentMap[schemaType] - if (componentType) { - enrichedColumns.push({ - name: column, - componentType, - type: schemaType, - }) - } - }) - return enrichedColumns.slice(0, 5) - } - // Builds a full details page URL for the card title const buildFullCardUrl = (link, url, repeaterId, linkColumn) => { if (!link || !url || !repeaterId) { diff --git a/packages/client/src/components/app/blocks/TableBlock.svelte b/packages/client/src/components/app/blocks/TableBlock.svelte index 4ae169ca72..e67124fc4f 100644 --- a/packages/client/src/components/app/blocks/TableBlock.svelte +++ b/packages/client/src/components/app/blocks/TableBlock.svelte @@ -4,6 +4,7 @@ import BlockComponent from "components/BlockComponent.svelte" import { Heading } from "@budibase/bbui" import { makePropSafe as safe } from "@budibase/string-templates" + import { enrichSearchColumns, enrichFilter } from "utils/blocks.js" export let title export let dataSource @@ -31,14 +32,6 @@ const { fetchDatasourceSchema, styleable } = getContext("sdk") const context = getContext("context") const component = getContext("component") - const schemaComponentMap = { - string: "stringfield", - options: "optionsfield", - number: "numberfield", - datetime: "datetimefield", - boolean: "booleanfield", - formula: "stringfield", - } let formId let dataProviderId @@ -58,40 +51,6 @@ }, ] - // Enrich the default filter with the specified search fields - const enrichFilter = (filter, columns, formId) => { - let enrichedFilter = [...(filter || [])] - columns?.forEach(column => { - const safePath = column.name.split(".").map(safe).join(".") - const stringType = column.type === "string" || column.type === "formula" - enrichedFilter.push({ - field: column.name, - type: column.type, - operator: stringType ? "string" : "equal", - valueType: "Binding", - value: `{{ ${safe(formId)}.${safePath} }}`, - }) - }) - return enrichedFilter - } - - // Determine data types for search fields and only use those that are valid - const enrichSearchColumns = (searchColumns, schema) => { - let enrichedColumns = [] - searchColumns?.forEach(column => { - const schemaType = schema?.[column]?.type - const componentType = schemaComponentMap[schemaType] - if (componentType) { - enrichedColumns.push({ - name: column, - componentType, - type: schemaType, - }) - } - }) - return enrichedColumns.slice(0, 5) - } - // Load the datasource schema so we can determine column types const fetchSchema = async dataSource => { if (dataSource) { @@ -109,7 +68,7 @@ {#if title || enrichedSearchColumns?.length || showTitleButton}
diff --git a/packages/client/src/components/app/forms/Form.svelte b/packages/client/src/components/app/forms/Form.svelte index a274fb24f0..320fc712aa 100644 --- a/packages/client/src/components/app/forms/Form.svelte +++ b/packages/client/src/components/app/forms/Form.svelte @@ -13,6 +13,10 @@ // for fields rendered in things like search blocks. export let disableValidation = false + // Not exposed as a builder setting. Used internally to allow searching on + // auto columns. + export let editAutoColumns = false + const context = getContext("context") const { API, fetchDatasourceSchema } = getContext("sdk") @@ -107,6 +111,7 @@ {table} {initialValues} {disableValidation} + {editAutoColumns} > diff --git a/packages/client/src/components/app/forms/InnerForm.svelte b/packages/client/src/components/app/forms/InnerForm.svelte index 752bc9a2eb..316b697043 100644 --- a/packages/client/src/components/app/forms/InnerForm.svelte +++ b/packages/client/src/components/app/forms/InnerForm.svelte @@ -11,6 +11,7 @@ export let schema export let table export let disableValidation = false + export let editAutoColumns = false const component = getContext("component") const { styleable, Provider, ActionTypes } = getContext("sdk") @@ -183,7 +184,8 @@ fieldId, value: initialValue, error: initialError, - disabled: disabled || fieldDisabled || isAutoColumn, + disabled: + disabled || fieldDisabled || (isAutoColumn && !editAutoColumns), defaultValue, validator, lastUpdate: Date.now(), diff --git a/packages/client/src/utils/blocks.js b/packages/client/src/utils/blocks.js new file mode 100644 index 0000000000..3b97544238 --- /dev/null +++ b/packages/client/src/utils/blocks.js @@ -0,0 +1,81 @@ +import { makePropSafe as safe } from "@budibase/string-templates" + +// Map of data types to component types for search fields inside blocks +const schemaComponentMap = { + string: "stringfield", + options: "optionsfield", + number: "numberfield", + datetime: "datetimefield", + boolean: "booleanfield", + formula: "stringfield", +} + +/** + * Determine data types for search fields and only use those that are valid + * @param searchColumns the search columns to use + * @param schema the data source schema + */ +export const enrichSearchColumns = (searchColumns, schema) => { + let enrichedColumns = [] + searchColumns?.forEach(column => { + const schemaType = schema?.[column]?.type + const componentType = schemaComponentMap[schemaType] + if (componentType) { + enrichedColumns.push({ + name: column, + componentType, + type: schemaType, + }) + } + }) + return enrichedColumns.slice(0, 5) +} + +/** + * Enriches a normal datasource filter with bindings representing the additional + * search fields configured as part of a searchable block. These bindings are + * fields inside a form used as part of the block. + * @param filter the normal data provider filter + * @param columns the enriched search column structure + * @param formId the ID of the form containing the search fields + */ +export const enrichFilter = (filter, columns, formId) => { + let enrichedFilter = [...(filter || [])] + columns?.forEach(column => { + const safePath = column.name.split(".").map(safe).join(".") + const stringType = column.type === "string" || column.type === "formula" + const dateType = column.type === "datetime" + const binding = `${safe(formId)}.${safePath}` + + // For dates, use a range of the entire day selected + if (dateType) { + enrichedFilter.push({ + field: column.name, + type: column.type, + operator: "rangeLow", + valueType: "Binding", + value: `{{ ${binding} }}`, + }) + const format = "YYYY-MM-DDTHH:mm:ss.SSSZ" + enrichedFilter.push({ + field: column.name, + type: column.type, + operator: "rangeHigh", + valueType: "Binding", + value: `{{ date (add (date ${binding} "x") 86399999) "${format}" }}`, + }) + } + + // For other fields, do an exact match + else { + enrichedFilter.push({ + field: column.name, + type: column.type, + operator: stringType ? "string" : "equal", + valueType: "Binding", + value: `{{ ${binding} }}`, + }) + } + }) + return enrichedFilter +} diff --git a/packages/frontend-core/src/utils/lucene.js b/packages/frontend-core/src/utils/lucene.js index 1001ec26a8..b27296af37 100644 --- a/packages/frontend-core/src/utils/lucene.js +++ b/packages/frontend-core/src/utils/lucene.js @@ -99,8 +99,16 @@ export const buildLuceneQuery = filter => { filter.forEach(expression => { let { operator, field, type, value, externalType } = expression // Parse all values into correct types - if (type === "datetime" && value) { - value = new Date(value).toISOString() + if (type === "datetime") { + // Ensure date value is a valid date and parse into correct format + if (!value) { + return + } + try { + value = new Date(value).toISOString() + } catch (error) { + return + } } if (type === "number" && !Array.isArray(value)) { if (operator === "oneOf") {