1
0
Fork 0
mirror of synced 2024-07-14 10:45:51 +12:00

Merge branch 'develop' of github.com:Budibase/budibase into feature/audit-logs

This commit is contained in:
mike12345567 2023-02-13 11:38:53 +00:00
commit b9c513c071
60 changed files with 1327 additions and 278 deletions

View file

@ -2,7 +2,7 @@
name: Bug report name: Bug report
about: Create a report to help us improve about: Create a report to help us improve
title: '' title: ''
labels: ["bug", "linear"] labels: bug
assignees: '' assignees: ''
--- ---

View file

@ -1,24 +0,0 @@
---
name: Epic
about: Plan a new project
title: ''
labels: epic
assignees: ''
---
## Description
Brief summary of what this Epic is, whether it's a larger project, goal, or user story. Describe the job to be done, which persona this Epic is mainly for, or if more multiple, break it down by user and job story.
## Spec
Link to confluence spec
## Teams and Stakeholders
Describe who needs to be kept up-to-date about this Epic, included in discussions, or updated along the way. Stakeholders can be both in Product/Engineering, as well as other teams like Customer Success who might want to keep customers updated on the Epic project.
## Workflow
- [ ] Spec Created and pasted above
- [ ] Product Review
- [ ] Designs created
- [ ] Individual Tasks created and assigned to Epic

View file

@ -2,7 +2,7 @@
name: Feature Request name: Feature Request
about: Request a new budibase feature or enhancement about: Request a new budibase feature or enhancement
title: '' title: ''
labels: ["enhancement", "linear"] labels: enhancement
assignees: '' assignees: ''
--- ---

View file

