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 a1cdc48806..8d7f50a527 100644 --- a/packages/builder/src/components/design/PropertiesPanel/PropertyControls/FilterEditor/FilterDrawer.svelte +++ b/packages/builder/src/components/design/PropertiesPanel/PropertyControls/FilterEditor/FilterDrawer.svelte @@ -14,7 +14,7 @@ import ClientBindingPanel from "components/common/bindings/ClientBindingPanel.svelte" import { generate } from "shortid" import { getValidOperatorsForType, OperatorOptions } from "constants/lucene" - import { tables } from "stores/backend" + import { getFields } from "helpers/searchFields" export let schemaFields export let filters = [] @@ -22,34 +22,6 @@ export let panel = ClientBindingPanel export let allowBindings = true - const BannedTypes = ["link", "attachment", "formula", "json", "jsonarray"] - - function getTableFields(linkField) { - const table = $tables.list.find(table => table._id === linkField.tableId) - if (!table || !table.sql) { - return [] - } - const linkFields = getFields(Object.values(table.schema), { - allowLinks: false, - }) - return linkFields.map(field => ({ - ...field, - name: `${linkField.name}.${field.name}`, - })) - } - - function getFields(fields, { allowLinks } = { allowLinks: true }) { - let fieldNames = fields.filter(field => !BannedTypes.includes(field.type)) - if (allowLinks) { - const linkFields = fields.filter(field => field.type === "link") - for (let linkField of linkFields) { - // only allow one depth of SQL relationship filtering - fieldNames = fieldNames.concat(getTableFields(linkField)) - } - } - return fieldNames - } - $: enrichedSchemaFields = getFields(schemaFields || []) $: fieldOptions = enrichedSchemaFields.map(field => field.name) || [] $: valueTypeOptions = allowBindings ? ["Value", "Binding"] : ["Value"] diff --git a/packages/builder/src/components/design/PropertiesPanel/PropertyControls/SearchFieldSelect.svelte b/packages/builder/src/components/design/PropertiesPanel/PropertyControls/SearchFieldSelect.svelte new file mode 100644 index 0000000000..ad8fad8a87 --- /dev/null +++ b/packages/builder/src/components/design/PropertiesPanel/PropertyControls/SearchFieldSelect.svelte @@ -0,0 +1,52 @@ + + + diff --git a/packages/builder/src/components/design/PropertiesPanel/PropertyControls/componentSettings.js b/packages/builder/src/components/design/PropertiesPanel/PropertyControls/componentSettings.js index e752240302..5e27cdce28 100644 --- a/packages/builder/src/components/design/PropertiesPanel/PropertyControls/componentSettings.js +++ b/packages/builder/src/components/design/PropertiesPanel/PropertyControls/componentSettings.js @@ -7,6 +7,7 @@ import ColorPicker from "./ColorPicker.svelte" import { IconSelect } from "./IconSelect" import FieldSelect from "./FieldSelect.svelte" import MultiFieldSelect from "./MultiFieldSelect.svelte" +import SearchFieldSelect from "./SearchFieldSelect.svelte" import SchemaSelect from "./SchemaSelect.svelte" import SectionSelect from "./SectionSelect.svelte" import NavigationEditor from "./NavigationEditor/NavigationEditor.svelte" @@ -30,6 +31,7 @@ const componentMap = { icon: IconSelect, field: FieldSelect, multifield: MultiFieldSelect, + searchfield: SearchFieldSelect, options: OptionsEditor, schema: SchemaSelect, section: SectionSelect, diff --git a/packages/builder/src/constants/backend/index.js b/packages/builder/src/constants/backend/index.js index c5fb294f80..c1eea5b0ef 100644 --- a/packages/builder/src/constants/backend/index.js +++ b/packages/builder/src/constants/backend/index.js @@ -229,3 +229,11 @@ export const PaginationLocations = [ { label: "Query parameters", value: "query" }, { label: "Request body", value: "body" }, ] + +export const BannedSearchTypes = [ + "link", + "attachment", + "formula", + "json", + "jsonarray", +] diff --git a/packages/builder/src/helpers/searchFields.js b/packages/builder/src/helpers/searchFields.js new file mode 100644 index 0000000000..3f3ca1ce7e --- /dev/null +++ b/packages/builder/src/helpers/searchFields.js @@ -0,0 +1,31 @@ +import { tables } from "../stores/backend" +import { BannedSearchTypes } from "../constants/backend" +import { get } from "svelte/store" + +export function getTableFields(linkField) { + const table = get(tables).list.find(table => table._id === linkField.tableId) + if (!table || !table.sql) { + return [] + } + const linkFields = getFields(Object.values(table.schema), { + allowLinks: false, + }) + return linkFields.map(field => ({ + ...field, + name: `${table.name}.${field.name}`, + })) +} + +export function getFields(fields, { allowLinks } = { allowLinks: true }) { + let fieldNames = fields.filter( + field => !BannedSearchTypes.includes(field.type) + ) + if (allowLinks) { + const linkFields = fields.filter(field => field.type === "link") + for (let linkField of linkFields) { + // only allow one depth of SQL relationship filtering + fieldNames = fieldNames.concat(getTableFields(linkField)) + } + } + return fieldNames +} diff --git a/packages/client/manifest.json b/packages/client/manifest.json index dfe9ae5a91..310cec7f4b 100644 --- a/packages/client/manifest.json +++ b/packages/client/manifest.json @@ -2811,7 +2811,7 @@ "key": "dataSource" }, { - "type": "multifield", + "type": "searchfield", "label": "Search Columns", "key": "searchColumns", "placeholder": "Choose search columns" diff --git a/packages/client/src/components/app/blocks/TableBlock.svelte b/packages/client/src/components/app/blocks/TableBlock.svelte index 936a8d1734..fef6be5c50 100644 --- a/packages/client/src/components/app/blocks/TableBlock.svelte +++ b/packages/client/src/components/app/blocks/TableBlock.svelte @@ -41,9 +41,11 @@ let dataProviderId let schema let schemaLoaded = false + let enrichedSearchColumns + let enrichedSearchColumnsLoaded = false $: fetchSchema(dataSource) - $: enrichedSearchColumns = enrichSearchColumns(searchColumns, schema) + $: enrichSearchColumns(searchColumns, schema) $: enrichedFilter = enrichFilter(filter, enrichedSearchColumns, formId) $: titleButtonAction = [ { @@ -71,9 +73,9 @@ } // Determine data types for search fields and only use those that are valid - const enrichSearchColumns = (searchColumns, schema) => { + const enrichSearchColumns = async (searchColumns, schema) => { let enrichedColumns = [] - searchColumns?.forEach(column => { + const addType = column => { const schemaType = schema?.[column]?.type const componentType = schemaComponentMap[schemaType] if (componentType) { @@ -82,9 +84,39 @@ componentType, type: schemaType, }) + return true } - }) - return enrichedColumns.slice(0, 3) + return false + } + for (let column of searchColumns || []) { + // if addType returns false, it didn't find one, look for SQL relationships + if (!addType(column) && column.includes(".")) { + const [tableName, linkColumn] = column.split(".") + for (let colSchema of Object.values(schema || {})) { + // found the related table + if ( + colSchema.type === "link" && + colSchema.tableId && + colSchema.tableId.endsWith(tableName) + ) { + try { + const linkSchema = await fetchDatasourceSchema({ + ...dataSource, + tableId: colSchema.tableId, + }) + if (linkSchema) { + schema[column] = linkSchema[linkColumn] + addType(column) + } + } catch (err) { + // ignore the error, couldn't get table + } + } + } + } + } + enrichedSearchColumns = enrichedColumns.slice(0, 3) + enrichedSearchColumnsLoaded = true } // Load the datasource schema so we can determine column types @@ -96,7 +128,7 @@ } -{#if schemaLoaded} +{#if schemaLoaded && enrichedSearchColumnsLoaded}
diff --git a/packages/string-templates/test/basic.spec.js b/packages/string-templates/test/basic.spec.js index b437e386fc..2fd6505410 100644 --- a/packages/string-templates/test/basic.spec.js +++ b/packages/string-templates/test/basic.spec.js @@ -146,3 +146,14 @@ describe("check manifest", () => { ) }) }) + +describe("check full stops that are safe", () => { + it("should allow using an escaped full stop", async () => { + const data = { + "c53a4a604fa754d33baaafd5bca4d3658-YXuUBqt5vI": { "persons.firstname": "1" } + } + const template = "{{ [c53a4a604fa754d33baaafd5bca4d3658-YXuUBqt5vI].[persons.firstname] }}" + const output = await processString(template, data) + expect(output).toEqual("1") + }) +})