Merge branch 'develop' of github.com:Budibase/budibase into feature/real-rich-text
This commit is contained in:
commit
90514af833
|
@ -104,12 +104,14 @@ Budibase is made to scale. With Budibase, you can self-host on your own infrastr
|
||||||
|
|
||||||
## 🏁 Get started
|
## 🏁 Get started
|
||||||
|
|
||||||
<img src="https://res.cloudinary.com/daog6scxm/image/upload/v1634808888/logo/deploy_npl9za.png" />
|
<a href="https://docs.budibase.com/self-hosting/self-host"><img src="https://res.cloudinary.com/daog6scxm/image/upload/v1634808888/logo/deploy_npl9za.png" /></a>
|
||||||
|
|
||||||
Deploy Budibase self-Hosted in your existing infrastructure, using Docker, Kubernetes, and Digital Ocean.
|
Deploy Budibase self-hosted in your existing infrastructure, using Docker, Kubernetes, and Digital Ocean.
|
||||||
Or use Budibase Cloud if you don't need to self-host, and would like to get started quickly.
|
Or use Budibase Cloud if you don't need to self-host, and would like to get started quickly.
|
||||||
|
|
||||||
### [Get started with Budibase](https://budibase.com)
|
### [Get started with self-hosting Budibase](https://docs.budibase.com/self-hosting/self-host)
|
||||||
|
|
||||||
|
### [Get started with Budibase Cloud](https://budibase.com)
|
||||||
|
|
||||||
|
|
||||||
<br /><br />
|
<br /><br />
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
{
|
{
|
||||||
"version": "1.0.49-alpha.7",
|
"version": "1.0.49-alpha.12",
|
||||||
"npmClient": "yarn",
|
"npmClient": "yarn",
|
||||||
"packages": [
|
"packages": [
|
||||||
"packages/*"
|
"packages/*"
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "@budibase/backend-core",
|
"name": "@budibase/backend-core",
|
||||||
"version": "1.0.49-alpha.7",
|
"version": "1.0.49-alpha.12",
|
||||||
"description": "Budibase backend core libraries used in server and worker",
|
"description": "Budibase backend core libraries used in server and worker",
|
||||||
"main": "src/index.js",
|
"main": "src/index.js",
|
||||||
"author": "Budibase",
|
"author": "Budibase",
|
||||||
|
|
|
@ -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": "1.0.49-alpha.7",
|
"version": "1.0.49-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",
|
||||||
|
|
|
@ -13,6 +13,7 @@
|
||||||
start: textarea.selectionStart,
|
start: textarea.selectionStart,
|
||||||
end: textarea.selectionEnd,
|
end: textarea.selectionEnd,
|
||||||
})
|
})
|
||||||
|
export let align = null
|
||||||
|
|
||||||
let focus = false
|
let focus = false
|
||||||
let textarea
|
let textarea
|
||||||
|
@ -46,6 +47,7 @@
|
||||||
bind:this={textarea}
|
bind:this={textarea}
|
||||||
placeholder={placeholder || ""}
|
placeholder={placeholder || ""}
|
||||||
class="spectrum-Textfield-input"
|
class="spectrum-Textfield-input"
|
||||||
|
style={align ? `text-align: ${align}` : ""}
|
||||||
{disabled}
|
{disabled}
|
||||||
{id}
|
{id}
|
||||||
on:focus={() => (focus = true)}
|
on:focus={() => (focus = true)}
|
||||||
|
|
|
@ -12,6 +12,7 @@
|
||||||
export let updateOnChange = true
|
export let updateOnChange = true
|
||||||
export let quiet = false
|
export let quiet = false
|
||||||
export let dataCy
|
export let dataCy
|
||||||
|
export let align
|
||||||
|
|
||||||
const dispatch = createEventDispatcher()
|
const dispatch = createEventDispatcher()
|
||||||
let focus = false
|
let focus = false
|
||||||
|
@ -92,8 +93,9 @@
|
||||||
on:input={onInput}
|
on:input={onInput}
|
||||||
on:keyup={updateValueOnEnter}
|
on:keyup={updateValueOnEnter}
|
||||||
{type}
|
{type}
|
||||||
inputmode={type === "number" ? "decimal" : "text"}
|
|
||||||
class="spectrum-Textfield-input"
|
class="spectrum-Textfield-input"
|
||||||
|
style={align ? `text-align: ${align};` : ""}
|
||||||
|
inputmode={type === "number" ? "decimal" : "text"}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
|
@ -8,10 +8,35 @@
|
||||||
copyToClipboard(value)
|
copyToClipboard(value)
|
||||||
}
|
}
|
||||||
|
|
||||||
function copyToClipboard(value) {
|
const copyToClipboard = value => {
|
||||||
navigator.clipboard.writeText(value).then(() => {
|
return new Promise(res => {
|
||||||
notifications.success("Copied")
|
if (navigator.clipboard && window.isSecureContext) {
|
||||||
|
// Try using the clipboard API first
|
||||||
|
navigator.clipboard.writeText(value).then(res)
|
||||||
|
} else {
|
||||||
|
// Fall back to the textarea hack
|
||||||
|
let textArea = document.createElement("textarea")
|
||||||
|
textArea.value = value
|
||||||
|
textArea.style.position = "fixed"
|
||||||
|
textArea.style.left = "-9999px"
|
||||||
|
textArea.style.top = "-9999px"
|
||||||
|
document.body.appendChild(textArea)
|
||||||
|
textArea.focus()
|
||||||
|
textArea.select()
|
||||||
|
document.execCommand("copy")
|
||||||
|
textArea.remove()
|
||||||
|
res()
|
||||||
|
}
|
||||||
})
|
})
|
||||||
|
.then(() => {
|
||||||
|
notifications.success("Copied to clipboard")
|
||||||
|
})
|
||||||
|
.catch(() => {
|
||||||
|
notifications.error(
|
||||||
|
"Failed to copy to clipboard. Check the dev console for the value."
|
||||||
|
)
|
||||||
|
console.warn("Failed to copy the value", value)
|
||||||
|
})
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "@budibase/builder",
|
"name": "@budibase/builder",
|
||||||
"version": "1.0.49-alpha.7",
|
"version": "1.0.49-alpha.12",
|
||||||
"license": "GPL-3.0",
|
"license": "GPL-3.0",
|
||||||
"private": true,
|
"private": true,
|
||||||
"scripts": {
|
"scripts": {
|
||||||
|
@ -66,10 +66,10 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@budibase/bbui": "^1.0.49-alpha.7",
|
"@budibase/bbui": "^1.0.49-alpha.12",
|
||||||
"@budibase/client": "^1.0.49-alpha.7",
|
"@budibase/client": "^1.0.49-alpha.12",
|
||||||
"@budibase/colorpicker": "1.1.2",
|
"@budibase/colorpicker": "1.1.2",
|
||||||
"@budibase/string-templates": "^1.0.49-alpha.7",
|
"@budibase/string-templates": "^1.0.49-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",
|
||||||
|
|
|
@ -65,6 +65,9 @@ export const getFrontendStore = () => {
|
||||||
const store = writable({ ...INITIAL_FRONTEND_STATE })
|
const store = writable({ ...INITIAL_FRONTEND_STATE })
|
||||||
|
|
||||||
store.actions = {
|
store.actions = {
|
||||||
|
reset: () => {
|
||||||
|
store.set({ ...INITIAL_FRONTEND_STATE })
|
||||||
|
},
|
||||||
initialise: async pkg => {
|
initialise: async pkg => {
|
||||||
const { layouts, screens, application, clientLibPath } = pkg
|
const { layouts, screens, application, clientLibPath } = pkg
|
||||||
const components = await fetchComponentLibDefinitions(application.appId)
|
const components = await fetchComponentLibDefinitions(application.appId)
|
||||||
|
|
|
@ -188,29 +188,27 @@
|
||||||
{:else}
|
{:else}
|
||||||
<Body size="S"><i>No tables found.</i></Body>
|
<Body size="S"><i>No tables found.</i></Body>
|
||||||
{/if}
|
{/if}
|
||||||
{#if plusTables?.length !== 0 && integration.relationships}
|
<Divider size="S" />
|
||||||
<Divider size="S" />
|
<div class="query-header">
|
||||||
<div class="query-header">
|
<Heading size="S">Relationships</Heading>
|
||||||
<Heading size="S">Relationships</Heading>
|
<Button primary on:click={() => openRelationshipModal()}>
|
||||||
<Button primary on:click={openRelationshipModal}>
|
Define relationship
|
||||||
Define relationship
|
</Button>
|
||||||
</Button>
|
</div>
|
||||||
</div>
|
<Body>
|
||||||
<Body>
|
Tell budibase how your tables are related to get even more smart features.
|
||||||
Tell budibase how your tables are related to get even more smart features.
|
</Body>
|
||||||
</Body>
|
{#if relationshipInfo && relationshipInfo.length > 0}
|
||||||
{#if relationshipInfo && relationshipInfo.length > 0}
|
<Table
|
||||||
<Table
|
on:click={({ detail }) => openRelationshipModal(detail.from, detail.to)}
|
||||||
on:click={({ detail }) => openRelationshipModal(detail.from, detail.to)}
|
schema={relationshipSchema}
|
||||||
schema={relationshipSchema}
|
data={relationshipInfo}
|
||||||
data={relationshipInfo}
|
allowEditColumns={false}
|
||||||
allowEditColumns={false}
|
allowEditRows={false}
|
||||||
allowEditRows={false}
|
allowSelectRows={false}
|
||||||
allowSelectRows={false}
|
/>
|
||||||
/>
|
{:else}
|
||||||
{:else}
|
<Body size="S"><i>No relationships configured.</i></Body>
|
||||||
<Body size="S"><i>No relationships configured.</i></Body>
|
|
||||||
{/if}
|
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
|
|
|
@ -22,6 +22,10 @@
|
||||||
|
|
||||||
let originalFromName = fromRelationship.name,
|
let originalFromName = fromRelationship.name,
|
||||||
originalToName = toRelationship.name
|
originalToName = toRelationship.name
|
||||||
|
let fromTable, toTable, through, linkTable, tableOptions
|
||||||
|
let isManyToMany, isManyToOne, relationshipTypes
|
||||||
|
let errors, valid
|
||||||
|
let currentTables = {}
|
||||||
|
|
||||||
if (fromRelationship && !fromRelationship.relationshipType) {
|
if (fromRelationship && !fromRelationship.relationshipType) {
|
||||||
fromRelationship.relationshipType = RelationshipTypes.MANY_TO_ONE
|
fromRelationship.relationshipType = RelationshipTypes.MANY_TO_ONE
|
||||||
|
@ -41,61 +45,52 @@
|
||||||
|
|
||||||
const touched = writable({})
|
const touched = writable({})
|
||||||
|
|
||||||
function checkForErrors(
|
function checkForErrors(fromRelate, toRelate) {
|
||||||
fromTable,
|
|
||||||
toTable,
|
|
||||||
throughTable,
|
|
||||||
fromRelate,
|
|
||||||
toRelate
|
|
||||||
) {
|
|
||||||
const isMany =
|
const isMany =
|
||||||
fromRelate.relationshipType === RelationshipTypes.MANY_TO_MANY
|
fromRelate.relationshipType === RelationshipTypes.MANY_TO_MANY
|
||||||
const tableNotSet = "Please specify a table"
|
const tableNotSet = "Please specify a table"
|
||||||
const errors = {}
|
const errObj = {}
|
||||||
if ($touched.from && !fromTable) {
|
if ($touched.from && !fromTable) {
|
||||||
errors.from = tableNotSet
|
errObj.from = tableNotSet
|
||||||
}
|
}
|
||||||
if ($touched.to && !toTable) {
|
if ($touched.to && !toTable) {
|
||||||
errors.to = tableNotSet
|
errObj.to = tableNotSet
|
||||||
}
|
}
|
||||||
if ($touched.through && isMany && !fromRelate.through) {
|
if ($touched.through && isMany && !fromRelate.through) {
|
||||||
errors.through = tableNotSet
|
errObj.through = tableNotSet
|
||||||
}
|
}
|
||||||
if ($touched.foreign && !isMany && !fromRelate.fieldName) {
|
if ($touched.foreign && !isMany && !fromRelate.fieldName) {
|
||||||
errors.foreign = "Please pick the foreign key"
|
errObj.foreign = "Please pick the foreign key"
|
||||||
}
|
}
|
||||||
const colNotSet = "Please specify a column name"
|
const colNotSet = "Please specify a column name"
|
||||||
if ($touched.fromCol && !fromRelate.name) {
|
if ($touched.fromCol && !fromRelate.name) {
|
||||||
errors.fromCol = colNotSet
|
errObj.fromCol = colNotSet
|
||||||
}
|
}
|
||||||
if ($touched.toCol && !toRelate.name) {
|
if ($touched.toCol && !toRelate.name) {
|
||||||
errors.toCol = colNotSet
|
errObj.toCol = colNotSet
|
||||||
}
|
}
|
||||||
if ($touched.primary && !fromPrimary) {
|
if ($touched.primary && !fromPrimary) {
|
||||||
errors.primary = "Please pick the primary key"
|
errObj.primary = "Please pick the primary key"
|
||||||
}
|
}
|
||||||
// currently don't support relationships back onto the table itself, needs to relate out
|
// currently don't support relationships back onto the table itself, needs to relate out
|
||||||
const tableError = "From/to/through tables must be different"
|
const tableError = "From/to/through tables must be different"
|
||||||
if (fromTable && (fromTable === toTable || fromTable === throughTable)) {
|
if (fromTable && (fromTable === toTable || fromTable === through)) {
|
||||||
errors.from = tableError
|
errObj.from = tableError
|
||||||
}
|
}
|
||||||
if (toTable && (toTable === fromTable || toTable === throughTable)) {
|
if (toTable && (toTable === fromTable || toTable === through)) {
|
||||||
errors.to = tableError
|
errObj.to = tableError
|
||||||
}
|
}
|
||||||
if (
|
if (through && (through === fromTable || through === toTable)) {
|
||||||
throughTable &&
|
errObj.through = tableError
|
||||||
(throughTable === fromTable || throughTable === toTable)
|
|
||||||
) {
|
|
||||||
errors.through = tableError
|
|
||||||
}
|
}
|
||||||
const colError = "Column name cannot be an existing column"
|
const colError = "Column name cannot be an existing column"
|
||||||
if (inSchema(fromTable, fromRelate.name, originalFromName)) {
|
if (inSchema(fromTable, fromRelate.name, originalFromName)) {
|
||||||
errors.fromCol = colError
|
errObj.fromCol = colError
|
||||||
}
|
}
|
||||||
if (inSchema(toTable, toRelate.name, originalToName)) {
|
if (inSchema(toTable, toRelate.name, originalToName)) {
|
||||||
errors.toCol = colError
|
errObj.toCol = colError
|
||||||
}
|
}
|
||||||
return errors
|
errors = errObj
|
||||||
}
|
}
|
||||||
|
|
||||||
let fromPrimary
|
let fromPrimary
|
||||||
|
@ -115,13 +110,7 @@
|
||||||
$: fromTable = plusTables.find(table => table._id === toRelationship?.tableId)
|
$: fromTable = plusTables.find(table => table._id === toRelationship?.tableId)
|
||||||
$: toTable = plusTables.find(table => table._id === fromRelationship?.tableId)
|
$: toTable = plusTables.find(table => table._id === fromRelationship?.tableId)
|
||||||
$: through = plusTables.find(table => table._id === fromRelationship?.through)
|
$: through = plusTables.find(table => table._id === fromRelationship?.through)
|
||||||
$: errors = checkForErrors(
|
$: checkForErrors(fromRelationship, toRelationship)
|
||||||
fromTable,
|
|
||||||
toTable,
|
|
||||||
through,
|
|
||||||
fromRelationship,
|
|
||||||
toRelationship
|
|
||||||
)
|
|
||||||
$: valid =
|
$: valid =
|
||||||
Object.keys(errors).length === 0 && Object.keys($touched).length !== 0
|
Object.keys(errors).length === 0 && Object.keys($touched).length !== 0
|
||||||
$: linkTable = through || toTable
|
$: linkTable = through || toTable
|
||||||
|
@ -239,19 +228,19 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
function tableChanged(fromTbl, toTbl) {
|
function tableChanged(fromTbl, toTbl) {
|
||||||
|
if (
|
||||||
|
(currentTables?.from?._id === fromTbl?._id &&
|
||||||
|
currentTables?.to?._id === toTbl?._id) ||
|
||||||
|
originalFromName ||
|
||||||
|
originalToName
|
||||||
|
) {
|
||||||
|
return
|
||||||
|
}
|
||||||
fromRelationship.name = toTbl?.name || ""
|
fromRelationship.name = toTbl?.name || ""
|
||||||
errors.fromCol = ""
|
errors.fromCol = ""
|
||||||
toRelationship.name = fromTbl?.name || ""
|
toRelationship.name = fromTbl?.name || ""
|
||||||
errors.toCol = ""
|
errors.toCol = ""
|
||||||
if (toTbl || fromTbl) {
|
currentTables = { from: fromTbl, to: toTbl }
|
||||||
checkForErrors(
|
|
||||||
fromTable,
|
|
||||||
toTable,
|
|
||||||
through,
|
|
||||||
fromRelationship,
|
|
||||||
toRelationship
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|
|
@ -26,6 +26,7 @@
|
||||||
|
|
||||||
async function getPermissions(queryToFetch) {
|
async function getPermissions(queryToFetch) {
|
||||||
if (fetched?._id === queryToFetch?._id) {
|
if (fetched?._id === queryToFetch?._id) {
|
||||||
|
loaded = true
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
fetched = queryToFetch
|
fetched = queryToFetch
|
||||||
|
|
|
@ -12,7 +12,7 @@
|
||||||
import Logo from "assets/bb-emblem.svg"
|
import Logo from "assets/bb-emblem.svg"
|
||||||
import { capitalise } from "helpers"
|
import { capitalise } from "helpers"
|
||||||
import UpgradeModal from "../../../../components/upgrade/UpgradeModal.svelte"
|
import UpgradeModal from "../../../../components/upgrade/UpgradeModal.svelte"
|
||||||
import { onMount } from "svelte"
|
import { onMount, onDestroy } from "svelte"
|
||||||
|
|
||||||
// Get Package and set store
|
// Get Package and set store
|
||||||
export let application
|
export let application
|
||||||
|
@ -81,6 +81,10 @@
|
||||||
hasSynced = true
|
hasSynced = true
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
onDestroy(() => {
|
||||||
|
store.actions.reset()
|
||||||
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
{#await promise}
|
{#await promise}
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "@budibase/cli",
|
"name": "@budibase/cli",
|
||||||
"version": "1.0.49-alpha.7",
|
"version": "1.0.49-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": {
|
||||||
|
|
|
@ -1583,9 +1583,9 @@ simple-concat@^1.0.0:
|
||||||
integrity sha512-cSFtAPtRhljv69IK0hTVZQ+OfE9nePi/rtJmw5UjHeVyVroEqJXP1sFztKUy1qU+xvz3u/sfYJLa947b7nAN2Q==
|
integrity sha512-cSFtAPtRhljv69IK0hTVZQ+OfE9nePi/rtJmw5UjHeVyVroEqJXP1sFztKUy1qU+xvz3u/sfYJLa947b7nAN2Q==
|
||||||
|
|
||||||
simple-get@^3.0.3:
|
simple-get@^3.0.3:
|
||||||
version "3.1.0"
|
version "3.1.1"
|
||||||
resolved "https://registry.yarnpkg.com/simple-get/-/simple-get-3.1.0.tgz#b45be062435e50d159540b576202ceec40b9c6b3"
|
resolved "https://registry.yarnpkg.com/simple-get/-/simple-get-3.1.1.tgz#cc7ba77cfbe761036fbfce3d021af25fc5584d55"
|
||||||
integrity sha512-bCR6cP+aTdScaQCnQKbPKtJOKDp/hj9EDLJo3Nw4y1QksqaovlW/bnptB6/c1e+qmNIDHRK+oXFDdEqBT8WzUA==
|
integrity sha512-CQ5LTKGfCpvE1K0n2us+kuMPbk/q0EKl82s4aheV9oXjFEz6W/Y7oQFVJuU6QG77hRT4Ghb5RURteF5vnWjupA==
|
||||||
dependencies:
|
dependencies:
|
||||||
decompress-response "^4.2.0"
|
decompress-response "^4.2.0"
|
||||||
once "^1.3.1"
|
once "^1.3.1"
|
||||||
|
|
|
@ -1942,6 +1942,35 @@
|
||||||
"type": "validation/string",
|
"type": "validation/string",
|
||||||
"label": "Validation",
|
"label": "Validation",
|
||||||
"key": "validation"
|
"key": "validation"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "select",
|
||||||
|
"label": "Alignment",
|
||||||
|
"key": "align",
|
||||||
|
"defaultValue": "left",
|
||||||
|
"showInBar": true,
|
||||||
|
"barStyle": "buttons",
|
||||||
|
"options": [{
|
||||||
|
"label": "Left",
|
||||||
|
"value": "left",
|
||||||
|
"barIcon": "TextAlignLeft",
|
||||||
|
"barTitle": "Align left"
|
||||||
|
}, {
|
||||||
|
"label": "Center",
|
||||||
|
"value": "center",
|
||||||
|
"barIcon": "TextAlignCenter",
|
||||||
|
"barTitle": "Align center"
|
||||||
|
}, {
|
||||||
|
"label": "Right",
|
||||||
|
"value": "right",
|
||||||
|
"barIcon": "TextAlignRight",
|
||||||
|
"barTitle": "Align right"
|
||||||
|
}, {
|
||||||
|
"label": "Justify",
|
||||||
|
"value": "justify",
|
||||||
|
"barIcon": "TextAlignJustify",
|
||||||
|
"barTitle": "Justify text"
|
||||||
|
}]
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
@ -2478,6 +2507,11 @@
|
||||||
"label": "Placeholder",
|
"label": "Placeholder",
|
||||||
"key": "placeholder"
|
"key": "placeholder"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"type": "text",
|
||||||
|
"label": "Default value",
|
||||||
|
"key": "defaultValue"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"type": "boolean",
|
"type": "boolean",
|
||||||
"label": "Autocomplete",
|
"label": "Autocomplete",
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "@budibase/client",
|
"name": "@budibase/client",
|
||||||
"version": "1.0.49-alpha.7",
|
"version": "1.0.49-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": "^1.0.49-alpha.7",
|
"@budibase/bbui": "^1.0.49-alpha.12",
|
||||||
"@budibase/standard-components": "^0.9.139",
|
"@budibase/standard-components": "^0.9.139",
|
||||||
"@budibase/string-templates": "^1.0.49-alpha.7",
|
"@budibase/string-templates": "^1.0.49-alpha.12",
|
||||||
"regexparam": "^1.3.0",
|
"regexparam": "^1.3.0",
|
||||||
"rollup-plugin-polyfill-node": "^0.8.0",
|
"rollup-plugin-polyfill-node": "^0.8.0",
|
||||||
"shortid": "^2.2.15",
|
"shortid": "^2.2.15",
|
||||||
|
|
|
@ -12,6 +12,7 @@
|
||||||
export let disabled = false
|
export let disabled = false
|
||||||
export let validation
|
export let validation
|
||||||
export let autocomplete = false
|
export let autocomplete = false
|
||||||
|
export let defaultValue
|
||||||
|
|
||||||
let fieldState
|
let fieldState
|
||||||
let fieldApi
|
let fieldApi
|
||||||
|
@ -27,6 +28,7 @@
|
||||||
$: singleValue = flatten(fieldState?.value)?.[0]
|
$: singleValue = flatten(fieldState?.value)?.[0]
|
||||||
$: multiValue = flatten(fieldState?.value) ?? []
|
$: multiValue = flatten(fieldState?.value) ?? []
|
||||||
$: component = multiselect ? CoreMultiselect : CoreSelect
|
$: component = multiselect ? CoreMultiselect : CoreSelect
|
||||||
|
$: expandedDefaultValue = expand(defaultValue)
|
||||||
|
|
||||||
const fetchTable = async id => {
|
const fetchTable = async id => {
|
||||||
if (id) {
|
if (id) {
|
||||||
|
@ -62,6 +64,16 @@
|
||||||
const multiHandler = e => {
|
const multiHandler = e => {
|
||||||
fieldApi.setValue(e.detail)
|
fieldApi.setValue(e.detail)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const expand = values => {
|
||||||
|
if (!values) {
|
||||||
|
return []
|
||||||
|
}
|
||||||
|
if (Array.isArray(values)) {
|
||||||
|
return values
|
||||||
|
}
|
||||||
|
return values.split(",").map(value => value.trim())
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<Field
|
<Field
|
||||||
|
@ -69,11 +81,11 @@
|
||||||
{field}
|
{field}
|
||||||
{disabled}
|
{disabled}
|
||||||
{validation}
|
{validation}
|
||||||
|
defaultValue={expandedDefaultValue}
|
||||||
type={FieldTypes.LINK}
|
type={FieldTypes.LINK}
|
||||||
bind:fieldState
|
bind:fieldState
|
||||||
bind:fieldApi
|
bind:fieldApi
|
||||||
bind:fieldSchema
|
bind:fieldSchema
|
||||||
defaultValue={[]}
|
|
||||||
>
|
>
|
||||||
{#if fieldState}
|
{#if fieldState}
|
||||||
<svelte:component
|
<svelte:component
|
||||||
|
|
|
@ -9,6 +9,7 @@
|
||||||
export let disabled = false
|
export let disabled = false
|
||||||
export let validation
|
export let validation
|
||||||
export let defaultValue = ""
|
export let defaultValue = ""
|
||||||
|
export let align
|
||||||
|
|
||||||
let fieldState
|
let fieldState
|
||||||
let fieldApi
|
let fieldApi
|
||||||
|
@ -34,6 +35,7 @@
|
||||||
id={fieldState.fieldId}
|
id={fieldState.fieldId}
|
||||||
{placeholder}
|
{placeholder}
|
||||||
{type}
|
{type}
|
||||||
|
{align}
|
||||||
/>
|
/>
|
||||||
{/if}
|
{/if}
|
||||||
</Field>
|
</Field>
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
{
|
{
|
||||||
"name": "@budibase/server",
|
"name": "@budibase/server",
|
||||||
"email": "hi@budibase.com",
|
"email": "hi@budibase.com",
|
||||||
"version": "1.0.49-alpha.7",
|
"version": "1.0.49-alpha.12",
|
||||||
"description": "Budibase Web Server",
|
"description": "Budibase Web Server",
|
||||||
"main": "src/index.ts",
|
"main": "src/index.ts",
|
||||||
"repository": {
|
"repository": {
|
||||||
|
@ -70,9 +70,9 @@
|
||||||
"license": "GPL-3.0",
|
"license": "GPL-3.0",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@apidevtools/swagger-parser": "^10.0.3",
|
"@apidevtools/swagger-parser": "^10.0.3",
|
||||||
"@budibase/backend-core": "^1.0.49-alpha.7",
|
"@budibase/backend-core": "^1.0.49-alpha.12",
|
||||||
"@budibase/client": "^1.0.49-alpha.7",
|
"@budibase/client": "^1.0.49-alpha.12",
|
||||||
"@budibase/string-templates": "^1.0.49-alpha.7",
|
"@budibase/string-templates": "^1.0.49-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",
|
||||||
|
|
|
@ -9,12 +9,16 @@ CREATE TABLE Persons (
|
||||||
);
|
);
|
||||||
CREATE TABLE Tasks (
|
CREATE TABLE Tasks (
|
||||||
TaskID SERIAL PRIMARY KEY,
|
TaskID SERIAL PRIMARY KEY,
|
||||||
PersonID INT,
|
ExecutorID INT,
|
||||||
|
QaID INT,
|
||||||
Completed BOOLEAN,
|
Completed BOOLEAN,
|
||||||
TaskName varchar(255),
|
TaskName varchar(255),
|
||||||
CONSTRAINT fkPersons
|
CONSTRAINT fkexecutor
|
||||||
FOREIGN KEY(PersonID)
|
FOREIGN KEY(ExecutorID)
|
||||||
REFERENCES Persons(PersonID)
|
REFERENCES Persons(PersonID),
|
||||||
|
CONSTRAINT fkqa
|
||||||
|
FOREIGN KEY(QaID)
|
||||||
|
REFERENCES Persons(PersonID)
|
||||||
);
|
);
|
||||||
CREATE TABLE Products (
|
CREATE TABLE Products (
|
||||||
ProductID SERIAL PRIMARY KEY,
|
ProductID SERIAL PRIMARY KEY,
|
||||||
|
@ -32,8 +36,9 @@ CREATE TABLE Products_Tasks (
|
||||||
PRIMARY KEY (ProductID, TaskID)
|
PRIMARY KEY (ProductID, TaskID)
|
||||||
);
|
);
|
||||||
INSERT INTO Persons (FirstName, LastName, Address, City) VALUES ('Mike', 'Hughes', '123 Fake Street', 'Belfast');
|
INSERT INTO Persons (FirstName, LastName, Address, City) VALUES ('Mike', 'Hughes', '123 Fake Street', 'Belfast');
|
||||||
INSERT INTO Tasks (PersonID, TaskName, Completed) VALUES (1, 'assembling', TRUE);
|
INSERT INTO Persons (FirstName, LastName, Address, City) Values ('John', 'Smith', '64 Updown Road', 'Dublin');
|
||||||
INSERT INTO Tasks (PersonID, TaskName, Completed) VALUES (1, 'processing', FALSE);
|
INSERT INTO Tasks (ExecutorID, QaID, TaskName, Completed) VALUES (1, 2, 'assembling', TRUE);
|
||||||
|
INSERT INTO Tasks (ExecutorID, QaID, TaskName, Completed) VALUES (2, 1, 'processing', FALSE);
|
||||||
INSERT INTO Products (ProductName) VALUES ('Computers');
|
INSERT INTO Products (ProductName) VALUES ('Computers');
|
||||||
INSERT INTO Products (ProductName) VALUES ('Laptops');
|
INSERT INTO Products (ProductName) VALUES ('Laptops');
|
||||||
INSERT INTO Products (ProductName) VALUES ('Chairs');
|
INSERT INTO Products (ProductName) VALUES ('Chairs');
|
||||||
|
|
|
@ -184,7 +184,7 @@ module External {
|
||||||
thisRow._id = generateIdForRow(row, table)
|
thisRow._id = generateIdForRow(row, table)
|
||||||
thisRow.tableId = table._id
|
thisRow.tableId = table._id
|
||||||
thisRow._rev = "rev"
|
thisRow._rev = "rev"
|
||||||
return thisRow
|
return processFormulas(table, thisRow)
|
||||||
}
|
}
|
||||||
|
|
||||||
function fixArrayTypes(row: Row, table: Table) {
|
function fixArrayTypes(row: Row, table: Table) {
|
||||||
|
@ -327,8 +327,12 @@ module External {
|
||||||
* This iterates through the returned rows and works out what elements of the rows
|
* This iterates through the returned rows and works out what elements of the rows
|
||||||
* actually match up to another row (based on primary keys) - this is pretty specific
|
* actually match up to another row (based on primary keys) - this is pretty specific
|
||||||
* to SQL and the way that SQL relationships are returned based on joins.
|
* to SQL and the way that SQL relationships are returned based on joins.
|
||||||
|
* This is complicated, but the idea is that when a SQL query returns all the relations
|
||||||
|
* will be separate rows, with all of the data in each row. We have to decipher what comes
|
||||||
|
* from where (which tables) and how to convert that into budibase columns.
|
||||||
*/
|
*/
|
||||||
updateRelationshipColumns(
|
updateRelationshipColumns(
|
||||||
|
table: Table,
|
||||||
row: Row,
|
row: Row,
|
||||||
rows: { [key: string]: Row },
|
rows: { [key: string]: Row },
|
||||||
relationships: RelationshipsJson[]
|
relationships: RelationshipsJson[]
|
||||||
|
@ -339,6 +343,13 @@ module External {
|
||||||
if (!linkedTable) {
|
if (!linkedTable) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
const fromColumn = `${table.name}.${relationship.from}`
|
||||||
|
const toColumn = `${linkedTable.name}.${relationship.to}`
|
||||||
|
// this is important when working with multiple relationships
|
||||||
|
// between the same tables, don't want to overlap/multiply the relations
|
||||||
|
if (!relationship.through && row[fromColumn] !== row[toColumn]) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
let linked = basicProcessing(row, linkedTable)
|
let linked = basicProcessing(row, linkedTable)
|
||||||
if (!linked._id) {
|
if (!linked._id) {
|
||||||
continue
|
continue
|
||||||
|
@ -386,6 +397,7 @@ module External {
|
||||||
// this is a relationship of some sort
|
// this is a relationship of some sort
|
||||||
if (finalRows[rowId]) {
|
if (finalRows[rowId]) {
|
||||||
finalRows = this.updateRelationshipColumns(
|
finalRows = this.updateRelationshipColumns(
|
||||||
|
table,
|
||||||
row,
|
row,
|
||||||
finalRows,
|
finalRows,
|
||||||
relationships
|
relationships
|
||||||
|
@ -399,6 +411,7 @@ module External {
|
||||||
finalRows[thisRow._id] = thisRow
|
finalRows[thisRow._id] = thisRow
|
||||||
// do this at end once its been added to the final rows
|
// do this at end once its been added to the final rows
|
||||||
finalRows = this.updateRelationshipColumns(
|
finalRows = this.updateRelationshipColumns(
|
||||||
|
table,
|
||||||
row,
|
row,
|
||||||
finalRows,
|
finalRows,
|
||||||
relationships
|
relationships
|
||||||
|
|
|
@ -31,23 +31,21 @@ async function handleRequest(operation, tableId, opts = {}) {
|
||||||
exports.handleRequest = handleRequest
|
exports.handleRequest = handleRequest
|
||||||
|
|
||||||
exports.patch = async ctx => {
|
exports.patch = async ctx => {
|
||||||
const appId = ctx.appId
|
|
||||||
const inputs = ctx.request.body
|
const inputs = ctx.request.body
|
||||||
const tableId = ctx.params.tableId
|
const tableId = ctx.params.tableId
|
||||||
const id = breakRowIdField(inputs._id)
|
const id = breakRowIdField(inputs._id)
|
||||||
// don't save the ID to db
|
// don't save the ID to db
|
||||||
delete inputs._id
|
delete inputs._id
|
||||||
return handleRequest(appId, DataSourceOperation.UPDATE, tableId, {
|
return handleRequest(DataSourceOperation.UPDATE, tableId, {
|
||||||
id,
|
id,
|
||||||
row: inputs,
|
row: inputs,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
exports.save = async ctx => {
|
exports.save = async ctx => {
|
||||||
const appId = ctx.appId
|
|
||||||
const inputs = ctx.request.body
|
const inputs = ctx.request.body
|
||||||
const tableId = ctx.params.tableId
|
const tableId = ctx.params.tableId
|
||||||
return handleRequest(appId, DataSourceOperation.CREATE, tableId, {
|
return handleRequest(DataSourceOperation.CREATE, tableId, {
|
||||||
row: inputs,
|
row: inputs,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -61,49 +59,35 @@ exports.fetchView = async ctx => {
|
||||||
}
|
}
|
||||||
|
|
||||||
exports.fetch = async ctx => {
|
exports.fetch = async ctx => {
|
||||||
const appId = ctx.appId
|
|
||||||
const tableId = ctx.params.tableId
|
const tableId = ctx.params.tableId
|
||||||
return handleRequest(appId, DataSourceOperation.READ, tableId)
|
return handleRequest(DataSourceOperation.READ, tableId)
|
||||||
}
|
}
|
||||||
|
|
||||||
exports.find = async ctx => {
|
exports.find = async ctx => {
|
||||||
const appId = ctx.appId
|
|
||||||
const id = ctx.params.rowId
|
const id = ctx.params.rowId
|
||||||
const tableId = ctx.params.tableId
|
const tableId = ctx.params.tableId
|
||||||
const response = await handleRequest(
|
const response = await handleRequest(DataSourceOperation.READ, tableId, {
|
||||||
appId,
|
id,
|
||||||
DataSourceOperation.READ,
|
})
|
||||||
tableId,
|
|
||||||
{
|
|
||||||
id,
|
|
||||||
}
|
|
||||||
)
|
|
||||||
return response ? response[0] : response
|
return response ? response[0] : response
|
||||||
}
|
}
|
||||||
|
|
||||||
exports.destroy = async ctx => {
|
exports.destroy = async ctx => {
|
||||||
const appId = ctx.appId
|
|
||||||
const tableId = ctx.params.tableId
|
const tableId = ctx.params.tableId
|
||||||
const id = ctx.request.body._id
|
const id = ctx.request.body._id
|
||||||
const { row } = await handleRequest(
|
const { row } = await handleRequest(DataSourceOperation.DELETE, tableId, {
|
||||||
appId,
|
id,
|
||||||
DataSourceOperation.DELETE,
|
})
|
||||||
tableId,
|
|
||||||
{
|
|
||||||
id,
|
|
||||||
}
|
|
||||||
)
|
|
||||||
return { response: { ok: true }, row }
|
return { response: { ok: true }, row }
|
||||||
}
|
}
|
||||||
|
|
||||||
exports.bulkDestroy = async ctx => {
|
exports.bulkDestroy = async ctx => {
|
||||||
const appId = ctx.appId
|
|
||||||
const { rows } = ctx.request.body
|
const { rows } = ctx.request.body
|
||||||
const tableId = ctx.params.tableId
|
const tableId = ctx.params.tableId
|
||||||
let promises = []
|
let promises = []
|
||||||
for (let row of rows) {
|
for (let row of rows) {
|
||||||
promises.push(
|
promises.push(
|
||||||
handleRequest(appId, DataSourceOperation.DELETE, tableId, {
|
handleRequest(DataSourceOperation.DELETE, tableId, {
|
||||||
id: breakRowIdField(row._id),
|
id: breakRowIdField(row._id),
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
|
@ -113,7 +97,6 @@ exports.bulkDestroy = async ctx => {
|
||||||
}
|
}
|
||||||
|
|
||||||
exports.search = async ctx => {
|
exports.search = async ctx => {
|
||||||
const appId = ctx.appId
|
|
||||||
const tableId = ctx.params.tableId
|
const tableId = ctx.params.tableId
|
||||||
const { paginate, query, ...params } = ctx.request.body
|
const { paginate, query, ...params } = ctx.request.body
|
||||||
let { bookmark, limit } = params
|
let { bookmark, limit } = params
|
||||||
|
@ -143,26 +126,21 @@ exports.search = async ctx => {
|
||||||
[params.sort]: direction,
|
[params.sort]: direction,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
const rows = await handleRequest(appId, DataSourceOperation.READ, tableId, {
|
const rows = await handleRequest(DataSourceOperation.READ, tableId, {
|
||||||
filters: query,
|
filters: query,
|
||||||
sort,
|
sort,
|
||||||
paginate: paginateObj,
|
paginate: paginateObj,
|
||||||
})
|
})
|
||||||
let hasNextPage = false
|
let hasNextPage = false
|
||||||
if (paginate && rows.length === limit) {
|
if (paginate && rows.length === limit) {
|
||||||
const nextRows = await handleRequest(
|
const nextRows = await handleRequest(DataSourceOperation.READ, tableId, {
|
||||||
appId,
|
filters: query,
|
||||||
DataSourceOperation.READ,
|
sort,
|
||||||
tableId,
|
paginate: {
|
||||||
{
|
limit: 1,
|
||||||
filters: query,
|
page: bookmark * limit + 1,
|
||||||
sort,
|
},
|
||||||
paginate: {
|
})
|
||||||
limit: 1,
|
|
||||||
page: bookmark * limit + 1,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
)
|
|
||||||
hasNextPage = nextRows.length > 0
|
hasNextPage = nextRows.length > 0
|
||||||
}
|
}
|
||||||
// need wrapper object for bookmarks etc when paginating
|
// need wrapper object for bookmarks etc when paginating
|
||||||
|
@ -175,7 +153,6 @@ exports.validate = async () => {
|
||||||
}
|
}
|
||||||
|
|
||||||
exports.fetchEnrichedRow = async ctx => {
|
exports.fetchEnrichedRow = async ctx => {
|
||||||
const appId = ctx.appId
|
|
||||||
const id = ctx.params.rowId
|
const id = ctx.params.rowId
|
||||||
const tableId = ctx.params.tableId
|
const tableId = ctx.params.tableId
|
||||||
const { datasourceId, tableName } = breakExternalTableId(tableId)
|
const { datasourceId, tableName } = breakExternalTableId(tableId)
|
||||||
|
@ -185,15 +162,10 @@ exports.fetchEnrichedRow = async ctx => {
|
||||||
ctx.throw(400, "Datasource has not been configured for plus API.")
|
ctx.throw(400, "Datasource has not been configured for plus API.")
|
||||||
}
|
}
|
||||||
const tables = datasource.entities
|
const tables = datasource.entities
|
||||||
const response = await handleRequest(
|
const response = await handleRequest(DataSourceOperation.READ, tableId, {
|
||||||
appId,
|
id,
|
||||||
DataSourceOperation.READ,
|
datasource,
|
||||||
tableId,
|
})
|
||||||
{
|
|
||||||
id,
|
|
||||||
datasource,
|
|
||||||
}
|
|
||||||
)
|
|
||||||
const table = tables[tableName]
|
const table = tables[tableName]
|
||||||
const row = response[0]
|
const row = response[0]
|
||||||
// this seems like a lot of work, but basically we need to dig deeper for the enrich
|
// this seems like a lot of work, but basically we need to dig deeper for the enrich
|
||||||
|
@ -212,7 +184,6 @@ exports.fetchEnrichedRow = async ctx => {
|
||||||
// don't support composite keys right now
|
// don't support composite keys right now
|
||||||
const linkedIds = links.map(link => breakRowIdField(link._id)[0])
|
const linkedIds = links.map(link => breakRowIdField(link._id)[0])
|
||||||
row[fieldName] = await handleRequest(
|
row[fieldName] = await handleRequest(
|
||||||
appId,
|
|
||||||
DataSourceOperation.READ,
|
DataSourceOperation.READ,
|
||||||
linkedTableId,
|
linkedTableId,
|
||||||
{
|
{
|
||||||
|
|
|
@ -191,29 +191,70 @@ class InternalBuilder {
|
||||||
if (!relationships) {
|
if (!relationships) {
|
||||||
return query
|
return query
|
||||||
}
|
}
|
||||||
|
const tableSets: Record<string, [any]> = {}
|
||||||
|
// aggregate into table sets (all the same to tables)
|
||||||
for (let relationship of relationships) {
|
for (let relationship of relationships) {
|
||||||
const from = relationship.from,
|
const keyObj: { toTable: string; throughTable: string | undefined } = {
|
||||||
to = relationship.to,
|
toTable: relationship.tableName,
|
||||||
toTable = relationship.tableName
|
throughTable: undefined,
|
||||||
if (!relationship.through) {
|
}
|
||||||
|
if (relationship.through) {
|
||||||
|
keyObj.throughTable = relationship.through
|
||||||
|
}
|
||||||
|
const key = JSON.stringify(keyObj)
|
||||||
|
if (tableSets[key]) {
|
||||||
|
tableSets[key].push(relationship)
|
||||||
|
} else {
|
||||||
|
tableSets[key] = [relationship]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (let [key, relationships] of Object.entries(tableSets)) {
|
||||||
|
const { toTable, throughTable } = JSON.parse(key)
|
||||||
|
if (!throughTable) {
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
query = query.leftJoin(
|
query = query.join(
|
||||||
toTable,
|
toTable,
|
||||||
`${fromTable}.${from}`,
|
function () {
|
||||||
`${toTable}.${to}`
|
for (let relationship of relationships) {
|
||||||
|
const from = relationship.from,
|
||||||
|
to = relationship.to
|
||||||
|
// @ts-ignore
|
||||||
|
this.orOn(`${fromTable}.${from}`, "=", `${toTable}.${to}`)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"left"
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
const throughTable = relationship.through
|
|
||||||
const fromPrimary = relationship.fromPrimary
|
|
||||||
const toPrimary = relationship.toPrimary
|
|
||||||
query = query
|
query = query
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
.leftJoin(
|
.join(
|
||||||
throughTable,
|
throughTable,
|
||||||
`${fromTable}.${fromPrimary}`,
|
function () {
|
||||||
`${throughTable}.${from}`
|
for (let relationship of relationships) {
|
||||||
|
const fromPrimary = relationship.fromPrimary
|
||||||
|
const from = relationship.from
|
||||||
|
// @ts-ignore
|
||||||
|
this.orOn(
|
||||||
|
`${fromTable}.${fromPrimary}`,
|
||||||
|
"=",
|
||||||
|
`${throughTable}.${from}`
|
||||||
|
)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"left"
|
||||||
|
)
|
||||||
|
.join(
|
||||||
|
toTable,
|
||||||
|
function () {
|
||||||
|
for (let relationship of relationships) {
|
||||||
|
const toPrimary = relationship.toPrimary
|
||||||
|
const to = relationship.to
|
||||||
|
// @ts-ignore
|
||||||
|
this.orOn(`${toTable}.${toPrimary}`, `${throughTable}.${to}`)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"left"
|
||||||
)
|
)
|
||||||
.leftJoin(toTable, `${toTable}.${toPrimary}`, `${throughTable}.${to}`)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return query.limit(BASE_LIMIT)
|
return query.limit(BASE_LIMIT)
|
||||||
|
|
|
@ -5,9 +5,6 @@ const { integrations } = require("../integrations")
|
||||||
const { processStringSync } = require("@budibase/string-templates")
|
const { processStringSync } = require("@budibase/string-templates")
|
||||||
const { doInAppContext, getAppDB } = require("@budibase/backend-core/context")
|
const { doInAppContext, getAppDB } = require("@budibase/backend-core/context")
|
||||||
|
|
||||||
const IS_TRIPLE_BRACE = new RegExp(/^{{3}.*}{3}$/)
|
|
||||||
const IS_HANDLEBARS = new RegExp(/^{{2}.*}{2}$/)
|
|
||||||
|
|
||||||
class QueryRunner {
|
class QueryRunner {
|
||||||
constructor(input, flags = { noRecursiveQuery: false }) {
|
constructor(input, flags = { noRecursiveQuery: false }) {
|
||||||
this.datasource = input.datasource
|
this.datasource = input.datasource
|
||||||
|
@ -188,12 +185,8 @@ class QueryRunner {
|
||||||
enrichedQuery[key] = this.enrichQueryFields(fields[key], parameters)
|
enrichedQuery[key] = this.enrichQueryFields(fields[key], parameters)
|
||||||
} else if (typeof fields[key] === "string") {
|
} else if (typeof fields[key] === "string") {
|
||||||
// enrich string value as normal
|
// enrich string value as normal
|
||||||
let value = fields[key]
|
enrichedQuery[key] = processStringSync(fields[key], parameters, {
|
||||||
// add triple brace to avoid escaping e.g. '=' in cookie header
|
noEscaping: true,
|
||||||
if (IS_HANDLEBARS.test(value) && !IS_TRIPLE_BRACE.test(value)) {
|
|
||||||
value = `{${value}}`
|
|
||||||
}
|
|
||||||
enrichedQuery[key] = processStringSync(value, parameters, {
|
|
||||||
noHelpers: true,
|
noHelpers: true,
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "@budibase/string-templates",
|
"name": "@budibase/string-templates",
|
||||||
"version": "1.0.49-alpha.7",
|
"version": "1.0.49-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",
|
||||||
|
|
|
@ -17,6 +17,7 @@ module.exports.processString = templates.processString
|
||||||
module.exports.processObject = templates.processObject
|
module.exports.processObject = templates.processObject
|
||||||
module.exports.doesContainStrings = templates.doesContainStrings
|
module.exports.doesContainStrings = templates.doesContainStrings
|
||||||
module.exports.doesContainString = templates.doesContainString
|
module.exports.doesContainString = templates.doesContainString
|
||||||
|
module.exports.disableEscaping = templates.disableEscaping
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Use vm2 to run JS scripts in a node env
|
* Use vm2 to run JS scripts in a node env
|
||||||
|
@ -27,4 +28,4 @@ setJSRunner((js, context) => {
|
||||||
timeout: 1000
|
timeout: 1000
|
||||||
})
|
})
|
||||||
return vm.run(js)
|
return vm.run(js)
|
||||||
})
|
})
|
||||||
|
|
|
@ -3,12 +3,12 @@ const { registerAll } = require("./helpers/index")
|
||||||
const processors = require("./processors")
|
const processors = require("./processors")
|
||||||
const { atob, btoa } = require("./utilities")
|
const { atob, btoa } = require("./utilities")
|
||||||
const manifest = require("../manifest.json")
|
const manifest = require("../manifest.json")
|
||||||
const { FIND_HBS_REGEX } = require("./utilities")
|
const { FIND_HBS_REGEX, FIND_DOUBLE_HBS_REGEX } = require("./utilities")
|
||||||
|
|
||||||
const hbsInstance = handlebars.create()
|
const hbsInstance = handlebars.create()
|
||||||
registerAll(hbsInstance)
|
registerAll(hbsInstance)
|
||||||
const hbsInstanceNoHelpers = handlebars.create()
|
const hbsInstanceNoHelpers = handlebars.create()
|
||||||
const defaultOpts = { noHelpers: false }
|
const defaultOpts = { noHelpers: false, noEscaping: false }
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* utility function to check if the object is valid
|
* utility function to check if the object is valid
|
||||||
|
@ -27,7 +27,7 @@ function testObject(object) {
|
||||||
* @param {object|array} object The input structure which is to be recursed, it is important to note that
|
* @param {object|array} object The input structure which is to be recursed, it is important to note that
|
||||||
* if the structure contains any cycles then this will fail.
|
* if the structure contains any cycles then this will fail.
|
||||||
* @param {object} context The context that handlebars should fill data from.
|
* @param {object} context The context that handlebars should fill data from.
|
||||||
* @param {object|null} opts optional - specify some options for processing.
|
* @param {object|undefined} opts optional - specify some options for processing.
|
||||||
* @returns {Promise<object|array>} The structure input, as fully updated as possible.
|
* @returns {Promise<object|array>} The structure input, as fully updated as possible.
|
||||||
*/
|
*/
|
||||||
module.exports.processObject = async (object, context, opts) => {
|
module.exports.processObject = async (object, context, opts) => {
|
||||||
|
@ -58,7 +58,7 @@ module.exports.processObject = async (object, context, opts) => {
|
||||||
* then nothing will occur.
|
* then nothing will occur.
|
||||||
* @param {string} string The template string which is the filled from the context object.
|
* @param {string} string The template string which is the filled from the context object.
|
||||||
* @param {object} context An object of information which will be used to enrich the string.
|
* @param {object} context An object of information which will be used to enrich the string.
|
||||||
* @param {object|null} opts optional - specify some options for processing.
|
* @param {object|undefined} opts optional - specify some options for processing.
|
||||||
* @returns {Promise<string>} The enriched string, all templates should have been replaced if they can be.
|
* @returns {Promise<string>} The enriched string, all templates should have been replaced if they can be.
|
||||||
*/
|
*/
|
||||||
module.exports.processString = async (string, context, opts) => {
|
module.exports.processString = async (string, context, opts) => {
|
||||||
|
@ -72,7 +72,7 @@ module.exports.processString = async (string, context, opts) => {
|
||||||
* @param {object|array} object The input structure which is to be recursed, it is important to note that
|
* @param {object|array} object The input structure which is to be recursed, it is important to note that
|
||||||
* if the structure contains any cycles then this will fail.
|
* if the structure contains any cycles then this will fail.
|
||||||
* @param {object} context The context that handlebars should fill data from.
|
* @param {object} context The context that handlebars should fill data from.
|
||||||
* @param {object|null} opts optional - specify some options for processing.
|
* @param {object|undefined} opts optional - specify some options for processing.
|
||||||
* @returns {object|array} The structure input, as fully updated as possible.
|
* @returns {object|array} The structure input, as fully updated as possible.
|
||||||
*/
|
*/
|
||||||
module.exports.processObjectSync = (object, context, opts) => {
|
module.exports.processObjectSync = (object, context, opts) => {
|
||||||
|
@ -93,7 +93,7 @@ module.exports.processObjectSync = (object, context, opts) => {
|
||||||
* then nothing will occur. This is a pure sync call and therefore does not have the full functionality of the async call.
|
* then nothing will occur. This is a pure sync call and therefore does not have the full functionality of the async call.
|
||||||
* @param {string} string The template string which is the filled from the context object.
|
* @param {string} string The template string which is the filled from the context object.
|
||||||
* @param {object} context An object of information which will be used to enrich the string.
|
* @param {object} context An object of information which will be used to enrich the string.
|
||||||
* @param {object|null} opts optional - specify some options for processing.
|
* @param {object|undefined} opts optional - specify some options for processing.
|
||||||
* @returns {string} The enriched string, all templates should have been replaced if they can be.
|
* @returns {string} The enriched string, all templates should have been replaced if they can be.
|
||||||
*/
|
*/
|
||||||
module.exports.processStringSync = (string, context, opts) => {
|
module.exports.processStringSync = (string, context, opts) => {
|
||||||
|
@ -110,7 +110,9 @@ module.exports.processStringSync = (string, context, opts) => {
|
||||||
string = processors.preprocess(string, shouldFinalise)
|
string = processors.preprocess(string, shouldFinalise)
|
||||||
// this does not throw an error when template can't be fulfilled, have to try correct beforehand
|
// this does not throw an error when template can't be fulfilled, have to try correct beforehand
|
||||||
const instance = opts.noHelpers ? hbsInstanceNoHelpers : hbsInstance
|
const instance = opts.noHelpers ? hbsInstanceNoHelpers : hbsInstance
|
||||||
const template = instance.compile(string, {
|
const templateString =
|
||||||
|
opts && opts.noEscaping ? exports.disableEscaping(string) : string
|
||||||
|
const template = instance.compile(templateString, {
|
||||||
strict: false,
|
strict: false,
|
||||||
})
|
})
|
||||||
const now = Math.floor(Date.now() / 1000) * 1000
|
const now = Math.floor(Date.now() / 1000) * 1000
|
||||||
|
@ -125,6 +127,24 @@ module.exports.processStringSync = (string, context, opts) => {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* By default with expressions like {{ name }} handlebars will escape various
|
||||||
|
* characters, which can be problematic. To fix this we use the syntax {{{ name }}},
|
||||||
|
* this function will find any double braces and switch to triple.
|
||||||
|
* @param string the string to have double HBS statements converted to triple.
|
||||||
|
*/
|
||||||
|
module.exports.disableEscaping = string => {
|
||||||
|
let regexp = new RegExp(FIND_DOUBLE_HBS_REGEX)
|
||||||
|
const matches = string.match(regexp)
|
||||||
|
if (matches == null) {
|
||||||
|
return string
|
||||||
|
}
|
||||||
|
for (let match of matches) {
|
||||||
|
string = string.replace(match, `{${match}}`)
|
||||||
|
}
|
||||||
|
return string
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Simple utility function which makes sure that a templating property has been wrapped in literal specifiers correctly.
|
* Simple utility function which makes sure that a templating property has been wrapped in literal specifiers correctly.
|
||||||
* @param {string} property The property which is to be wrapped.
|
* @param {string} property The property which is to be wrapped.
|
||||||
|
|
|
@ -17,6 +17,7 @@ export const processString = templates.processString
|
||||||
export const processObject = templates.processObject
|
export const processObject = templates.processObject
|
||||||
export const doesContainStrings = templates.doesContainStrings
|
export const doesContainStrings = templates.doesContainStrings
|
||||||
export const doesContainString = templates.doesContainString
|
export const doesContainString = templates.doesContainString
|
||||||
|
export const disableEscaping = templates.disableEscaping
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Use polyfilled vm to run JS scripts in a browser Env
|
* Use polyfilled vm to run JS scripts in a browser Env
|
||||||
|
@ -30,4 +31,4 @@ setJSRunner((js, context) => {
|
||||||
}
|
}
|
||||||
vm.createContext(context)
|
vm.createContext(context)
|
||||||
return vm.runInNewContext(js, context, { timeout: 1000 })
|
return vm.runInNewContext(js, context, { timeout: 1000 })
|
||||||
})
|
})
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
const ALPHA_NUMERIC_REGEX = /^[A-Za-z0-9]+$/g
|
const ALPHA_NUMERIC_REGEX = /^[A-Za-z0-9]+$/g
|
||||||
|
|
||||||
module.exports.FIND_HBS_REGEX = /{{([^{].*?)}}/g
|
module.exports.FIND_HBS_REGEX = /{{([^{].*?)}}/g
|
||||||
|
module.exports.FIND_DOUBLE_HBS_REGEX = /(?<!{){{[^{}]+}}(?!})/g
|
||||||
|
|
||||||
module.exports.isAlphaNumeric = char => {
|
module.exports.isAlphaNumeric = char => {
|
||||||
return char.match(ALPHA_NUMERIC_REGEX)
|
return char.match(ALPHA_NUMERIC_REGEX)
|
||||||
|
|
|
@ -6,6 +6,7 @@ const {
|
||||||
getManifest,
|
getManifest,
|
||||||
encodeJSBinding,
|
encodeJSBinding,
|
||||||
doesContainString,
|
doesContainString,
|
||||||
|
disableEscaping,
|
||||||
} = require("../src/index.cjs")
|
} = require("../src/index.cjs")
|
||||||
|
|
||||||
describe("Test that the string processing works correctly", () => {
|
describe("Test that the string processing works correctly", () => {
|
||||||
|
@ -176,3 +177,22 @@ describe("check does contain string function", () => {
|
||||||
expect(doesContainString(js, "foo")).toEqual(true)
|
expect(doesContainString(js, "foo")).toEqual(true)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
describe("check that disabling escaping function works", () => {
|
||||||
|
it("should work for a single statement", () => {
|
||||||
|
expect(disableEscaping("{{ name }}")).toEqual("{{{ name }}}")
|
||||||
|
})
|
||||||
|
|
||||||
|
it("should work for two statements", () => {
|
||||||
|
expect(disableEscaping("{{ name }} welcome to {{ platform }}")).toEqual("{{{ name }}} welcome to {{{ platform }}}")
|
||||||
|
})
|
||||||
|
|
||||||
|
it("shouldn't convert triple braces", () => {
|
||||||
|
expect(disableEscaping("{{{ name }}}")).toEqual("{{{ name }}}")
|
||||||
|
})
|
||||||
|
|
||||||
|
it("should work with a combination", () => {
|
||||||
|
expect(disableEscaping("{{ name }} welcome to {{{ platform }}}")).toEqual("{{{ name }}} welcome to {{{ platform }}}")
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
{
|
{
|
||||||
"name": "@budibase/worker",
|
"name": "@budibase/worker",
|
||||||
"email": "hi@budibase.com",
|
"email": "hi@budibase.com",
|
||||||
"version": "1.0.49-alpha.7",
|
"version": "1.0.49-alpha.12",
|
||||||
"description": "Budibase background service",
|
"description": "Budibase background service",
|
||||||
"main": "src/index.ts",
|
"main": "src/index.ts",
|
||||||
"repository": {
|
"repository": {
|
||||||
|
@ -34,8 +34,8 @@
|
||||||
"author": "Budibase",
|
"author": "Budibase",
|
||||||
"license": "GPL-3.0",
|
"license": "GPL-3.0",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@budibase/backend-core": "^1.0.49-alpha.7",
|
"@budibase/backend-core": "^1.0.49-alpha.12",
|
||||||
"@budibase/string-templates": "^1.0.49-alpha.7",
|
"@budibase/string-templates": "^1.0.49-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",
|
||||||
|
|
Loading…
Reference in New Issue