diff --git a/hosting/docker-compose.yaml b/hosting/docker-compose.yaml index 9cdf2b2114..560b273668 100644 --- a/hosting/docker-compose.yaml +++ b/hosting/docker-compose.yaml @@ -119,6 +119,8 @@ services: watchtower-service: image: containrrr/watchtower + ports: + - "${WATCHTOWER_PORT}:8080" volumes: - /var/run/docker.sock:/var/run/docker.sock command: --debug --http-api-update bbapps bbworker @@ -128,8 +130,6 @@ services: - WATCHTOWER_CLEANUP=true labels: - "com.centurylinklabs.watchtower.enable=false" - ports: - - 6161:8080 volumes: diff --git a/hosting/hosting.properties b/hosting/hosting.properties index d11972bc4b..c8e2f5c606 100644 --- a/hosting/hosting.properties +++ b/hosting/hosting.properties @@ -17,4 +17,5 @@ WORKER_PORT=4003 MINIO_PORT=4004 COUCH_DB_PORT=4005 REDIS_PORT=6379 +WATCHTOWER_PORT=6161 BUDIBASE_ENVIRONMENT=PRODUCTION diff --git a/lerna.json b/lerna.json index 57277a07a1..67abcbf62d 100644 --- a/lerna.json +++ b/lerna.json @@ -1,5 +1,5 @@ { - "version": "0.9.105-alpha.32", + "version": "0.9.116-alpha.3", "npmClient": "yarn", "packages": [ "packages/*" diff --git a/packages/auth/package.json b/packages/auth/package.json index 9cfa8e880f..239265b9f0 100644 --- a/packages/auth/package.json +++ b/packages/auth/package.json @@ -1,6 +1,6 @@ { "name": "@budibase/auth", - "version": "0.9.105-alpha.32", + "version": "0.9.116-alpha.3", "description": "Authentication middlewares for budibase builder and apps", "main": "src/index.js", "author": "Budibase", diff --git a/packages/bbui/package.json b/packages/bbui/package.json index 4650c988dd..c8b5e1430e 100644 --- a/packages/bbui/package.json +++ b/packages/bbui/package.json @@ -1,7 +1,7 @@ { "name": "@budibase/bbui", "description": "A UI solution used in the different Budibase projects.", - "version": "0.9.105-alpha.32", + "version": "0.9.116-alpha.3", "license": "AGPL-3.0", "svelte": "src/index.js", "module": "dist/bbui.es.js", diff --git a/packages/bbui/src/Table/ArrayRenderer.svelte b/packages/bbui/src/Table/ArrayRenderer.svelte new file mode 100644 index 0000000000..679973a03a --- /dev/null +++ b/packages/bbui/src/Table/ArrayRenderer.svelte @@ -0,0 +1,17 @@ + + +{#each badges as badge} + {badge} +{/each} +{#if leftover} +
+{leftover} more
+{/if} diff --git a/packages/bbui/src/Table/CellRenderer.svelte b/packages/bbui/src/Table/CellRenderer.svelte index 2d073b7782..d6a2f3196d 100644 --- a/packages/bbui/src/Table/CellRenderer.svelte +++ b/packages/bbui/src/Table/CellRenderer.svelte @@ -4,7 +4,7 @@ import DateTimeRenderer from "./DateTimeRenderer.svelte" import RelationshipRenderer from "./RelationshipRenderer.svelte" import AttachmentRenderer from "./AttachmentRenderer.svelte" - + import ArrayRenderer from "./ArrayRenderer.svelte" export let row export let schema export let value @@ -19,6 +19,7 @@ options: StringRenderer, number: StringRenderer, longform: StringRenderer, + array: ArrayRenderer, } $: type = schema?.type ?? "string" $: customRenderer = customRenderers?.find(x => x.column === schema?.name) diff --git a/packages/builder/package.json b/packages/builder/package.json index a4ad8f7703..e98706a229 100644 --- a/packages/builder/package.json +++ b/packages/builder/package.json @@ -1,6 +1,6 @@ { "name": "@budibase/builder", - "version": "0.9.105-alpha.32", + "version": "0.9.116-alpha.3", "license": "AGPL-3.0", "private": true, "scripts": { @@ -65,10 +65,10 @@ } }, "dependencies": { - "@budibase/bbui": "^0.9.105-alpha.32", - "@budibase/client": "^0.9.105-alpha.32", + "@budibase/bbui": "^0.9.116-alpha.3", + "@budibase/client": "^0.9.116-alpha.3", "@budibase/colorpicker": "1.1.2", - "@budibase/string-templates": "^0.9.105-alpha.32", + "@budibase/string-templates": "^0.9.116-alpha.3", "@sentry/browser": "5.19.1", "@spectrum-css/page": "^3.0.1", "@spectrum-css/vars": "^3.0.1", diff --git a/packages/builder/src/analytics.js b/packages/builder/src/analytics.js index bcc59eb311..5b130a8e6b 100644 --- a/packages/builder/src/analytics.js +++ b/packages/builder/src/analytics.js @@ -23,6 +23,7 @@ async function activate() { if (posthogConfigured) { posthog.init(process.env.POSTHOG_TOKEN, { autocapture: false, + capture_pageview: false, api_host: process.env.POSTHOG_URL, }) posthog.set_config({ persistence: "cookie" }) @@ -79,6 +80,7 @@ const isFeedbackTimeElapsed = sinceDateStr => { const feedbackMilliseconds = feedbackHours * 60 * 60 * 1000 return Date.now() > sinceDate + feedbackMilliseconds } + function submitFeedback(values) { if (!analyticsEnabled || !process.env.POSTHOG_TOKEN) return localStorage.setItem(FEEDBACK_SUBMITTED_KEY, Date.now()) diff --git a/packages/builder/src/builderStore/store/screenTemplates/newRowScreen.js b/packages/builder/src/builderStore/store/screenTemplates/newRowScreen.js index b890d42d54..c2dffef4b6 100644 --- a/packages/builder/src/builderStore/store/screenTemplates/newRowScreen.js +++ b/packages/builder/src/builderStore/store/screenTemplates/newRowScreen.js @@ -43,6 +43,7 @@ const createScreen = table => { tableId: table._id, type: "table", }, + size: "spectrum--medium", }) const fieldGroup = new Component("@budibase/standard-components/fieldgroup") diff --git a/packages/builder/src/builderStore/store/screenTemplates/rowDetailScreen.js b/packages/builder/src/builderStore/store/screenTemplates/rowDetailScreen.js index ec737fe36b..3ba8be10b5 100644 --- a/packages/builder/src/builderStore/store/screenTemplates/rowDetailScreen.js +++ b/packages/builder/src/builderStore/store/screenTemplates/rowDetailScreen.js @@ -101,7 +101,6 @@ const createScreen = table => { .instanceName("Form") .customProps({ actionType: "Update", - theme: "spectrum--lightest", size: "spectrum--medium", dataSource: { label: table.name, diff --git a/packages/builder/src/builderStore/store/screenTemplates/rowListScreen.js b/packages/builder/src/builderStore/store/screenTemplates/rowListScreen.js index ccf1fceb29..188682ed3f 100644 --- a/packages/builder/src/builderStore/store/screenTemplates/rowListScreen.js +++ b/packages/builder/src/builderStore/store/screenTemplates/rowListScreen.js @@ -65,6 +65,7 @@ const createScreen = table => { tableId: table._id, type: "table", }, + size: "spectrum--medium", paginate: true, limit: 8, }) diff --git a/packages/builder/src/builderStore/store/screenTemplates/utils/commonComponents.js b/packages/builder/src/builderStore/store/screenTemplates/utils/commonComponents.js index 1a64a8958f..5b3bc041ff 100644 --- a/packages/builder/src/builderStore/store/screenTemplates/utils/commonComponents.js +++ b/packages/builder/src/builderStore/store/screenTemplates/utils/commonComponents.js @@ -131,6 +131,7 @@ const fieldTypeToComponentMap = { string: "stringfield", number: "numberfield", options: "optionsfield", + array: "multifieldselect", boolean: "booleanfield", longform: "longformfield", datetime: "datetimefield", @@ -167,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/backend/DataTable/RowFieldControl.svelte b/packages/builder/src/components/backend/DataTable/RowFieldControl.svelte index 0724016679..e82c55679a 100644 --- a/packages/builder/src/components/backend/DataTable/RowFieldControl.svelte +++ b/packages/builder/src/components/backend/DataTable/RowFieldControl.svelte @@ -1,5 +1,12 @@ diff --git a/packages/builder/src/components/design/AppPreview/componentStructure.json b/packages/builder/src/components/design/AppPreview/componentStructure.json index cea20a7dcf..3bc2554fde 100644 --- a/packages/builder/src/components/design/AppPreview/componentStructure.json +++ b/packages/builder/src/components/design/AppPreview/componentStructure.json @@ -21,7 +21,8 @@ "datetimefield", "attachmentfield", "relationshipfield", - "daterangepicker" + "daterangepicker", + "multifieldselect" ] }, { diff --git a/packages/builder/src/components/design/NavigationPanel/NewScreenModal.svelte b/packages/builder/src/components/design/NavigationPanel/NewScreenModal.svelte index 3b16d2ba74..ed0c764956 100644 --- a/packages/builder/src/components/design/NavigationPanel/NewScreenModal.svelte +++ b/packages/builder/src/components/design/NavigationPanel/NewScreenModal.svelte @@ -1,6 +1,7 @@ @@ -122,7 +132,7 @@ /> {:else if ["string", "longform", "number"].includes(filter.type)} - {:else if filter.type === "options"} + {:else if filter.type === "options" || "array"} { return ConstraintMap[type] } @@ -190,6 +196,7 @@ valueType: "Binding", type: fieldType, id: generate(), + value: fieldType == "array" ? [] : null, }, ] } @@ -275,7 +282,7 @@ disabled={rule.constraint === "required"} on:change={e => (rule.value = e.detail)} /> - {:else if ["maxLength", "minLength", "regex", "notRegex", "contains", "notContains"].includes(rule.constraint)} + {:else if rule.type !== "array" && ["maxLength", "minLength", "regex", "notRegex", "contains", "notContains"].includes(rule.constraint)} + {:else if rule.type === "array"} + { @@ -55,6 +63,8 @@ export const getValidOperatorsForType = type => { ] } else if (type === "options") { return [Op.Equals, Op.NotEquals, Op.Empty, Op.NotEmpty] + } else if (type === "array") { + return [Op.Contains, Op.NotContains, Op.Empty, Op.NotEmpty] } else if (type === "boolean") { return [Op.Equals, Op.NotEquals, Op.Empty, Op.NotEmpty] } else if (type === "longform") { diff --git a/packages/builder/src/stores/backend/tables.js b/packages/builder/src/stores/backend/tables.js index 11a1e2de17..e0b614a63e 100644 --- a/packages/builder/src/stores/backend/tables.js +++ b/packages/builder/src/stores/backend/tables.js @@ -87,7 +87,6 @@ export function createTablesStore() { draft: {}, }) }, - getDataSources: () => get(store).list.filter(t => t.name !== "Users"), delete: async table => { await api.delete(`/api/tables/${table._id}/${table._rev}`) update(state => ({ diff --git a/packages/cli/package.json b/packages/cli/package.json index 74a28976c8..6d55d1cbed 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -1,6 +1,6 @@ { "name": "@budibase/cli", - "version": "0.9.105-alpha.32", + "version": "0.9.116-alpha.3", "description": "Budibase CLI, for developers, self hosting and migrations.", "main": "src/index.js", "bin": { diff --git a/packages/cli/src/constants.js b/packages/cli/src/constants.js index fe7c61f4fa..7a3b1463ca 100644 --- a/packages/cli/src/constants.js +++ b/packages/cli/src/constants.js @@ -15,5 +15,6 @@ exports.AnalyticsEvents = { SelfHostInit: "hosting_init", } -exports.BUDIBASE_POSTHOG_URL = "https://posthog.budi.live" -exports.BUDIBASE_POSTHOG_TOKEN = "Oeq9KzIpZYaNsXIvHw5QTZWNpfiG_EOjAOpjTyAiitY" +exports.BUDIBASE_POSTHOG_URL = "https://app.posthog.com" +exports.BUDIBASE_POSTHOG_TOKEN = + "phc_yGOn4i7jWKaCTapdGR6lfA4AvmuEQ2ijn5zAVSFYPlS" diff --git a/packages/cli/src/hosting/makeEnv.js b/packages/cli/src/hosting/makeEnv.js index 8806c2e1e6..d1d23999f8 100644 --- a/packages/cli/src/hosting/makeEnv.js +++ b/packages/cli/src/hosting/makeEnv.js @@ -26,6 +26,7 @@ WORKER_PORT=4003 MINIO_PORT=4004 COUCH_DB_PORT=4005 REDIS_PORT=6379 +WATCHTOWER_PORT=6161 BUDIBASE_ENVIRONMENT=PRODUCTION` } diff --git a/packages/client/package.json b/packages/client/package.json index 45d3b44a65..cb6e95fe8b 100644 --- a/packages/client/package.json +++ b/packages/client/package.json @@ -1,6 +1,6 @@ { "name": "@budibase/client", - "version": "0.9.105-alpha.32", + "version": "0.9.116-alpha.3", "license": "MPL-2.0", "module": "dist/budibase-client.js", "main": "dist/budibase-client.js", @@ -18,9 +18,9 @@ "dev:builder": "rollup -cw" }, "dependencies": { - "@budibase/bbui": "^0.9.105-alpha.32", - "@budibase/standard-components": "^0.9.105-alpha.32", - "@budibase/string-templates": "^0.9.105-alpha.32", + "@budibase/bbui": "^0.9.116-alpha.3", + "@budibase/standard-components": "^0.9.116-alpha.3", + "@budibase/string-templates": "^0.9.116-alpha.3", "regexparam": "^1.3.0", "shortid": "^2.2.15", "svelte-spa-router": "^3.0.5" diff --git a/packages/server/package.json b/packages/server/package.json index 09ad3acdde..418ac4017b 100644 --- a/packages/server/package.json +++ b/packages/server/package.json @@ -1,7 +1,7 @@ { "name": "@budibase/server", "email": "hi@budibase.com", - "version": "0.9.105-alpha.32", + "version": "0.9.116-alpha.3", "description": "Budibase Web Server", "main": "src/index.js", "repository": { @@ -62,9 +62,9 @@ "author": "Budibase", "license": "AGPL-3.0-or-later", "dependencies": { - "@budibase/auth": "^0.9.105-alpha.32", - "@budibase/client": "^0.9.105-alpha.32", - "@budibase/string-templates": "^0.9.105-alpha.32", + "@budibase/auth": "^0.9.116-alpha.3", + "@budibase/client": "^0.9.116-alpha.3", + "@budibase/string-templates": "^0.9.116-alpha.3", "@elastic/elasticsearch": "7.10.0", "@koa/router": "8.0.0", "@sendgrid/mail": "7.1.1", @@ -117,7 +117,7 @@ "devDependencies": { "@babel/core": "^7.14.3", "@babel/preset-env": "^7.14.4", - "@budibase/standard-components": "^0.9.105-alpha.32", + "@budibase/standard-components": "^0.9.116-alpha.3", "@jest/test-sequencer": "^24.8.0", "@types/bull": "^3.15.1", "@types/jest": "^26.0.23", diff --git a/packages/server/src/api/controllers/datasource.js b/packages/server/src/api/controllers/datasource.js index df1cf7b40e..38b6e68932 100644 --- a/packages/server/src/api/controllers/datasource.js +++ b/packages/server/src/api/controllers/datasource.js @@ -60,8 +60,8 @@ exports.buildSchemaFromDb = async function (ctx) { exports.update = async function (ctx) { const db = new CouchDB(ctx.appId) const datasourceId = ctx.params.datasourceId - const datasource = await db.get(datasourceId) - datasource.name = ctx.request.body.name + let datasource = await db.get(datasourceId) + datasource = { ...datasource, ...ctx.request.body } const response = await db.put(datasource) datasource._rev = response.rev diff --git a/packages/server/src/api/controllers/row/internalSearch.js b/packages/server/src/api/controllers/row/internalSearch.js index cc35da40ec..793454e601 100644 --- a/packages/server/src/api/controllers/row/internalSearch.js +++ b/packages/server/src/api/controllers/row/internalSearch.js @@ -211,7 +211,6 @@ class QueryBuilder { if (this.query.notEmpty) { build(this.query.notEmpty, key => `${key}:["" TO *]`) } - return query } @@ -253,6 +252,7 @@ const runQuery = async (url, body) => { method: "POST", }) const json = await response.json() + let output = { rows: [], } diff --git a/packages/server/src/api/controllers/row/utils.js b/packages/server/src/api/controllers/row/utils.js index 16c48181d1..cb9a5e166c 100644 --- a/packages/server/src/api/controllers/row/utils.js +++ b/packages/server/src/api/controllers/row/utils.js @@ -58,12 +58,24 @@ exports.validate = async ({ appId, tableId, row, table }) => { const constraints = cloneDeep(table.schema[fieldName].constraints) // special case for options, need to always allow unselected (null) if ( - table.schema[fieldName].type === FieldTypes.OPTIONS && + table.schema[fieldName].type === + (FieldTypes.OPTIONS || FieldTypes.ARRAY) && constraints.inclusion ) { constraints.inclusion.push(null) } - const res = validateJs.single(row[fieldName], constraints) + let res + + // Validate.js doesn't seem to handle array + if (table.schema[fieldName].type === FieldTypes.ARRAY) { + row[fieldName].map(val => { + if (!constraints.inclusion.includes(val)) { + errors[fieldName] = "Field not in list" + } + }) + } else { + res = validateJs.single(row[fieldName], constraints) + } if (res) errors[fieldName] = res } return { valid: Object.keys(errors).length === 0, errors } diff --git a/packages/server/src/api/controllers/table/utils.js b/packages/server/src/api/controllers/table/utils.js index 78dae60ab1..154a9ba8f5 100644 --- a/packages/server/src/api/controllers/table/utils.js +++ b/packages/server/src/api/controllers/table/utils.js @@ -1,4 +1,5 @@ const CouchDB = require("../../../db") +const linkRows = require("../../../db/linkedRows") const csvParser = require("../../../utilities/csvParser") const { getRowParams, @@ -74,6 +75,16 @@ exports.handleDataImport = async (appId, user, table, dataImport) => { const processed = inputProcessing(user, table, row) table = processed.table row = processed.row + + // make sure link rows are up to date + row = await linkRows.updateLinks({ + appId, + eventType: linkRows.EventType.ROW_SAVE, + row, + tableId: row.tableId, + table, + }) + for (let [fieldName, schema] of Object.entries(table.schema)) { // check whether the options need to be updated for inclusion as part of the data import if ( diff --git a/packages/server/src/api/routes/tests/datasource.spec.js b/packages/server/src/api/routes/tests/datasource.spec.js index 7387dd3c46..98a99717fd 100644 --- a/packages/server/src/api/routes/tests/datasource.spec.js +++ b/packages/server/src/api/routes/tests/datasource.spec.js @@ -94,7 +94,7 @@ describe("/datasources", () => { .expect(200) // this is mock data, can't test it expect(res.body).toBeDefined() - expect(pg.queryMock).toHaveBeenCalledWith(`select "users"."name" as "users.name", "users"."age" as "users.age" from "users" where "users"."name" like $1 limit $2`, ["John%", 5000]) + expect(pg.queryMock).toHaveBeenCalledWith(`select "users"."name" as "users.name", "users"."age" as "users.age" from "users" where "users"."name" ilike $1 limit $2`, ["John%", 5000]) }) }) diff --git a/packages/server/src/constants/index.js b/packages/server/src/constants/index.js index 703b33deb1..bc7b5b368f 100644 --- a/packages/server/src/constants/index.js +++ b/packages/server/src/constants/index.js @@ -12,6 +12,7 @@ exports.FieldTypes = { OPTIONS: "options", NUMBER: "number", BOOLEAN: "boolean", + ARRAY: "array", DATETIME: "datetime", ATTACHMENT: "attachment", LINK: "link", diff --git a/packages/server/src/db/views/staticViews.js b/packages/server/src/db/views/staticViews.js index 23f320d7eb..6e52e9699c 100644 --- a/packages/server/src/db/views/staticViews.js +++ b/packages/server/src/db/views/staticViews.js @@ -99,7 +99,14 @@ 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 (let val in input[key]) { + // eslint-disable-next-line no-undef + index(idxKey, input[key][val], { + store: true, + }) + } + } else if (key === "_id" || key === "_rev" || input[key] == null) { continue } if (typeof input[key] === "string") { diff --git a/packages/server/src/integrations/base/sql.ts b/packages/server/src/integrations/base/sql.ts index ca6dcb15fd..f03c0dbd37 100644 --- a/packages/server/src/integrations/base/sql.ts +++ b/packages/server/src/integrations/base/sql.ts @@ -52,7 +52,7 @@ function addFilters( if (filters.string) { iterate(filters.string, (key, value) => { const fnc = allOr ? "orWhere" : "where" - query = query[fnc](key, "like", `${value}%`) + query = query[fnc](key, "ilike", `${value}%`) }) } if (filters.range) { diff --git a/packages/server/src/integrations/tests/sql.spec.js b/packages/server/src/integrations/tests/sql.spec.js index a02a7e8198..fa8bcd1d86 100644 --- a/packages/server/src/integrations/tests/sql.spec.js +++ b/packages/server/src/integrations/tests/sql.spec.js @@ -82,7 +82,7 @@ describe("SQL query builder", () => { })) expect(query).toEqual({ bindings: ["John%", limit], - sql: `select * from "${TABLE_NAME}" where "${TABLE_NAME}"."name" like $1 limit $2` + sql: `select * from "${TABLE_NAME}" where "${TABLE_NAME}"."name" ilike $1 limit $2` }) }) diff --git a/packages/server/src/utilities/rowProcessor.js b/packages/server/src/utilities/rowProcessor.js index c067d4de87..3c43a20409 100644 --- a/packages/server/src/utilities/rowProcessor.js +++ b/packages/server/src/utilities/rowProcessor.js @@ -29,6 +29,11 @@ const TYPE_TRANSFORM_MAP = { [null]: null, [undefined]: undefined, }, + [FieldTypes.ARRAY]: { + "": [], + [null]: [], + [undefined]: undefined, + }, [FieldTypes.STRING]: { "": "", [null]: "", diff --git a/packages/standard-components/manifest.json b/packages/standard-components/manifest.json index da3736a076..194706744d 100644 --- a/packages/standard-components/manifest.json +++ b/packages/standard-components/manifest.json @@ -1726,6 +1726,21 @@ "label": "Custom" } }, + { + "type": "select", + "label": "Size", + "key": "size", + "options": [ + { + "label": "Medium", + "value": "spectrum--medium" + }, { + "label": "Large", + "value": "spectrum--large" + } + ], + "defaultValue": "spectrum--medium" + }, { "type": "boolean", "label": "Disabled", @@ -2041,6 +2056,108 @@ } ] }, + "multifieldselect": { + "name": "Multi-select Picker", + "icon": "ViewList", + "styles": ["size"], + "illegalChildren": ["section"], + "settings": [ + { + "type": "field/array", + "label": "Field", + "key": "field" + }, + { + "type": "text", + "label": "Label", + "key": "label" + }, + { + "type": "text", + "label": "Placeholder", + "key": "placeholder", + "placeholder": "Choose an option" + }, + { + "type": "text", + "label": "Default value", + "key": "defaultValue" + }, + { + "type": "boolean", + "label": "Autocomplete", + "key": "autocomplete", + "defaultValue": false + }, + { + "type": "boolean", + "label": "Disabled", + "key": "disabled", + "defaultValue": false + }, + { + "type": "select", + "label": "Options source", + "key": "optionsSource", + "defaultValue": "schema", + "placeholder": "Pick an options source", + "options": [ + { + "label": "Schema", + "value": "schema" + }, + { + "label": "Data provider", + "value": "provider" + }, + { + "label": "Custom", + "value": "custom" + } + ] + }, + { + "type": "dataProvider", + "label": "Options Provider", + "key": "dataProvider", + "dependsOn": { + "setting": "optionsSource", + "value": "provider" + } + }, + { + "type": "field", + "label": "Label Column", + "key": "labelColumn", + "dependsOn": { + "setting": "optionsSource", + "value": "provider" + } + }, + { + "type": "field", + "label": "Value Column", + "key": "valueColumn", + "dependsOn": { + "setting": "optionsSource", + "value": "provider" + } + }, + { + "type": "options", + "key": "customOptions", + "dependsOn": { + "setting": "optionsSource", + "value": "custom" + } + }, + { + "type": "validation/array", + "label": "Validation", + "key": "validation" + } + ] + }, "booleanfield": { "name": "Checkbox", "icon": "Checkmark", @@ -2346,6 +2463,22 @@ "dependsOn": "dataProvider", "placeholder": "All columns" }, + { + "type": "select", + "label": "Size", + "key": "size", + "defaultValue": "spectrum--medium", + "options": [ + { + "label": "Medium", + "value": "spectrum--medium" + }, + { + "label": "Large", + "value": "spectrum--large" + } + ] + }, { "type": "boolean", "label": "Quiet", diff --git a/packages/standard-components/package.json b/packages/standard-components/package.json index d38fe6ff4b..47eaba6bec 100644 --- a/packages/standard-components/package.json +++ b/packages/standard-components/package.json @@ -29,11 +29,11 @@ "keywords": [ "svelte" ], - "version": "0.9.105-alpha.32", + "version": "0.9.116-alpha.3", "license": "MIT", "gitHead": "d1836a898cab3f8ab80ee6d8f42be1a9eed7dcdc", "dependencies": { - "@budibase/bbui": "^0.9.105-alpha.32", + "@budibase/bbui": "^0.9.116-alpha.3", "@spectrum-css/button": "^3.0.3", "@spectrum-css/card": "^3.0.3", "@spectrum-css/divider": "^1.0.3", diff --git a/packages/standard-components/src/forms/Form.svelte b/packages/standard-components/src/forms/Form.svelte index a065432030..1fa8d7aa15 100644 --- a/packages/standard-components/src/forms/Form.svelte +++ b/packages/standard-components/src/forms/Form.svelte @@ -29,12 +29,9 @@ if (["user", "url"].includes(context.closestComponentId)) { return {} } - // Only inherit values if the table ID matches + // Always inherit the closest data source const closestContext = context[`${context.closestComponentId}`] || {} - if (dataSource.tableId !== closestContext?.tableId) { - return {} - } - return closestContext + return closestContext || {} } // Fetches the form schema from this form's dataSource, if one exists diff --git a/packages/standard-components/src/forms/InnerForm.svelte b/packages/standard-components/src/forms/InnerForm.svelte index 660b784fd0..4c0f6e2b15 100644 --- a/packages/standard-components/src/forms/InnerForm.svelte +++ b/packages/standard-components/src/forms/InnerForm.svelte @@ -7,6 +7,7 @@ export let dataSource export let disabled = false export let initialValues + export let size export let schema export let table @@ -270,7 +271,7 @@ -
+
diff --git a/packages/standard-components/src/forms/MultiFieldSelect.svelte b/packages/standard-components/src/forms/MultiFieldSelect.svelte new file mode 100644 index 0000000000..cecc569b6f --- /dev/null +++ b/packages/standard-components/src/forms/MultiFieldSelect.svelte @@ -0,0 +1,58 @@ + + + + {#if fieldState} + x : x => x.label} + getOptionValue={flatOptions ? x => x : x => x.value} + id={fieldState.fieldId} + disabled={fieldState.disabled} + on:change={e => fieldApi.setValue(e.detail)} + {placeholder} + {options} + {autocomplete} + /> + {/if} + diff --git a/packages/standard-components/src/forms/OptionsField.svelte b/packages/standard-components/src/forms/OptionsField.svelte index c5efa6f58d..4ad8f4611e 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") { + 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/forms/validation.js b/packages/standard-components/src/forms/validation.js index 30b6fd7ca7..deb228c4c0 100644 --- a/packages/standard-components/src/forms/validation.js +++ b/packages/standard-components/src/forms/validation.js @@ -25,7 +25,7 @@ export const createValidatorFromConstraints = ( schemaConstraints.presence?.allowEmpty === false ) { rules.push({ - type: "string", + type: schemaConstraints.type == "array" ? "array" : "string", constraint: "required", error: "Required", }) @@ -63,7 +63,10 @@ export const createValidatorFromConstraints = ( } // Inclusion constraint - if (exists(schemaConstraints.inclusion)) { + if ( + exists(schemaConstraints.inclusion) && + schemaConstraints.type !== "array" + ) { const options = schemaConstraints.inclusion || [] rules.push({ type: "string", @@ -142,7 +145,7 @@ const evaluateRule = (rule, value) => { * in the same format. * @param value the value to parse * @param type the type to parse - * @returns {boolean|string|*|number|null} the parsed value, or null if invalid + * @returns {boolean|string|*|number|null|array} the parsed value, or null if invalid */ const parseType = (value, type) => { // Treat nulls or empty strings as null @@ -202,6 +205,13 @@ const parseType = (value, type) => { return value } + if (type === "array") { + if (!Array.isArray(value) || !value.length) { + return null + } + return value + } + // If some unknown type, treat as null to avoid breaking validators return null } diff --git a/packages/standard-components/src/lucene.js b/packages/standard-components/src/lucene.js index d7af0e2512..03baa751cc 100644 --- a/packages/standard-components/src/lucene.js +++ b/packages/standard-components/src/lucene.js @@ -11,6 +11,8 @@ export const buildLuceneQuery = filter => { notEqual: {}, empty: {}, notEmpty: {}, + contains: {}, + notContains: {}, } if (Array.isArray(filter)) { filter.forEach(expression => { diff --git a/packages/standard-components/src/table/Table.svelte b/packages/standard-components/src/table/Table.svelte index 3620146ced..ba9f3aae97 100644 --- a/packages/standard-components/src/table/Table.svelte +++ b/packages/standard-components/src/table/Table.svelte @@ -8,6 +8,7 @@ export let showAutoColumns export let rowCount export let quiet + export let size const component = getContext("component") const { styleable } = getContext("sdk") @@ -71,7 +72,7 @@ } -
+