@ -55,7 +55,7 @@ http {
set $csp_style "style-src 'self' 'unsafe-inline' https://cdn.jsdelivr.net https://fonts.googleapis.com https://rsms.me https://maxcdn.bootstrapcdn.com"; set $csp_style "style-src 'self' 'unsafe-inline' https://cdn.jsdelivr.net https://fonts.googleapis.com https://rsms.me https://maxcdn.bootstrapcdn.com";
set $csp_object "object-src 'none'"; set $csp_object "object-src 'none'";
set $csp_base_uri "base-uri 'self'"; set $csp_base_uri "base-uri 'self'";
set $csp_connect "connect-src 'self' https://*.budibase.net https://api-iam.intercom.io https://api-iam.intercom.io https://api-ping.intercom.io https://app.posthog.com wss://nexus-websocket-a.intercom.io wss://nexus-websocket-b.intercom.io https://nexus-websocket-a.intercom.io https://nexus-websocket-b.intercom.io https://uploads.intercomcdn.com https://uploads.intercomusercontent.com https://*.s3.amazonaws.com https://*.s3.us-east-2.amazonaws.com https://*.s3.us-east-1.amazonaws.com https://*.s3.us-west-1.amazonaws.com https://*.s3.us-west-2.amazonaws.com https://*.s3.af-south-1.amazonaws.com https://*.s3.ap-east-1.amazonaws.com https://*.s3.ap-southeast-3.amazonaws.com https://*.s3.ap-south-1.amazonaws.com https://*.s3.ap-northeast-3.amazonaws.com https://*.s3.ap-northeast-2.amazonaws.com https://*.s3.ap-southeast-1.amazonaws.com https://*.s3.ap-southeast-2.amazonaws.com https://*.s3.ap-northeast-1.amazonaws.com https://*.s3.ca-central-1.amazonaws.com https://*.s3.cn-north-1.amazonaws.com https://*.s3.cn-northwest-1.amazonaws.com https://*.s3.eu-central-1.amazonaws.com https://*.s3.eu-west-1.amazonaws.com https://*.s3.eu-west-2.amazonaws.com https://*.s3.eu-south-1.amazonaws.com https://*.s3.eu-west-3.amazonaws.com https://*.s3.eu-north-1.amazonaws.com https://*.s3.sa-east-1.amazonaws.com https://*.s3.me-south-1.amazonaws.com https://*.s3.us-gov-east-1.amazonaws.com https://*.s3.us-gov-west-1.amazonaws.com"; set $csp_connect "connect-src 'self' https://*.budibase.net https://api-iam.intercom.io https://api-iam.intercom.io https://api-ping.intercom.io https://app.posthog.com wss://nexus-websocket-a.intercom.io wss://nexus-websocket-b.intercom.io https://nexus-websocket-a.intercom.io https://nexus-websocket-b.intercom.io https://uploads.intercomcdn.com https://uploads.intercomusercontent.com https://*.s3.amazonaws.com https://*.s3.us-east-2.amazonaws.com https://*.s3.us-east-1.amazonaws.com https://*.s3.us-west-1.amazonaws.com https://*.s3.us-west-2.amazonaws.com https://*.s3.af-south-1.amazonaws.com https://*.s3.ap-east-1.amazonaws.com https://*.s3.ap-southeast-3.amazonaws.com https://*.s3.ap-south-1.amazonaws.com https://*.s3.ap-northeast-3.amazonaws.com https://*.s3.ap-northeast-2.amazonaws.com https://*.s3.ap-southeast-1.amazonaws.com https://*.s3.ap-southeast-2.amazonaws.com https://*.s3.ap-northeast-1.amazonaws.com https://*.s3.ca-central-1.amazonaws.com https://*.s3.cn-north-1.amazonaws.com https://*.s3.cn-northwest-1.amazonaws.com https://*.s3.eu-central-1.amazonaws.com https://*.s3.eu-west-1.amazonaws.com https://*.s3.eu-west-2.amazonaws.com https://*.s3.eu-south-1.amazonaws.com https://*.s3.eu-west-3.amazonaws.com https://*.s3.eu-north-1.amazonaws.com https://*.s3.sa-east-1.amazonaws.com https://*.s3.me-south-1.amazonaws.com https://*.s3.us-gov-east-1.amazonaws.com https://*.s3.us-gov-west-1.amazonaws.com https://api.github.com";
set $csp_font "font-src 'self' data: https://cdn.jsdelivr.net https://fonts.gstatic.com https://rsms.me https://maxcdn.bootstrapcdn.com https://js.intercomcdn.com https://fonts.intercomcdn.com"; set $csp_font "font-src 'self' data: https://cdn.jsdelivr.net https://fonts.gstatic.com https://rsms.me https://maxcdn.bootstrapcdn.com https://js.intercomcdn.com https://fonts.intercomcdn.com";
set $csp_frame "frame-src 'self' https:"; set $csp_frame "frame-src 'self' https:";
set $csp_img "img-src http: https: data: blob:"; set $csp_img "img-src http: https: data: blob:";

View file

@ -1,5 +1,5 @@
{ {
"version": "2.3.2-alpha.0", "version": "2.3.11-alpha.0",
"npmClient": "yarn", "npmClient": "yarn",
"packages": [ "packages": [
"packages/*" "packages/*"

View file

@ -1,6 +1,6 @@
{ {
"name": "@budibase/backend-core", "name": "@budibase/backend-core",
"version": "2.3.2-alpha.0", "version": "2.3.11-alpha.0",
"description": "Budibase backend core libraries used in server and worker", "description": "Budibase backend core libraries used in server and worker",
"main": "dist/src/index.js", "main": "dist/src/index.js",
"types": "dist/src/index.d.ts", "types": "dist/src/index.d.ts",
@ -24,7 +24,7 @@
"dependencies": { "dependencies": {
"@budibase/nano": "10.1.1", "@budibase/nano": "10.1.1",
"@budibase/pouchdb-replication-stream": "1.2.10", "@budibase/pouchdb-replication-stream": "1.2.10",
"@budibase/types": "2.3.2-alpha.0", "@budibase/types": "2.3.11-alpha.0",
"@shopify/jest-koa-mocks": "5.0.1", "@shopify/jest-koa-mocks": "5.0.1",
"@techpass/passport-openidconnect": "0.3.2", "@techpass/passport-openidconnect": "0.3.2",
"aws-cloudfront-sign": "2.2.0", "aws-cloudfront-sign": "2.2.0",

View file

@ -74,6 +74,10 @@ export const useGroups = () => {
return useFeature(Feature.USER_GROUPS) return useFeature(Feature.USER_GROUPS)
} }
export const useEnvironmentVariables = () => {
return useFeature(Feature.ENVIRONMENT_VARIABLES)
}
// QUOTAS // QUOTAS
export const setAutomationLogsQuota = (value: number) => { export const setAutomationLogsQuota = (value: number) => {

View file

@ -1,4 +1,7 @@
function getTestContainerSettings(serverName: string, key: string) { function getTestContainerSettings(
serverName: string,
key: string
): string | null {
const entry = Object.entries(global).find( const entry = Object.entries(global).find(
([k]) => ([k]) =>
k.includes(`_${serverName.toUpperCase()}`) && k.includes(`_${serverName.toUpperCase()}`) &&
@ -10,20 +13,25 @@ function getTestContainerSettings(serverName: string, key: string) {
return entry[1] return entry[1]
} }
function getCouchConfig() { function getContainerInfo(containerName: string, port: number) {
const port = getTestContainerSettings("COUCHDB-SERVICE", "PORT_5984") const assignedPort = getTestContainerSettings(
containerName.toUpperCase(),
`PORT_${port}`
)
const host = getTestContainerSettings(containerName.toUpperCase(), "IP")
return { return {
port, port: assignedPort,
url: `http://${getTestContainerSettings("COUCHDB-SERVICE", "IP")}:${port}`, host,
url: host && assignedPort && `http://${host}:${assignedPort}`,
} }
} }
function getCouchConfig() {
return getContainerInfo("couchdb-service", 5984)
}
function getMinioConfig() { function getMinioConfig() {
const port = getTestContainerSettings("MINIO-SERVICE", "PORT_9000") return getContainerInfo("minio-service", 9000)
return {
port,
url: `http://${getTestContainerSettings("MINIO-SERVICE", "IP")}:${port}`,
}
} }
export function setupEnv(...envs: any[]) { export function setupEnv(...envs: any[]) {
@ -34,7 +42,7 @@ export function setupEnv(...envs: any[]) {
{ key: "MINIO_URL", value: getMinioConfig().url }, { key: "MINIO_URL", value: getMinioConfig().url },
] ]
for (const config of configs.filter(x => x.value !== null)) { for (const config of configs.filter(x => !!x.value)) {
for (const env of envs) { for (const env of envs) {
env._set(config.key, config.value) env._set(config.key, config.value)
} }

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": "2.3.2-alpha.0", "version": "2.3.11-alpha.0",
"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",
@ -38,7 +38,7 @@
], ],
"dependencies": { "dependencies": {
"@adobe/spectrum-css-workflow-icons": "1.2.1", "@adobe/spectrum-css-workflow-icons": "1.2.1",
"@budibase/string-templates": "2.3.2-alpha.0", "@budibase/string-templates": "2.3.11-alpha.0",
"@spectrum-css/accordion": "3.0.24", "@spectrum-css/accordion": "3.0.24",
"@spectrum-css/actionbutton": "1.0.1", "@spectrum-css/actionbutton": "1.0.1",
"@spectrum-css/actiongroup": "1.0.1", "@spectrum-css/actiongroup": "1.0.1",

View file

@ -1,5 +1,5 @@
<script> <script>
import { createEventDispatcher } from "svelte" import { createEventDispatcher, onMount } from "svelte"
import FancyField from "./FancyField.svelte" import FancyField from "./FancyField.svelte"
import FancyFieldLabel from "./FancyFieldLabel.svelte" import FancyFieldLabel from "./FancyFieldLabel.svelte"
import { fade } from "svelte/transition" import { fade } from "svelte/transition"
@ -14,8 +14,11 @@
const dispatch = createEventDispatcher() const dispatch = createEventDispatcher()
let ref
let focused = false let focused = false
$: placeholder = !focused && !value let autofilled = false
$: placeholder = !autofilled && !focused && !value
const onChange = e => { const onChange = e => {
const newValue = e.target.value const newValue = e.target.value
@ -25,6 +28,27 @@
error = validate(newValue) error = validate(newValue)
} }
} }
onMount(() => {
// Start watching for autofill every 100ms
const interval = setInterval(() => {
autofilled = ref?.matches(":-webkit-autofill")
if (autofilled) {
clearInterval(interval)
}
}, 100)
// Give up after 2 seconds and assume autofill has not been used
const timeout = setTimeout(() => {
clearInterval(interval)
}, 2000)
// Cleanup
return () => {
clearInterval(interval)
clearTimeout(timeout)
}
})
</script> </script>
<FancyField {error} {value} {validate} {disabled} {focused}> <FancyField {error} {value} {validate} {disabled} {focused}>
@ -39,6 +63,7 @@
on:focus={() => (focused = true)} on:focus={() => (focused = true)}
on:blur={() => (focused = false)} on:blur={() => (focused = false)}
class:placeholder class:placeholder
bind:this={ref}
/> />
{#if suffix && !placeholder} {#if suffix && !placeholder}
<div in:fade|local={{ duration: 130 }} class="suffix">{suffix}</div> <div in:fade|local={{ duration: 130 }} class="suffix">{suffix}</div>
@ -74,4 +99,11 @@
line-height: 17px; line-height: 17px;
font-family: var(--font-sans); font-family: var(--font-sans);
} }
input:-webkit-autofill {
border-radius: 2px;
-webkit-box-shadow: 0 0 0 100px var(--spectrum-global-color-gray-300) inset;
-webkit-text-fill-color: var(--spectrum-global-color-gray-900);
transition: -webkit-box-shadow 130ms 200ms, background-color 0s 86400s;
padding: 3px 8px 4px 8px;
}
</style> </style>

View file

@ -13,6 +13,7 @@
export let quiet = false export let quiet = false
export let align export let align
export let autofocus = false export let autofocus = false
export let autocomplete = null
const dispatch = createEventDispatcher() const dispatch = createEventDispatcher()
@ -103,6 +104,7 @@
class="spectrum-Textfield-input" class="spectrum-Textfield-input"
style={align ? `text-align: ${align};` : ""} style={align ? `text-align: ${align};` : ""}
inputmode={type === "number" ? "decimal" : "text"} inputmode={type === "number" ? "decimal" : "text"}
{autocomplete}
/> />
</div> </div>

View file

@ -14,6 +14,7 @@
export let updateOnChange = true export let updateOnChange = true
export let quiet = false export let quiet = false
export let autofocus export let autofocus
export let autocomplete
const dispatch = createEventDispatcher() const dispatch = createEventDispatcher()
const onChange = e => { const onChange = e => {
@ -33,6 +34,7 @@
{type} {type}
{quiet} {quiet}
{autofocus} {autofocus}
{autocomplete}
on:change={onChange} on:change={onChange}
on:click on:click
on:input on:input

View file

@ -1,6 +1,6 @@
{ {
"name": "@budibase/builder", "name": "@budibase/builder",
"version": "2.3.2-alpha.0", "version": "2.3.11-alpha.0",
"license": "GPL-3.0", "license": "GPL-3.0",
"private": true, "private": true,
"scripts": { "scripts": {
@ -58,10 +58,10 @@
} }
}, },
"dependencies": { "dependencies": {
"@budibase/bbui": "2.3.2-alpha.0", "@budibase/bbui": "2.3.11-alpha.0",
"@budibase/client": "2.3.2-alpha.0", "@budibase/client": "2.3.11-alpha.0",
"@budibase/frontend-core": "2.3.2-alpha.0", "@budibase/frontend-core": "2.3.11-alpha.0",
"@budibase/string-templates": "2.3.2-alpha.0", "@budibase/string-templates": "2.3.11-alpha.0",
"@fortawesome/fontawesome-svg-core": "^6.2.1", "@fortawesome/fontawesome-svg-core": "^6.2.1",
"@fortawesome/free-brands-svg-icons": "^6.2.1", "@fortawesome/free-brands-svg-icons": "^6.2.1",
"@fortawesome/free-solid-svg-icons": "^6.2.1", "@fortawesome/free-solid-svg-icons": "^6.2.1",

View file

@ -75,16 +75,20 @@
editableColumn.constraints.presence = { allowEmpty: false } editableColumn.constraints.presence = { allowEmpty: false }
} }
$: if (field && !savingColumn) { const initialiseField = (field, savingColumn) => {
editableColumn = cloneDeep(field) if (field && !savingColumn) {
originalName = editableColumn.name ? editableColumn.name + "" : null editableColumn = cloneDeep(field)
linkEditDisabled = originalName != null originalName = editableColumn.name ? editableColumn.name + "" : null
isCreating = originalName == null linkEditDisabled = originalName != null
primaryDisplay = isCreating = originalName == null
$tables.selected.primaryDisplay == null || primaryDisplay =
$tables.selected.primaryDisplay === editableColumn.name $tables.selected.primaryDisplay == null ||
$tables.selected.primaryDisplay === editableColumn.name
}
} }
$: initialiseField(field, savingColumn)
$: checkConstraints(editableColumn) $: checkConstraints(editableColumn)
$: required = !!editableColumn?.constraints?.presence || primaryDisplay $: required = !!editableColumn?.constraints?.presence || primaryDisplay
$: uneditable = $: uneditable =
@ -583,7 +587,12 @@
title="Formula" title="Formula"
label="Formula" label="Formula"
value={editableColumn.formula} value={editableColumn.formula}
on:change={e => (editableColumn.formula = e.detail)} on:change={e => {
editableColumn = {
...editableColumn,
formula: e.detail,
}
}}
bindings={getBindings({ table })} bindings={getBindings({ table })}
allowJS allowJS
/> />

View file

@ -94,6 +94,11 @@
validateHash = newValidateHash validateHash = newValidateHash
} }
const handleChange = (name, e) => {
schema[name].type = e.detail
schema[name].constraints = FIELDS[e.detail.toUpperCase()].constraints
}
</script> </script>
<div class="dropzone"> <div class="dropzone">
@ -118,12 +123,12 @@
</div> </div>
{#if rows.length > 0 && !error} {#if rows.length > 0 && !error}
<div class="schema-fields"> <div class="schema-fields">
{#each Object.values(schema) as column} {#each Object.entries(schema) as [name, column]}
<div class="field"> <div class="field">
<span>{column.name}</span> <span>{column.name}</span>
<Select <Select
bind:value={column.type} bind:value={column.type}
on:change={e => (column.type = e.detail)} on:change={e => handleChange(name, e)}
options={typeOptions} options={typeOptions}
placeholder={null} placeholder={null}
getOptionLabel={option => option.label} getOptionLabel={option => option.label}

View file

@ -71,6 +71,7 @@
} }
}} }}
value={productionValue} value={productionValue}
autocomplete="new-password"
/> />
</div> </div>
<div> <div>
@ -83,6 +84,7 @@
disabled={useProductionValue} disabled={useProductionValue}
label="Value" label="Value"
value={useProductionValue ? productionValue : developmentValue} value={useProductionValue ? productionValue : developmentValue}
autocomplete="new-password"
/> />
<Checkbox bind:value={useProductionValue} text="Use production value" /> <Checkbox bind:value={useProductionValue} text="Use production value" />
</div> </div>

View file

@ -62,6 +62,7 @@ const getTours = () => {
id: TOUR_STEP_KEYS.BUILDER_APP_PUBLISH, id: TOUR_STEP_KEYS.BUILDER_APP_PUBLISH,
title: "Publish", title: "Publish",
layout: OnboardingPublish, layout: OnboardingPublish,
route: "/builder/app/:application/design",
query: ".toprightnav #builder-app-publish-button", query: ".toprightnav #builder-app-publish-button",
onLoad: () => { onLoad: () => {
tourEvent(TOUR_STEP_KEYS.BUILDER_APP_PUBLISH) tourEvent(TOUR_STEP_KEYS.BUILDER_APP_PUBLISH)

View file

@ -147,8 +147,8 @@
options: setting.options || [], options: setting.options || [],
// Number fields // Number fields
min: setting.min || null, min: setting.min ?? null,
max: setting.max || null, max: setting.max ?? null,
}} }}
{bindings} {bindings}
{componentBindings} {componentBindings}

View file

@ -16,6 +16,7 @@
import CreateTableModal from "components/backend/TableNavigator/modals/CreateTableModal.svelte" import CreateTableModal from "components/backend/TableNavigator/modals/CreateTableModal.svelte"
import createFromScratchScreen from "builderStore/store/screenTemplates/createFromScratchScreen" import createFromScratchScreen from "builderStore/store/screenTemplates/createFromScratchScreen"
import { Roles } from "constants/backend" import { Roles } from "constants/backend"
import Spinner from "components/common/Spinner.svelte"
let name = "My first app" let name = "My first app"
let url = "my-first-app" let url = "my-first-app"
@ -25,39 +26,46 @@
let plusIntegrations = {} let plusIntegrations = {}
let integrationsLoading = true let integrationsLoading = true
$: getIntegrations() $: getIntegrations()
let creationLoading = false
let uploadModal let uploadModal
const createApp = async useSampleData => { const createApp = async useSampleData => {
creationLoading = true
// Create form data to create app // Create form data to create app
// This is form based and not JSON // This is form based and not JSON
let data = new FormData() try {
data.append("name", name.trim()) let data = new FormData()
data.append("url", url.trim()) data.append("name", name.trim())
data.append("useTemplate", false) data.append("url", url.trim())
data.append("useTemplate", false)
if (useSampleData) { if (useSampleData) {
data.append("sampleData", true) data.append("sampleData", true)
}
const createdApp = await API.createApp(data)
// Select Correct Application/DB in prep for creating user
const pkg = await API.fetchAppPackage(createdApp.instance._id)
await store.actions.initialise(pkg)
await automationStore.actions.fetch()
// Update checklist - in case first app
await admin.init()
// Create user
await auth.setInitInfo({})
let defaultScreenTemplate = createFromScratchScreen.create()
defaultScreenTemplate.routing.route = "/home"
defaultScreenTemplate.routing.roldId = Roles.BASIC
await store.actions.screens.save(defaultScreenTemplate)
appId = createdApp.instance._id
} catch (e) {
creationLoading = false
throw e
} }
const createdApp = await API.createApp(data)
// Select Correct Application/DB in prep for creating user
const pkg = await API.fetchAppPackage(createdApp.instance._id)
await store.actions.initialise(pkg)
await automationStore.actions.fetch()
// Update checklist - in case first app
await admin.init()
// Create user
await auth.setInitInfo({})
let defaultScreenTemplate = createFromScratchScreen.create()
defaultScreenTemplate.routing.route = "/home"
defaultScreenTemplate.routing.roldId = Roles.BASIC
await store.actions.screens.save(defaultScreenTemplate)
appId = createdApp.instance._id
} }
const getIntegrations = async () => { const getIntegrations = async () => {
@ -102,6 +110,7 @@
goToApp() goToApp()
} catch (e) { } catch (e) {
console.log(e) console.log(e)
creationLoading = false
notifications.error("There was a problem creating your app") notifications.error("There was a problem creating your app")
} }
} }
@ -118,8 +127,10 @@
<SplitPage> <SplitPage>
{#if stage === "name"} {#if stage === "name"}
<NamePanel bind:name bind:url onNext={() => (stage = "data")} /> <NamePanel bind:name bind:url onNext={() => (stage = "data")} />
{:else if integrationsLoading} {:else if integrationsLoading || creationLoading}
<p>loading...</p> <div class="spinner">
<Spinner />
</div>
{:else if stage === "data"} {:else if stage === "data"}
<DataPanel onBack={() => (stage = "name")}> <DataPanel onBack={() => (stage = "name")}>
<div class="dataButton"> <div class="dataButton">
@ -175,6 +186,13 @@
</SplitPage> </SplitPage>
<style> <style>
.spinner {
display: flex;
justify-content: center;
align-items: center;
min-height: 400px;
}
.dataButton { .dataButton {
margin-bottom: 12px; margin-bottom: 12px;
} }

View file

@ -1,6 +1,6 @@
{ {
"name": "@budibase/cli", "name": "@budibase/cli",
"version": "2.3.2-alpha.0", "version": "2.3.11-alpha.0",
"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": {
@ -26,9 +26,9 @@
"outputPath": "build" "outputPath": "build"
}, },
"dependencies": { "dependencies": {
"@budibase/backend-core": "2.3.2-alpha.0", "@budibase/backend-core": "2.3.11-alpha.0",
"@budibase/string-templates": "2.3.2-alpha.0", "@budibase/string-templates": "2.3.11-alpha.0",
"@budibase/types": "2.3.2-alpha.0", "@budibase/types": "2.3.11-alpha.0",
"axios": "0.21.2", "axios": "0.21.2",
"chalk": "4.1.0", "chalk": "4.1.0",
"cli-progress": "3.11.2", "cli-progress": "3.11.2",

View file

@ -1,6 +1,6 @@
{ {
"name": "@budibase/client", "name": "@budibase/client",
"version": "2.3.2-alpha.0", "version": "2.3.11-alpha.0",
"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": "2.3.2-alpha.0", "@budibase/bbui": "2.3.11-alpha.0",
"@budibase/frontend-core": "2.3.2-alpha.0", "@budibase/frontend-core": "2.3.11-alpha.0",
"@budibase/string-templates": "2.3.2-alpha.0", "@budibase/string-templates": "2.3.11-alpha.0",
"@spectrum-css/button": "^3.0.3", "@spectrum-css/button": "^3.0.3",
"@spectrum-css/card": "^3.0.3", "@spectrum-css/card": "^3.0.3",
"@spectrum-css/divider": "^1.0.3", "@spectrum-css/divider": "^1.0.3",

View file

@ -1,12 +1,12 @@
{ {
"name": "@budibase/frontend-core", "name": "@budibase/frontend-core",
"version": "2.3.2-alpha.0", "version": "2.3.11-alpha.0",
"description": "Budibase frontend core libraries used in builder and client", "description": "Budibase frontend core libraries used in builder and client",
"author": "Budibase", "author": "Budibase",
"license": "MPL-2.0", "license": "MPL-2.0",
"svelte": "src/index.js", "svelte": "src/index.js",
"dependencies": { "dependencies": {
"@budibase/bbui": "2.3.2-alpha.0", "@budibase/bbui": "2.3.11-alpha.0",
"lodash": "^4.17.21", "lodash": "^4.17.21",
"svelte": "^3.46.2" "svelte": "^3.46.2"
} }

View file

@ -1,6 +1,6 @@
{ {
"name": "@budibase/sdk", "name": "@budibase/sdk",
"version": "2.3.2-alpha.0", "version": "2.3.11-alpha.0",
"description": "Budibase Public API SDK", "description": "Budibase Public API SDK",
"author": "Budibase", "author": "Budibase",
"license": "MPL-2.0", "license": "MPL-2.0",

View file

@ -1,7 +1,7 @@
{ {
"name": "@budibase/server", "name": "@budibase/server",
"email": "hi@budibase.com", "email": "hi@budibase.com",
"version": "2.3.2-alpha.0", "version": "2.3.11-alpha.0",
"description": "Budibase Web Server", "description": "Budibase Web Server",
"main": "src/index.ts", "main": "src/index.ts",
"repository": { "repository": {
@ -43,11 +43,11 @@
"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": "2.3.2-alpha.0", "@budibase/backend-core": "2.3.11-alpha.0",
"@budibase/client": "2.3.2-alpha.0", "@budibase/client": "2.3.11-alpha.0",
"@budibase/pro": "2.3.2-alpha.0", "@budibase/pro": "2.3.11-alpha.0",
"@budibase/string-templates": "2.3.2-alpha.0", "@budibase/string-templates": "2.3.11-alpha.0",
"@budibase/types": "2.3.2-alpha.0", "@budibase/types": "2.3.11-alpha.0",
"@bull-board/api": "3.7.0", "@bull-board/api": "3.7.0",
"@bull-board/koa": "3.9.4", "@bull-board/koa": "3.9.4",
"@elastic/elasticsearch": "7.10.0", "@elastic/elasticsearch": "7.10.0",

View file

@ -14,7 +14,6 @@ import { invalidateDynamicVariables } from "../../threads/utils"
import { db as dbCore, context, events } from "@budibase/backend-core" import { db as dbCore, context, events } from "@budibase/backend-core"
import { UserCtx, Datasource, Row } from "@budibase/types" import { UserCtx, Datasource, Row } from "@budibase/types"
import sdk from "../../sdk" import sdk from "../../sdk"
import { mergeConfigs } from "../../sdk/app/datasources/datasources"
export async function fetch(ctx: UserCtx) { export async function fetch(ctx: UserCtx) {
// Get internal tables // Get internal tables

View file

@ -186,6 +186,8 @@ export async function preview(ctx: any) {
schemaFields[key] = fieldType schemaFields[key] = fieldType
} }
} }
// remove configuration before sending event
delete datasource.config
await events.query.previewed(datasource, query) await events.query.previewed(datasource, query)
ctx.body = { ctx.body = {
rows, rows,

View file

@ -10,6 +10,7 @@ import {
FieldSchema, FieldSchema,
Row, Row,
Table, Table,
RelationshipTypes,
} from "@budibase/types" } from "@budibase/types"
import { import {
breakRowIdField, breakRowIdField,
@ -18,7 +19,7 @@ import {
convertRowId, convertRowId,
} from "../../../integrations/utils" } from "../../../integrations/utils"
import { getDatasourceAndQuery } from "./utils" import { getDatasourceAndQuery } from "./utils"
import { FieldTypes, RelationshipTypes } from "../../../constants" import { FieldTypes } from "../../../constants"
import { breakExternalTableId, isSQL } from "../../../integrations/utils" import { breakExternalTableId, isSQL } from "../../../integrations/utils"
import { processObjectSync } from "@budibase/string-templates" import { processObjectSync } from "@budibase/string-templates"
import { cloneDeep } from "lodash/fp" import { cloneDeep } from "lodash/fp"
@ -44,6 +45,7 @@ export interface RunConfig {
row?: Row row?: Row
rows?: Row[] rows?: Row[]
tables?: Record<string, Table> tables?: Record<string, Table>
includeSqlRelationships?: IncludeRelationship
} }
function buildFilters( function buildFilters(
@ -706,7 +708,9 @@ export class ExternalRequest {
}, },
resource: { resource: {
// have to specify the fields to avoid column overlap (for SQL) // have to specify the fields to avoid column overlap (for SQL)
fields: isSql ? this.buildFields(table) : [], fields: isSql
? this.buildFields(table, config.includeSqlRelationships)
: [],
}, },
filters, filters,
sort, sort,

View file

@ -18,6 +18,7 @@ import {
PaginationJson, PaginationJson,
Table, Table,
Datasource, Datasource,
IncludeRelationship,
} from "@budibase/types" } from "@budibase/types"
import sdk from "../../../sdk" import sdk from "../../../sdk"
@ -57,6 +58,7 @@ export async function patch(ctx: BBContext) {
return handleRequest(Operation.UPDATE, tableId, { return handleRequest(Operation.UPDATE, tableId, {
id: breakRowIdField(id), id: breakRowIdField(id),
row: inputs, row: inputs,
includeSqlRelationships: IncludeRelationship.EXCLUDE,
}) })
} }
@ -65,6 +67,7 @@ export async function save(ctx: BBContext) {
const tableId = ctx.params.tableId const tableId = ctx.params.tableId
return handleRequest(Operation.CREATE, tableId, { return handleRequest(Operation.CREATE, tableId, {
row: inputs, row: inputs,
includeSqlRelationships: IncludeRelationship.EXCLUDE,
}) })
} }
@ -78,7 +81,9 @@ export async function fetchView(ctx: BBContext) {
export async function fetch(ctx: BBContext) { export async function fetch(ctx: BBContext) {
const tableId = ctx.params.tableId const tableId = ctx.params.tableId
return handleRequest(Operation.READ, tableId) return handleRequest(Operation.READ, tableId, {
includeSqlRelationships: IncludeRelationship.INCLUDE,
})
} }
export async function find(ctx: BBContext) { export async function find(ctx: BBContext) {
@ -86,6 +91,7 @@ export async function find(ctx: BBContext) {
const tableId = ctx.params.tableId const tableId = ctx.params.tableId
const response = (await handleRequest(Operation.READ, tableId, { const response = (await handleRequest(Operation.READ, tableId, {
id: breakRowIdField(id), id: breakRowIdField(id),
includeSqlRelationships: IncludeRelationship.EXCLUDE,
})) as Row[] })) as Row[]
return response ? response[0] : response return response ? response[0] : response
} }
@ -95,6 +101,7 @@ export async function destroy(ctx: BBContext) {
const id = ctx.request.body._id const id = ctx.request.body._id
const { row } = (await handleRequest(Operation.DELETE, tableId, { const { row } = (await handleRequest(Operation.DELETE, tableId, {
id: breakRowIdField(id), id: breakRowIdField(id),
includeSqlRelationships: IncludeRelationship.EXCLUDE,
})) as { row: Row } })) as { row: Row }
return { response: { ok: true }, row } return { response: { ok: true }, row }
} }
@ -107,6 +114,7 @@ export async function bulkDestroy(ctx: BBContext) {
promises.push( promises.push(
handleRequest(Operation.DELETE, tableId, { handleRequest(Operation.DELETE, tableId, {
id: breakRowIdField(row._id), id: breakRowIdField(row._id),
includeSqlRelationships: IncludeRelationship.EXCLUDE,
}) })
) )
} }
@ -149,6 +157,7 @@ export async function search(ctx: BBContext) {
filters: query, filters: query,
sort, sort,
paginate: paginateObj as PaginationJson, paginate: paginateObj as PaginationJson,
includeSqlRelationships: IncludeRelationship.INCLUDE,
})) as Row[] })) as Row[]
let hasNextPage = false let hasNextPage = false
if (paginate && rows.length === limit) { if (paginate && rows.length === limit) {
@ -159,6 +168,7 @@ export async function search(ctx: BBContext) {
limit: 1, limit: 1,
page: bookmark * limit + 1, page: bookmark * limit + 1,
}, },
includeSqlRelationships: IncludeRelationship.INCLUDE,
})) as Row[] })) as Row[]
hasNextPage = nextRows.length > 0 hasNextPage = nextRows.length > 0
} }
@ -181,7 +191,7 @@ export async function validate(ctx: BBContext) {
} }
export async function exportRows(ctx: BBContext) { export async function exportRows(ctx: BBContext) {
const { datasourceId } = breakExternalTableId(ctx.params.tableId) const { datasourceId, tableName } = breakExternalTableId(ctx.params.tableId)
const format = ctx.query.format const format = ctx.query.format
const { columns } = ctx.request.body const { columns } = ctx.request.body
const datasource = await sdk.datasources.get(datasourceId!) const datasource = await sdk.datasources.get(datasourceId!)
@ -217,7 +227,9 @@ export async function exportRows(ctx: BBContext) {
rows = result.rows rows = result.rows
} }
// @ts-ignore if (!tableName) {
ctx.throw(400, "Could not find table name.")
}
let schema = datasource.entities[tableName].schema let schema = datasource.entities[tableName].schema
let exportRows = cleanExportRows(rows, schema, format, columns) let exportRows = cleanExportRows(rows, schema, format, columns)
@ -247,6 +259,7 @@ export async function fetchEnrichedRow(ctx: BBContext) {
const response = (await handleRequest(Operation.READ, tableId, { const response = (await handleRequest(Operation.READ, tableId, {
id, id,
datasource, datasource,
includeSqlRelationships: IncludeRelationship.INCLUDE,
})) as Row[] })) as Row[]
const table: Table = tables[tableName] const table: Table = tables[tableName]
const row = response[0] const row = response[0]
@ -274,6 +287,7 @@ export async function fetchEnrichedRow(ctx: BBContext) {
[primaryLink]: linkedIds, [primaryLink]: linkedIds,
}, },
}, },
includeSqlRelationships: IncludeRelationship.INCLUDE,
}) })
} }
return row return row

