Merge branch 'master' of github.com:Budibase/budibase into budi-day/cli
This commit is contained in:
commit
3524eecd9b
|
@ -1,5 +1,5 @@
|
||||||
{
|
{
|
||||||
"version": "0.7.8",
|
"version": "0.8.2",
|
||||||
"npmClient": "yarn",
|
"npmClient": "yarn",
|
||||||
"packages": [
|
"packages": [
|
||||||
"packages/*"
|
"packages/*"
|
||||||
|
|
|
@ -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",
|
||||||
|
|
|
@ -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%;
|
||||||
|
|
|
@ -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>
|
||||||
|
|
||||||
|
|
|
@ -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)
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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);
|
||||||
|
|
|
@ -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}
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -37,8 +37,10 @@
|
||||||
return [...acc, ...viewsArr]
|
return [...acc, ...viewsArr]
|
||||||
}, [])
|
}, [])
|
||||||
$: queries = $backendUiStore.queries
|
$: queries = $backendUiStore.queries
|
||||||
.filter(query => showAllQueries || (query.queryVerb === "read" || query.readable))
|
.filter(
|
||||||
.map(query => ({
|
query => showAllQueries || query.queryVerb === "read" || query.readable
|
||||||
|
)
|
||||||
|
.map(query => ({
|
||||||
label: query.name,
|
label: query.name,
|
||||||
name: query.name,
|
name: query.name,
|
||||||
tableId: query._id,
|
tableId: query._id,
|
||||||
|
|
|
@ -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
|
@ -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",
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
||||||
|
|
|
@ -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",
|
||||||
|
|
|
@ -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,7 +141,11 @@ exports.save = async function(ctx) {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!inputs._rev && !inputs._id) {
|
if (!inputs._rev && !inputs._id) {
|
||||||
inputs._id = generateRowID(inputs.tableId)
|
if (inputs.tableId === ViewNames.USERS) {
|
||||||
|
inputs._id = generateUserID(inputs.email)
|
||||||
|
} else {
|
||||||
|
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
|
||||||
|
@ -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 }
|
||||||
|
|
|
@ -89,16 +89,20 @@ exports.save = async function(ctx) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// update linked rows
|
// update linked rows
|
||||||
const linkResp = await linkRows.updateLinks({
|
try {
|
||||||
appId,
|
const linkResp = await linkRows.updateLinks({
|
||||||
eventType: oldTable
|
appId,
|
||||||
? linkRows.EventType.TABLE_UPDATED
|
eventType: oldTable
|
||||||
: linkRows.EventType.TABLE_SAVE,
|
? linkRows.EventType.TABLE_UPDATED
|
||||||
table: tableToSave,
|
: linkRows.EventType.TABLE_SAVE,
|
||||||
oldTable: oldTable,
|
table: tableToSave,
|
||||||
})
|
oldTable: oldTable,
|
||||||
if (linkResp != null && linkResp._rev) {
|
})
|
||||||
tableToSave._rev = linkResp._rev
|
if (linkResp != null && 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
|
||||||
|
|
|
@ -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")
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
|
@ -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",
|
||||||
|
|
|
@ -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: {},
|
||||||
},
|
},
|
||||||
|
|
|
@ -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",
|
||||||
},
|
},
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -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": {
|
||||||
|
|
|
@ -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 }])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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",
|
||||||
|
|
|
@ -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": {
|
||||||
|
|
Loading…
Reference in New Issue