Merge branch 'master' of github.com:Budibase/budibase into budi-day/cli

This commit is contained in:
mike12345567 2021-02-25 17:17:40 +00:00
commit 7249654615
33 changed files with 221 additions and 934 deletions

View File

@ -1,5 +1,5 @@
{ {
"version": "0.7.8", "version": "0.8.2",
"npmClient": "yarn", "npmClient": "yarn",
"packages": [ "packages": [
"packages/*" "packages/*"

View File

@ -1,6 +1,6 @@
{ {
"name": "@budibase/builder", "name": "@budibase/builder",
"version": "0.7.8", "version": "0.8.2",
"license": "AGPL-3.0", "license": "AGPL-3.0",
"private": true, "private": true,
"scripts": { "scripts": {
@ -64,9 +64,9 @@
}, },
"dependencies": { "dependencies": {
"@budibase/bbui": "^1.58.13", "@budibase/bbui": "^1.58.13",
"@budibase/client": "^0.7.8", "@budibase/client": "^0.8.2",
"@budibase/colorpicker": "1.0.1", "@budibase/colorpicker": "1.0.1",
"@budibase/string-templates": "^0.7.8", "@budibase/string-templates": "^0.8.2",
"@budibase/svelte-ag-grid": "^1.0.4", "@budibase/svelte-ag-grid": "^1.0.4",
"@sentry/browser": "5.19.1", "@sentry/browser": "5.19.1",
"@svelteschool/svelte-forms": "0.7.0", "@svelteschool/svelte-forms": "0.7.0",

View File

@ -143,10 +143,18 @@
margin-top: 0; margin-top: 0;
} }
.bindings__wrapper { .bindings__wrapper {
overflow-y: auto; overflow-y: scroll;
overflow-x: hidden;
position: relative; position: relative;
flex: 1 1 auto; flex: 1 1 auto;
-ms-overflow-style: none;
} }
.bindings__wrapper::-webkit-scrollbar {
width: 0;
height: 0;
}
.bindings__list { .bindings__list {
position: absolute; position: absolute;
width: 100%; width: 100%;

View File

@ -11,7 +11,7 @@
class:link={!!items.length} class:link={!!items.length}
on:click={() => selectRelationship(row, columnName)}> on:click={() => selectRelationship(row, columnName)}>
{#each items as item} {#each items as item}
<div class="item">{item}</div> <div class="item">{item?.primaryDisplay ?? ''}</div>
{/each} {/each}
</div> </div>

View File

@ -28,7 +28,11 @@
.flat() .flat()
// Prevent modal closing if there were errors // Prevent modal closing if there were errors
return false return false
} else if (rowResponse.status === 400 || rowResponse.status === 500) {
errors = [{ message: rowResponse.message }]
return false
} }
notifier.success("Row saved successfully.") notifier.success("Row saved successfully.")
backendUiStore.actions.rows.save(rowResponse) backendUiStore.actions.rows.save(rowResponse)
} }

View File

@ -40,9 +40,6 @@
if (!row.email) { if (!row.email) {
errors = [...errors, { message: "Email is required" }] errors = [...errors, { message: "Email is required" }]
} }
if (!row.password) {
errors = [...errors, { message: "Password is required" }]
}
if (!row.roleId) { if (!row.roleId) {
errors = [...errors, { message: "Role is required" }] errors = [...errors, { message: "Role is required" }]
} }
@ -63,7 +60,7 @@
.flat() .flat()
} }
return false return false
} else if (rowResponse.status === 400 && rowResponse.message) { } else if (rowResponse.status === 400 || rowResponse.status === 500) {
errors = [{ message: rowResponse.message }] errors = [{ message: rowResponse.message }]
return false return false
} }

View File

@ -10,10 +10,12 @@
<form> <form>
{#each Object.keys(schema) as configKey} {#each Object.keys(schema) as configKey}
{#if typeof schema[configKey].type === 'object'} {#if schema[configKey].type === 'object'}
<Label small>{configKey}</Label> <Label small>{configKey}</Label>
<Spacer small /> <Spacer small />
<KeyValueBuilder bind:object={integration[configKey]} on:change /> <KeyValueBuilder
defaults={schema[configKey].default}
bind:object={integration[configKey]} />
{:else} {:else}
<div class="form-row"> <div class="form-row">
<Label small>{configKey}</Label> <Label small>{configKey}</Label>

View File

@ -24,7 +24,7 @@
<div class="container" bind:this={anchor}> <div class="container" bind:this={anchor}>
<Input {...inputProps} bind:value /> <Input {...inputProps} bind:value />
<div class="icon" on:click={popover.show}> <div class="icon" on:click={popover.show}>
<Icon name="edit" /> <Icon name="lightning" />
</div> </div>
</div> </div>
<GenericBindingPopover <GenericBindingPopover
@ -54,6 +54,7 @@
border-bottom-right-radius: var(--border-radius-m); border-bottom-right-radius: var(--border-radius-m);
color: var(--grey-7); color: var(--grey-7);
font-size: 16px; font-size: 16px;
margin-top: 20px;
} }
.icon:hover { .icon:hover {
color: var(--ink); color: var(--ink);

View File

@ -1,5 +1,4 @@
<script> <script>
import { onMount } from "svelte"
import { backendUiStore } from "builderStore" import { backendUiStore } from "builderStore"
import api from "builderStore/api" import api from "builderStore/api"
import { Select, Label, Multiselect } from "@budibase/bbui" import { Select, Label, Multiselect } from "@budibase/bbui"
@ -9,7 +8,9 @@
export let linkedRows = [] export let linkedRows = []
let rows = [] let rows = []
let linkedIds = (linkedRows || [])?.map(row => row?._id || row)
$: linkedRows = linkedIds
$: label = capitalise(schema.name) $: label = capitalise(schema.name)
$: linkedTableId = schema.tableId $: linkedTableId = schema.tableId
$: linkedTable = $backendUiStore.tables.find( $: linkedTable = $backendUiStore.tables.find(
@ -45,12 +46,12 @@
<Select <Select
thin thin
secondary secondary
on:change={e => (linkedRows = [e.target.value])} on:change={e => (linkedIds = e.target.value ? [e.target.value] : [])}
name={label} name={label}
{label}> {label}>
<option value="">Choose an option</option> <option value="">Choose an option</option>
{#each rows as row} {#each rows as row}
<option selected={row._id === linkedRows[0]} value={row._id}> <option selected={row._id === linkedIds[0]} value={row._id}>
{getPrettyName(row)} {getPrettyName(row)}
</option> </option>
{/each} {/each}
@ -58,7 +59,7 @@
{:else} {:else}
<Multiselect <Multiselect
secondary secondary
bind:value={linkedRows} bind:value={linkedIds}
{label} {label}
placeholder="Choose some options"> placeholder="Choose some options">
{#each rows as row} {#each rows as row}

View File

@ -40,13 +40,14 @@
function addToText(readableBinding) { function addToText(readableBinding) {
const position = getCaretPosition() const position = getCaretPosition()
const toAdd = `{{ ${readableBinding} }}` const toAdd = `{{ ${readableBinding} }}`
if (position.start) { if (position.start) {
value = value =
value.substring(0, position.start) + value.substring(0, position.start) +
toAdd + toAdd +
value.substring(position.end, value.length) value.substring(position.end, value.length)
} else { } else {
value += toAdd value = toAdd
} }
} }
@ -110,7 +111,8 @@
bind:getCaretPosition bind:getCaretPosition
thin thin
bind:value bind:value
placeholder="Add text, or click the objects on the left to add them to the textbox." /> placeholder="Add text, or click the objects on the left to add them to
the textbox." />
{#if !valid} {#if !valid}
<p class="syntax-error"> <p class="syntax-error">
Current Handlebars syntax is invalid, please check the guide Current Handlebars syntax is invalid, please check the guide

View File

@ -37,7 +37,9 @@
return [...acc, ...viewsArr] return [...acc, ...viewsArr]
}, []) }, [])
$: queries = $backendUiStore.queries $: queries = $backendUiStore.queries
.filter(query => showAllQueries || (query.queryVerb === "read" || query.readable)) .filter(
query => showAllQueries || query.queryVerb === "read" || query.readable
)
.map(query => ({ .map(query => ({
label: query.name, label: query.name,
name: query.name, name: query.name,

View File

@ -1,7 +1,8 @@
<script> <script>
import { Button, Input } from "@budibase/bbui" import { Button, Input } from "@budibase/bbui"
export let object = {} export let defaults
export let object = defaults || {}
export let readOnly export let readOnly
let fields = Object.entries(object).map(([name, value]) => ({ name, value })) let fields = Object.entries(object).map(([name, value]) => ({ name, value }))

File diff suppressed because it is too large Load Diff

View File

@ -1,6 +1,6 @@
{ {
"name": "@budibase/client", "name": "@budibase/client",
"version": "0.7.8", "version": "0.8.2",
"license": "MPL-2.0", "license": "MPL-2.0",
"main": "dist/budibase-client.js", "main": "dist/budibase-client.js",
"module": "dist/budibase-client.js", "module": "dist/budibase-client.js",
@ -9,14 +9,14 @@
"dev:builder": "rollup -cw" "dev:builder": "rollup -cw"
}, },
"dependencies": { "dependencies": {
"@budibase/string-templates": "^0.7.8", "@budibase/string-templates": "^0.8.2",
"deep-equal": "^2.0.1", "deep-equal": "^2.0.1",
"regexparam": "^1.3.0", "regexparam": "^1.3.0",
"shortid": "^2.2.15", "shortid": "^2.2.15",
"svelte-spa-router": "^3.0.5" "svelte-spa-router": "^3.0.5"
}, },
"devDependencies": { "devDependencies": {
"@budibase/standard-components": "^0.7.8", "@budibase/standard-components": "^0.8.2",
"@rollup/plugin-commonjs": "^16.0.0", "@rollup/plugin-commonjs": "^16.0.0",
"@rollup/plugin-node-resolve": "^10.0.0", "@rollup/plugin-node-resolve": "^10.0.0",
"fs-extra": "^8.1.0", "fs-extra": "^8.1.0",

View File

@ -120,7 +120,11 @@ export const enrichRows = async (rows, tableId) => {
const type = schema[key].type const type = schema[key].type
if (type === "link") { if (type === "link") {
// Enrich row a string join of relationship fields // Enrich row a string join of relationship fields
row[`${key}_text`] = row[key]?.join(", ") || "" row[`${key}_text`] =
row[key]
?.map(option => option?.primaryDisplay)
.filter(option => !!option)
.join(", ") || ""
} else if (type === "attachment") { } else if (type === "attachment") {
// Enrich row with the first image URL for any attachment fields // Enrich row with the first image URL for any attachment fields
let url = null let url = null

View File

@ -3,6 +3,7 @@ FROM node:12-alpine
WORKDIR /app WORKDIR /app
ENV CLOUD=1 ENV CLOUD=1
ENV PORT=4001
ENV COUCH_DB_URL=https://couchdb.budi.live:5984 ENV COUCH_DB_URL=https://couchdb.budi.live:5984
ENV BUDIBASE_ENVIRONMENT=PRODUCTION ENV BUDIBASE_ENVIRONMENT=PRODUCTION

View File

@ -1,7 +1,7 @@
{ {
"name": "@budibase/server", "name": "@budibase/server",
"email": "hi@budibase.com", "email": "hi@budibase.com",
"version": "0.7.8", "version": "0.8.2",
"description": "Budibase Web Server", "description": "Budibase Web Server",
"main": "src/electron.js", "main": "src/electron.js",
"repository": { "repository": {
@ -50,8 +50,8 @@
"author": "Budibase", "author": "Budibase",
"license": "AGPL-3.0-or-later", "license": "AGPL-3.0-or-later",
"dependencies": { "dependencies": {
"@budibase/client": "^0.7.8", "@budibase/client": "^0.8.2",
"@budibase/string-templates": "^0.7.8", "@budibase/string-templates": "^0.8.2",
"@elastic/elasticsearch": "7.10.0", "@elastic/elasticsearch": "7.10.0",
"@koa/router": "8.0.0", "@koa/router": "8.0.0",
"@sendgrid/mail": "7.1.1", "@sendgrid/mail": "7.1.1",

View File

@ -7,6 +7,7 @@ const {
DocumentTypes, DocumentTypes,
SEPARATOR, SEPARATOR,
ViewNames, ViewNames,
generateUserID,
} = require("../../db/utils") } = require("../../db/utils")
const usersController = require("./user") const usersController = require("./user")
const { const {
@ -15,6 +16,7 @@ const {
} = require("../../utilities/rowProcessor") } = require("../../utilities/rowProcessor")
const { FieldTypes } = require("../../constants") const { FieldTypes } = require("../../constants")
const { isEqual } = require("lodash") const { isEqual } = require("lodash")
const { cloneDeep } = require("lodash/fp")
const TABLE_VIEW_BEGINS_WITH = `all${SEPARATOR}${DocumentTypes.TABLE}${SEPARATOR}` const TABLE_VIEW_BEGINS_WITH = `all${SEPARATOR}${DocumentTypes.TABLE}${SEPARATOR}`
@ -139,8 +141,12 @@ exports.save = async function(ctx) {
} }
if (!inputs._rev && !inputs._id) { if (!inputs._rev && !inputs._id) {
if (inputs.tableId === ViewNames.USERS) {
inputs._id = generateUserID(inputs.email)
} else {
inputs._id = generateRowID(inputs.tableId) inputs._id = generateRowID(inputs.tableId)
} }
}
// this returns the table and row incase they have been updated // this returns the table and row incase they have been updated
const dbTable = await db.get(inputs.tableId) const dbTable = await db.get(inputs.tableId)
@ -351,10 +357,15 @@ async function validate({ appId, tableId, row, table }) {
} }
const errors = {} const errors = {}
for (let fieldName of Object.keys(table.schema)) { for (let fieldName of Object.keys(table.schema)) {
const res = validateJs.single( const constraints = cloneDeep(table.schema[fieldName].constraints)
row[fieldName], // special case for options, need to always allow unselected (null)
table.schema[fieldName].constraints if (
) table.schema[fieldName].type === FieldTypes.OPTIONS &&
constraints.inclusion
) {
constraints.inclusion.push(null)
}
const res = validateJs.single(row[fieldName], constraints)
if (res) errors[fieldName] = res if (res) errors[fieldName] = res
} }
return { valid: Object.keys(errors).length === 0, errors } return { valid: Object.keys(errors).length === 0, errors }

View File

@ -89,6 +89,7 @@ exports.save = async function(ctx) {
} }
// update linked rows // update linked rows
try {
const linkResp = await linkRows.updateLinks({ const linkResp = await linkRows.updateLinks({
appId, appId,
eventType: oldTable eventType: oldTable
@ -100,6 +101,9 @@ exports.save = async function(ctx) {
if (linkResp != null && linkResp._rev) { if (linkResp != null && linkResp._rev) {
tableToSave._rev = linkResp._rev tableToSave._rev = linkResp._rev
} }
} catch (err) {
ctx.throw(400, err)
}
// don't perform any updates until relationships have been // don't perform any updates until relationships have been
// checked by the updateLinks function // checked by the updateLinks function

View File

@ -282,12 +282,13 @@ describe("/rows", () => {
const secondRow = (await createRow({ const secondRow = (await createRow({
name: "Test 2", name: "Test 2",
description: "og desc", description: "og desc",
link: [firstRow._id], link: [{_id: firstRow._id}],
tableId: table._id, tableId: table._id,
})).body })).body
const enriched = await outputProcessing(appId, table, [secondRow]) const enriched = await outputProcessing(appId, table, [secondRow])
expect(enriched[0].link.length).toBe(1) expect(enriched[0].link.length).toBe(1)
expect(enriched[0].link[0]).toBe("Test Contact") expect(enriched[0].link[0]._id).toBe(firstRow._id)
expect(enriched[0].link[0].primaryDisplay).toBe("Test Contact")
}) })
}) })

View File

@ -12,6 +12,11 @@ exports.FieldTypes = {
AUTO: "auto", AUTO: "auto",
} }
exports.RelationshipTypes = {
ONE_TO_MANY: "one-to-many",
MANY_TO_MANY: "many-to-many",
}
exports.AuthTypes = { exports.AuthTypes = {
APP: "app", APP: "app",
BUILDER: "builder", BUILDER: "builder",

View File

@ -185,8 +185,7 @@ const BASE_LAYOUTS = [
"margin-right": "auto", "margin-right": "auto",
"margin-left": "auto", "margin-left": "auto",
"min-height": "100%", "min-height": "100%",
"background-image": background: "#f5f5f5",
"linear-gradient(135deg, rgba(252,215,212,1) 20%, rgba(207,218,255,1) 100%);",
}, },
selected: {}, selected: {},
}, },
@ -240,8 +239,7 @@ const BASE_LAYOUTS = [
"margin-right": "auto", "margin-right": "auto",
"margin-left": "auto", "margin-left": "auto",
"min-height": "100%", "min-height": "100%",
"background-image": background: "#f5f5f5",
"linear-gradient(135deg, rgba(252,215,212,1) 20%, rgba(207,218,255,1) 100%);",
}, },
selected: {}, selected: {},
}, },

View File

@ -39,55 +39,6 @@ exports.createHomeScreen = () => ({
_instanceName: "Heading", _instanceName: "Heading",
_children: [], _children: [],
}, },
{
_id: "cbbf41b27c2b44d1abba38bb694880c6a",
_component: "@budibase/standard-components/container",
_styles: {
normal: {
display: "flex",
"flex-direction": "column",
"justify-content": "center",
"align-items": "stretch",
flex: "1 1 auto",
"border-width": "4px",
"border-style": "Dashed",
"margin-bottom": "32px",
},
hover: {},
active: {},
selected: {},
},
type: "div",
_instanceName: "Video Container",
_children: [
{
_id: "c07d752cb3e544b418088fa9be84ba2e4",
_component: "@budibase/standard-components/embed",
_styles: {
normal: {
width: "100%",
flex: "1 1 auto",
opacity: "0",
"transition-property": "Opacity",
"transition-duration": "1s",
"transition-timing-function:": "ease-in",
},
hover: {
"transition-property": "Opacity",
"transition-duration": "1s",
"transition-timing-function:": "ease-out",
opacity: "1",
},
active: {},
selected: {},
},
embed:
'<iframe width="560" height="315" src="https://www.youtube.com/embed/dQw4w9WgXcQ" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe>',
_instanceName: "Rick Astley Video",
_children: [],
},
],
},
], ],
_instanceName: "Home", _instanceName: "Home",
}, },

View File

@ -2,7 +2,7 @@ const CouchDB = require("../index")
const { IncludeDocs, getLinkDocuments } = require("./linkUtils") const { IncludeDocs, getLinkDocuments } = require("./linkUtils")
const { generateLinkID } = require("../utils") const { generateLinkID } = require("../utils")
const Sentry = require("@sentry/node") const Sentry = require("@sentry/node")
const { FieldTypes } = require("../../constants") const { FieldTypes, RelationshipTypes } = require("../../constants")
/** /**
* Creates a new link document structure which can be put to the database. It is important to * Creates a new link document structure which can be put to the database. It is important to
@ -113,6 +113,38 @@ class LinkController {
}) })
} }
/**
* Makes sure the passed in table schema contains valid relationship structures.
*/
validateTable(table) {
const usedAlready = []
for (let schema of Object.values(table.schema)) {
if (schema.type !== FieldTypes.LINK) {
continue
}
const unique = schema.tableId + schema.fieldName
if (usedAlready.indexOf(unique) !== -1) {
throw new Error(
"Cannot re-use the linked column name for a linked table."
)
}
usedAlready.push(unique)
}
}
/**
* Returns whether the two schemas are equal (in the important parts, not a pure equality check)
*/
areSchemasEqual(schema1, schema2) {
const compareFields = ["name", "type", "tableId", "fieldName", "autocolumn"]
for (let field of compareFields) {
if (schema1[field] !== schema2[field]) {
return false
}
}
return true
}
// all operations here will assume that the table // all operations here will assume that the table
// this operation is related to has linked rows // this operation is related to has linked rows
/** /**
@ -143,8 +175,32 @@ class LinkController {
? linkDoc.doc2.rowId ? linkDoc.doc2.rowId
: linkDoc.doc1.rowId : linkDoc.doc1.rowId
}) })
// if 1:N, ensure that this ID is not already attached to another record
const linkedTable = await this._db.get(field.tableId)
const linkedSchema = linkedTable.schema[field.fieldName]
// iterate through the link IDs in the row field, see if any don't exist already // iterate through the link IDs in the row field, see if any don't exist already
for (let linkId of rowField) { for (let linkId of rowField) {
if (linkedSchema.relationshipType === RelationshipTypes.ONE_TO_MANY) {
const links = (
await getLinkDocuments({
appId: this._appId,
tableId: field.tableId,
rowId: linkId,
includeDocs: IncludeDocs.EXCLUDE,
})
).filter(link => link.id !== row._id)
// The 1 side of 1:N is already related to something else
// You must remove the existing relationship
if (links.length > 0) {
throw new Error(
`1:N Relationship Error: Record already linked to another.`
)
}
}
if (linkId && linkId !== "" && linkDocIds.indexOf(linkId) === -1) { if (linkId && linkId !== "" && linkDocIds.indexOf(linkId) === -1) {
// first check the doc we're linking to exists // first check the doc we're linking to exists
try { try {
@ -246,6 +302,8 @@ class LinkController {
*/ */
async tableSaved() { async tableSaved() {
const table = await this.table() const table = await this.table()
// validate the table first
this.validateTable(table)
const schema = table.schema const schema = table.schema
for (let fieldName of Object.keys(schema)) { for (let fieldName of Object.keys(schema)) {
const field = schema[fieldName] const field = schema[fieldName]
@ -266,9 +324,25 @@ class LinkController {
tableId: table._id, tableId: table._id,
fieldName: fieldName, fieldName: fieldName,
} }
if (field.autocolumn) { if (field.autocolumn) {
linkConfig.autocolumn = field.autocolumn linkConfig.autocolumn = field.autocolumn
} }
if (field.relationshipType) {
// Ensure that the other side of the relationship is locked to one record
linkConfig.relationshipType = field.relationshipType
delete field.relationshipType
}
// check the linked table to make sure we aren't overwriting an existing column
const existingSchema = linkedTable.schema[field.fieldName]
if (
existingSchema != null &&
!this.areSchemasEqual(existingSchema, linkConfig)
) {
throw new Error("Cannot overwrite existing column.")
}
// create the link field in the other table // create the link field in the other table
linkedTable.schema[field.fieldName] = linkConfig linkedTable.schema[field.fieldName] = linkConfig
const response = await this._db.put(linkedTable) const response = await this._db.put(linkedTable)

View File

@ -175,11 +175,12 @@ exports.attachLinkedPrimaryDisplay = async (appId, table, rows) => {
if (!linkedRow || !linkedTable) { if (!linkedRow || !linkedTable) {
continue continue
} }
// need to handle an edge case where relationship just wasn't found const obj = { _id: linkedRow._id }
const value = linkedRow[linkedTable.primaryDisplay] || linkedRow._id // if we know the display column, add it
if (value) { if (linkedRow[linkedTable.primaryDisplay] != null) {
row[link.fieldName].push(value) obj.primaryDisplay = linkedRow[linkedTable.primaryDisplay]
} }
row[link.fieldName].push(obj)
} }
} }
return rows return rows

View File

@ -14,7 +14,7 @@ const SCHEMA = {
}, },
port: { port: {
type: FIELD_TYPES.NUMBER, type: FIELD_TYPES.NUMBER,
default: 1433, default: 3306,
required: false, required: false,
}, },
user: { user: {
@ -31,6 +31,10 @@ const SCHEMA = {
type: FIELD_TYPES.STRING, type: FIELD_TYPES.STRING,
required: true, required: true,
}, },
ssl: {
type: FIELD_TYPES.OBJECT,
required: false,
},
}, },
query: { query: {
create: { create: {
@ -51,6 +55,9 @@ const SCHEMA = {
class MySQLIntegration { class MySQLIntegration {
constructor(config) { constructor(config) {
this.config = config this.config = config
if (Object.keys(config.ssl).length === 0) {
delete config.ssl
}
this.client = mysql.createConnection(config) this.client = mysql.createConnection(config)
} }

View File

@ -15,6 +15,9 @@ const TYPE_TRANSFORM_MAP = {
[null]: [], [null]: [],
[undefined]: undefined, [undefined]: undefined,
parse: link => { parse: link => {
if (Array.isArray(link) && typeof link[0] === "object") {
return link.map(el => (el && el._id ? el._id : el))
}
if (typeof link === "string") { if (typeof link === "string") {
return [link] return [link]
} }
@ -22,8 +25,8 @@ const TYPE_TRANSFORM_MAP = {
}, },
}, },
[FieldTypes.OPTIONS]: { [FieldTypes.OPTIONS]: {
"": "", "": null,
[null]: "", [null]: null,
[undefined]: undefined, [undefined]: undefined,
}, },
[FieldTypes.STRING]: { [FieldTypes.STRING]: {
@ -136,6 +139,8 @@ exports.coerce = (row, type) => {
*/ */
exports.inputProcessing = (user, table, row) => { exports.inputProcessing = (user, table, row) => {
let clonedRow = cloneDeep(row) let clonedRow = cloneDeep(row)
// need to copy the table so it can be differenced on way out
const copiedTable = cloneDeep(table)
for (let [key, value] of Object.entries(clonedRow)) { for (let [key, value] of Object.entries(clonedRow)) {
const field = table.schema[key] const field = table.schema[key]
if (!field) { if (!field) {
@ -144,7 +149,7 @@ exports.inputProcessing = (user, table, row) => {
clonedRow[key] = exports.coerce(value, field.type) clonedRow[key] = exports.coerce(value, field.type)
} }
// handle auto columns - this returns an object like {table, row} // handle auto columns - this returns an object like {table, row}
return processAutoColumn(user, table, clonedRow) return processAutoColumn(user, copiedTable, clonedRow)
} }
/** /**

View File

@ -35,7 +35,7 @@
"keywords": [ "keywords": [
"svelte" "svelte"
], ],
"version": "0.7.8", "version": "0.8.2",
"license": "MIT", "license": "MIT",
"gitHead": "1a80b09fd093f2599a68f7db72ad639dd50922dd", "gitHead": "1a80b09fd093f2599a68f7db72ad639dd50922dd",
"dependencies": { "dependencies": {

View File

@ -22,8 +22,8 @@
const setFieldText = value => { const setFieldText = value => {
if (fieldSchema?.relationshipType === "one-to-many") { if (fieldSchema?.relationshipType === "one-to-many") {
if (value?.length && options?.length) { if (value?.length && options?.length) {
const row = options.find(row => row._id === value[0]) const row = options.find(row => row._id === value[0]?._id)
return row.name return getDisplayName(row)
} else { } else {
return placeholder || "Choose an option" return placeholder || "Choose an option"
} }
@ -60,27 +60,30 @@
} }
const getDisplayName = row => { const getDisplayName = row => {
return row[tableDefinition?.primaryDisplay || "_id"] return row?.[tableDefinition?.primaryDisplay || "_id"] || "-"
} }
const getValueLookupMap = value => { const getValueLookupMap = value => {
let map = {} let map = {}
if (value?.length) { if (value?.length) {
value.forEach(option => { value.forEach(option => {
map[option] = true if (option?._id) {
map[option._id] = true
}
}) })
} }
return map return map
} }
const toggleOption = option => { const toggleOption = id => {
if (fieldSchema.type === "one-to-many") { if (fieldSchema.relationshipType === "one-to-many") {
fieldApi.setValue([option]) fieldApi.setValue(id ? [{ _id: id }] : [])
} else { } else {
if ($fieldState.value.includes(option)) { if ($fieldState.value.find(option => option?._id === id)) {
fieldApi.setValue($fieldState.value.filter(x => x !== option)) const filtered = $fieldState.value.filter(option => option?._id !== id)
fieldApi.setValue(filtered)
} else { } else {
fieldApi.setValue([...$fieldState.value, option]) fieldApi.setValue([...$fieldState.value, { _id: id }])
} }
} }
} }

View File

@ -7,7 +7,7 @@
<div class="container"> <div class="container">
{#each items as item} {#each items as item}
<div class="item">{item}</div> <div class="item">{item?.primaryDisplay ?? ''}</div>
{/each} {/each}
</div> </div>

View File

@ -5,7 +5,7 @@ import AttachmentCell from "./AttachmentCell/Button.svelte"
import ViewDetails from "./ViewDetails/Cell.svelte" import ViewDetails from "./ViewDetails/Cell.svelte"
import Select from "./Select/Wrapper.svelte" import Select from "./Select/Wrapper.svelte"
import DatePicker from "./DateTime/Wrapper.svelte" import DatePicker from "./DateTime/Wrapper.svelte"
import RelationshipCount from "./Relationship/RelationshipCount.svelte" import RelationshipLabel from "./Relationship/RelationshipLabel.svelte"
const renderers = new Map([ const renderers = new Map([
["boolean", booleanRenderer], ["boolean", booleanRenderer],
@ -127,7 +127,7 @@ function linkedRowRenderer(options, constraints, editable, SDK) {
container.style.placeItems = "center" container.style.placeItems = "center"
container.style.height = "100%" container.style.height = "100%"
new RelationshipCount({ new RelationshipLabel({
target: container, target: container,
props: { props: {
row: params.data, row: params.data,

View File

@ -1,6 +1,6 @@
{ {
"name": "@budibase/string-templates", "name": "@budibase/string-templates",
"version": "0.7.8", "version": "0.8.2",
"description": "Handlebars wrapper for Budibase templating.", "description": "Handlebars wrapper for Budibase templating.",
"main": "src/index.js", "main": "src/index.js",
"module": "src/index.js", "module": "src/index.js",

View File

@ -1,7 +1,7 @@
{ {
"name": "@budibase/deployment", "name": "@budibase/deployment",
"email": "hi@budibase.com", "email": "hi@budibase.com",
"version": "0.7.8", "version": "0.8.2",
"description": "Budibase Deployment Server", "description": "Budibase Deployment Server",
"main": "src/index.js", "main": "src/index.js",
"repository": { "repository": {