View file

@ -8,7 +8,7 @@ import {
foreignKeyStructure, foreignKeyStructure,
hasTypeChanged, hasTypeChanged,
} from "./utils" } from "./utils"
import { FieldTypes, RelationshipTypes } from "../../../constants" import { FieldTypes } from "../../../constants"
import { makeExternalQuery } from "../../../integrations/base/query" import { makeExternalQuery } from "../../../integrations/base/query"
import { handleRequest } from "../row/external" import { handleRequest } from "../row/external"
import { events, context } from "@budibase/backend-core" import { events, context } from "@budibase/backend-core"
@ -22,6 +22,7 @@ import {
FieldSchema, FieldSchema,
BBContext, BBContext,
TableRequest, TableRequest,
RelationshipTypes,
} from "@budibase/types" } from "@budibase/types"
import sdk from "../../../sdk" import sdk from "../../../sdk"
const { cloneDeep } = require("lodash/fp") const { cloneDeep } = require("lodash/fp")
@ -146,7 +147,7 @@ function generateLinkSchema(
column: FieldSchema, column: FieldSchema,
table: Table, table: Table,
relatedTable: Table, relatedTable: Table,
type: string type: RelationshipTypes
) { ) {
if (!table.primary || !relatedTable.primary) { if (!table.primary || !relatedTable.primary) {
throw new Error("Unable to generate link schema, no primary keys") throw new Error("Unable to generate link schema, no primary keys")

View file

@ -104,7 +104,6 @@ export function importToRows(data: any, table: any, user: any = {}) {
const processed: any = inputProcessing(user, table, row, { const processed: any = inputProcessing(user, table, row, {
noAutoRelationships: true, noAutoRelationships: true,
}) })
table = processed.table
row = processed.row row = processed.row
let fieldName: any let fieldName: any
@ -113,6 +112,7 @@ export function importToRows(data: any, table: any, user: any = {}) {
// check whether the options need to be updated for inclusion as part of the data import // check whether the options need to be updated for inclusion as part of the data import
if ( if (
schema.type === FieldTypes.OPTIONS && schema.type === FieldTypes.OPTIONS &&
row[fieldName] &&
(!schema.constraints.inclusion || (!schema.constraints.inclusion ||
schema.constraints.inclusion.indexOf(row[fieldName]) === -1) schema.constraints.inclusion.indexOf(row[fieldName]) === -1)
) { ) {
@ -120,6 +120,7 @@ export function importToRows(data: any, table: any, user: any = {}) {
...schema.constraints.inclusion, ...schema.constraints.inclusion,
row[fieldName], row[fieldName],
] ]
schema.constraints.inclusion.sort()
} }
} }

View file

@ -13,7 +13,7 @@ beforeAll(async () => {
app = await config.init() app = await config.init()
table = await config.updateTable() table = await config.updateTable()
apiKey = await config.generateApiKey() apiKey = await config.generateApiKey()
makeRequest = generateMakeRequest(apiKey, setup) makeRequest = generateMakeRequest(apiKey)
}) })
afterAll(setup.afterAll) afterAll(setup.afterAll)

