diff --git a/lerna.json b/lerna.json index bdf45a24e2..a689dd4f8d 100644 --- a/lerna.json +++ b/lerna.json @@ -1,5 +1,5 @@ { - "version": "0.9.116", + "version": "0.9.117-alpha.1", "npmClient": "yarn", "packages": [ "packages/*" diff --git a/packages/auth/package.json b/packages/auth/package.json index 41c24569f1..60f13a6191 100644 --- a/packages/auth/package.json +++ b/packages/auth/package.json @@ -1,6 +1,6 @@ { "name": "@budibase/auth", - "version": "0.9.116", + "version": "0.9.117-alpha.1", "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 8ff06fa191..55a5594f9e 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.116", + "version": "0.9.117-alpha.1", "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 5d9da6c162..fac5e5719e 100644 --- a/packages/builder/package.json +++ b/packages/builder/package.json @@ -1,6 +1,6 @@ { "name": "@budibase/builder", - "version": "0.9.116", + "version": "0.9.117-alpha.1", "license": "AGPL-3.0", "private": true, "scripts": { @@ -65,10 +65,10 @@ } }, "dependencies": { - "@budibase/bbui": "^0.9.116", - "@budibase/client": "^0.9.116", + "@budibase/bbui": "^0.9.117-alpha.1", + "@budibase/client": "^0.9.117-alpha.1", "@budibase/colorpicker": "1.1.2", - "@budibase/string-templates": "^0.9.116", + "@budibase/string-templates": "^0.9.117-alpha.1", "@sentry/browser": "5.19.1", "@spectrum-css/page": "^3.0.1", "@spectrum-css/vars": "^3.0.1", 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/PropertiesPanel/PropertyControls/FilterEditor/FilterDrawer.svelte b/packages/builder/src/components/design/PropertiesPanel/PropertyControls/FilterEditor/FilterDrawer.svelte index d41d28e4e3..ad647d1550 100644 --- a/packages/builder/src/components/design/PropertiesPanel/PropertyControls/FilterEditor/FilterDrawer.svelte +++ b/packages/builder/src/components/design/PropertiesPanel/PropertyControls/FilterEditor/FilterDrawer.svelte @@ -59,6 +59,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 +82,9 @@ const getFieldOptions = field => { const schema = schemaFields.find(x => x.name === field) - return schema?.constraints?.inclusion || [] + const opt = schema?.constraints?.inclusion || [] + + return opt } @@ -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/pages/builder/portal/settings/organisation.svelte b/packages/builder/src/pages/builder/portal/settings/organisation.svelte index b036585e22..1bd3dc9677 100644 --- a/packages/builder/src/pages/builder/portal/settings/organisation.svelte +++ b/packages/builder/src/pages/builder/portal/settings/organisation.svelte @@ -38,7 +38,7 @@ let data = new FormData() data.append("file", file) const res = await post( - "/api/admin/configs/upload/settings/logoUrl", + "/api/global/configs/upload/settings/logoUrl", data, {} ) diff --git a/packages/cli/package.json b/packages/cli/package.json index 5a8e9b6557..0e17120d83 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -1,6 +1,6 @@ { "name": "@budibase/cli", - "version": "0.9.116", + "version": "0.9.117-alpha.1", "description": "Budibase CLI, for developers, self hosting and migrations.", "main": "src/index.js", "bin": { diff --git a/packages/client/package.json b/packages/client/package.json index 30b80d94aa..9bec383eec 100644 --- a/packages/client/package.json +++ b/packages/client/package.json @@ -1,6 +1,6 @@ { "name": "@budibase/client", - "version": "0.9.116", + "version": "0.9.117-alpha.1", "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.116", - "@budibase/standard-components": "^0.9.116", - "@budibase/string-templates": "^0.9.116", + "@budibase/bbui": "^0.9.117-alpha.1", + "@budibase/standard-components": "^0.9.117-alpha.1", + "@budibase/string-templates": "^0.9.117-alpha.1", "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 6c76ccf71d..e33caa7a1b 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.116", + "version": "0.9.117-alpha.1", "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.116", - "@budibase/client": "^0.9.116", - "@budibase/string-templates": "^0.9.116", + "@budibase/auth": "^0.9.117-alpha.1", + "@budibase/client": "^0.9.117-alpha.1", + "@budibase/string-templates": "^0.9.117-alpha.1", "@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.116", + "@budibase/standard-components": "^0.9.117-alpha.1", "@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/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/rest.ts b/packages/server/src/integrations/rest.ts index c55e991980..d6cf71e324 100644 --- a/packages/server/src/integrations/rest.ts +++ b/packages/server/src/integrations/rest.ts @@ -89,6 +89,26 @@ module RestModule { }, }, }, + patch: { + displayName: "PATCH", + readable: true, + type: QueryTypes.FIELDS, + urlDisplay: true, + fields: { + path: { + type: DatasourceFieldTypes.STRING, + }, + queryString: { + type: DatasourceFieldTypes.STRING, + }, + headers: { + type: DatasourceFieldTypes.OBJECT, + }, + requestBody: { + type: DatasourceFieldTypes.JSON, + }, + }, + }, delete: { displayName: "DELETE", type: QueryTypes.FIELDS, @@ -175,6 +195,21 @@ module RestModule { return await this.parseResponse(response) } + async patch({ path = "", queryString = "", headers = {}, json = {} }) { + this.headers = { + ...this.config.defaultHeaders, + ...headers, + } + + const response = await fetch(this.config.url + path + queryString, { + method: "PATCH", + headers: this.headers, + body: JSON.stringify(json), + }) + + return await this.parseResponse(response) + } + async delete({ path = "", queryString = "", headers = {} }) { this.headers = { ...this.config.defaultHeaders, 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 d6f69984a7..b8f6c1f0a1 100644 --- a/packages/standard-components/package.json +++ b/packages/standard-components/package.json @@ -29,11 +29,11 @@ "keywords": [ "svelte" ], - "version": "0.9.116", + "version": "0.9.117-alpha.1", "license": "MIT", "gitHead": "d1836a898cab3f8ab80ee6d8f42be1a9eed7dcdc", "dependencies": { - "@budibase/bbui": "^0.9.116", + "@budibase/bbui": "^0.9.117-alpha.1", "@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/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 50aae1f32c..36f6026a06 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 @@ } -
+
{ const apps = await getAllApps(CouchDB, { dev: true }) const body = {} for (let app of apps) { - app = app.value let url = app.url || encodeURI(`${app.name}`) url = `/${url.replace(URL_REGEX_SLASH, "")}` body[url] = {