Merge branch 'develop' of github.com:Budibase/budibase into feature/audit-logs
This commit is contained in:
commit
c2b7c6f388
|
@ -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: ''
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
|
@ -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
|
|
|
@ -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: ''
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
{
|
{
|
||||||
"version": "2.3.1",
|
"version": "2.3.2-alpha.3",
|
||||||
"npmClient": "yarn",
|
"npmClient": "yarn",
|
||||||
"packages": [
|
"packages": [
|
||||||
"packages/*"
|
"packages/*"
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "@budibase/backend-core",
|
"name": "@budibase/backend-core",
|
||||||
"version": "2.3.1",
|
"version": "2.3.2-alpha.3",
|
||||||
"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",
|
||||||
|
@ -23,7 +23,8 @@
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@budibase/nano": "10.1.1",
|
"@budibase/nano": "10.1.1",
|
||||||
"@budibase/types": "^2.3.1",
|
"@budibase/pouchdb-replication-stream": "1.2.10",
|
||||||
|
"@budibase/types": "2.3.2-alpha.3",
|
||||||
"@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",
|
||||||
|
@ -48,7 +49,6 @@
|
||||||
"posthog-node": "1.3.0",
|
"posthog-node": "1.3.0",
|
||||||
"pouchdb": "7.3.0",
|
"pouchdb": "7.3.0",
|
||||||
"pouchdb-find": "7.2.2",
|
"pouchdb-find": "7.2.2",
|
||||||
"@budibase/pouchdb-replication-stream": "1.2.10",
|
|
||||||
"redlock": "4.2.0",
|
"redlock": "4.2.0",
|
||||||
"sanitize-s3-objectkey": "0.0.1",
|
"sanitize-s3-objectkey": "0.0.1",
|
||||||
"semver": "7.3.7",
|
"semver": "7.3.7",
|
||||||
|
|
|
@ -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) => {
|
||||||
|
|
|
@ -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)
|
||||||
}
|
}
|
||||||
|
|
|
@ -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.1",
|
"version": "2.3.2-alpha.3",
|
||||||
"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.1",
|
"@budibase/string-templates": "2.3.2-alpha.3",
|
||||||
"@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",
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "@budibase/builder",
|
"name": "@budibase/builder",
|
||||||
"version": "2.3.1",
|
"version": "2.3.2-alpha.3",
|
||||||
"license": "GPL-3.0",
|
"license": "GPL-3.0",
|
||||||
"private": true,
|
"private": true,
|
||||||
"scripts": {
|
"scripts": {
|
||||||
|
@ -58,10 +58,10 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@budibase/bbui": "^2.3.1",
|
"@budibase/bbui": "2.3.2-alpha.3",
|
||||||
"@budibase/client": "^2.3.1",
|
"@budibase/client": "2.3.2-alpha.3",
|
||||||
"@budibase/frontend-core": "^2.3.1",
|
"@budibase/frontend-core": "2.3.2-alpha.3",
|
||||||
"@budibase/string-templates": "^2.3.1",
|
"@budibase/string-templates": "2.3.2-alpha.3",
|
||||||
"@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",
|
||||||
|
|
|
@ -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}
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "@budibase/cli",
|
"name": "@budibase/cli",
|
||||||
"version": "2.3.1",
|
"version": "2.3.2-alpha.3",
|
||||||
"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.1",
|
"@budibase/backend-core": "2.3.2-alpha.3",
|
||||||
"@budibase/string-templates": "^2.3.1",
|
"@budibase/string-templates": "2.3.2-alpha.3",
|
||||||
"@budibase/types": "^2.3.1",
|
"@budibase/types": "2.3.2-alpha.3",
|
||||||
"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",
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "@budibase/client",
|
"name": "@budibase/client",
|
||||||
"version": "2.3.1",
|
"version": "2.3.2-alpha.3",
|
||||||
"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.1",
|
"@budibase/bbui": "2.3.2-alpha.3",
|
||||||
"@budibase/frontend-core": "^2.3.1",
|
"@budibase/frontend-core": "2.3.2-alpha.3",
|
||||||
"@budibase/string-templates": "^2.3.1",
|
"@budibase/string-templates": "2.3.2-alpha.3",
|
||||||
"@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",
|
||||||
|
|
|
@ -1,12 +1,12 @@
|
||||||
{
|
{
|
||||||
"name": "@budibase/frontend-core",
|
"name": "@budibase/frontend-core",
|
||||||
"version": "2.3.1",
|
"version": "2.3.2-alpha.3",
|
||||||
"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.1",
|
"@budibase/bbui": "2.3.2-alpha.3",
|
||||||
"lodash": "^4.17.21",
|
"lodash": "^4.17.21",
|
||||||
"svelte": "^3.46.2"
|
"svelte": "^3.46.2"
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "@budibase/sdk",
|
"name": "@budibase/sdk",
|
||||||
"version": "2.3.1",
|
"version": "2.3.2-alpha.3",
|
||||||
"description": "Budibase Public API SDK",
|
"description": "Budibase Public API SDK",
|
||||||
"author": "Budibase",
|
"author": "Budibase",
|
||||||
"license": "MPL-2.0",
|
"license": "MPL-2.0",
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
{
|
{
|
||||||
"name": "@budibase/server",
|
"name": "@budibase/server",
|
||||||
"email": "hi@budibase.com",
|
"email": "hi@budibase.com",
|
||||||
"version": "2.3.1",
|
"version": "2.3.2-alpha.3",
|
||||||
"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.1",
|
"@budibase/backend-core": "2.3.2-alpha.3",
|
||||||
"@budibase/client": "^2.3.1",
|
"@budibase/client": "2.3.2-alpha.3",
|
||||||
"@budibase/pro": "2.3.1",
|
"@budibase/pro": "2.3.2-alpha.3",
|
||||||
"@budibase/string-templates": "^2.3.1",
|
"@budibase/string-templates": "2.3.2-alpha.3",
|
||||||
"@budibase/types": "^2.3.1",
|
"@budibase/types": "2.3.2-alpha.3",
|
||||||
"@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",
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
@ -247,6 +257,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 +285,7 @@ export async function fetchEnrichedRow(ctx: BBContext) {
|
||||||
[primaryLink]: linkedIds,
|
[primaryLink]: linkedIds,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
includeSqlRelationships: IncludeRelationship.INCLUDE,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
return row
|
return row
|
||||||
|
|
|
@ -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")
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
|
@ -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)
|
||||||
}
|
}
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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 })
|
||||||
|
})
|
||||||
|
})
|
|
@ -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)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
|
@ -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",
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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"
|
||||||
|
|
|
@ -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)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
|
@ -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))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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 }
|
||||||
}
|
}
|
||||||
|
|
|
@ -65,6 +65,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) {
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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
|
||||||
|
@ -360,6 +367,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 +472,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 +522,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 +531,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 +613,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 +636,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 +645,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 +673,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`)
|
||||||
|
|
|
@ -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,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -1278,13 +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.1":
|
"@budibase/backend-core@2.3.2-alpha.3":
|
||||||
version "2.3.1"
|
version "2.3.2-alpha.3"
|
||||||
resolved "https://registry.yarnpkg.com/@budibase/backend-core/-/backend-core-2.3.1.tgz#9dc2effb3d62c4a2de5934a5ac712be1b402266a"
|
resolved "https://registry.yarnpkg.com/@budibase/backend-core/-/backend-core-2.3.2-alpha.3.tgz#798782018e5e4e1076d56bfc50cec01f48d73f5c"
|
||||||
integrity sha512-IXwSPJ/6yTCJAocpgw2aZznb7T3YeYspL3DfriwCL67tHZRq3zkq/uYYCAs9uYQObBr7OMKPJPTFOuh2MJk12w==
|
integrity sha512-S5yXD8YWSrxGv89Z4y3dYnbRTUZFlNVXuE6rtin4JesORi6TmverZ0DmerxD6+bTm57WraaQ+UBIKXgug3fZ+g==
|
||||||
dependencies:
|
dependencies:
|
||||||
"@budibase/nano" "10.1.1"
|
"@budibase/nano" "10.1.1"
|
||||||
"@budibase/types" "^2.3.1"
|
"@budibase/pouchdb-replication-stream" "1.2.10"
|
||||||
|
"@budibase/types" "2.3.2-alpha.3"
|
||||||
"@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"
|
||||||
|
@ -1309,7 +1310,6 @@
|
||||||
posthog-node "1.3.0"
|
posthog-node "1.3.0"
|
||||||
pouchdb "7.3.0"
|
pouchdb "7.3.0"
|
||||||
pouchdb-find "7.2.2"
|
pouchdb-find "7.2.2"
|
||||||
pouchdb-replication-stream "1.2.9"
|
|
||||||
redlock "4.2.0"
|
redlock "4.2.0"
|
||||||
sanitize-s3-objectkey "0.0.1"
|
sanitize-s3-objectkey "0.0.1"
|
||||||
semver "7.3.7"
|
semver "7.3.7"
|
||||||
|
@ -1379,13 +1379,26 @@
|
||||||
qs "^6.11.0"
|
qs "^6.11.0"
|
||||||
tough-cookie "^4.1.2"
|
tough-cookie "^4.1.2"
|
||||||
|
|
||||||
"@budibase/pro@2.3.1":
|
"@budibase/pouchdb-replication-stream@1.2.10":
|
||||||
version "2.3.1"
|
version "1.2.10"
|
||||||
resolved "https://registry.yarnpkg.com/@budibase/pro/-/pro-2.3.1.tgz#24d0bac89aee0270d92bab7bb7e2c987914ef902"
|
resolved "https://registry.yarnpkg.com/@budibase/pouchdb-replication-stream/-/pouchdb-replication-stream-1.2.10.tgz#4100df2effd7c823edadddcdbdc380f6827eebf5"
|
||||||
integrity sha512-vdpMAZbowrUEmdCxEvc7If0REK/9Q6KHPLJ6K99SYtS6STprYaAcO4Sa5GmmluHxuFnQjpYxF4R0CNS8C+C6xA==
|
integrity sha512-1zeorOwbelZ7HF5vFB+pKE8Mnh31om8k1M6T3AZXVULYTHLsyJrMTozSv5CJ1P8ZfOIJab09HDzCXDh2icFekg==
|
||||||
dependencies:
|
dependencies:
|
||||||
"@budibase/backend-core" "2.3.1"
|
argsarray "0.0.1"
|
||||||
"@budibase/types" "2.3.1"
|
inherits "^2.0.3"
|
||||||
|
lodash.pick "^4.0.0"
|
||||||
|
ndjson "^1.4.3"
|
||||||
|
pouch-stream "^0.4.0"
|
||||||
|
pouchdb-promise "^6.0.4"
|
||||||
|
through2 "^2.0.0"
|
||||||
|
|
||||||
|
"@budibase/pro@2.3.2-alpha.3":
|
||||||
|
version "2.3.2-alpha.3"
|
||||||
|
resolved "https://registry.yarnpkg.com/@budibase/pro/-/pro-2.3.2-alpha.3.tgz#e16d60721aab59f226fcbc69d749a52875357345"
|
||||||
|
integrity sha512-/AyS3WtzTyXYAxuzSkRHS8PWSH9Ug/e3iXN4TcawZXOnYYh6UatLQBvds13IZNtCppqx/Gicaq21Jo9939oGcQ==
|
||||||
|
dependencies:
|
||||||
|
"@budibase/backend-core" "2.3.2-alpha.3"
|
||||||
|
"@budibase/types" "2.3.2-alpha.3"
|
||||||
"@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"
|
||||||
|
@ -1411,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.1", "@budibase/types@^2.3.1":
|
"@budibase/types@2.3.2-alpha.3":
|
||||||
version "2.3.1"
|
version "2.3.2-alpha.3"
|
||||||
resolved "https://registry.yarnpkg.com/@budibase/types/-/types-2.3.1.tgz#5f8fb84f4273b920619a6308e956ca176ead4304"
|
resolved "https://registry.yarnpkg.com/@budibase/types/-/types-2.3.2-alpha.3.tgz#619c31f355786bdccc1553bcacd6274518ab9435"
|
||||||
integrity sha512-8+Qo7JPGnPv9FWx3AdOScWSElBsYcC7zZFFLeoqBy6y8a4UjtEEDIUZHpRf5gpcSUXuqbzAPbC+CpYJ6NVEvPw==
|
integrity sha512-eA0NdLEpE/2QwMPoP16bpyN7mqw85YxRzNYmjoqsiSNrQmuFc3uA1XuDoxRYB2f6WCEh0VaOXzYaOQvGctzDvw==
|
||||||
|
|
||||||
"@bull-board/api@3.7.0":
|
"@bull-board/api@3.7.0":
|
||||||
version "3.7.0"
|
version "3.7.0"
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "@budibase/string-templates",
|
"name": "@budibase/string-templates",
|
||||||
"version": "2.3.1",
|
"version": "2.3.2-alpha.3",
|
||||||
"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",
|
||||||
|
|
|
@ -5,6 +5,7 @@
|
||||||
"allowJs": true,
|
"allowJs": true,
|
||||||
"declaration": true,
|
"declaration": true,
|
||||||
"emitDeclarationOnly": true,
|
"emitDeclarationOnly": true,
|
||||||
"outDir": "dist"
|
"outDir": "dist",
|
||||||
|
"esModuleInterop": true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "@budibase/types",
|
"name": "@budibase/types",
|
||||||
"version": "2.3.1",
|
"version": "2.3.2-alpha.3",
|
||||||
"description": "Budibase types",
|
"description": "Budibase types",
|
||||||
"main": "dist/index.js",
|
"main": "dist/index.js",
|
||||||
"types": "dist/index.d.ts",
|
"types": "dist/index.d.ts",
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
{
|
{
|
||||||
"name": "@budibase/worker",
|
"name": "@budibase/worker",
|
||||||
"email": "hi@budibase.com",
|
"email": "hi@budibase.com",
|
||||||
"version": "2.3.1",
|
"version": "2.3.2-alpha.3",
|
||||||
"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.1",
|
"@budibase/backend-core": "2.3.2-alpha.3",
|
||||||
"@budibase/pro": "2.3.1",
|
"@budibase/pro": "2.3.2-alpha.3",
|
||||||
"@budibase/string-templates": "^2.3.1",
|
"@budibase/string-templates": "2.3.2-alpha.3",
|
||||||
"@budibase/types": "^2.3.1",
|
"@budibase/types": "2.3.2-alpha.3",
|
||||||
"@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",
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -475,13 +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.1":
|
"@budibase/backend-core@2.3.2-alpha.3":
|
||||||
version "2.3.1"
|
version "2.3.2-alpha.3"
|
||||||
resolved "https://registry.yarnpkg.com/@budibase/backend-core/-/backend-core-2.3.1.tgz#9dc2effb3d62c4a2de5934a5ac712be1b402266a"
|
resolved "https://registry.yarnpkg.com/@budibase/backend-core/-/backend-core-2.3.2-alpha.3.tgz#798782018e5e4e1076d56bfc50cec01f48d73f5c"
|
||||||
integrity sha512-IXwSPJ/6yTCJAocpgw2aZznb7T3YeYspL3DfriwCL67tHZRq3zkq/uYYCAs9uYQObBr7OMKPJPTFOuh2MJk12w==
|
integrity sha512-S5yXD8YWSrxGv89Z4y3dYnbRTUZFlNVXuE6rtin4JesORi6TmverZ0DmerxD6+bTm57WraaQ+UBIKXgug3fZ+g==
|
||||||
dependencies:
|
dependencies:
|
||||||
"@budibase/nano" "10.1.1"
|
"@budibase/nano" "10.1.1"
|
||||||
"@budibase/types" "^2.3.1"
|
"@budibase/pouchdb-replication-stream" "1.2.10"
|
||||||
|
"@budibase/types" "2.3.2-alpha.3"
|
||||||
"@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"
|
||||||
|
@ -506,7 +507,6 @@
|
||||||
posthog-node "1.3.0"
|
posthog-node "1.3.0"
|
||||||
pouchdb "7.3.0"
|
pouchdb "7.3.0"
|
||||||
pouchdb-find "7.2.2"
|
pouchdb-find "7.2.2"
|
||||||
pouchdb-replication-stream "1.2.9"
|
|
||||||
redlock "4.2.0"
|
redlock "4.2.0"
|
||||||
sanitize-s3-objectkey "0.0.1"
|
sanitize-s3-objectkey "0.0.1"
|
||||||
semver "7.3.7"
|
semver "7.3.7"
|
||||||
|
@ -526,13 +526,26 @@
|
||||||
qs "^6.11.0"
|
qs "^6.11.0"
|
||||||
tough-cookie "^4.1.2"
|
tough-cookie "^4.1.2"
|
||||||
|
|
||||||
"@budibase/pro@2.3.1":
|
"@budibase/pouchdb-replication-stream@1.2.10":
|
||||||
version "2.3.1"
|
version "1.2.10"
|
||||||
resolved "https://registry.yarnpkg.com/@budibase/pro/-/pro-2.3.1.tgz#24d0bac89aee0270d92bab7bb7e2c987914ef902"
|
resolved "https://registry.yarnpkg.com/@budibase/pouchdb-replication-stream/-/pouchdb-replication-stream-1.2.10.tgz#4100df2effd7c823edadddcdbdc380f6827eebf5"
|
||||||
integrity sha512-vdpMAZbowrUEmdCxEvc7If0REK/9Q6KHPLJ6K99SYtS6STprYaAcO4Sa5GmmluHxuFnQjpYxF4R0CNS8C+C6xA==
|
integrity sha512-1zeorOwbelZ7HF5vFB+pKE8Mnh31om8k1M6T3AZXVULYTHLsyJrMTozSv5CJ1P8ZfOIJab09HDzCXDh2icFekg==
|
||||||
dependencies:
|
dependencies:
|
||||||
"@budibase/backend-core" "2.3.1"
|
argsarray "0.0.1"
|
||||||
"@budibase/types" "2.3.1"
|
inherits "^2.0.3"
|
||||||
|
lodash.pick "^4.0.0"
|
||||||
|
ndjson "^1.4.3"
|
||||||
|
pouch-stream "^0.4.0"
|
||||||
|
pouchdb-promise "^6.0.4"
|
||||||
|
through2 "^2.0.0"
|
||||||
|
|
||||||
|
"@budibase/pro@2.3.2-alpha.3":
|
||||||
|
version "2.3.2-alpha.3"
|
||||||
|
resolved "https://registry.yarnpkg.com/@budibase/pro/-/pro-2.3.2-alpha.3.tgz#e16d60721aab59f226fcbc69d749a52875357345"
|
||||||
|
integrity sha512-/AyS3WtzTyXYAxuzSkRHS8PWSH9Ug/e3iXN4TcawZXOnYYh6UatLQBvds13IZNtCppqx/Gicaq21Jo9939oGcQ==
|
||||||
|
dependencies:
|
||||||
|
"@budibase/backend-core" "2.3.2-alpha.3"
|
||||||
|
"@budibase/types" "2.3.2-alpha.3"
|
||||||
"@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"
|
||||||
|
@ -540,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.1", "@budibase/types@^2.3.1":
|
"@budibase/types@2.3.2-alpha.3":
|
||||||
version "2.3.1"
|
version "2.3.2-alpha.3"
|
||||||
resolved "https://registry.yarnpkg.com/@budibase/types/-/types-2.3.1.tgz#5f8fb84f4273b920619a6308e956ca176ead4304"
|
resolved "https://registry.yarnpkg.com/@budibase/types/-/types-2.3.2-alpha.3.tgz#619c31f355786bdccc1553bcacd6274518ab9435"
|
||||||
integrity sha512-8+Qo7JPGnPv9FWx3AdOScWSElBsYcC7zZFFLeoqBy6y8a4UjtEEDIUZHpRf5gpcSUXuqbzAPbC+CpYJ6NVEvPw==
|
integrity sha512-eA0NdLEpE/2QwMPoP16bpyN7mqw85YxRzNYmjoqsiSNrQmuFc3uA1XuDoxRYB2f6WCEh0VaOXzYaOQvGctzDvw==
|
||||||
|
|
||||||
"@cspotcode/source-map-support@^0.8.0":
|
"@cspotcode/source-map-support@^0.8.0":
|
||||||
version "0.8.1"
|
version "0.8.1"
|
||||||
|
@ -6548,19 +6561,6 @@ pouchdb-promise@6.4.3, pouchdb-promise@^6.0.4:
|
||||||
dependencies:
|
dependencies:
|
||||||
lie "3.1.1"
|
lie "3.1.1"
|
||||||
|
|
||||||
pouchdb-replication-stream@1.2.9:
|
|
||||||
version "1.2.9"
|
|
||||||
resolved "https://registry.yarnpkg.com/pouchdb-replication-stream/-/pouchdb-replication-stream-1.2.9.tgz#aa4fa5d8f52df4825392f18e07c7e11acffc650a"
|
|
||||||
integrity sha512-hM8XRBfamTTUwRhKwLS/jSNouBhn9R/4ugdHNRD1EvJzwV8iImh6sDYbCU9PGuznjyOjXz6vpFRzKeI2KYfwnQ==
|
|
||||||
dependencies:
|
|
||||||
argsarray "0.0.1"
|
|
||||||
inherits "^2.0.3"
|
|
||||||
lodash.pick "^4.0.0"
|
|
||||||
ndjson "^1.4.3"
|
|
||||||
pouch-stream "^0.4.0"
|
|
||||||
pouchdb-promise "^6.0.4"
|
|
||||||
through2 "^2.0.0"
|
|
||||||
|
|
||||||
pouchdb-selector-core@7.2.2:
|
pouchdb-selector-core@7.2.2:
|
||||||
version "7.2.2"
|
version "7.2.2"
|
||||||
resolved "https://registry.yarnpkg.com/pouchdb-selector-core/-/pouchdb-selector-core-7.2.2.tgz#264d7436a8c8ac3801f39960e79875ef7f3879a0"
|
resolved "https://registry.yarnpkg.com/pouchdb-selector-core/-/pouchdb-selector-core-7.2.2.tgz#264d7436a8c8ac3801f39960e79875ef7f3879a0"
|
||||||
|
|
Loading…
Reference in New Issue