Merge branch 'develop' of github.com:Budibase/budibase into feature/audit-logs
This commit is contained in:
commit
b9c513c071
|
@ -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: ''
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
|
@ -55,7 +55,7 @@ http {
|
||||||
set $csp_style "style-src 'self' 'unsafe-inline' https://cdn.jsdelivr.net https://fonts.googleapis.com https://rsms.me https://maxcdn.bootstrapcdn.com";
|
set $csp_style "style-src 'self' 'unsafe-inline' https://cdn.jsdelivr.net https://fonts.googleapis.com https://rsms.me https://maxcdn.bootstrapcdn.com";
|
||||||
set $csp_object "object-src 'none'";
|
set $csp_object "object-src 'none'";
|
||||||
set $csp_base_uri "base-uri 'self'";
|
set $csp_base_uri "base-uri 'self'";
|
||||||
set $csp_connect "connect-src 'self' https://*.budibase.net https://api-iam.intercom.io https://api-iam.intercom.io https://api-ping.intercom.io https://app.posthog.com wss://nexus-websocket-a.intercom.io wss://nexus-websocket-b.intercom.io https://nexus-websocket-a.intercom.io https://nexus-websocket-b.intercom.io https://uploads.intercomcdn.com https://uploads.intercomusercontent.com https://*.s3.amazonaws.com https://*.s3.us-east-2.amazonaws.com https://*.s3.us-east-1.amazonaws.com https://*.s3.us-west-1.amazonaws.com https://*.s3.us-west-2.amazonaws.com https://*.s3.af-south-1.amazonaws.com https://*.s3.ap-east-1.amazonaws.com https://*.s3.ap-southeast-3.amazonaws.com https://*.s3.ap-south-1.amazonaws.com https://*.s3.ap-northeast-3.amazonaws.com https://*.s3.ap-northeast-2.amazonaws.com https://*.s3.ap-southeast-1.amazonaws.com https://*.s3.ap-southeast-2.amazonaws.com https://*.s3.ap-northeast-1.amazonaws.com https://*.s3.ca-central-1.amazonaws.com https://*.s3.cn-north-1.amazonaws.com https://*.s3.cn-northwest-1.amazonaws.com https://*.s3.eu-central-1.amazonaws.com https://*.s3.eu-west-1.amazonaws.com https://*.s3.eu-west-2.amazonaws.com https://*.s3.eu-south-1.amazonaws.com https://*.s3.eu-west-3.amazonaws.com https://*.s3.eu-north-1.amazonaws.com https://*.s3.sa-east-1.amazonaws.com https://*.s3.me-south-1.amazonaws.com https://*.s3.us-gov-east-1.amazonaws.com https://*.s3.us-gov-west-1.amazonaws.com";
|
set $csp_connect "connect-src 'self' https://*.budibase.net https://api-iam.intercom.io https://api-iam.intercom.io https://api-ping.intercom.io https://app.posthog.com wss://nexus-websocket-a.intercom.io wss://nexus-websocket-b.intercom.io https://nexus-websocket-a.intercom.io https://nexus-websocket-b.intercom.io https://uploads.intercomcdn.com https://uploads.intercomusercontent.com https://*.s3.amazonaws.com https://*.s3.us-east-2.amazonaws.com https://*.s3.us-east-1.amazonaws.com https://*.s3.us-west-1.amazonaws.com https://*.s3.us-west-2.amazonaws.com https://*.s3.af-south-1.amazonaws.com https://*.s3.ap-east-1.amazonaws.com https://*.s3.ap-southeast-3.amazonaws.com https://*.s3.ap-south-1.amazonaws.com https://*.s3.ap-northeast-3.amazonaws.com https://*.s3.ap-northeast-2.amazonaws.com https://*.s3.ap-southeast-1.amazonaws.com https://*.s3.ap-southeast-2.amazonaws.com https://*.s3.ap-northeast-1.amazonaws.com https://*.s3.ca-central-1.amazonaws.com https://*.s3.cn-north-1.amazonaws.com https://*.s3.cn-northwest-1.amazonaws.com https://*.s3.eu-central-1.amazonaws.com https://*.s3.eu-west-1.amazonaws.com https://*.s3.eu-west-2.amazonaws.com https://*.s3.eu-south-1.amazonaws.com https://*.s3.eu-west-3.amazonaws.com https://*.s3.eu-north-1.amazonaws.com https://*.s3.sa-east-1.amazonaws.com https://*.s3.me-south-1.amazonaws.com https://*.s3.us-gov-east-1.amazonaws.com https://*.s3.us-gov-west-1.amazonaws.com https://api.github.com";
|
||||||
set $csp_font "font-src 'self' data: https://cdn.jsdelivr.net https://fonts.gstatic.com https://rsms.me https://maxcdn.bootstrapcdn.com https://js.intercomcdn.com https://fonts.intercomcdn.com";
|
set $csp_font "font-src 'self' data: https://cdn.jsdelivr.net https://fonts.gstatic.com https://rsms.me https://maxcdn.bootstrapcdn.com https://js.intercomcdn.com https://fonts.intercomcdn.com";
|
||||||
set $csp_frame "frame-src 'self' https:";
|
set $csp_frame "frame-src 'self' https:";
|
||||||
set $csp_img "img-src http: https: data: blob:";
|
set $csp_img "img-src http: https: data: blob:";
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
{
|
{
|
||||||
"version": "2.3.2-alpha.0",
|
"version": "2.3.11-alpha.0",
|
||||||
"npmClient": "yarn",
|
"npmClient": "yarn",
|
||||||
"packages": [
|
"packages": [
|
||||||
"packages/*"
|
"packages/*"
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "@budibase/backend-core",
|
"name": "@budibase/backend-core",
|
||||||
"version": "2.3.2-alpha.0",
|
"version": "2.3.11-alpha.0",
|
||||||
"description": "Budibase backend core libraries used in server and worker",
|
"description": "Budibase backend core libraries used in server and worker",
|
||||||
"main": "dist/src/index.js",
|
"main": "dist/src/index.js",
|
||||||
"types": "dist/src/index.d.ts",
|
"types": "dist/src/index.d.ts",
|
||||||
|
@ -24,7 +24,7 @@
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@budibase/nano": "10.1.1",
|
"@budibase/nano": "10.1.1",
|
||||||
"@budibase/pouchdb-replication-stream": "1.2.10",
|
"@budibase/pouchdb-replication-stream": "1.2.10",
|
||||||
"@budibase/types": "2.3.2-alpha.0",
|
"@budibase/types": "2.3.11-alpha.0",
|
||||||
"@shopify/jest-koa-mocks": "5.0.1",
|
"@shopify/jest-koa-mocks": "5.0.1",
|
||||||
"@techpass/passport-openidconnect": "0.3.2",
|
"@techpass/passport-openidconnect": "0.3.2",
|
||||||
"aws-cloudfront-sign": "2.2.0",
|
"aws-cloudfront-sign": "2.2.0",
|
||||||
|
|
|
@ -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.2-alpha.0",
|
"version": "2.3.11-alpha.0",
|
||||||
"license": "MPL-2.0",
|
"license": "MPL-2.0",
|
||||||
"svelte": "src/index.js",
|
"svelte": "src/index.js",
|
||||||
"module": "dist/bbui.es.js",
|
"module": "dist/bbui.es.js",
|
||||||
|
@ -38,7 +38,7 @@
|
||||||
],
|
],
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@adobe/spectrum-css-workflow-icons": "1.2.1",
|
"@adobe/spectrum-css-workflow-icons": "1.2.1",
|
||||||
"@budibase/string-templates": "2.3.2-alpha.0",
|
"@budibase/string-templates": "2.3.11-alpha.0",
|
||||||
"@spectrum-css/accordion": "3.0.24",
|
"@spectrum-css/accordion": "3.0.24",
|
||||||
"@spectrum-css/actionbutton": "1.0.1",
|
"@spectrum-css/actionbutton": "1.0.1",
|
||||||
"@spectrum-css/actiongroup": "1.0.1",
|
"@spectrum-css/actiongroup": "1.0.1",
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
<script>
|
<script>
|
||||||
import { createEventDispatcher } from "svelte"
|
import { createEventDispatcher, onMount } from "svelte"
|
||||||
import FancyField from "./FancyField.svelte"
|
import FancyField from "./FancyField.svelte"
|
||||||
import FancyFieldLabel from "./FancyFieldLabel.svelte"
|
import FancyFieldLabel from "./FancyFieldLabel.svelte"
|
||||||
import { fade } from "svelte/transition"
|
import { fade } from "svelte/transition"
|
||||||
|
@ -14,8 +14,11 @@
|
||||||
|
|
||||||
const dispatch = createEventDispatcher()
|
const dispatch = createEventDispatcher()
|
||||||
|
|
||||||
|
let ref
|
||||||
let focused = false
|
let focused = false
|
||||||
$: placeholder = !focused && !value
|
let autofilled = false
|
||||||
|
|
||||||
|
$: placeholder = !autofilled && !focused && !value
|
||||||
|
|
||||||
const onChange = e => {
|
const onChange = e => {
|
||||||
const newValue = e.target.value
|
const newValue = e.target.value
|
||||||
|
@ -25,6 +28,27 @@
|
||||||
error = validate(newValue)
|
error = validate(newValue)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
onMount(() => {
|
||||||
|
// Start watching for autofill every 100ms
|
||||||
|
const interval = setInterval(() => {
|
||||||
|
autofilled = ref?.matches(":-webkit-autofill")
|
||||||
|
if (autofilled) {
|
||||||
|
clearInterval(interval)
|
||||||
|
}
|
||||||
|
}, 100)
|
||||||
|
|
||||||
|
// Give up after 2 seconds and assume autofill has not been used
|
||||||
|
const timeout = setTimeout(() => {
|
||||||
|
clearInterval(interval)
|
||||||
|
}, 2000)
|
||||||
|
|
||||||
|
// Cleanup
|
||||||
|
return () => {
|
||||||
|
clearInterval(interval)
|
||||||
|
clearTimeout(timeout)
|
||||||
|
}
|
||||||
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<FancyField {error} {value} {validate} {disabled} {focused}>
|
<FancyField {error} {value} {validate} {disabled} {focused}>
|
||||||
|
@ -39,6 +63,7 @@
|
||||||
on:focus={() => (focused = true)}
|
on:focus={() => (focused = true)}
|
||||||
on:blur={() => (focused = false)}
|
on:blur={() => (focused = false)}
|
||||||
class:placeholder
|
class:placeholder
|
||||||
|
bind:this={ref}
|
||||||
/>
|
/>
|
||||||
{#if suffix && !placeholder}
|
{#if suffix && !placeholder}
|
||||||
<div in:fade|local={{ duration: 130 }} class="suffix">{suffix}</div>
|
<div in:fade|local={{ duration: 130 }} class="suffix">{suffix}</div>
|
||||||
|
@ -74,4 +99,11 @@
|
||||||
line-height: 17px;
|
line-height: 17px;
|
||||||
font-family: var(--font-sans);
|
font-family: var(--font-sans);
|
||||||
}
|
}
|
||||||
|
input:-webkit-autofill {
|
||||||
|
border-radius: 2px;
|
||||||
|
-webkit-box-shadow: 0 0 0 100px var(--spectrum-global-color-gray-300) inset;
|
||||||
|
-webkit-text-fill-color: var(--spectrum-global-color-gray-900);
|
||||||
|
transition: -webkit-box-shadow 130ms 200ms, background-color 0s 86400s;
|
||||||
|
padding: 3px 8px 4px 8px;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
|
@ -13,6 +13,7 @@
|
||||||
export let quiet = false
|
export let quiet = false
|
||||||
export let align
|
export let align
|
||||||
export let autofocus = false
|
export let autofocus = false
|
||||||
|
export let autocomplete = null
|
||||||
|
|
||||||
const dispatch = createEventDispatcher()
|
const dispatch = createEventDispatcher()
|
||||||
|
|
||||||
|
@ -103,6 +104,7 @@
|
||||||
class="spectrum-Textfield-input"
|
class="spectrum-Textfield-input"
|
||||||
style={align ? `text-align: ${align};` : ""}
|
style={align ? `text-align: ${align};` : ""}
|
||||||
inputmode={type === "number" ? "decimal" : "text"}
|
inputmode={type === "number" ? "decimal" : "text"}
|
||||||
|
{autocomplete}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
|
@ -14,6 +14,7 @@
|
||||||
export let updateOnChange = true
|
export let updateOnChange = true
|
||||||
export let quiet = false
|
export let quiet = false
|
||||||
export let autofocus
|
export let autofocus
|
||||||
|
export let autocomplete
|
||||||
|
|
||||||
const dispatch = createEventDispatcher()
|
const dispatch = createEventDispatcher()
|
||||||
const onChange = e => {
|
const onChange = e => {
|
||||||
|
@ -33,6 +34,7 @@
|
||||||
{type}
|
{type}
|
||||||
{quiet}
|
{quiet}
|
||||||
{autofocus}
|
{autofocus}
|
||||||
|
{autocomplete}
|
||||||
on:change={onChange}
|
on:change={onChange}
|
||||||
on:click
|
on:click
|
||||||
on:input
|
on:input
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "@budibase/builder",
|
"name": "@budibase/builder",
|
||||||
"version": "2.3.2-alpha.0",
|
"version": "2.3.11-alpha.0",
|
||||||
"license": "GPL-3.0",
|
"license": "GPL-3.0",
|
||||||
"private": true,
|
"private": true,
|
||||||
"scripts": {
|
"scripts": {
|
||||||
|
@ -58,10 +58,10 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@budibase/bbui": "2.3.2-alpha.0",
|
"@budibase/bbui": "2.3.11-alpha.0",
|
||||||
"@budibase/client": "2.3.2-alpha.0",
|
"@budibase/client": "2.3.11-alpha.0",
|
||||||
"@budibase/frontend-core": "2.3.2-alpha.0",
|
"@budibase/frontend-core": "2.3.11-alpha.0",
|
||||||
"@budibase/string-templates": "2.3.2-alpha.0",
|
"@budibase/string-templates": "2.3.11-alpha.0",
|
||||||
"@fortawesome/fontawesome-svg-core": "^6.2.1",
|
"@fortawesome/fontawesome-svg-core": "^6.2.1",
|
||||||
"@fortawesome/free-brands-svg-icons": "^6.2.1",
|
"@fortawesome/free-brands-svg-icons": "^6.2.1",
|
||||||
"@fortawesome/free-solid-svg-icons": "^6.2.1",
|
"@fortawesome/free-solid-svg-icons": "^6.2.1",
|
||||||
|
|
|
@ -75,16 +75,20 @@
|
||||||
editableColumn.constraints.presence = { allowEmpty: false }
|
editableColumn.constraints.presence = { allowEmpty: false }
|
||||||
}
|
}
|
||||||
|
|
||||||
$: if (field && !savingColumn) {
|
const initialiseField = (field, savingColumn) => {
|
||||||
editableColumn = cloneDeep(field)
|
if (field && !savingColumn) {
|
||||||
originalName = editableColumn.name ? editableColumn.name + "" : null
|
editableColumn = cloneDeep(field)
|
||||||
linkEditDisabled = originalName != null
|
originalName = editableColumn.name ? editableColumn.name + "" : null
|
||||||
isCreating = originalName == null
|
linkEditDisabled = originalName != null
|
||||||
primaryDisplay =
|
isCreating = originalName == null
|
||||||
$tables.selected.primaryDisplay == null ||
|
primaryDisplay =
|
||||||
$tables.selected.primaryDisplay === editableColumn.name
|
$tables.selected.primaryDisplay == null ||
|
||||||
|
$tables.selected.primaryDisplay === editableColumn.name
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$: initialiseField(field, savingColumn)
|
||||||
|
|
||||||
$: checkConstraints(editableColumn)
|
$: checkConstraints(editableColumn)
|
||||||
$: required = !!editableColumn?.constraints?.presence || primaryDisplay
|
$: required = !!editableColumn?.constraints?.presence || primaryDisplay
|
||||||
$: uneditable =
|
$: uneditable =
|
||||||
|
@ -583,7 +587,12 @@
|
||||||
title="Formula"
|
title="Formula"
|
||||||
label="Formula"
|
label="Formula"
|
||||||
value={editableColumn.formula}
|
value={editableColumn.formula}
|
||||||
on:change={e => (editableColumn.formula = e.detail)}
|
on:change={e => {
|
||||||
|
editableColumn = {
|
||||||
|
...editableColumn,
|
||||||
|
formula: e.detail,
|
||||||
|
}
|
||||||
|
}}
|
||||||
bindings={getBindings({ table })}
|
bindings={getBindings({ table })}
|
||||||
allowJS
|
allowJS
|
||||||
/>
|
/>
|
||||||
|
|
|
@ -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}
|
||||||
|
|
|
@ -71,6 +71,7 @@
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
value={productionValue}
|
value={productionValue}
|
||||||
|
autocomplete="new-password"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
|
@ -83,6 +84,7 @@
|
||||||
disabled={useProductionValue}
|
disabled={useProductionValue}
|
||||||
label="Value"
|
label="Value"
|
||||||
value={useProductionValue ? productionValue : developmentValue}
|
value={useProductionValue ? productionValue : developmentValue}
|
||||||
|
autocomplete="new-password"
|
||||||
/>
|
/>
|
||||||
<Checkbox bind:value={useProductionValue} text="Use production value" />
|
<Checkbox bind:value={useProductionValue} text="Use production value" />
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -62,6 +62,7 @@ const getTours = () => {
|
||||||
id: TOUR_STEP_KEYS.BUILDER_APP_PUBLISH,
|
id: TOUR_STEP_KEYS.BUILDER_APP_PUBLISH,
|
||||||
title: "Publish",
|
title: "Publish",
|
||||||
layout: OnboardingPublish,
|
layout: OnboardingPublish,
|
||||||
|
route: "/builder/app/:application/design",
|
||||||
query: ".toprightnav #builder-app-publish-button",
|
query: ".toprightnav #builder-app-publish-button",
|
||||||
onLoad: () => {
|
onLoad: () => {
|
||||||
tourEvent(TOUR_STEP_KEYS.BUILDER_APP_PUBLISH)
|
tourEvent(TOUR_STEP_KEYS.BUILDER_APP_PUBLISH)
|
||||||
|
|
|
@ -147,8 +147,8 @@
|
||||||
options: setting.options || [],
|
options: setting.options || [],
|
||||||
|
|
||||||
// Number fields
|
// Number fields
|
||||||
min: setting.min || null,
|
min: setting.min ?? null,
|
||||||
max: setting.max || null,
|
max: setting.max ?? null,
|
||||||
}}
|
}}
|
||||||
{bindings}
|
{bindings}
|
||||||
{componentBindings}
|
{componentBindings}
|
||||||
|
|
|
@ -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.2-alpha.0",
|
"version": "2.3.11-alpha.0",
|
||||||
"description": "Budibase CLI, for developers, self hosting and migrations.",
|
"description": "Budibase CLI, for developers, self hosting and migrations.",
|
||||||
"main": "src/index.js",
|
"main": "src/index.js",
|
||||||
"bin": {
|
"bin": {
|
||||||
|
@ -26,9 +26,9 @@
|
||||||
"outputPath": "build"
|
"outputPath": "build"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@budibase/backend-core": "2.3.2-alpha.0",
|
"@budibase/backend-core": "2.3.11-alpha.0",
|
||||||
"@budibase/string-templates": "2.3.2-alpha.0",
|
"@budibase/string-templates": "2.3.11-alpha.0",
|
||||||
"@budibase/types": "2.3.2-alpha.0",
|
"@budibase/types": "2.3.11-alpha.0",
|
||||||
"axios": "0.21.2",
|
"axios": "0.21.2",
|
||||||
"chalk": "4.1.0",
|
"chalk": "4.1.0",
|
||||||
"cli-progress": "3.11.2",
|
"cli-progress": "3.11.2",
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "@budibase/client",
|
"name": "@budibase/client",
|
||||||
"version": "2.3.2-alpha.0",
|
"version": "2.3.11-alpha.0",
|
||||||
"license": "MPL-2.0",
|
"license": "MPL-2.0",
|
||||||
"module": "dist/budibase-client.js",
|
"module": "dist/budibase-client.js",
|
||||||
"main": "dist/budibase-client.js",
|
"main": "dist/budibase-client.js",
|
||||||
|
@ -19,9 +19,9 @@
|
||||||
"dev:builder": "rollup -cw"
|
"dev:builder": "rollup -cw"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@budibase/bbui": "2.3.2-alpha.0",
|
"@budibase/bbui": "2.3.11-alpha.0",
|
||||||
"@budibase/frontend-core": "2.3.2-alpha.0",
|
"@budibase/frontend-core": "2.3.11-alpha.0",
|
||||||
"@budibase/string-templates": "2.3.2-alpha.0",
|
"@budibase/string-templates": "2.3.11-alpha.0",
|
||||||
"@spectrum-css/button": "^3.0.3",
|
"@spectrum-css/button": "^3.0.3",
|
||||||
"@spectrum-css/card": "^3.0.3",
|
"@spectrum-css/card": "^3.0.3",
|
||||||
"@spectrum-css/divider": "^1.0.3",
|
"@spectrum-css/divider": "^1.0.3",
|
||||||
|
|
|
@ -1,12 +1,12 @@
|
||||||
{
|
{
|
||||||
"name": "@budibase/frontend-core",
|
"name": "@budibase/frontend-core",
|
||||||
"version": "2.3.2-alpha.0",
|
"version": "2.3.11-alpha.0",
|
||||||
"description": "Budibase frontend core libraries used in builder and client",
|
"description": "Budibase frontend core libraries used in builder and client",
|
||||||
"author": "Budibase",
|
"author": "Budibase",
|
||||||
"license": "MPL-2.0",
|
"license": "MPL-2.0",
|
||||||
"svelte": "src/index.js",
|
"svelte": "src/index.js",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@budibase/bbui": "2.3.2-alpha.0",
|
"@budibase/bbui": "2.3.11-alpha.0",
|
||||||
"lodash": "^4.17.21",
|
"lodash": "^4.17.21",
|
||||||
"svelte": "^3.46.2"
|
"svelte": "^3.46.2"
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "@budibase/sdk",
|
"name": "@budibase/sdk",
|
||||||
"version": "2.3.2-alpha.0",
|
"version": "2.3.11-alpha.0",
|
||||||
"description": "Budibase Public API SDK",
|
"description": "Budibase Public API SDK",
|
||||||
"author": "Budibase",
|
"author": "Budibase",
|
||||||
"license": "MPL-2.0",
|
"license": "MPL-2.0",
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
{
|
{
|
||||||
"name": "@budibase/server",
|
"name": "@budibase/server",
|
||||||
"email": "hi@budibase.com",
|
"email": "hi@budibase.com",
|
||||||
"version": "2.3.2-alpha.0",
|
"version": "2.3.11-alpha.0",
|
||||||
"description": "Budibase Web Server",
|
"description": "Budibase Web Server",
|
||||||
"main": "src/index.ts",
|
"main": "src/index.ts",
|
||||||
"repository": {
|
"repository": {
|
||||||
|
@ -43,11 +43,11 @@
|
||||||
"license": "GPL-3.0",
|
"license": "GPL-3.0",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@apidevtools/swagger-parser": "10.0.3",
|
"@apidevtools/swagger-parser": "10.0.3",
|
||||||
"@budibase/backend-core": "2.3.2-alpha.0",
|
"@budibase/backend-core": "2.3.11-alpha.0",
|
||||||
"@budibase/client": "2.3.2-alpha.0",
|
"@budibase/client": "2.3.11-alpha.0",
|
||||||
"@budibase/pro": "2.3.2-alpha.0",
|
"@budibase/pro": "2.3.11-alpha.0",
|
||||||
"@budibase/string-templates": "2.3.2-alpha.0",
|
"@budibase/string-templates": "2.3.11-alpha.0",
|
||||||
"@budibase/types": "2.3.2-alpha.0",
|
"@budibase/types": "2.3.11-alpha.0",
|
||||||
"@bull-board/api": "3.7.0",
|
"@bull-board/api": "3.7.0",
|
||||||
"@bull-board/koa": "3.9.4",
|
"@bull-board/koa": "3.9.4",
|
||||||
"@elastic/elasticsearch": "7.10.0",
|
"@elastic/elasticsearch": "7.10.0",
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
@ -181,7 +191,7 @@ export async function validate(ctx: BBContext) {
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function exportRows(ctx: BBContext) {
|
export async function exportRows(ctx: BBContext) {
|
||||||
const { datasourceId } = breakExternalTableId(ctx.params.tableId)
|
const { datasourceId, tableName } = breakExternalTableId(ctx.params.tableId)
|
||||||
const format = ctx.query.format
|
const format = ctx.query.format
|
||||||
const { columns } = ctx.request.body
|
const { columns } = ctx.request.body
|
||||||
const datasource = await sdk.datasources.get(datasourceId!)
|
const datasource = await sdk.datasources.get(datasourceId!)
|
||||||
|
@ -217,7 +227,9 @@ export async function exportRows(ctx: BBContext) {
|
||||||
rows = result.rows
|
rows = result.rows
|
||||||
}
|
}
|
||||||
|
|
||||||
// @ts-ignore
|
if (!tableName) {
|
||||||
|
ctx.throw(400, "Could not find table name.")
|
||||||
|
}
|
||||||
let schema = datasource.entities[tableName].schema
|
let schema = datasource.entities[tableName].schema
|
||||||
let exportRows = cleanExportRows(rows, schema, format, columns)
|
let exportRows = cleanExportRows(rows, schema, format, columns)
|
||||||
|
|
||||||
|
@ -247,6 +259,7 @@ export async function fetchEnrichedRow(ctx: BBContext) {
|
||||||
const response = (await handleRequest(Operation.READ, tableId, {
|
const response = (await handleRequest(Operation.READ, tableId, {
|
||||||
id,
|
id,
|
||||||
datasource,
|
datasource,
|
||||||
|
includeSqlRelationships: IncludeRelationship.INCLUDE,
|
||||||
})) as Row[]
|
})) as Row[]
|
||||||
const table: Table = tables[tableName]
|
const table: Table = tables[tableName]
|
||||||
const row = response[0]
|
const row = response[0]
|
||||||
|
@ -274,6 +287,7 @@ export async function fetchEnrichedRow(ctx: BBContext) {
|
||||||
[primaryLink]: linkedIds,
|
[primaryLink]: linkedIds,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
includeSqlRelationships: IncludeRelationship.INCLUDE,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
return row
|
return row
|
||||||
|
|
|
@ -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")
|
||||||
|
|
|
@ -104,7 +104,6 @@ export function importToRows(data: any, table: any, user: any = {}) {
|
||||||
const processed: any = inputProcessing(user, table, row, {
|
const processed: any = inputProcessing(user, table, row, {
|
||||||
noAutoRelationships: true,
|
noAutoRelationships: true,
|
||||||
})
|
})
|
||||||
table = processed.table
|
|
||||||
row = processed.row
|
row = processed.row
|
||||||
|
|
||||||
let fieldName: any
|
let fieldName: any
|
||||||
|
@ -113,6 +112,7 @@ export function importToRows(data: any, table: any, user: any = {}) {
|
||||||
// check whether the options need to be updated for inclusion as part of the data import
|
// check whether the options need to be updated for inclusion as part of the data import
|
||||||
if (
|
if (
|
||||||
schema.type === FieldTypes.OPTIONS &&
|
schema.type === FieldTypes.OPTIONS &&
|
||||||
|
row[fieldName] &&
|
||||||
(!schema.constraints.inclusion ||
|
(!schema.constraints.inclusion ||
|
||||||
schema.constraints.inclusion.indexOf(row[fieldName]) === -1)
|
schema.constraints.inclusion.indexOf(row[fieldName]) === -1)
|
||||||
) {
|
) {
|
||||||
|
@ -120,6 +120,7 @@ export function importToRows(data: any, table: any, user: any = {}) {
|
||||||
...schema.constraints.inclusion,
|
...schema.constraints.inclusion,
|
||||||
row[fieldName],
|
row[fieldName],
|
||||||
]
|
]
|
||||||
|
schema.constraints.inclusion.sort()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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,4 @@
|
||||||
const { roles } = require("@budibase/backend-core")
|
const { roles, utils } = require("@budibase/backend-core")
|
||||||
const { checkPermissionsEndpoint } = require("./utilities/TestFunctions")
|
const { checkPermissionsEndpoint } = require("./utilities/TestFunctions")
|
||||||
const setup = require("./utilities")
|
const setup = require("./utilities")
|
||||||
const { BUILTIN_ROLE_IDS } = roles
|
const { BUILTIN_ROLE_IDS } = roles
|
||||||
|
@ -28,8 +28,8 @@ describe("/users", () => {
|
||||||
|
|
||||||
describe("fetch", () => {
|
describe("fetch", () => {
|
||||||
it("returns a list of users from an instance db", async () => {
|
it("returns a list of users from an instance db", async () => {
|
||||||
await config.createUser("uuidx")
|
await config.createUser({ id: "uuidx" })
|
||||||
await config.createUser("uuidy")
|
await config.createUser({ id: "uuidy" })
|
||||||
const res = await request
|
const res = await request
|
||||||
.get(`/api/users/metadata`)
|
.get(`/api/users/metadata`)
|
||||||
.set(config.defaultHeaders())
|
.set(config.defaultHeaders())
|
||||||
|
@ -56,7 +56,7 @@ describe("/users", () => {
|
||||||
|
|
||||||
describe("update", () => {
|
describe("update", () => {
|
||||||
it("should be able to update the user", async () => {
|
it("should be able to update the user", async () => {
|
||||||
const user = await config.createUser()
|
const user = await config.createUser({ id: `us_update${Math.random()}` })
|
||||||
user.roleId = BUILTIN_ROLE_IDS.BASIC
|
user.roleId = BUILTIN_ROLE_IDS.BASIC
|
||||||
const res = await request
|
const res = await request
|
||||||
.put(`/api/users/metadata`)
|
.put(`/api/users/metadata`)
|
||||||
|
@ -180,14 +180,11 @@ describe("/users", () => {
|
||||||
const app1 = await config.createApp('App 1')
|
const app1 = await config.createApp('App 1')
|
||||||
const app2 = await config.createApp('App 2')
|
const app2 = await config.createApp('App 2')
|
||||||
|
|
||||||
let user = await config.createUser(
|
let user = await config.createUser({
|
||||||
undefined,
|
builder: false,
|
||||||
undefined,
|
admin: true,
|
||||||
undefined,
|
roles: { [app1.appId]: 'ADMIN' }
|
||||||
undefined,
|
})
|
||||||
false,
|
|
||||||
true,
|
|
||||||
{ [app1.appId]: 'ADMIN' })
|
|
||||||
let res = await request
|
let res = await request
|
||||||
.post(`/api/users/metadata/sync/${user._id}`)
|
.post(`/api/users/metadata/sync/${user._id}`)
|
||||||
.set(config.defaultHeaders())
|
.set(config.defaultHeaders())
|
||||||
|
|
|
@ -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))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -67,6 +67,15 @@ if (
|
||||||
INTEGRATIONS[SourceName.ORACLE] = oracle.integration
|
INTEGRATIONS[SourceName.ORACLE] = oracle.integration
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function getDefinition(source: SourceName): Promise<Integration> {
|
||||||
|
// check if its integrated, faster
|
||||||
|
if (DEFINITIONS[source]) {
|
||||||
|
return DEFINITIONS[source]
|
||||||
|
}
|
||||||
|
const allDefinitions = await getDefinitions()
|
||||||
|
return allDefinitions[source]
|
||||||
|
}
|
||||||
|
|
||||||
export async function getDefinitions() {
|
export async function getDefinitions() {
|
||||||
const pluginSchemas: { [key: string]: Integration } = {}
|
const pluginSchemas: { [key: string]: Integration } = {}
|
||||||
if (env.SELF_HOSTED) {
|
if (env.SELF_HOSTED) {
|
||||||
|
|
|
@ -26,7 +26,7 @@ interface MSSQLConfig {
|
||||||
user: string
|
user: string
|
||||||
password: string
|
password: string
|
||||||
server: string
|
server: string
|
||||||
port: number
|
port: number | string
|
||||||
database: string
|
database: string
|
||||||
schema: string
|
schema: string
|
||||||
encrypt?: boolean
|
encrypt?: boolean
|
||||||
|
|
|
@ -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 }
|
||||||
}
|
}
|
||||||
|
|
|
@ -93,24 +93,16 @@ describe("migrations", () => {
|
||||||
await clearMigrations()
|
await clearMigrations()
|
||||||
const appId = config.prodAppId
|
const appId = config.prodAppId
|
||||||
const roles = { [appId]: "role_12345" }
|
const roles = { [appId]: "role_12345" }
|
||||||
await config.createUser(
|
await config.createUser({
|
||||||
undefined,
|
builder: false,
|
||||||
undefined,
|
admin: true,
|
||||||
undefined,
|
roles,
|
||||||
undefined,
|
}) // admin only
|
||||||
false,
|
await config.createUser({
|
||||||
true,
|
builder: false,
|
||||||
roles
|
admin: false,
|
||||||
) // admin only
|
roles,
|
||||||
await config.createUser(
|
}) // non admin non builder
|
||||||
undefined,
|
|
||||||
undefined,
|
|
||||||
undefined,
|
|
||||||
undefined,
|
|
||||||
false,
|
|
||||||
false,
|
|
||||||
roles
|
|
||||||
) // non admin non builder
|
|
||||||
await config.createTable()
|
await config.createTable()
|
||||||
await config.createRow()
|
await config.createRow()
|
||||||
await config.createRow()
|
await config.createRow()
|
||||||
|
|
|
@ -3,6 +3,7 @@ import { findHBSBlocks, processObjectSync } from "@budibase/string-templates"
|
||||||
import {
|
import {
|
||||||
Datasource,
|
Datasource,
|
||||||
DatasourceFieldType,
|
DatasourceFieldType,
|
||||||
|
Integration,
|
||||||
PASSWORD_REPLACEMENT,
|
PASSWORD_REPLACEMENT,
|
||||||
RestAuthConfig,
|
RestAuthConfig,
|
||||||
RestAuthType,
|
RestAuthType,
|
||||||
|
@ -11,16 +12,38 @@ import {
|
||||||
} from "@budibase/types"
|
} from "@budibase/types"
|
||||||
import { cloneDeep } from "lodash/fp"
|
import { cloneDeep } from "lodash/fp"
|
||||||
import { getEnvironmentVariables } from "../../utils"
|
import { getEnvironmentVariables } from "../../utils"
|
||||||
import { getDefinitions } from "../../../integrations"
|
import { getDefinitions, getDefinition } from "../../../integrations"
|
||||||
|
|
||||||
const ENV_VAR_PREFIX = "env."
|
const ENV_VAR_PREFIX = "env."
|
||||||
|
|
||||||
|
export function checkDatasourceTypes(schema: Integration, config: any) {
|
||||||
|
for (let key of Object.keys(config)) {
|
||||||
|
if (!schema.datasource[key]) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
const type = schema.datasource[key].type
|
||||||
|
if (
|
||||||
|
type === DatasourceFieldType.NUMBER &&
|
||||||
|
typeof config[key] === "string"
|
||||||
|
) {
|
||||||
|
config[key] = parseFloat(config[key])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return config
|
||||||
|
}
|
||||||
|
|
||||||
async function enrichDatasourceWithValues(datasource: Datasource) {
|
async function enrichDatasourceWithValues(datasource: Datasource) {
|
||||||
const cloned = cloneDeep(datasource)
|
const cloned = cloneDeep(datasource)
|
||||||
const env = await getEnvironmentVariables()
|
const env = await getEnvironmentVariables()
|
||||||
const processed = processObjectSync(cloned, { env }, { onlyFound: true })
|
const processed = processObjectSync(
|
||||||
|
cloned,
|
||||||
|
{ env },
|
||||||
|
{ onlyFound: true }
|
||||||
|
) as Datasource
|
||||||
|
const definition = await getDefinition(processed.source)
|
||||||
|
processed.config = checkDatasourceTypes(definition, processed.config)
|
||||||
return {
|
return {
|
||||||
datasource: processed as Datasource,
|
datasource: processed,
|
||||||
envVars: env as Record<string, string>,
|
envVars: env as Record<string, string>,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -65,6 +88,9 @@ export async function removeSecrets(datasources: Datasource[]) {
|
||||||
const definitions = await getDefinitions()
|
const definitions = await getDefinitions()
|
||||||
for (let datasource of datasources) {
|
for (let datasource of datasources) {
|
||||||
const schema = definitions[datasource.source]
|
const schema = definitions[datasource.source]
|
||||||
|
if (!schema) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
if (datasource.config) {
|
if (datasource.config) {
|
||||||
// strip secrets from response, so they don't show in the network request
|
// strip secrets from response, so they don't show in the network request
|
||||||
if (datasource.config.auth) {
|
if (datasource.config.auth) {
|
||||||
|
|
|
@ -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
|
||||||
|
@ -268,14 +275,24 @@ class TestConfiguration {
|
||||||
}
|
}
|
||||||
|
|
||||||
async createUser(
|
async createUser(
|
||||||
id = null,
|
user: {
|
||||||
firstName = this.defaultUserValues.firstName,
|
id?: string
|
||||||
lastName = this.defaultUserValues.lastName,
|
firstName?: string
|
||||||
email = this.defaultUserValues.email,
|
lastName?: string
|
||||||
builder = true,
|
email?: string
|
||||||
admin = false,
|
builder?: boolean
|
||||||
roles = {}
|
admin?: boolean
|
||||||
|
roles?: any
|
||||||
|
} = {}
|
||||||
) {
|
) {
|
||||||
|
let { id, firstName, lastName, email, builder, admin, roles } = user
|
||||||
|
firstName = firstName || this.defaultUserValues.firstName
|
||||||
|
lastName = lastName || this.defaultUserValues.lastName
|
||||||
|
email = email || this.defaultUserValues.email
|
||||||
|
roles = roles || {}
|
||||||
|
if (builder == null) {
|
||||||
|
builder = true
|
||||||
|
}
|
||||||
const globalId = !id ? `us_${Math.random()}` : `us_${id}`
|
const globalId = !id ? `us_${Math.random()}` : `us_${id}`
|
||||||
const resp = await this.globalUser({
|
const resp = await this.globalUser({
|
||||||
id: globalId,
|
id: globalId,
|
||||||
|
@ -360,6 +377,7 @@ class TestConfiguration {
|
||||||
[constants.Header.CSRF_TOKEN]: this.defaultUserValues.csrfToken,
|
[constants.Header.CSRF_TOKEN]: this.defaultUserValues.csrfToken,
|
||||||
...extras,
|
...extras,
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.appId) {
|
if (this.appId) {
|
||||||
headers[constants.Header.APP_ID] = this.appId
|
headers[constants.Header.APP_ID] = this.appId
|
||||||
}
|
}
|
||||||
|
@ -464,13 +482,13 @@ class TestConfiguration {
|
||||||
|
|
||||||
// TABLE
|
// TABLE
|
||||||
|
|
||||||
async updateTable(config?: any) {
|
async updateTable(config?: any): Promise<Table> {
|
||||||
config = config || basicTable()
|
config = config || basicTable()
|
||||||
this.table = await this._req(config, null, controllers.table.save)
|
this.table = await this._req(config, null, controllers.table.save)
|
||||||
return this.table
|
return this.table
|
||||||
}
|
}
|
||||||
|
|
||||||
async createTable(config?: any) {
|
async createTable(config?: Table) {
|
||||||
if (config != null && config._id) {
|
if (config != null && config._id) {
|
||||||
delete config._id
|
delete config._id
|
||||||
}
|
}
|
||||||
|
@ -514,7 +532,7 @@ class TestConfiguration {
|
||||||
|
|
||||||
// ROW
|
// ROW
|
||||||
|
|
||||||
async createRow(config: any = null) {
|
async createRow(config?: Row): Promise<Row> {
|
||||||
if (!this.table) {
|
if (!this.table) {
|
||||||
throw "Test requires table to be configured."
|
throw "Test requires table to be configured."
|
||||||
}
|
}
|
||||||
|
@ -523,7 +541,7 @@ class TestConfiguration {
|
||||||
return this._req(config, { tableId }, controllers.row.save)
|
return this._req(config, { tableId }, controllers.row.save)
|
||||||
}
|
}
|
||||||
|
|
||||||
async getRow(tableId: string, rowId: string) {
|
async getRow(tableId: string, rowId: string): Promise<Row> {
|
||||||
return this._req(null, { tableId, rowId }, controllers.row.find)
|
return this._req(null, { tableId, rowId }, controllers.row.find)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -605,7 +623,9 @@ class TestConfiguration {
|
||||||
|
|
||||||
// DATASOURCE
|
// DATASOURCE
|
||||||
|
|
||||||
async createDatasource(config?: any) {
|
async createDatasource(config?: {
|
||||||
|
datasource: Datasource
|
||||||
|
}): Promise<Datasource> {
|
||||||
config = config || basicDatasource()
|
config = config || basicDatasource()
|
||||||
const response = await this._req(config, null, controllers.datasource.save)
|
const response = await this._req(config, null, controllers.datasource.save)
|
||||||
this.datasource = response.datasource
|
this.datasource = response.datasource
|
||||||
|
@ -626,7 +646,7 @@ class TestConfiguration {
|
||||||
return this.createDatasource({
|
return this.createDatasource({
|
||||||
datasource: {
|
datasource: {
|
||||||
...basicDatasource().datasource,
|
...basicDatasource().datasource,
|
||||||
source: "REST",
|
source: SourceName.REST,
|
||||||
config: cfg || {},
|
config: cfg || {},
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
@ -635,7 +655,7 @@ class TestConfiguration {
|
||||||
async dynamicVariableDatasource() {
|
async dynamicVariableDatasource() {
|
||||||
let datasource = await this.restDatasource()
|
let datasource = await this.restDatasource()
|
||||||
const basedOnQuery = await this.createQuery({
|
const basedOnQuery = await this.createQuery({
|
||||||
...basicQuery(datasource._id),
|
...basicQuery(datasource._id!),
|
||||||
fields: {
|
fields: {
|
||||||
path: "www.google.com",
|
path: "www.google.com",
|
||||||
},
|
},
|
||||||
|
@ -663,7 +683,7 @@ class TestConfiguration {
|
||||||
datasource: any,
|
datasource: any,
|
||||||
fields: any,
|
fields: any,
|
||||||
params: any,
|
params: any,
|
||||||
verb: string
|
verb?: string
|
||||||
) {
|
) {
|
||||||
return request
|
return request
|
||||||
.post(`/api/queries/preview`)
|
.post(`/api/queries/preview`)
|
||||||
|
|
|
@ -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,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -464,13 +464,17 @@ export function execute(job: Job, callback: WorkerCallback) {
|
||||||
throw new Error("Unable to execute, event doesn't contain app ID.")
|
throw new Error("Unable to execute, event doesn't contain app ID.")
|
||||||
}
|
}
|
||||||
return context.doInAppContext(appId, async () => {
|
return context.doInAppContext(appId, async () => {
|
||||||
const automationOrchestrator = new Orchestrator(job)
|
const envVars = await sdkUtils.getEnvironmentVariables()
|
||||||
try {
|
// put into automation thread for whole context
|
||||||
const response = await automationOrchestrator.execute()
|
await context.doInEnvironmentContext(envVars, async () => {
|
||||||
callback(null, response)
|
const automationOrchestrator = new Orchestrator(job)
|
||||||
} catch (err) {
|
try {
|
||||||
callback(err)
|
const response = await automationOrchestrator.execute()
|
||||||
}
|
callback(null, response)
|
||||||
|
} catch (err) {
|
||||||
|
callback(err)
|
||||||
|
}
|
||||||
|
})
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -480,11 +484,7 @@ export const removeStalled = async (job: Job) => {
|
||||||
throw new Error("Unable to execute, event doesn't contain app ID.")
|
throw new Error("Unable to execute, event doesn't contain app ID.")
|
||||||
}
|
}
|
||||||
await context.doInAppContext(appId, async () => {
|
await context.doInAppContext(appId, async () => {
|
||||||
const envVars = await sdkUtils.getEnvironmentVariables()
|
const automationOrchestrator = new Orchestrator(job)
|
||||||
// put into automation thread for whole context
|
await automationOrchestrator.stopCron("stalled")
|
||||||
await context.doInEnvironmentContext(envVars, async () => {
|
|
||||||
const automationOrchestrator = new Orchestrator(job)
|
|
||||||
await automationOrchestrator.stopCron("stalled")
|
|
||||||
})
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
|
@ -1278,14 +1278,14 @@
|
||||||
resolved "https://registry.yarnpkg.com/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz#75a2e8b51cb758a7553d6804a5932d7aace75c39"
|
resolved "https://registry.yarnpkg.com/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz#75a2e8b51cb758a7553d6804a5932d7aace75c39"
|
||||||
integrity sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==
|
integrity sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==
|
||||||
|
|
||||||
"@budibase/backend-core@2.3.2-alpha.0":
|
"@budibase/backend-core@2.3.11-alpha.0":
|
||||||
version "2.3.2-alpha.0"
|
version "2.3.11-alpha.0"
|
||||||
resolved "https://registry.yarnpkg.com/@budibase/backend-core/-/backend-core-2.3.2-alpha.0.tgz#1ee32fe176f6144460c7722264a09045be10f0a7"
|
resolved "https://registry.yarnpkg.com/@budibase/backend-core/-/backend-core-2.3.11-alpha.0.tgz#361e30139a1a26d023902c6bdb4fdcac2610b69f"
|
||||||
integrity sha512-J5TbzhkgJt6/+ehKoVPQNoNnaTCCXD4iQhsB3Td7xbuwjDq49P1a9ZJXKtsqr+CzcD7MsiGh7DiSslie6scBFA==
|
integrity sha512-hlaeTkYsSJJYIwwqL3LJ7Pxzq0tOgSjQ38+nFiBPIzjkDSaV0UPwi0rZAZq/kPvaT7AKdTEcf8tn1wiui/VkYA==
|
||||||
dependencies:
|
dependencies:
|
||||||
"@budibase/nano" "10.1.1"
|
"@budibase/nano" "10.1.1"
|
||||||
"@budibase/pouchdb-replication-stream" "1.2.10"
|
"@budibase/pouchdb-replication-stream" "1.2.10"
|
||||||
"@budibase/types" "2.3.2-alpha.0"
|
"@budibase/types" "2.3.11-alpha.0"
|
||||||
"@shopify/jest-koa-mocks" "5.0.1"
|
"@shopify/jest-koa-mocks" "5.0.1"
|
||||||
"@techpass/passport-openidconnect" "0.3.2"
|
"@techpass/passport-openidconnect" "0.3.2"
|
||||||
aws-cloudfront-sign "2.2.0"
|
aws-cloudfront-sign "2.2.0"
|
||||||
|
@ -1392,13 +1392,13 @@
|
||||||
pouchdb-promise "^6.0.4"
|
pouchdb-promise "^6.0.4"
|
||||||
through2 "^2.0.0"
|
through2 "^2.0.0"
|
||||||
|
|
||||||
"@budibase/pro@2.3.2-alpha.0":
|
"@budibase/pro@2.3.11-alpha.0":
|
||||||
version "2.3.2-alpha.0"
|
version "2.3.11-alpha.0"
|
||||||
resolved "https://registry.yarnpkg.com/@budibase/pro/-/pro-2.3.2-alpha.0.tgz#b4406ec116f5fa008c7fc7327471302d9de4f29e"
|
resolved "https://registry.yarnpkg.com/@budibase/pro/-/pro-2.3.11-alpha.0.tgz#b59edba2c04e2a490f3d9246cd328d8dd3213295"
|
||||||
integrity sha512-vVI/xZ6ybFSVGbHZvfomxCNyy0KvrBpYiOdUHjexPd60590tkT+cfZAuPrh3FdTTM8g2VipsxNWuO1OEQU7dcw==
|
integrity sha512-z7tLgRKYKm1psNZGdRErQT8tV+vSj9hXJkD/H/uFxsJ3IQkjWbcFP5FUBPads5RnvCtqOfYu8OpRHCJfNNBoCA==
|
||||||
dependencies:
|
dependencies:
|
||||||
"@budibase/backend-core" "2.3.2-alpha.0"
|
"@budibase/backend-core" "2.3.11-alpha.0"
|
||||||
"@budibase/types" "2.3.2-alpha.0"
|
"@budibase/types" "2.3.11-alpha.0"
|
||||||
"@koa/router" "8.0.8"
|
"@koa/router" "8.0.8"
|
||||||
bull "4.10.1"
|
bull "4.10.1"
|
||||||
joi "17.6.0"
|
joi "17.6.0"
|
||||||
|
@ -1424,10 +1424,10 @@
|
||||||
svelte-apexcharts "^1.0.2"
|
svelte-apexcharts "^1.0.2"
|
||||||
svelte-flatpickr "^3.1.0"
|
svelte-flatpickr "^3.1.0"
|
||||||
|
|
||||||
"@budibase/types@2.3.2-alpha.0":
|
"@budibase/types@2.3.11-alpha.0":
|
||||||
version "2.3.2-alpha.0"
|
version "2.3.11-alpha.0"
|
||||||
resolved "https://registry.yarnpkg.com/@budibase/types/-/types-2.3.2-alpha.0.tgz#00b1afc4b937a5a49772bd78d189443f8e4eb0e3"
|
resolved "https://registry.yarnpkg.com/@budibase/types/-/types-2.3.11-alpha.0.tgz#55cdcef6fb30c79e9d7d90ae8789e4f0cc6a2b63"
|
||||||
integrity sha512-kVwXzA8ROg27tIyNZnTd7ZszRepP4D7NgrW2gZsygsUoiYZ+WO1rCIlWj1SFPBur+V/k8dShQkh1hwxpj8JPVg==
|
integrity sha512-SFW9vManFRJ45bgMW7wkDiNxiQQfAME8HOmSqCHKDC0OPzsDydygIl7BNeF/domFvCXxDD3NPu7HC2JXMlV6cw==
|
||||||
|
|
||||||
"@bull-board/api@3.7.0":
|
"@bull-board/api@3.7.0":
|
||||||
version "3.7.0"
|
version "3.7.0"
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "@budibase/string-templates",
|
"name": "@budibase/string-templates",
|
||||||
"version": "2.3.2-alpha.0",
|
"version": "2.3.11-alpha.0",
|
||||||
"description": "Handlebars wrapper for Budibase templating.",
|
"description": "Handlebars wrapper for Budibase templating.",
|
||||||
"main": "src/index.cjs",
|
"main": "src/index.cjs",
|
||||||
"module": "dist/bundle.mjs",
|
"module": "dist/bundle.mjs",
|
||||||
|
|
|
@ -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.2-alpha.0",
|
"version": "2.3.11-alpha.0",
|
||||||
"description": "Budibase types",
|
"description": "Budibase types",
|
||||||
"main": "dist/index.js",
|
"main": "dist/index.js",
|
||||||
"types": "dist/index.d.ts",
|
"types": "dist/index.d.ts",
|
||||||
|
|
|
@ -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.2-alpha.0",
|
"version": "2.3.11-alpha.0",
|
||||||
"description": "Budibase background service",
|
"description": "Budibase background service",
|
||||||
"main": "src/index.ts",
|
"main": "src/index.ts",
|
||||||
"repository": {
|
"repository": {
|
||||||
|
@ -36,10 +36,10 @@
|
||||||
"author": "Budibase",
|
"author": "Budibase",
|
||||||
"license": "GPL-3.0",
|
"license": "GPL-3.0",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@budibase/backend-core": "2.3.2-alpha.0",
|
"@budibase/backend-core": "2.3.11-alpha.0",
|
||||||
"@budibase/pro": "2.3.2-alpha.0",
|
"@budibase/pro": "2.3.11-alpha.0",
|
||||||
"@budibase/string-templates": "2.3.2-alpha.0",
|
"@budibase/string-templates": "2.3.11-alpha.0",
|
||||||
"@budibase/types": "2.3.2-alpha.0",
|
"@budibase/types": "2.3.11-alpha.0",
|
||||||
"@koa/router": "8.0.8",
|
"@koa/router": "8.0.8",
|
||||||
"@sentry/node": "6.17.7",
|
"@sentry/node": "6.17.7",
|
||||||
"@techpass/passport-openidconnect": "0.3.2",
|
"@techpass/passport-openidconnect": "0.3.2",
|
||||||
|
|
|
@ -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,14 +475,14 @@
|
||||||
resolved "https://registry.yarnpkg.com/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz#75a2e8b51cb758a7553d6804a5932d7aace75c39"
|
resolved "https://registry.yarnpkg.com/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz#75a2e8b51cb758a7553d6804a5932d7aace75c39"
|
||||||
integrity sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==
|
integrity sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==
|
||||||
|
|
||||||
"@budibase/backend-core@2.3.2-alpha.0":
|
"@budibase/backend-core@2.3.11-alpha.0":
|
||||||
version "2.3.2-alpha.0"
|
version "2.3.11-alpha.0"
|
||||||
resolved "https://registry.yarnpkg.com/@budibase/backend-core/-/backend-core-2.3.2-alpha.0.tgz#1ee32fe176f6144460c7722264a09045be10f0a7"
|
resolved "https://registry.yarnpkg.com/@budibase/backend-core/-/backend-core-2.3.11-alpha.0.tgz#361e30139a1a26d023902c6bdb4fdcac2610b69f"
|
||||||
integrity sha512-J5TbzhkgJt6/+ehKoVPQNoNnaTCCXD4iQhsB3Td7xbuwjDq49P1a9ZJXKtsqr+CzcD7MsiGh7DiSslie6scBFA==
|
integrity sha512-hlaeTkYsSJJYIwwqL3LJ7Pxzq0tOgSjQ38+nFiBPIzjkDSaV0UPwi0rZAZq/kPvaT7AKdTEcf8tn1wiui/VkYA==
|
||||||
dependencies:
|
dependencies:
|
||||||
"@budibase/nano" "10.1.1"
|
"@budibase/nano" "10.1.1"
|
||||||
"@budibase/pouchdb-replication-stream" "1.2.10"
|
"@budibase/pouchdb-replication-stream" "1.2.10"
|
||||||
"@budibase/types" "2.3.2-alpha.0"
|
"@budibase/types" "2.3.11-alpha.0"
|
||||||
"@shopify/jest-koa-mocks" "5.0.1"
|
"@shopify/jest-koa-mocks" "5.0.1"
|
||||||
"@techpass/passport-openidconnect" "0.3.2"
|
"@techpass/passport-openidconnect" "0.3.2"
|
||||||
aws-cloudfront-sign "2.2.0"
|
aws-cloudfront-sign "2.2.0"
|
||||||
|
@ -539,13 +539,13 @@
|
||||||
pouchdb-promise "^6.0.4"
|
pouchdb-promise "^6.0.4"
|
||||||
through2 "^2.0.0"
|
through2 "^2.0.0"
|
||||||
|
|
||||||
"@budibase/pro@2.3.2-alpha.0":
|
"@budibase/pro@2.3.11-alpha.0":
|
||||||
version "2.3.2-alpha.0"
|
version "2.3.11-alpha.0"
|
||||||
resolved "https://registry.yarnpkg.com/@budibase/pro/-/pro-2.3.2-alpha.0.tgz#b4406ec116f5fa008c7fc7327471302d9de4f29e"
|
resolved "https://registry.yarnpkg.com/@budibase/pro/-/pro-2.3.11-alpha.0.tgz#b59edba2c04e2a490f3d9246cd328d8dd3213295"
|
||||||
integrity sha512-vVI/xZ6ybFSVGbHZvfomxCNyy0KvrBpYiOdUHjexPd60590tkT+cfZAuPrh3FdTTM8g2VipsxNWuO1OEQU7dcw==
|
integrity sha512-z7tLgRKYKm1psNZGdRErQT8tV+vSj9hXJkD/H/uFxsJ3IQkjWbcFP5FUBPads5RnvCtqOfYu8OpRHCJfNNBoCA==
|
||||||
dependencies:
|
dependencies:
|
||||||
"@budibase/backend-core" "2.3.2-alpha.0"
|
"@budibase/backend-core" "2.3.11-alpha.0"
|
||||||
"@budibase/types" "2.3.2-alpha.0"
|
"@budibase/types" "2.3.11-alpha.0"
|
||||||
"@koa/router" "8.0.8"
|
"@koa/router" "8.0.8"
|
||||||
bull "4.10.1"
|
bull "4.10.1"
|
||||||
joi "17.6.0"
|
joi "17.6.0"
|
||||||
|
@ -553,10 +553,10 @@
|
||||||
lru-cache "^7.14.1"
|
lru-cache "^7.14.1"
|
||||||
node-fetch "^2.6.1"
|
node-fetch "^2.6.1"
|
||||||
|
|
||||||
"@budibase/types@2.3.2-alpha.0":
|
"@budibase/types@2.3.11-alpha.0":
|
||||||
version "2.3.2-alpha.0"
|
version "2.3.11-alpha.0"
|
||||||
resolved "https://registry.yarnpkg.com/@budibase/types/-/types-2.3.2-alpha.0.tgz#00b1afc4b937a5a49772bd78d189443f8e4eb0e3"
|
resolved "https://registry.yarnpkg.com/@budibase/types/-/types-2.3.11-alpha.0.tgz#55cdcef6fb30c79e9d7d90ae8789e4f0cc6a2b63"
|
||||||
integrity sha512-kVwXzA8ROg27tIyNZnTd7ZszRepP4D7NgrW2gZsygsUoiYZ+WO1rCIlWj1SFPBur+V/k8dShQkh1hwxpj8JPVg==
|
integrity sha512-SFW9vManFRJ45bgMW7wkDiNxiQQfAME8HOmSqCHKDC0OPzsDydygIl7BNeF/domFvCXxDD3NPu7HC2JXMlV6cw==
|
||||||
|
|
||||||
"@cspotcode/source-map-support@^0.8.0":
|
"@cspotcode/source-map-support@^0.8.0":
|
||||||
version "0.8.1"
|
version "0.8.1"
|
||||||
|
|
Loading…
Reference in New Issue