View file

@ -10,7 +10,7 @@ beforeAll(async () => {
await config.init() await config.init()
globalUser = await config.globalUser() globalUser = await config.globalUser()
apiKey = await config.generateApiKey(globalUser._id) apiKey = await config.generateApiKey(globalUser._id)
makeRequest = generateMakeRequest(apiKey, setup) makeRequest = generateMakeRequest(apiKey)
workerRequests.readGlobalUser.mockReturnValue(globalUser) workerRequests.readGlobalUser.mockReturnValue(globalUser)
}) })

View file

@ -1,13 +1,27 @@
import * as setup from "../../tests/utilities"
import { checkSlashesInUrl } from "../../../../utilities" import { checkSlashesInUrl } from "../../../../utilities"
import supertest from "supertest"
export function generateMakeRequest(apiKey: string, setup: any) { export type HttpMethod = "post" | "get" | "put" | "delete" | "patch"
const request = setup.getRequest()
const config = setup.getConfig() export type MakeRequestResponse = (
method: HttpMethod,
endpoint: string,
body?: any,
intAppId?: string
) => Promise<supertest.Response>
export function generateMakeRequest(
apiKey: string,
isInternal = false
): MakeRequestResponse {
const request = setup.getRequest()!
const config = setup.getConfig()!
return async ( return async (
method: string, method: HttpMethod,
endpoint: string, endpoint: string,
body?: any, body?: any,
intAppId: string = config.getAppId() intAppId: string | null = config.getAppId()
) => { ) => {
const extraHeaders: any = { const extraHeaders: any = {
"x-budibase-api-key": apiKey, "x-budibase-api-key": apiKey,
@ -15,9 +29,12 @@ export function generateMakeRequest(apiKey: string, setup: any) {
if (intAppId) { if (intAppId) {
extraHeaders["x-budibase-app-id"] = intAppId extraHeaders["x-budibase-app-id"] = intAppId
} }
const req = request[method](
checkSlashesInUrl(`/api/public/v1/${endpoint}`) const url = isInternal
).set(config.defaultHeaders(extraHeaders)) ? endpoint
: checkSlashesInUrl(`/api/public/v1/${endpoint}`)
const req = request[method](url).set(config.defaultHeaders(extraHeaders))
if (body) { if (body) {
req.send(body) req.send(body)
} }

View file

@ -7,7 +7,7 @@ import * as setup from "./utilities"
import { wipeDb } from "./utilities/TestFunctions" import { wipeDb } from "./utilities/TestFunctions"
describe("/cloud", () => { describe("/cloud", () => {
let request = setup.getRequest() let request = setup.getRequest()!
let config = setup.getConfig() let config = setup.getConfig()
afterAll(setup.afterAll) afterAll(setup.afterAll)

View file

@ -0,0 +1,144 @@
const pg = require("pg")
jest.mock("pg", () => {
return {
Client: jest.fn().mockImplementation(() => ({
connect: jest.fn(),
query: jest.fn().mockImplementation(() => ({ rows: [] })),
end: jest.fn().mockImplementation((fn: any) => fn()),
})),
queryMock: jest.fn().mockImplementation(() => {}),
on: jest.fn(),
}
})
import * as setup from "./utilities"
import { mocks } from "@budibase/backend-core/tests"
import { env, events } from "@budibase/backend-core"
const structures = setup.structures
env._set("ENCRYPTION_KEY", "budibase")
mocks.licenses.useEnvironmentVariables()
describe("/api/env/variables", () => {
let request = setup.getRequest()
let config = setup.getConfig()
afterAll(setup.afterAll)
beforeAll(async () => {
await config.init()
})
it("should be able check the status of env var API", async () => {
const res = await request
.get(`/api/env/variables/status`)
.set(config.defaultHeaders())
.expect("Content-Type", /json/)
.expect(200)
expect(res.body.encryptionKeyAvailable).toEqual(true)
})
it("should be able to create an environment variable", async () => {
await request
.post(`/api/env/variables`)
.send(structures.basicEnvironmentVariable("test", "test"))
.set(config.defaultHeaders())
.expect(200)
})
it("should be able to fetch the 'test' variable name", async () => {
const res = await request
.get(`/api/env/variables`)
.set(config.defaultHeaders())
.expect("Content-Type", /json/)
.expect(200)
expect(res.body.variables.length).toEqual(1)
expect(res.body.variables[0]).toEqual("test")
})
it("should be able to update the environment variable 'test'", async () => {
const varName = "test"
await request
.patch(`/api/env/variables/${varName}`)
.send(structures.basicEnvironmentVariable("test", "test1"))
.set(config.defaultHeaders())
.expect(200)
})
it("should be able to delete the environment variable 'test'", async () => {
const varName = "test"
await request
.delete(`/api/env/variables/${varName}`)
.set(config.defaultHeaders())
.expect(200)
})
it("should create a datasource (using the environment variable) and query", async () => {
const datasourceBase = structures.basicDatasource()
await request
.post(`/api/env/variables`)
.send(structures.basicEnvironmentVariable("test", "test"))
.set(config.defaultHeaders())
datasourceBase.datasource.config = {
password: "{{ env.test }}",
}
const response = await request
.post(`/api/datasources`)
.send(datasourceBase)
.set(config.defaultHeaders())
.expect("Content-Type", /json/)
.expect(200)
expect(response.body.datasource._id).toBeDefined()
const response2 = await request
.post(`/api/queries`)
.send(structures.basicQuery(response.body.datasource._id))
.set(config.defaultHeaders())
.expect("Content-Type", /json/)
.expect(200)
expect(response2.body._id).toBeDefined()
})
it("should run a query preview and check the mocked results", async () => {
const datasourceBase = structures.basicDatasource()
await request
.post(`/api/env/variables`)
.send(structures.basicEnvironmentVariable("test", "test"))
.set(config.defaultHeaders())
datasourceBase.datasource.config = {
password: "{{ env.test }}",
}
const response = await request
.post(`/api/datasources`)
.send(datasourceBase)
.set(config.defaultHeaders())
.expect("Content-Type", /json/)
.expect(200)
expect(response.body.datasource._id).toBeDefined()
const query = {
datasourceId: response.body.datasource._id,
parameters: {},
fields: {},
queryVerb: "read",
name: response.body.datasource.name,
}
const res = await request
.post(`/api/queries/preview`)
.send(query)
.set(config.defaultHeaders())
.expect("Content-Type", /json/)
.expect(200)
expect(res.body.rows.length).toEqual(0)
expect(events.query.previewed).toBeCalledTimes(1)
// API doesn't include config in response
delete response.body.datasource.config
expect(events.query.previewed).toBeCalledWith(
response.body.datasource,
query
)
expect(pg.Client).toHaveBeenCalledWith({ password: "test", ssl: undefined })
})
})

View file

@ -242,6 +242,7 @@ describe("/queries", () => {
}) })
expect(res.body.rows.length).toEqual(1) expect(res.body.rows.length).toEqual(1)
expect(events.query.previewed).toBeCalledTimes(1) expect(events.query.previewed).toBeCalledTimes(1)
delete datasource.config
expect(events.query.previewed).toBeCalledWith(datasource, query) expect(events.query.previewed).toBeCalledWith(datasource, query)
}) })

View file

@ -1,4 +1,4 @@
const { roles } = require("@budibase/backend-core") const { roles, utils } = require("@budibase/backend-core")
const { checkPermissionsEndpoint } = require("./utilities/TestFunctions") const { checkPermissionsEndpoint } = require("./utilities/TestFunctions")
const setup = require("./utilities") const setup = require("./utilities")
const { BUILTIN_ROLE_IDS } = roles const { BUILTIN_ROLE_IDS } = roles
@ -28,8 +28,8 @@ describe("/users", () => {
describe("fetch", () => { describe("fetch", () => {
it("returns a list of users from an instance db", async () => { it("returns a list of users from an instance db", async () => {
await config.createUser("uuidx") await config.createUser({ id: "uuidx" })
await config.createUser("uuidy") await config.createUser({ id: "uuidy" })
const res = await request const res = await request
.get(`/api/users/metadata`) .get(`/api/users/metadata`)
.set(config.defaultHeaders()) .set(config.defaultHeaders())
@ -56,7 +56,7 @@ describe("/users", () => {
describe("update", () => { describe("update", () => {
it("should be able to update the user", async () => { it("should be able to update the user", async () => {
const user = await config.createUser() const user = await config.createUser({ id: `us_update${Math.random()}` })
user.roleId = BUILTIN_ROLE_IDS.BASIC user.roleId = BUILTIN_ROLE_IDS.BASIC
const res = await request const res = await request
.put(`/api/users/metadata`) .put(`/api/users/metadata`)
@ -180,14 +180,11 @@ describe("/users", () => {
const app1 = await config.createApp('App 1') const app1 = await config.createApp('App 1')
const app2 = await config.createApp('App 2') const app2 = await config.createApp('App 2')
let user = await config.createUser( let user = await config.createUser({
undefined, builder: false,
undefined, admin: true,
undefined, roles: { [app1.appId]: 'ADMIN' }
undefined, })
false,
true,
{ [app1.appId]: 'ADMIN' })
let res = await request let res = await request
.post(`/api/users/metadata/sync/${user._id}`) .post(`/api/users/metadata/sync/${user._id}`)
.set(config.defaultHeaders()) .set(config.defaultHeaders())

View file

@ -1,4 +1,5 @@
import { objectStore, roles, constants } from "@budibase/backend-core" import { objectStore, roles, constants } from "@budibase/backend-core"
export { FieldType as FieldTypes, RelationshipTypes } from "@budibase/types"
export enum FilterTypes { export enum FilterTypes {
STRING = "string", STRING = "string",
@ -22,23 +23,6 @@ export const NoEmptyFilterStrings = [
FilterTypes.NOT_CONTAINS, FilterTypes.NOT_CONTAINS,
] ]
export enum FieldTypes {
STRING = "string",
BARCODEQR = "barcodeqr",
LONGFORM = "longform",
OPTIONS = "options",
NUMBER = "number",
BOOLEAN = "boolean",
ARRAY = "array",
DATETIME = "datetime",
ATTACHMENT = "attachment",
LINK = "link",
FORMULA = "formula",
AUTO = "auto",
JSON = "json",
INTERNAL = "internal",
}
export const CanSwitchTypes = [ export const CanSwitchTypes = [
[exports.FieldTypes.JSON, exports.FieldTypes.ARRAY], [exports.FieldTypes.JSON, exports.FieldTypes.ARRAY],
[ [
@ -54,12 +38,6 @@ export const SwitchableTypes = CanSwitchTypes.reduce((prev, current) =>
prev ? prev.concat(current) : current prev ? prev.concat(current) : current
) )
export enum RelationshipTypes {
ONE_TO_MANY = "one-to-many",
MANY_TO_ONE = "many-to-one",
MANY_TO_MANY = "many-to-many",
}
export enum FormulaTypes { export enum FormulaTypes {
STATIC = "static", STATIC = "static",
DYNAMIC = "dynamic", DYNAMIC = "dynamic",

View file

@ -1,8 +1,4 @@
import { import { FieldTypes, AutoFieldSubTypes } from "../../constants"
FieldTypes,
AutoFieldSubTypes,
RelationshipTypes,
} from "../../constants"
import { importToRows } from "../../api/controllers/table/utils" import { importToRows } from "../../api/controllers/table/utils"
import { cloneDeep } from "lodash/fp" import { cloneDeep } from "lodash/fp"
import LinkDocument from "../linkedRows/LinkDocument" import LinkDocument from "../linkedRows/LinkDocument"
@ -11,7 +7,7 @@ import { employeeImport } from "./employeeImport"
import { jobsImport } from "./jobsImport" import { jobsImport } from "./jobsImport"
import { expensesImport } from "./expensesImport" import { expensesImport } from "./expensesImport"
import { db as dbCore } from "@budibase/backend-core" import { db as dbCore } from "@budibase/backend-core"
import { Table, Row } from "@budibase/types" import { Table, Row, RelationshipTypes } from "@budibase/types"
export const DEFAULT_JOBS_TABLE_ID = "ta_bb_jobs" export const DEFAULT_JOBS_TABLE_ID = "ta_bb_jobs"
export const DEFAULT_INVENTORY_TABLE_ID = "ta_bb_inventory" export const DEFAULT_INVENTORY_TABLE_ID = "ta_bb_inventory"
@ -190,7 +186,7 @@ export const DEFAULT_INVENTORY_TABLE_SCHEMA: Table = {
}, },
} }
export const DEFAULT_EMPLOYEE_TABLE_SCHEMA = { export const DEFAULT_EMPLOYEE_TABLE_SCHEMA: Table = {
_id: DEFAULT_EMPLOYEE_TABLE_ID, _id: DEFAULT_EMPLOYEE_TABLE_ID,
type: "internal", type: "internal",
views: {}, views: {},
@ -287,7 +283,7 @@ export const DEFAULT_EMPLOYEE_TABLE_SCHEMA = {
sortable: false, sortable: false,
}, },
"Badge Photo": { "Badge Photo": {
type: "attachment", type: FieldTypes.ATTACHMENT,
constraints: { constraints: {
type: FieldTypes.ARRAY, type: FieldTypes.ARRAY,
presence: false, presence: false,
@ -466,7 +462,7 @@ export const DEFAULT_JOBS_TABLE_SCHEMA: Table = {
// sortable: true, // sortable: true,
}, },
"Works End": { "Works End": {
type: "datetime", type: FieldTypes.DATETIME,
constraints: { constraints: {
type: "string", type: "string",
length: {}, length: {},
@ -480,7 +476,7 @@ export const DEFAULT_JOBS_TABLE_SCHEMA: Table = {
ignoreTimezones: true, ignoreTimezones: true,
}, },
"Updated Price": { "Updated Price": {
type: "number", type: FieldTypes.NUMBER,
constraints: { constraints: {
type: "number", type: "number",
presence: false, presence: false,

View file

@ -1,13 +1,14 @@
import { IncludeDocs, getLinkDocuments } from "./linkUtils" import { IncludeDocs, getLinkDocuments } from "./linkUtils"
import { InternalTables, getUserMetadataParams } from "../utils" import { InternalTables, getUserMetadataParams } from "../utils"
import Sentry from "@sentry/node" import Sentry from "@sentry/node"
import { FieldTypes, RelationshipTypes } from "../../constants" import { FieldTypes } from "../../constants"
import { context } from "@budibase/backend-core" import { context } from "@budibase/backend-core"
import LinkDocument from "./LinkDocument" import LinkDocument from "./LinkDocument"
import { import {
Database, Database,
FieldSchema, FieldSchema,
LinkDocumentValue, LinkDocumentValue,
RelationshipTypes,
Row, Row,
Table, Table,
} from "@budibase/types" } from "@budibase/types"

View file

@ -0,0 +1,752 @@
import {
generateMakeRequest,
MakeRequestResponse,
} from "../api/routes/public/tests/utils"
import * as setup from "../api/routes/tests/utilities"
import {
Datasource,
FieldType,
RelationshipTypes,
Row,
SourceName,
Table,
} from "@budibase/types"
import _ from "lodash"
import { generator } from "@budibase/backend-core/tests"
import { utils } from "@budibase/backend-core"
import { GenericContainer } from "testcontainers"
const config = setup.getConfig()!
jest.setTimeout(30000)
jest.unmock("pg")
describe("row api - postgres", () => {
let makeRequest: MakeRequestResponse,
postgresDatasource: Datasource,
primaryPostgresTable: Table,
auxPostgresTable: Table
let host: string
let port: number
beforeAll(async () => {
const container = await new GenericContainer("postgres")
.withExposedPorts(5432)
.withEnv("POSTGRES_PASSWORD", "password")
.start()
host = container.getContainerIpAddress()
port = container.getMappedPort(5432)
await config.init()
const apiKey = await config.generateApiKey()
makeRequest = generateMakeRequest(apiKey, true)
})
beforeEach(async () => {
postgresDatasource = await config.createDatasource({
datasource: {
type: "datasource",
source: SourceName.POSTGRES,
plus: true,
config: {
host,
port,
database: "postgres",
user: "postgres",
password: "password",
schema: "public",
ssl: false,
rejectUnauthorized: false,
ca: false,
},
},
})
auxPostgresTable = await config.createTable({
name: generator.word({ length: 10 }),
type: "external",
primary: ["id"],
schema: {
id: {
name: "id",
type: FieldType.AUTO,
constraints: {
presence: true,
},
},
title: {
name: "title",
type: FieldType.STRING,
constraints: {
presence: true,
},
},
},
sourceId: postgresDatasource._id,
})
primaryPostgresTable = await config.createTable({
name: generator.word({ length: 10 }),
type: "external",
primary: ["id"],
schema: {
id: {
name: "id",
type: FieldType.AUTO,
constraints: {
presence: true,
},
},
name: {
name: "name",
type: FieldType.STRING,
constraints: {
presence: true,
},
},
description: {
name: "description",
type: FieldType.STRING,
},
value: {
name: "value",
type: FieldType.NUMBER,
},
linkedField: {
type: FieldType.LINK,
constraints: {
type: "array",
presence: false,
},
fieldName: "foreignField",
name: "linkedField",
relationshipType: RelationshipTypes.ONE_TO_MANY,
tableId: auxPostgresTable._id,
},
},
sourceId: postgresDatasource._id,
})
})
afterAll(async () => {
await config.end()
})
function generateRandomPrimaryRowData() {
return {
name: generator.name(),
description: generator.paragraph(),
value: generator.age(),
}
}
type PrimaryRowData = {
name: string
description: string
value: number
}
async function createPrimaryRow(opts: {
rowData: PrimaryRowData
createForeignRow?: boolean
}) {
let { rowData } = opts
let foreignRow: Row | undefined
if (opts?.createForeignRow) {
foreignRow = await config.createRow({
tableId: auxPostgresTable._id,
title: generator.name(),
})
rowData = {
...rowData,
[`fk_${auxPostgresTable.name}_foreignField`]: foreignRow.id,
}
}
const row = await config.createRow({
tableId: primaryPostgresTable._id,
...rowData,
})
return { row, foreignRow }
}
async function createDefaultPgTable() {
return await config.createTable({
name: generator.word({ length: 10 }),
type: "external",
primary: ["id"],
schema: {
id: {
name: "id",
type: FieldType.AUTO,
constraints: {
presence: true,
},
},
},
sourceId: postgresDatasource._id,
})
}
async function populatePrimaryRows(
count: number,
opts?: {
createForeignRow?: boolean
}
) {
return await Promise.all(
Array(count)
.fill({})
.map(async () => {
const rowData = generateRandomPrimaryRowData()
return {
rowData,
...(await createPrimaryRow({
rowData,
createForeignRow: opts?.createForeignRow,
})),
}
})
)
}
it("validate table schema", async () => {
const res = await makeRequest(
"get",
`/api/datasources/${postgresDatasource._id}`
)
expect(res.status).toBe(200)
expect(res.body).toEqual({
config: {
ca: false,
database: "postgres",
host,
password: "--secret-value--",
port,
rejectUnauthorized: false,
schema: "public",
ssl: false,
user: "postgres",
},
plus: true,
source: "POSTGRES",
type: "datasource",
_id: expect.any(String),
_rev: expect.any(String),
createdAt: expect.any(String),
updatedAt: expect.any(String),
entities: expect.any(Object),
})
})
describe("POST /api/:tableId/rows", () => {
const createRow = (tableId: string | undefined, body: object) =>
makeRequest("post", `/api/${tableId}/rows`, body)
describe("given than no row exists", () => {
it("adding a new one persists it", async () => {
const newRow = generateRandomPrimaryRowData()
const res = await createRow(primaryPostgresTable._id, newRow)
expect(res.status).toBe(200)
const persistedRows = await config.getRows(primaryPostgresTable._id!)
expect(persistedRows).toHaveLength(1)
const expected = {
...res.body,
...newRow,
}
expect(persistedRows).toEqual([expect.objectContaining(expected)])
})
it("multiple rows can be persisted", async () => {
const numberOfRows = 10
const newRows = Array(numberOfRows).fill(generateRandomPrimaryRowData())
for (const newRow of newRows) {
const res = await createRow(primaryPostgresTable._id, newRow)
expect(res.status).toBe(200)
}
const persistedRows = await config.getRows(primaryPostgresTable._id!)
expect(persistedRows).toHaveLength(numberOfRows)
expect(persistedRows).toEqual(
expect.arrayContaining(newRows.map(expect.objectContaining))
)
})
})
})
describe("PATCH /api/:tableId/rows", () => {
const updateRow = (tableId: string | undefined, body: Row) =>
makeRequest("patch", `/api/${tableId}/rows`, body)
describe("given than a row exists", () => {
let row: Row
beforeEach(async () => {
let rowResponse = _.sample(await populatePrimaryRows(10))!
row = rowResponse.row
})
it("updating it persists it", async () => {
const newName = generator.name()
const newValue = generator.age()
const updatedRow = {
...row,
name: newName,
value: newValue,
}
const res = await updateRow(primaryPostgresTable._id, updatedRow)
expect(res.status).toBe(200)
expect(res.body).toEqual(updatedRow)
const persistedRow = await config.getRow(
primaryPostgresTable._id!,
row.id
)
expect(persistedRow).toEqual(
expect.objectContaining({
id: row.id,
name: newName,
value: newValue,
})
)
})
})
})
describe("DELETE /api/:tableId/rows", () => {
const deleteRow = (
tableId: string | undefined,
body: Row | { rows: Row[] }
) => makeRequest("delete", `/api/${tableId}/rows`, body)
describe("given than multiple row exist", () => {
const numberOfInitialRows = 5
let rows: Row[]
beforeEach(async () => {
rows = (await populatePrimaryRows(numberOfInitialRows)).map(x => x.row)
})
it("delete request removes it", async () => {
const row = _.sample(rows)!
const res = await deleteRow(primaryPostgresTable._id, row)
expect(res.status).toBe(200)
const persistedRows = await config.getRows(primaryPostgresTable._id!)
expect(persistedRows).toHaveLength(numberOfInitialRows - 1)
expect(row.id).toBeDefined()
expect(persistedRows).not.toContain(
expect.objectContaining({ _id: row.id })
)
})
it("multiple rows can be removed at once", async () => {
let rowsToDelete = _.sampleSize(rows, 3)!
const res = await deleteRow(primaryPostgresTable._id, {
rows: rowsToDelete,
})
expect(res.status).toBe(200)
const persistedRows = await config.getRows(primaryPostgresTable._id!)
expect(persistedRows).toHaveLength(numberOfInitialRows - 3)
for (const row of rowsToDelete) {
expect(persistedRows).not.toContain(
expect.objectContaining({ _id: row.id })
)
}
})
})
})
describe("GET /api/:tableId/rows/:rowId", () => {
const getRow = (tableId: string | undefined, rowId?: string | undefined) =>
makeRequest("get", `/api/${tableId}/rows/${rowId}`)
describe("given than a table have a single row", () => {
let rowData: PrimaryRowData, row: Row
beforeEach(async () => {
const [createdRow] = await populatePrimaryRows(1)
rowData = createdRow.rowData
row = createdRow.row
})
it("the row can be retrieved successfully", async () => {
const res = await getRow(primaryPostgresTable._id, row.id)
expect(res.status).toBe(200)
expect(res.body).toEqual(expect.objectContaining(rowData))
})
})
describe("given than a table have a multiple rows", () => {
let rows: { row: Row; rowData: PrimaryRowData }[]
beforeEach(async () => {
rows = await populatePrimaryRows(10)
})
it("a single row can be retrieved successfully", async () => {
const { rowData, row } = _.sample(rows)!
const res = await getRow(primaryPostgresTable._id, row.id)
expect(res.status).toBe(200)
expect(res.body).toEqual(expect.objectContaining(rowData))
})
})
describe("given a row with relation data", () => {
let row: Row
beforeEach(async () => {
let [createdRow] = await populatePrimaryRows(1, {
createForeignRow: true,
})
row = createdRow.row
})
it("foreign key fields are not retrieved", async () => {
const res = await getRow(primaryPostgresTable._id, row.id)
expect(res.status).toBe(200)
expect(res.body).toEqual({
...row,
_id: expect.any(String),
_rev: expect.any(String),
})
expect(res.body.foreignField).toBeUndefined()
})
})
})
describe("POST /api/:tableId/search", () => {
const search = (tableId: string | undefined, body?: object) =>
makeRequest("post", `/api/${tableId}/search`, body)
describe("search without parameters", () => {
describe("given than a table has no rows", () => {
it("search without query returns empty", async () => {
const res = await search(primaryPostgresTable._id)
expect(res.status).toBe(200)
expect(res.body).toEqual({
rows: [],
bookmark: null,
hasNextPage: false,
})
})
})
describe("given than a table has multiple rows", () => {
const rowsCount = 6
let rows: {
row: Row
rowData: PrimaryRowData
}[]
beforeEach(async () => {
rows = await populatePrimaryRows(rowsCount)
})
it("search without query returns all of them", async () => {
const res = await search(primaryPostgresTable._id)
expect(res.status).toBe(200)
expect(res.body).toEqual({
rows: expect.arrayContaining(
rows.map(r => expect.objectContaining(r.rowData))
),
bookmark: null,
hasNextPage: false,
})
expect(res.body.rows).toHaveLength(rowsCount)
})
})
describe("given than multiple tables have multiple rows", () => {
const rowsCount = 6
beforeEach(async () => {
const createRandomTableWithRows = async () =>
await config.createRow({
tableId: (await createDefaultPgTable())._id,
title: generator.name(),
})
await createRandomTableWithRows()
await createRandomTableWithRows()
await populatePrimaryRows(rowsCount)
await createRandomTableWithRows()
})
it("search only return the requested ones", async () => {
const res = await search(primaryPostgresTable._id)
expect(res.status).toBe(200)
expect(res.body.rows).toHaveLength(rowsCount)
})
})
})
it("Querying by a string field returns the rows with field containing or starting by that value", async () => {
const name = generator.name()
const rowsToFilter = [
...Array(2).fill({
name,
description: generator.paragraph(),
value: generator.age(),
}),
...Array(2).fill({
name: `${name}${utils.newid()}`,
description: generator.paragraph(),
value: generator.age(),
}),
]
await populatePrimaryRows(3)
for (const row of rowsToFilter) {
await createPrimaryRow({
rowData: row,
})
}
await populatePrimaryRows(1)
const res = await search(primaryPostgresTable._id, {
query: {
string: {
name,
},
},
})
expect(res.status).toBe(200)
expect(res.body).toEqual({
rows: expect.arrayContaining(rowsToFilter.map(expect.objectContaining)),
bookmark: null,
hasNextPage: false,
})
expect(res.body.rows).toHaveLength(4)
})
it("Querying respects the limit fields", async () => {
await populatePrimaryRows(6)
const res = await search(primaryPostgresTable._id, {
limit: 2,
})
expect(res.status).toBe(200)
expect(res.body.rows).toHaveLength(2)
})
describe("sort", () => {
beforeEach(async () => {
const defaultValue = generateRandomPrimaryRowData()
await createPrimaryRow({
rowData: {
...defaultValue,
name: "d",
value: 3,
},
})
await createPrimaryRow({
rowData: { ...defaultValue, name: "aaa", value: 40 },
})
await createPrimaryRow({
rowData: { ...defaultValue, name: "ccccc", value: -5 },
})
await createPrimaryRow({
rowData: { ...defaultValue, name: "bb", value: 0 },
})
})
it("Querying respects the sort order when sorting ascending by a string value", async () => {
const res = await search(primaryPostgresTable._id, {
sort: "name",
sortOrder: "ascending",
sortType: "string",
})
expect(res.status).toBe(200)
expect(res.body.rows).toEqual([
expect.objectContaining({ name: "aaa" }),
expect.objectContaining({ name: "bb" }),
expect.objectContaining({ name: "ccccc" }),
expect.objectContaining({ name: "d" }),
])
})
it("Querying respects the sort order when sorting descending by a string value", async () => {
const res = await search(primaryPostgresTable._id, {
sort: "name",
sortOrder: "descending",
sortType: "string",
})
expect(res.status).toBe(200)
expect(res.body.rows).toEqual([
expect.objectContaining({ name: "d" }),
expect.objectContaining({ name: "ccccc" }),
expect.objectContaining({ name: "bb" }),
expect.objectContaining({ name: "aaa" }),
])
})
it("Querying respects the sort order when sorting ascending by a numeric value", async () => {
const res = await search(primaryPostgresTable._id, {
sort: "value",
sortOrder: "ascending",
sortType: "number",
})
expect(res.status).toBe(200)
expect(res.body.rows).toEqual([
expect.objectContaining({ value: -5 }),
expect.objectContaining({ value: 0 }),
expect.objectContaining({ value: 3 }),
expect.objectContaining({ value: 40 }),
])
})
it("Querying respects the sort order when sorting descending by a numeric value", async () => {
const res = await search(primaryPostgresTable._id, {
sort: "value",
sortOrder: "descending",
sortType: "number",
})
expect(res.status).toBe(200)
expect(res.body.rows).toEqual([
expect.objectContaining({ value: 40 }),
expect.objectContaining({ value: 3 }),
expect.objectContaining({ value: 0 }),
expect.objectContaining({ value: -5 }),
])
})
})
})
describe("GET /api/:tableId/:rowId/enrich", () => {
const getAll = (tableId: string | undefined, rowId: string | undefined) =>
makeRequest("get", `/api/${tableId}/${rowId}/enrich`)
describe("given a row with relation data", () => {
let row: Row, foreignRow: Row | undefined
beforeEach(async () => {
const rowsInfo = await createPrimaryRow({
rowData: generateRandomPrimaryRowData(),
createForeignRow: true,
})
row = rowsInfo.row
foreignRow = rowsInfo.foreignRow
})
it("enrich populates the foreign field", async () => {
const res = await getAll(primaryPostgresTable._id, row.id)
expect(res.status).toBe(200)
expect(foreignRow).toBeDefined()
expect(res.body).toEqual({
...row,
linkedField: [
{
...foreignRow,
},
],
})
})
})
})
describe("GET /api/:tableId/rows", () => {
const getAll = (tableId: string | undefined) =>
makeRequest("get", `/api/${tableId}/rows`)
describe("given a table with no rows", () => {
it("get request returns empty", async () => {
const res = await getAll(primaryPostgresTable._id)
expect(res.status).toBe(200)
expect(res.body).toHaveLength(0)
})
})
describe("given a table with multiple rows", () => {
const rowsCount = 6
let rows: {
row: Row
foreignRow: Row | undefined
rowData: PrimaryRowData
}[]
beforeEach(async () => {
rows = await populatePrimaryRows(rowsCount)
})
it("get request returns all of them", async () => {
const res = await getAll(primaryPostgresTable._id)
expect(res.status).toBe(200)
expect(res.body).toHaveLength(rowsCount)
expect(res.body).toEqual(
expect.arrayContaining(
rows.map(r => expect.objectContaining(r.rowData))
)
)
})
})
describe("given multiple tables with multiple rows", () => {
const rowsCount = 6
beforeEach(async () => {
const createRandomTableWithRows = async () =>
await config.createRow({
tableId: (await createDefaultPgTable())._id,
title: generator.name(),
})
await createRandomTableWithRows()
await populatePrimaryRows(rowsCount)
await createRandomTableWithRows()
})
it("get returns the requested ones", async () => {
const res = await getAll(primaryPostgresTable._id)
expect(res.status).toBe(200)
expect(res.body).toHaveLength(rowsCount)
})
})
})
})

View file

@ -90,10 +90,15 @@ function parseFilters(filters: SearchFilters | undefined): SearchFilters {
function generateSelectStatement( function generateSelectStatement(
json: QueryJson, json: QueryJson,
knex: Knex knex: Knex
): (string | Knex.Raw)[] { ): (string | Knex.Raw)[] | "*" {
const { resource, meta } = json const { resource, meta } = json
if (!resource) {
return "*"
}
const schema = meta?.table?.schema const schema = meta?.table?.schema
return resource!.fields.map(field => { return resource.fields.map(field => {
const fieldNames = field.split(/\./g) const fieldNames = field.split(/\./g)
const tableName = fieldNames[0] const tableName = fieldNames[0]
const columnName = fieldNames[1] const columnName = fieldNames[1]
@ -392,11 +397,14 @@ class InternalBuilder {
delete parsedBody[key] delete parsedBody[key]
} }
} }
// mysql can't use returning // mysql can't use returning
if (opts.disableReturning) { if (opts.disableReturning) {
return query.insert(parsedBody) return query.insert(parsedBody)
} else { } else {
return query.insert(parsedBody).returning("*") return query
.insert(parsedBody)
.returning(generateSelectStatement(json, knex))
} }
} }
@ -481,7 +489,9 @@ class InternalBuilder {
if (opts.disableReturning) { if (opts.disableReturning) {
return query.update(parsedBody) return query.update(parsedBody)
} else { } else {
return query.update(parsedBody).returning("*") return query
.update(parsedBody)
.returning(generateSelectStatement(json, knex))
} }
} }
@ -496,7 +506,7 @@ class InternalBuilder {
if (opts.disableReturning) { if (opts.disableReturning) {
return query.delete() return query.delete()
} else { } else {
return query.delete().returning("*") return query.delete().returning(generateSelectStatement(json, knex))
} }
} }
} }

View file

@ -67,6 +67,15 @@ if (
INTEGRATIONS[SourceName.ORACLE] = oracle.integration INTEGRATIONS[SourceName.ORACLE] = oracle.integration
} }
export async function getDefinition(source: SourceName): Promise<Integration> {
// check if its integrated, faster
if (DEFINITIONS[source]) {
return DEFINITIONS[source]
}
const allDefinitions = await getDefinitions()
return allDefinitions[source]
}
export async function getDefinitions() { export async function getDefinitions() {
const pluginSchemas: { [key: string]: Integration } = {} const pluginSchemas: { [key: string]: Integration } = {}
if (env.SELF_HOSTED) { if (env.SELF_HOSTED) {

View file

@ -26,7 +26,7 @@ interface MSSQLConfig {
user: string user: string
password: string password: string
server: string server: string
port: number port: number | string
database: string database: string
schema: string schema: string
encrypt?: boolean encrypt?: boolean

View file

@ -247,7 +247,7 @@ class OracleIntegration extends Sql implements DatasourcePlus {
) )
} }
private internalConvertType(column: OracleColumn): { type: string } { private internalConvertType(column: OracleColumn): { type: FieldTypes } {
if (this.isBooleanType(column)) { if (this.isBooleanType(column)) {
return { type: FieldTypes.BOOLEAN } return { type: FieldTypes.BOOLEAN }
} }

View file

@ -93,24 +93,16 @@ describe("migrations", () => {
await clearMigrations() await clearMigrations()
const appId = config.prodAppId const appId = config.prodAppId
const roles = { [appId]: "role_12345" } const roles = { [appId]: "role_12345" }
await config.createUser( await config.createUser({
undefined, builder: false,
undefined, admin: true,
undefined, roles,
undefined, }) // admin only
false, await config.createUser({
true, builder: false,
roles admin: false,
) // admin only roles,
await config.createUser( }) // non admin non builder
undefined,
undefined,
undefined,
undefined,
false,
false,
roles
) // non admin non builder
await config.createTable() await config.createTable()
await config.createRow() await config.createRow()
await config.createRow() await config.createRow()

View file

@ -3,6 +3,7 @@ import { findHBSBlocks, processObjectSync } from "@budibase/string-templates"
import { import {
Datasource, Datasource,
DatasourceFieldType, DatasourceFieldType,
Integration,
PASSWORD_REPLACEMENT, PASSWORD_REPLACEMENT,
RestAuthConfig, RestAuthConfig,
RestAuthType, RestAuthType,
@ -11,16 +12,38 @@ import {
} from "@budibase/types" } from "@budibase/types"
import { cloneDeep } from "lodash/fp" import { cloneDeep } from "lodash/fp"
import { getEnvironmentVariables } from "../../utils" import { getEnvironmentVariables } from "../../utils"
import { getDefinitions } from "../../../integrations" import { getDefinitions, getDefinition } from "../../../integrations"
const ENV_VAR_PREFIX = "env." const ENV_VAR_PREFIX = "env."
export function checkDatasourceTypes(schema: Integration, config: any) {
for (let key of Object.keys(config)) {
if (!schema.datasource[key]) {
continue
}
const type = schema.datasource[key].type
if (
type === DatasourceFieldType.NUMBER &&
typeof config[key] === "string"
) {
config[key] = parseFloat(config[key])
}
}
return config
}
async function enrichDatasourceWithValues(datasource: Datasource) { async function enrichDatasourceWithValues(datasource: Datasource) {
const cloned = cloneDeep(datasource) const cloned = cloneDeep(datasource)
const env = await getEnvironmentVariables() const env = await getEnvironmentVariables()
const processed = processObjectSync(cloned, { env }, { onlyFound: true }) const processed = processObjectSync(
cloned,
{ env },
{ onlyFound: true }
) as Datasource
const definition = await getDefinition(processed.source)
processed.config = checkDatasourceTypes(definition, processed.config)
return { return {
datasource: processed as Datasource, datasource: processed,
envVars: env as Record<string, string>, envVars: env as Record<string, string>,
} }
} }
@ -65,6 +88,9 @@ export async function removeSecrets(datasources: Datasource[]) {
const definitions = await getDefinitions() const definitions = await getDefinitions()
for (let datasource of datasources) { for (let datasource of datasources) {
const schema = definitions[datasource.source] const schema = definitions[datasource.source]
if (!schema) {
continue
}
if (datasource.config) { if (datasource.config) {
// strip secrets from response, so they don't show in the network request // strip secrets from response, so they don't show in the network request
if (datasource.config.auth) { if (datasource.config.auth) {

View file

@ -10,7 +10,9 @@ if (!process.env.DEBUG) {
if (!process.env.CI) { if (!process.env.CI) {
// set a longer timeout in dev for debugging // set a longer timeout in dev for debugging
// 100 seconds // 100 seconds
jest.setTimeout(100000) jest.setTimeout(100 * 1000)
} else {
jest.setTimeout(10 * 1000)
} }
testContainerUtils.setupEnv(env, coreEnv) testContainerUtils.setupEnv(env, coreEnv)

View file

@ -39,8 +39,15 @@ import { cleanup } from "../../utilities/fileSystem"
import newid from "../../db/newid" import newid from "../../db/newid"
import { generateUserMetadataID } from "../../db/utils" import { generateUserMetadataID } from "../../db/utils"
import { startup } from "../../startup" import { startup } from "../../startup"
import { AuthToken, Database } from "@budibase/types" import supertest from "supertest"
const supertest = require("supertest") import {
AuthToken,
Database,
Datasource,
Row,
SourceName,
Table,
} from "@budibase/types"
type DefaultUserValues = { type DefaultUserValues = {
globalUserId: string globalUserId: string
@ -52,7 +59,7 @@ type DefaultUserValues = {
class TestConfiguration { class TestConfiguration {
server: any server: any
request: any request: supertest.SuperTest<supertest.Test> | undefined
started: boolean started: boolean
appId: string | null appId: string | null
allApps: any[] allApps: any[]
@ -197,7 +204,7 @@ class TestConfiguration {
// UTILS // UTILS
async _req(body: any, params: any, controlFunc: any) { _req(body: any, params: any, controlFunc: any) {
// create a fake request ctx // create a fake request ctx
const request: any = {} const request: any = {}
const appId = this.appId const appId = this.appId
@ -268,14 +275,24 @@ class TestConfiguration {
} }
async createUser( async createUser(
id = null, user: {
firstName = this.defaultUserValues.firstName, id?: string
lastName = this.defaultUserValues.lastName, firstName?: string
email = this.defaultUserValues.email, lastName?: string
builder = true, email?: string
admin = false, builder?: boolean
roles = {} admin?: boolean
roles?: any
} = {}
) { ) {
let { id, firstName, lastName, email, builder, admin, roles } = user
firstName = firstName || this.defaultUserValues.firstName
lastName = lastName || this.defaultUserValues.lastName
email = email || this.defaultUserValues.email
roles = roles || {}
if (builder == null) {
builder = true
}
const globalId = !id ? `us_${Math.random()}` : `us_${id}` const globalId = !id ? `us_${Math.random()}` : `us_${id}`
const resp = await this.globalUser({ const resp = await this.globalUser({
id: globalId, id: globalId,
@ -360,6 +377,7 @@ class TestConfiguration {
[constants.Header.CSRF_TOKEN]: this.defaultUserValues.csrfToken, [constants.Header.CSRF_TOKEN]: this.defaultUserValues.csrfToken,
...extras, ...extras,
} }
if (this.appId) { if (this.appId) {
headers[constants.Header.APP_ID] = this.appId headers[constants.Header.APP_ID] = this.appId
} }
@ -464,13 +482,13 @@ class TestConfiguration {
// TABLE // TABLE
async updateTable(config?: any) { async updateTable(config?: any): Promise<Table> {
config = config || basicTable() config = config || basicTable()
this.table = await this._req(config, null, controllers.table.save) this.table = await this._req(config, null, controllers.table.save)
return this.table return this.table
} }
async createTable(config?: any) { async createTable(config?: Table) {
if (config != null && config._id) { if (config != null && config._id) {
delete config._id delete config._id
} }
@ -514,7 +532,7 @@ class TestConfiguration {
// ROW // ROW
async createRow(config: any = null) { async createRow(config?: Row): Promise<Row> {
if (!this.table) { if (!this.table) {
throw "Test requires table to be configured." throw "Test requires table to be configured."
} }
@ -523,7 +541,7 @@ class TestConfiguration {
return this._req(config, { tableId }, controllers.row.save) return this._req(config, { tableId }, controllers.row.save)
} }
async getRow(tableId: string, rowId: string) { async getRow(tableId: string, rowId: string): Promise<Row> {
return this._req(null, { tableId, rowId }, controllers.row.find) return this._req(null, { tableId, rowId }, controllers.row.find)
} }
@ -605,7 +623,9 @@ class TestConfiguration {
// DATASOURCE // DATASOURCE
async createDatasource(config?: any) { async createDatasource(config?: {
datasource: Datasource
}): Promise<Datasource> {
config = config || basicDatasource() config = config || basicDatasource()
const response = await this._req(config, null, controllers.datasource.save) const response = await this._req(config, null, controllers.datasource.save)
this.datasource = response.datasource this.datasource = response.datasource
@ -626,7 +646,7 @@ class TestConfiguration {
return this.createDatasource({ return this.createDatasource({
datasource: { datasource: {
...basicDatasource().datasource, ...basicDatasource().datasource,
source: "REST", source: SourceName.REST,
config: cfg || {}, config: cfg || {},
}, },
}) })
@ -635,7 +655,7 @@ class TestConfiguration {
async dynamicVariableDatasource() { async dynamicVariableDatasource() {
let datasource = await this.restDatasource() let datasource = await this.restDatasource()
const basedOnQuery = await this.createQuery({ const basedOnQuery = await this.createQuery({
...basicQuery(datasource._id), ...basicQuery(datasource._id!),
fields: { fields: {
path: "www.google.com", path: "www.google.com",
}, },
@ -663,7 +683,7 @@ class TestConfiguration {
datasource: any, datasource: any,
fields: any, fields: any,
params: any, params: any,
verb: string verb?: string
) { ) {
return request return request
.post(`/api/queries/preview`) .post(`/api/queries/preview`)

View file

@ -7,6 +7,8 @@ import {
Automation, Automation,
AutomationActionStepId, AutomationActionStepId,
AutomationTriggerStepId, AutomationTriggerStepId,
Datasource,
SourceName,
} from "@budibase/types" } from "@budibase/types"
const { v4: uuidv4 } = require("uuid") const { v4: uuidv4 } = require("uuid")
@ -207,12 +209,12 @@ export function basicRole() {
} }
} }
export function basicDatasource() { export function basicDatasource(): { datasource: Datasource } {
return { return {
datasource: { datasource: {
type: "datasource", type: "datasource",
name: "Test", name: "Test",
source: "POSTGRES", source: SourceName.POSTGRES,
config: {}, config: {},
}, },
} }
@ -255,3 +257,15 @@ export function basicWebhook(automationId: string) {
}, },
} }
} }
export function basicEnvironmentVariable(
name: string,
prod: string,
dev?: string
) {
return {
name,
production: prod,
development: dev || prod,
}
}

View file

@ -464,13 +464,17 @@ export function execute(job: Job, callback: WorkerCallback) {
throw new Error("Unable to execute, event doesn't contain app ID.") throw new Error("Unable to execute, event doesn't contain app ID.")
} }
return context.doInAppContext(appId, async () => { return context.doInAppContext(appId, async () => {
const automationOrchestrator = new Orchestrator(job) const envVars = await sdkUtils.getEnvironmentVariables()
try { // put into automation thread for whole context
const response = await automationOrchestrator.execute() await context.doInEnvironmentContext(envVars, async () => {
callback(null, response) const automationOrchestrator = new Orchestrator(job)
} catch (err) { try {
callback(err) const response = await automationOrchestrator.execute()
} callback(null, response)
} catch (err) {
callback(err)
}
})
}) })
} }
@ -480,11 +484,7 @@ export const removeStalled = async (job: Job) => {
throw new Error("Unable to execute, event doesn't contain app ID.") throw new Error("Unable to execute, event doesn't contain app ID.")
} }
await context.doInAppContext(appId, async () => { await context.doInAppContext(appId, async () => {
const envVars = await sdkUtils.getEnvironmentVariables() const automationOrchestrator = new Orchestrator(job)
// put into automation thread for whole context await automationOrchestrator.stopCron("stalled")
await context.doInEnvironmentContext(envVars, async () => {
const automationOrchestrator = new Orchestrator(job)
await automationOrchestrator.stopCron("stalled")
})
}) })
} }

View file

@ -1278,14 +1278,14 @@
resolved "https://registry.yarnpkg.com/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz#75a2e8b51cb758a7553d6804a5932d7aace75c39" resolved "https://registry.yarnpkg.com/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz#75a2e8b51cb758a7553d6804a5932d7aace75c39"
integrity sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw== integrity sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==
"@budibase/backend-core@2.3.2-alpha.0": "@budibase/backend-core@2.3.11-alpha.0":
version "2.3.2-alpha.0" version "2.3.11-alpha.0"
resolved "https://registry.yarnpkg.com/@budibase/backend-core/-/backend-core-2.3.2-alpha.0.tgz#1ee32fe176f6144460c7722264a09045be10f0a7" resolved "https://registry.yarnpkg.com/@budibase/backend-core/-/backend-core-2.3.11-alpha.0.tgz#361e30139a1a26d023902c6bdb4fdcac2610b69f"
integrity sha512-J5TbzhkgJt6/+ehKoVPQNoNnaTCCXD4iQhsB3Td7xbuwjDq49P1a9ZJXKtsqr+CzcD7MsiGh7DiSslie6scBFA== integrity sha512-hlaeTkYsSJJYIwwqL3LJ7Pxzq0tOgSjQ38+nFiBPIzjkDSaV0UPwi0rZAZq/kPvaT7AKdTEcf8tn1wiui/VkYA==
dependencies: dependencies:
"@budibase/nano" "10.1.1" "@budibase/nano" "10.1.1"
"@budibase/pouchdb-replication-stream" "1.2.10" "@budibase/pouchdb-replication-stream" "1.2.10"
"@budibase/types" "2.3.2-alpha.0" "@budibase/types" "2.3.11-alpha.0"
"@shopify/jest-koa-mocks" "5.0.1" "@shopify/jest-koa-mocks" "5.0.1"
"@techpass/passport-openidconnect" "0.3.2" "@techpass/passport-openidconnect" "0.3.2"
aws-cloudfront-sign "2.2.0" aws-cloudfront-sign "2.2.0"
@ -1392,13 +1392,13 @@
pouchdb-promise "^6.0.4" pouchdb-promise "^6.0.4"
through2 "^2.0.0" through2 "^2.0.0"
"@budibase/pro@2.3.2-alpha.0": "@budibase/pro@2.3.11-alpha.0":
version "2.3.2-alpha.0" version "2.3.11-alpha.0"
resolved "https://registry.yarnpkg.com/@budibase/pro/-/pro-2.3.2-alpha.0.tgz#b4406ec116f5fa008c7fc7327471302d9de4f29e" resolved "https://registry.yarnpkg.com/@budibase/pro/-/pro-2.3.11-alpha.0.tgz#b59edba2c04e2a490f3d9246cd328d8dd3213295"
integrity sha512-vVI/xZ6ybFSVGbHZvfomxCNyy0KvrBpYiOdUHjexPd60590tkT+cfZAuPrh3FdTTM8g2VipsxNWuO1OEQU7dcw== integrity sha512-z7tLgRKYKm1psNZGdRErQT8tV+vSj9hXJkD/H/uFxsJ3IQkjWbcFP5FUBPads5RnvCtqOfYu8OpRHCJfNNBoCA==
dependencies: dependencies:
"@budibase/backend-core" "2.3.2-alpha.0" "@budibase/backend-core" "2.3.11-alpha.0"
"@budibase/types" "2.3.2-alpha.0" "@budibase/types" "2.3.11-alpha.0"
"@koa/router" "8.0.8" "@koa/router" "8.0.8"
bull "4.10.1" bull "4.10.1"
joi "17.6.0" joi "17.6.0"
@ -1424,10 +1424,10 @@
svelte-apexcharts "^1.0.2" svelte-apexcharts "^1.0.2"
svelte-flatpickr "^3.1.0" svelte-flatpickr "^3.1.0"
"@budibase/types@2.3.2-alpha.0": "@budibase/types@2.3.11-alpha.0":
version "2.3.2-alpha.0" version "2.3.11-alpha.0"
resolved "https://registry.yarnpkg.com/@budibase/types/-/types-2.3.2-alpha.0.tgz#00b1afc4b937a5a49772bd78d189443f8e4eb0e3" resolved "https://registry.yarnpkg.com/@budibase/types/-/types-2.3.11-alpha.0.tgz#55cdcef6fb30c79e9d7d90ae8789e4f0cc6a2b63"
integrity sha512-kVwXzA8ROg27tIyNZnTd7ZszRepP4D7NgrW2gZsygsUoiYZ+WO1rCIlWj1SFPBur+V/k8dShQkh1hwxpj8JPVg== integrity sha512-SFW9vManFRJ45bgMW7wkDiNxiQQfAME8HOmSqCHKDC0OPzsDydygIl7BNeF/domFvCXxDD3NPu7HC2JXMlV6cw==
"@bull-board/api@3.7.0": "@bull-board/api@3.7.0":
version "3.7.0" version "3.7.0"

View file

@ -1,6 +1,6 @@
{ {
"name": "@budibase/string-templates", "name": "@budibase/string-templates",
"version": "2.3.2-alpha.0", "version": "2.3.11-alpha.0",
"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

@ -5,6 +5,7 @@
"allowJs": true, "allowJs": true,
"declaration": true, "declaration": true,
"emitDeclarationOnly": true, "emitDeclarationOnly": true,
"outDir": "dist" "outDir": "dist",
"esModuleInterop": true
} }
} }

View file

@ -1,6 +1,6 @@
{ {
"name": "@budibase/types", "name": "@budibase/types",
"version": "2.3.2-alpha.0", "version": "2.3.11-alpha.0",
"description": "Budibase types", "description": "Budibase types",
"main": "dist/index.js", "main": "dist/index.js",
"types": "dist/index.d.ts", "types": "dist/index.d.ts",

View file

@ -14,6 +14,7 @@ export enum FieldType {
AUTO = "auto", AUTO = "auto",
JSON = "json", JSON = "json",
INTERNAL = "internal", INTERNAL = "internal",
BARCODEQR = "barcodeqr",
} }
export interface RowAttachment { export interface RowAttachment {

View file

@ -1,16 +1,22 @@
import { Document } from "../document" import { Document } from "../document"
import { View } from "./view" import { View } from "./view"
import { RenameColumn } from "../../sdk" import { RenameColumn } from "../../sdk"
import { FieldType } from "./row"
export enum RelationshipTypes {
ONE_TO_MANY = "one-to-many",
MANY_TO_ONE = "many-to-one",
MANY_TO_MANY = "many-to-many",
}
export interface FieldSchema { export interface FieldSchema {
// TODO: replace with field types enum when done type: FieldType
type: string
externalType?: string externalType?: string
fieldName?: string fieldName?: string
name: string name: string
sortable?: boolean sortable?: boolean
tableId?: string tableId?: string
relationshipType?: string relationshipType?: RelationshipTypes
through?: string through?: string
foreignKey?: string foreignKey?: string
icon?: string icon?: string

View file

@ -1,7 +1,7 @@
{ {
"name": "@budibase/worker", "name": "@budibase/worker",
"email": "hi@budibase.com", "email": "hi@budibase.com",
"version": "2.3.2-alpha.0", "version": "2.3.11-alpha.0",
"description": "Budibase background service", "description": "Budibase background service",
"main": "src/index.ts", "main": "src/index.ts",
"repository": { "repository": {
@ -36,10 +36,10 @@
"author": "Budibase", "author": "Budibase",
"license": "GPL-3.0", "license": "GPL-3.0",
"dependencies": { "dependencies": {
"@budibase/backend-core": "2.3.2-alpha.0", "@budibase/backend-core": "2.3.11-alpha.0",
"@budibase/pro": "2.3.2-alpha.0", "@budibase/pro": "2.3.11-alpha.0",
"@budibase/string-templates": "2.3.2-alpha.0", "@budibase/string-templates": "2.3.11-alpha.0",
"@budibase/types": "2.3.2-alpha.0", "@budibase/types": "2.3.11-alpha.0",
"@koa/router": "8.0.8", "@koa/router": "8.0.8",
"@sentry/node": "6.17.7", "@sentry/node": "6.17.7",
"@techpass/passport-openidconnect": "0.3.2", "@techpass/passport-openidconnect": "0.3.2",

View file

@ -18,7 +18,9 @@ if (!process.env.DEBUG) {
if (!process.env.CI) { if (!process.env.CI) {
// set a longer timeout in dev for debugging // set a longer timeout in dev for debugging
// 100 seconds // 100 seconds
jest.setTimeout(100000) jest.setTimeout(100 * 1000)
} else {
jest.setTimeout(10 * 1000)
} }
testContainerUtils.setupEnv(env, coreEnv) testContainerUtils.setupEnv(env, coreEnv)

View file

@ -475,14 +475,14 @@
resolved "https://registry.yarnpkg.com/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz#75a2e8b51cb758a7553d6804a5932d7aace75c39" resolved "https://registry.yarnpkg.com/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz#75a2e8b51cb758a7553d6804a5932d7aace75c39"
integrity sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw== integrity sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==
"@budibase/backend-core@2.3.2-alpha.0": "@budibase/backend-core@2.3.11-alpha.0":
version "2.3.2-alpha.0" version "2.3.11-alpha.0"
resolved "https://registry.yarnpkg.com/@budibase/backend-core/-/backend-core-2.3.2-alpha.0.tgz#1ee32fe176f6144460c7722264a09045be10f0a7" resolved "https://registry.yarnpkg.com/@budibase/backend-core/-/backend-core-2.3.11-alpha.0.tgz#361e30139a1a26d023902c6bdb4fdcac2610b69f"
integrity sha512-J5TbzhkgJt6/+ehKoVPQNoNnaTCCXD4iQhsB3Td7xbuwjDq49P1a9ZJXKtsqr+CzcD7MsiGh7DiSslie6scBFA== integrity sha512-hlaeTkYsSJJYIwwqL3LJ7Pxzq0tOgSjQ38+nFiBPIzjkDSaV0UPwi0rZAZq/kPvaT7AKdTEcf8tn1wiui/VkYA==
dependencies: dependencies:
"@budibase/nano" "10.1.1" "@budibase/nano" "10.1.1"
"@budibase/pouchdb-replication-stream" "1.2.10" "@budibase/pouchdb-replication-stream" "1.2.10"
"@budibase/types" "2.3.2-alpha.0" "@budibase/types" "2.3.11-alpha.0"
"@shopify/jest-koa-mocks" "5.0.1" "@shopify/jest-koa-mocks" "5.0.1"
"@techpass/passport-openidconnect" "0.3.2" "@techpass/passport-openidconnect" "0.3.2"
aws-cloudfront-sign "2.2.0" aws-cloudfront-sign "2.2.0"
@ -539,13 +539,13 @@
pouchdb-promise "^6.0.4" pouchdb-promise "^6.0.4"
through2 "^2.0.0" through2 "^2.0.0"
"@budibase/pro@2.3.2-alpha.0": "@budibase/pro@2.3.11-alpha.0":
version "2.3.2-alpha.0" version "2.3.11-alpha.0"
resolved "https://registry.yarnpkg.com/@budibase/pro/-/pro-2.3.2-alpha.0.tgz#b4406ec116f5fa008c7fc7327471302d9de4f29e" resolved "https://registry.yarnpkg.com/@budibase/pro/-/pro-2.3.11-alpha.0.tgz#b59edba2c04e2a490f3d9246cd328d8dd3213295"
integrity sha512-vVI/xZ6ybFSVGbHZvfomxCNyy0KvrBpYiOdUHjexPd60590tkT+cfZAuPrh3FdTTM8g2VipsxNWuO1OEQU7dcw== integrity sha512-z7tLgRKYKm1psNZGdRErQT8tV+vSj9hXJkD/H/uFxsJ3IQkjWbcFP5FUBPads5RnvCtqOfYu8OpRHCJfNNBoCA==
dependencies: dependencies:
"@budibase/backend-core" "2.3.2-alpha.0" "@budibase/backend-core" "2.3.11-alpha.0"
"@budibase/types" "2.3.2-alpha.0" "@budibase/types" "2.3.11-alpha.0"
"@koa/router" "8.0.8" "@koa/router" "8.0.8"
bull "4.10.1" bull "4.10.1"
joi "17.6.0" joi "17.6.0"
@ -553,10 +553,10 @@
lru-cache "^7.14.1" lru-cache "^7.14.1"
node-fetch "^2.6.1" node-fetch "^2.6.1"
"@budibase/types@2.3.2-alpha.0": "@budibase/types@2.3.11-alpha.0":
version "2.3.2-alpha.0" version "2.3.11-alpha.0"
resolved "https://registry.yarnpkg.com/@budibase/types/-/types-2.3.2-alpha.0.tgz#00b1afc4b937a5a49772bd78d189443f8e4eb0e3" resolved "https://registry.yarnpkg.com/@budibase/types/-/types-2.3.11-alpha.0.tgz#55cdcef6fb30c79e9d7d90ae8789e4f0cc6a2b63"
integrity sha512-kVwXzA8ROg27tIyNZnTd7ZszRepP4D7NgrW2gZsygsUoiYZ+WO1rCIlWj1SFPBur+V/k8dShQkh1hwxpj8JPVg== integrity sha512-SFW9vManFRJ45bgMW7wkDiNxiQQfAME8HOmSqCHKDC0OPzsDydygIl7BNeF/domFvCXxDD3NPu7HC2JXMlV6cw==
"@cspotcode/source-map-support@^0.8.0": "@cspotcode/source-map-support@^0.8.0":
version "0.8.1" version "0.8.1"