Merge branch 'feature/json-backend' of github.com:Budibase/budibase into feature/json-backend

This commit is contained in:
Andrew Kingston 2021-11-30 13:41:58 +00:00
commit 1aa6b0bdd6
19 changed files with 179 additions and 55 deletions

View File

@ -1,5 +1,5 @@
{ {
"version": "0.9.190-alpha.11", "version": "0.9.190-alpha.12",
"npmClient": "yarn", "npmClient": "yarn",
"packages": [ "packages": [
"packages/*" "packages/*"

View File

@ -1,6 +1,6 @@
{ {
"name": "@budibase/auth", "name": "@budibase/auth",
"version": "0.9.190-alpha.11", "version": "0.9.190-alpha.12",
"description": "Authentication middlewares for budibase builder and apps", "description": "Authentication middlewares for budibase builder and apps",
"main": "src/index.js", "main": "src/index.js",
"author": "Budibase", "author": "Budibase",

View File

@ -1,7 +1,7 @@
{ {
"name": "@budibase/bbui", "name": "@budibase/bbui",
"description": "A UI solution used in the different Budibase projects.", "description": "A UI solution used in the different Budibase projects.",
"version": "0.9.190-alpha.11", "version": "0.9.190-alpha.12",
"license": "MPL-2.0", "license": "MPL-2.0",
"svelte": "src/index.js", "svelte": "src/index.js",
"module": "dist/bbui.es.js", "module": "dist/bbui.es.js",

View File

@ -1,6 +1,6 @@
{ {
"name": "@budibase/builder", "name": "@budibase/builder",
"version": "0.9.190-alpha.11", "version": "0.9.190-alpha.12",
"license": "GPL-3.0", "license": "GPL-3.0",
"private": true, "private": true,
"scripts": { "scripts": {
@ -65,10 +65,10 @@
} }
}, },
"dependencies": { "dependencies": {
"@budibase/bbui": "^0.9.190-alpha.11", "@budibase/bbui": "^0.9.190-alpha.12",
"@budibase/client": "^0.9.190-alpha.11", "@budibase/client": "^0.9.190-alpha.12",
"@budibase/colorpicker": "1.1.2", "@budibase/colorpicker": "1.1.2",
"@budibase/string-templates": "^0.9.190-alpha.11", "@budibase/string-templates": "^0.9.190-alpha.12",
"@sentry/browser": "5.19.1", "@sentry/browser": "5.19.1",
"@spectrum-css/page": "^3.0.1", "@spectrum-css/page": "^3.0.1",
"@spectrum-css/vars": "^3.0.1", "@spectrum-css/vars": "^3.0.1",

View File

@ -0,0 +1,56 @@
import { FIELDS } from "constants/backend"
function baseConversion(type) {
if (type === "string") {
return {
type: FIELDS.STRING.type,
}
} else if (type === "boolean") {
return {
type: FIELDS.BOOLEAN.type,
}
} else if (type === "number") {
return {
type: FIELDS.NUMBER.type,
}
}
}
function recurse(schemaLevel = {}, objectLevel) {
if (!objectLevel) {
return null
}
const baseType = typeof objectLevel
if (baseType !== "object") {
return baseConversion(baseType)
}
for (let [key, value] of Object.entries(objectLevel)) {
const type = typeof value
// check array first, since arrays are objects
if (Array.isArray(value)) {
const schema = recurse(schemaLevel[key], value[0])
if (schema) {
schemaLevel[key] = {
type: FIELDS.ARRAY.type,
schema,
}
}
} else if (type === "object") {
const schema = recurse(schemaLevel[key], objectLevel[key])
if (schema) {
schemaLevel[key] = schema
}
} else {
schemaLevel[key] = baseConversion(type)
}
}
if (!schemaLevel.type) {
return { type: FIELDS.JSON.type, schema: schemaLevel }
} else {
return schemaLevel
}
}
export function generate(object) {
return recurse({}, object).schema
}

View File

@ -524,7 +524,7 @@ export const getFrontendStore = () => {
} }
} }
}, },
paste: async (targetComponent, mode) => { paste: async (targetComponent, mode, preserveBindings = false) => {
let promises = [] let promises = []
store.update(state => { store.update(state => {
// Stop if we have nothing to paste // Stop if we have nothing to paste
@ -536,7 +536,7 @@ export const getFrontendStore = () => {
const cut = state.componentToPaste.isCut const cut = state.componentToPaste.isCut
// immediately need to remove bindings, currently these aren't valid when pasted // immediately need to remove bindings, currently these aren't valid when pasted
if (!cut) { if (!cut && !preserveBindings) {
state.componentToPaste = removeBindings(state.componentToPaste) state.componentToPaste = removeBindings(state.componentToPaste)
} }

View File

@ -6,16 +6,20 @@
Toggle, Toggle,
TextArea, TextArea,
Multiselect, Multiselect,
Label,
} from "@budibase/bbui" } from "@budibase/bbui"
import Dropzone from "components/common/Dropzone.svelte" import Dropzone from "components/common/Dropzone.svelte"
import { capitalise } from "helpers" import { capitalise } from "helpers"
import LinkedRowSelector from "components/common/LinkedRowSelector.svelte" import LinkedRowSelector from "components/common/LinkedRowSelector.svelte"
import Editor from "../../integration/QueryEditor.svelte"
export let defaultValue export let defaultValue
export let meta export let meta
export let value = defaultValue || (meta.type === "boolean" ? false : "") export let value = defaultValue || (meta.type === "boolean" ? false : "")
export let readonly export let readonly
$: stringVal =
typeof value === "object" ? JSON.stringify(value, null, 2) : value
$: type = meta?.type $: type = meta?.type
$: label = meta.name ? capitalise(meta.name) : "" $: label = meta.name ? capitalise(meta.name) : ""
</script> </script>
@ -40,6 +44,14 @@
<LinkedRowSelector bind:linkedRows={value} schema={meta} /> <LinkedRowSelector bind:linkedRows={value} schema={meta} />
{:else if type === "longform"} {:else if type === "longform"}
<TextArea {label} bind:value /> <TextArea {label} bind:value />
{:else if type === "json"}
<Label>{label}</Label>
<Editor
editorHeight="250"
mode="json"
on:change={({ detail }) => (value = detail.value)}
value={stringVal}
/>
{:else} {:else}
<Input <Input
{label} {label}

View File

@ -87,7 +87,10 @@
field.subtype !== AUTO_COLUMN_SUB_TYPES.CREATED_BY && field.subtype !== AUTO_COLUMN_SUB_TYPES.CREATED_BY &&
field.subtype !== AUTO_COLUMN_SUB_TYPES.UPDATED_BY && field.subtype !== AUTO_COLUMN_SUB_TYPES.UPDATED_BY &&
field.type !== FORMULA_TYPE field.type !== FORMULA_TYPE
$: canBeDisplay = field.type !== LINK_TYPE && field.type !== AUTO_TYPE $: canBeDisplay =
field.type !== LINK_TYPE &&
field.type !== AUTO_TYPE &&
field.type !== JSON_TYPE
$: canBeRequired = $: canBeRequired =
field.type !== LINK_TYPE && !uneditable && field.type !== AUTO_TYPE field.type !== LINK_TYPE && !uneditable && field.type !== AUTO_TYPE
$: relationshipOptions = getRelationshipOptions(field) $: relationshipOptions = getRelationshipOptions(field)
@ -452,7 +455,14 @@
</div> </div>
</ModalContent> </ModalContent>
<Modal bind:this={jsonSchemaModal}> <Modal bind:this={jsonSchemaModal}>
<JSONSchemaModal on:save={({ detail }) => console.log(detail)} /> <JSONSchemaModal
schema={field.schema}
json={field.json}
on:save={({ detail }) => {
field.schema = detail.schema
field.json = detail.json
}}
/>
</Modal> </Modal>
<ConfirmDialog <ConfirmDialog
bind:this={confirmDeleteDialog} bind:this={confirmDeleteDialog}

View File

@ -9,36 +9,42 @@
Select, Select,
} from "@budibase/bbui" } from "@budibase/bbui"
import { onMount, createEventDispatcher } from "svelte" import { onMount, createEventDispatcher } from "svelte"
import { FIELDS } from "constants/backend"
import { generate } from "builderStore/schemaGenerator"
export let schema = {} export let schema = {}
export let json
let dispatcher = createEventDispatcher() let dispatcher = createEventDispatcher()
let mode = "Key/Value" let mode = "Key/Value"
let json
let fieldCount = 0 let fieldCount = 0
let fieldKeys = {}, let fieldKeys = {},
fieldTypes = {} fieldTypes = {}
let keyValueOptions = ["String", "Number", "Boolean", "Object", "Array"] let keyValueOptions = [
{ label: "String", value: FIELDS.STRING.type },
{ label: "Number", value: FIELDS.NUMBER.type },
{ label: "Boolean", value: FIELDS.BOOLEAN.type },
{ label: "Object", value: FIELDS.JSON.type },
{ label: "Array", value: FIELDS.ARRAY.type },
]
let invalid = false
$: invalid = false async function onJsonUpdate({ detail }) {
function onJsonUpdate({ detail }) {
// TODO: make request
const input = detail.value const input = detail.value
console.log(input) json = input
} try {
// check json valid first
function saveSchema() { let inputJson = JSON.parse(input)
for (let i of Object.keys(fieldKeys)) { schema = generate(inputJson)
const key = fieldKeys[i] updateCounts()
schema[key] = { invalid = false
type: fieldTypes[i], } catch (err) {
} // json not currently valid
invalid = true
} }
dispatcher("save", schema)
} }
onMount(() => { function updateCounts() {
if (!schema) { if (!schema) {
schema = {} schema = {}
} }
@ -49,6 +55,24 @@
i++ i++
} }
fieldCount = i fieldCount = i
}
function saveSchema() {
for (let i of Object.keys(fieldKeys)) {
const key = fieldKeys[i]
// they were added to schema, rather than generated
if (!schema[key]) {
schema[key] = {
type: fieldTypes[i],
}
}
}
dispatcher("save", { schema, json })
}
onMount(() => {
updateCounts()
}) })
</script> </script>
@ -56,7 +80,7 @@
title={"Key/Value Schema Editor"} title={"Key/Value Schema Editor"}
confirmText="Save Column" confirmText="Save Column"
onConfirm={saveSchema} onConfirm={saveSchema}
disabled={invalid} bind:disabled={invalid}
size="L" size="L"
> >
<Tabs selected={mode} noPadding> <Tabs selected={mode} noPadding>
@ -68,7 +92,8 @@
label="Type" label="Type"
options={keyValueOptions} options={keyValueOptions}
bind:value={fieldTypes[i]} bind:value={fieldTypes[i]}
getOptionValue={field => field.toLowerCase()} getOptionValue={field => field.value}
getOptionLabel={field => field.label}
/> />
</div> </div>
{/each} {/each}

View File

@ -53,7 +53,7 @@
const duplicateComponent = () => { const duplicateComponent = () => {
storeComponentForCopy(false) storeComponentForCopy(false)
pasteComponent("below") pasteComponent("below", true)
} }
const deleteComponent = async () => { const deleteComponent = async () => {
@ -69,9 +69,9 @@
store.actions.components.copy(component, cut) store.actions.components.copy(component, cut)
} }
const pasteComponent = mode => { const pasteComponent = (mode, preserveBindings = false) => {
// lives in store - also used by drag drop // lives in store - also used by drag drop
store.actions.components.paste(component, mode) store.actions.components.paste(component, mode, preserveBindings)
} }
</script> </script>

View File

@ -2,13 +2,15 @@
import { Button, ActionButton, Drawer } from "@budibase/bbui" import { Button, ActionButton, Drawer } from "@budibase/bbui"
import { createEventDispatcher } from "svelte" import { createEventDispatcher } from "svelte"
import NavigationDrawer from "./NavigationDrawer.svelte" import NavigationDrawer from "./NavigationDrawer.svelte"
import { cloneDeep } from "lodash/fp"
export let value = [] export let value = []
let drawer let drawer
let links = cloneDeep(value)
const dispatch = createEventDispatcher() const dispatch = createEventDispatcher()
const save = () => { const save = () => {
dispatch("change", value) dispatch("change", links)
drawer.hide() drawer.hide()
} }
</script> </script>
@ -19,5 +21,5 @@
Configure the links in your navigation bar. Configure the links in your navigation bar.
</svelte:fragment> </svelte:fragment>
<Button cta slot="buttons" on:click={save}>Save</Button> <Button cta slot="buttons" on:click={save}>Save</Button>
<NavigationDrawer slot="body" bind:links={value} /> <NavigationDrawer slot="body" bind:links />
</Drawer> </Drawer>

View File

@ -1,6 +1,6 @@
{ {
"name": "@budibase/cli", "name": "@budibase/cli",
"version": "0.9.190-alpha.11", "version": "0.9.190-alpha.12",
"description": "Budibase CLI, for developers, self hosting and migrations.", "description": "Budibase CLI, for developers, self hosting and migrations.",
"main": "src/index.js", "main": "src/index.js",
"bin": { "bin": {

View File

@ -1,6 +1,6 @@
{ {
"name": "@budibase/client", "name": "@budibase/client",
"version": "0.9.190-alpha.11", "version": "0.9.190-alpha.12",
"license": "MPL-2.0", "license": "MPL-2.0",
"module": "dist/budibase-client.js", "module": "dist/budibase-client.js",
"main": "dist/budibase-client.js", "main": "dist/budibase-client.js",
@ -19,9 +19,9 @@
"dev:builder": "rollup -cw" "dev:builder": "rollup -cw"
}, },
"dependencies": { "dependencies": {
"@budibase/bbui": "^0.9.190-alpha.11", "@budibase/bbui": "^0.9.190-alpha.12",
"@budibase/standard-components": "^0.9.139", "@budibase/standard-components": "^0.9.139",
"@budibase/string-templates": "^0.9.190-alpha.11", "@budibase/string-templates": "^0.9.190-alpha.12",
"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"

View File

@ -313,6 +313,9 @@
height: 100%; height: 100%;
overflow: auto; overflow: auto;
} }
.desktop.layout--left .links {
overflow-y: auto;
}
.desktop .nav--left { .desktop .nav--left {
width: 250px; width: 250px;
@ -379,6 +382,7 @@
justify-content: flex-start; justify-content: flex-start;
align-items: stretch; align-items: stretch;
padding: var(--spacing-xl); padding: var(--spacing-xl);
overflow-y: auto;
} }
.mobile .link { .mobile .link {
width: calc(100% - 30px); width: calc(100% - 30px);

View File

@ -1,7 +1,7 @@
{ {
"name": "@budibase/server", "name": "@budibase/server",
"email": "hi@budibase.com", "email": "hi@budibase.com",
"version": "0.9.190-alpha.11", "version": "0.9.190-alpha.12",
"description": "Budibase Web Server", "description": "Budibase Web Server",
"main": "src/index.ts", "main": "src/index.ts",
"repository": { "repository": {
@ -69,9 +69,9 @@
"author": "Budibase", "author": "Budibase",
"license": "GPL-3.0", "license": "GPL-3.0",
"dependencies": { "dependencies": {
"@budibase/auth": "^0.9.190-alpha.11", "@budibase/auth": "^0.9.190-alpha.12",
"@budibase/client": "^0.9.190-alpha.11", "@budibase/client": "^0.9.190-alpha.12",
"@budibase/string-templates": "^0.9.190-alpha.11", "@budibase/string-templates": "^0.9.190-alpha.12",
"@bull-board/api": "^3.7.0", "@bull-board/api": "^3.7.0",
"@bull-board/koa": "^3.7.0", "@bull-board/koa": "^3.7.0",
"@elastic/elasticsearch": "7.10.0", "@elastic/elasticsearch": "7.10.0",

View File

@ -50,10 +50,10 @@ exports.validate = async ({ 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 constraints = cloneDeep(table.schema[fieldName].constraints) const constraints = cloneDeep(table.schema[fieldName].constraints)
const type = table.schema[fieldName].type
// special case for options, need to always allow unselected (null) // special case for options, need to always allow unselected (null)
if ( if (
table.schema[fieldName].type === (type === FieldTypes.OPTIONS || type === FieldTypes.ARRAY) &&
(FieldTypes.OPTIONS || FieldTypes.ARRAY) &&
constraints.inclusion constraints.inclusion
) { ) {
constraints.inclusion.push(null) constraints.inclusion.push(null)
@ -61,17 +61,20 @@ exports.validate = async ({ appId, tableId, row, table }) => {
let res let res
// Validate.js doesn't seem to handle array // Validate.js doesn't seem to handle array
if ( if (type === FieldTypes.ARRAY && row[fieldName] && row[fieldName].length) {
table.schema[fieldName].type === FieldTypes.ARRAY &&
row[fieldName] &&
row[fieldName].length
) {
row[fieldName].map(val => { row[fieldName].map(val => {
if (!constraints.inclusion.includes(val)) { if (!constraints.inclusion.includes(val)) {
errors[fieldName] = "Field not in list" errors[fieldName] = "Field not in list"
} }
}) })
} else if (table.schema[fieldName].type === FieldTypes.FORMULA) { } else if (type === FieldTypes.JSON && typeof row[fieldName] === "string") {
// this should only happen if there is an error
try {
JSON.parse(row[fieldName])
} catch (err) {
errors[fieldName] = [`Contains invalid JSON`]
}
} else if (type === FieldTypes.FORMULA) {
res = validateJs.single( res = validateJs.single(
processStringSync(table.schema[fieldName].formula, row), processStringSync(table.schema[fieldName].formula, row),
constraints constraints

View File

@ -81,6 +81,18 @@ const TYPE_TRANSFORM_MAP = {
[FieldTypes.AUTO]: { [FieldTypes.AUTO]: {
parse: () => undefined, parse: () => undefined,
}, },
[FieldTypes.JSON]: {
parse: input => {
try {
if (input === "") {
return undefined
}
return JSON.parse(input)
} catch (err) {
return input
}
},
},
} }
/** /**

View File

@ -1,6 +1,6 @@
{ {
"name": "@budibase/string-templates", "name": "@budibase/string-templates",
"version": "0.9.190-alpha.11", "version": "0.9.190-alpha.12",
"description": "Handlebars wrapper for Budibase templating.", "description": "Handlebars wrapper for Budibase templating.",
"main": "src/index.cjs", "main": "src/index.cjs",
"module": "dist/bundle.mjs", "module": "dist/bundle.mjs",

View File

@ -1,7 +1,7 @@
{ {
"name": "@budibase/worker", "name": "@budibase/worker",
"email": "hi@budibase.com", "email": "hi@budibase.com",
"version": "0.9.190-alpha.11", "version": "0.9.190-alpha.12",
"description": "Budibase background service", "description": "Budibase background service",
"main": "src/index.js", "main": "src/index.js",
"repository": { "repository": {
@ -29,8 +29,8 @@
"author": "Budibase", "author": "Budibase",
"license": "GPL-3.0", "license": "GPL-3.0",
"dependencies": { "dependencies": {
"@budibase/auth": "^0.9.190-alpha.11", "@budibase/auth": "^0.9.190-alpha.12",
"@budibase/string-templates": "^0.9.190-alpha.11", "@budibase/string-templates": "^0.9.190-alpha.12",
"@koa/router": "^8.0.0", "@koa/router": "^8.0.0",
"@sentry/node": "^6.0.0", "@sentry/node": "^6.0.0",
"@techpass/passport-openidconnect": "^0.3.0", "@techpass/passport-openidconnect": "^0.3.0",