diff --git a/.github/workflows/budibase_ci.yml b/.github/workflows/budibase_ci.yml
index 0380f92253..96db14b77a 100644
--- a/.github/workflows/budibase_ci.yml
+++ b/.github/workflows/budibase_ci.yml
@@ -7,10 +7,12 @@ on:
branches:
- master
- develop
+ - next
pull_request:
branches:
- master
- develop
+ - next
jobs:
build:
diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml
index 792191af7a..14c0cc06a3 100644
--- a/.github/workflows/release.yml
+++ b/.github/workflows/release.yml
@@ -52,8 +52,8 @@ jobs:
mac_certs: ${{ secrets.mac_certs }}
mac_certs_password: ${{ secrets.mac_certs_password }}
- windows_certs: ${{ secrets.windows_certs }}
- windows_certs_password: ${{ secrets.windows_certs_password }}
+ # windows_certs: ${{ secrets.windows_certs }}
+ # windows_certs_password: ${{ secrets.windows_certs_password }}
# release the app after building
release: ${{ startsWith(github.ref, 'refs/tags/v') }}
diff --git a/README.md b/README.md
index 75b48a6107..04bb71fb50 100644
--- a/README.md
+++ b/README.md
@@ -38,7 +38,7 @@
-
+
diff --git a/lerna.json b/lerna.json
index 3425a979b8..f63871e1c3 100644
--- a/lerna.json
+++ b/lerna.json
@@ -1,5 +1,5 @@
{
- "version": "0.8.5",
+ "version": "0.8.9",
"npmClient": "yarn",
"packages": [
"packages/*"
diff --git a/packages/builder/package.json b/packages/builder/package.json
index ac957fa141..32389a985b 100644
--- a/packages/builder/package.json
+++ b/packages/builder/package.json
@@ -1,6 +1,6 @@
{
"name": "@budibase/builder",
- "version": "0.8.5",
+ "version": "0.8.9",
"license": "AGPL-3.0",
"private": true,
"scripts": {
@@ -64,9 +64,9 @@
},
"dependencies": {
"@budibase/bbui": "^1.58.13",
- "@budibase/client": "^0.8.5",
+ "@budibase/client": "^0.8.9",
"@budibase/colorpicker": "1.1.2",
- "@budibase/string-templates": "^0.8.5",
+ "@budibase/string-templates": "^0.8.9",
"@budibase/svelte-ag-grid": "^1.0.4",
"@sentry/browser": "5.19.1",
"@svelteschool/svelte-forms": "0.7.0",
diff --git a/packages/builder/src/builderStore/api.js b/packages/builder/src/builderStore/api.js
index 46304e1486..0202c5e8ab 100644
--- a/packages/builder/src/builderStore/api.js
+++ b/packages/builder/src/builderStore/api.js
@@ -7,9 +7,10 @@ const apiCall = method => async (
headers = { "Content-Type": "application/json" }
) => {
headers["x-budibase-app-id"] = svelteGet(store).appId
+ const json = headers["Content-Type"] === "application/json"
return await fetch(url, {
method: method,
- body: body && JSON.stringify(body),
+ body: json ? JSON.stringify(body) : body,
headers,
})
}
diff --git a/packages/builder/src/components/backend/DataTable/modals/CreateEditColumn.svelte b/packages/builder/src/components/backend/DataTable/modals/CreateEditColumn.svelte
index ad2371f3ea..d269124219 100644
--- a/packages/builder/src/components/backend/DataTable/modals/CreateEditColumn.svelte
+++ b/packages/builder/src/components/backend/DataTable/modals/CreateEditColumn.svelte
@@ -53,7 +53,11 @@
$: uneditable =
$backendUiStore.selectedTable?._id === TableNames.USERS &&
UNEDITABLE_USER_FIELDS.includes(field.name)
- $: invalid = field.type === LINK_TYPE && !field.tableId
+ $: invalid =
+ (field.type === LINK_TYPE && !field.tableId) ||
+ Object.keys($backendUiStore.draftTable.schema).some(
+ key => key === field.name
+ )
// used to select what different options can be displayed for column type
$: canBeSearched =
diff --git a/packages/builder/src/components/common/Dropzone.svelte b/packages/builder/src/components/common/Dropzone.svelte
index be93829084..0b98f2b256 100644
--- a/packages/builder/src/components/common/Dropzone.svelte
+++ b/packages/builder/src/components/common/Dropzone.svelte
@@ -15,18 +15,11 @@
}
async function processFiles(fileList) {
- const fileArray = Array.from(fileList)
-
- const filesToProcess = fileArray.map(({ name, path, size, type }) => ({
- name,
- path,
- size,
- type,
- }))
-
- const response = await api.post(`/api/attachments/process`, {
- files: filesToProcess,
- })
+ let data = new FormData()
+ for (let i = 0; i < fileList.length; i++) {
+ data.append("file", fileList[i])
+ }
+ const response = await api.post(`/api/attachments/process`, data, {})
return await response.json()
}
diff --git a/packages/builder/src/components/design/PropertiesPanel/PropertyControls/ColorPicker.svelte b/packages/builder/src/components/design/PropertiesPanel/PropertyControls/ColorPicker.svelte
index 6235e744f8..c777f79666 100644
--- a/packages/builder/src/components/design/PropertiesPanel/PropertyControls/ColorPicker.svelte
+++ b/packages/builder/src/components/design/PropertiesPanel/PropertyControls/ColorPicker.svelte
@@ -1,38 +1,42 @@
diff --git a/packages/builder/src/components/start/CreateAppModal.svelte b/packages/builder/src/components/start/CreateAppModal.svelte
index 7f1d73f744..5102179cde 100644
--- a/packages/builder/src/components/start/CreateAppModal.svelte
+++ b/packages/builder/src/components/start/CreateAppModal.svelte
@@ -123,13 +123,19 @@
async function createNewApp() {
submitting = true
try {
- // Create App
- const appResp = await post("/api/applications", {
- name: $createAppStore.values.applicationName,
- template,
- })
- const appJson = await appResp.json()
+ // Create form data to create app
+ let data = new FormData()
+ data.append("name", $createAppStore.values.applicationName)
+ data.append("useTemplate", template != null)
+ if (template) {
+ data.append("templateName", template.name)
+ data.append("templateKey", template.key)
+ data.append("templateFile", template.file)
+ }
+ // Create App
+ const appResp = await post("/api/applications", data, {})
+ const appJson = await appResp.json()
if (!appResp.ok) {
throw new Error(appJson.message)
}
diff --git a/packages/builder/src/components/start/Steps/Info.svelte b/packages/builder/src/components/start/Steps/Info.svelte
index e14e327bce..fa74e8b3a0 100644
--- a/packages/builder/src/components/start/Steps/Info.svelte
+++ b/packages/builder/src/components/start/Steps/Info.svelte
@@ -1,6 +1,5 @@
diff --git a/packages/cli/package.json b/packages/cli/package.json
index a957e8415d..5c58c80560 100644
--- a/packages/cli/package.json
+++ b/packages/cli/package.json
@@ -1,6 +1,6 @@
{
"name": "cli",
- "version": "0.8.4",
+ "version": "0.8.7",
"description": "Budibase CLI, for developers, self hosting and migrations.",
"main": "src/index.js",
"bin": "src/index.js",
diff --git a/packages/client/package.json b/packages/client/package.json
index 49ece2ed43..192bacfbc4 100644
--- a/packages/client/package.json
+++ b/packages/client/package.json
@@ -1,6 +1,6 @@
{
"name": "@budibase/client",
- "version": "0.8.5",
+ "version": "0.8.9",
"license": "MPL-2.0",
"main": "dist/budibase-client.js",
"module": "dist/budibase-client.js",
@@ -9,14 +9,13 @@
"dev:builder": "rollup -cw"
},
"dependencies": {
- "@budibase/string-templates": "^0.8.5",
- "deep-equal": "^2.0.1",
+ "@budibase/string-templates": "^0.8.9",
"regexparam": "^1.3.0",
"shortid": "^2.2.15",
"svelte-spa-router": "^3.0.5"
},
"devDependencies": {
- "@budibase/standard-components": "^0.8.5",
+ "@budibase/standard-components": "^0.8.9",
"@rollup/plugin-commonjs": "^16.0.0",
"@rollup/plugin-node-resolve": "^10.0.0",
"fs-extra": "^8.1.0",
@@ -30,5 +29,5 @@
"svelte": "^3.30.0",
"svelte-jester": "^1.0.6"
},
- "gitHead": "768a9d59da12cfefcd7ccaba05f975e878669504"
+ "gitHead": "1b95326b20d1352d36305910259228b96a683dc7"
}
diff --git a/packages/client/src/store/notification.js b/packages/client/src/store/notification.js
index d69a39080e..3efd40a882 100644
--- a/packages/client/src/store/notification.js
+++ b/packages/client/src/store/notification.js
@@ -13,8 +13,17 @@ const createNotificationStore = () => {
_notifications.set([])
}
})
+ let block = false
+
+ const blockNotifications = (timeout = 1000) => {
+ block = true
+ setTimeout(() => (block = false), timeout)
+ }
const send = (message, type = "default") => {
+ if (block) {
+ return
+ }
let _id = id()
_notifications.update(state => {
return [...state, { id: _id, type, message }]
@@ -36,6 +45,7 @@ const createNotificationStore = () => {
warning: msg => send(msg, "warning"),
info: msg => send(msg, "info"),
success: msg => send(msg, "success"),
+ blockNotifications,
}
}
diff --git a/packages/client/yarn.lock b/packages/client/yarn.lock
index b82ea3d13d..bcef9d320d 100644
--- a/packages/client/yarn.lock
+++ b/packages/client/yarn.lock
@@ -150,11 +150,6 @@ ansi-styles@^3.2.1:
dependencies:
color-convert "^1.9.0"
-array-filter@^1.0.0:
- version "1.0.0"
- resolved "https://registry.yarnpkg.com/array-filter/-/array-filter-1.0.0.tgz#baf79e62e6ef4c2a4c0b831232daffec251f9d83"
- integrity sha1-uveeYubvTCpMC4MSMtr/7CUfnYM=
-
asn1.js@^5.2.0:
version "5.4.1"
resolved "https://registry.yarnpkg.com/asn1.js/-/asn1.js-5.4.1.tgz#11a980b84ebb91781ce35b0fdc2ee294e3783f07"
@@ -182,13 +177,6 @@ asynckit@^0.4.0:
resolved "https://registry.yarnpkg.com/asynckit/-/asynckit-0.4.0.tgz#c79ed97f7f34cb8f2ba1bc9790bcc366474b4b79"
integrity sha1-x57Zf380y48robyXkLzDZkdLS3k=
-available-typed-arrays@^1.0.0, available-typed-arrays@^1.0.2:
- version "1.0.2"
- resolved "https://registry.yarnpkg.com/available-typed-arrays/-/available-typed-arrays-1.0.2.tgz#6b098ca9d8039079ee3f77f7b783c4480ba513f5"
- integrity sha512-XWX3OX8Onv97LMk/ftVyBibpGwY5a8SmuxZPzeOxqmuEqUCOM9ZE+uIaD1VNJ5QnvU2UQusvmKbuM1FR8QWGfQ==
- dependencies:
- array-filter "^1.0.0"
-
aws-sign2@~0.7.0:
version "0.7.0"
resolved "https://registry.yarnpkg.com/aws-sign2/-/aws-sign2-0.7.0.tgz#b46e890934a9591f2d2f6f86d7e6a9f1b3fe76a8"
@@ -507,26 +495,6 @@ decimal.js@^10.2.0:
resolved "https://registry.yarnpkg.com/decimal.js/-/decimal.js-10.2.0.tgz#39466113a9e036111d02f82489b5fd6b0b5ed231"
integrity sha512-vDPw+rDgn3bZe1+F/pyEwb1oMG2XTlRVgAa6B4KccTEpYgF8w6eQllVbQcfIJnZyvzFtFpxnpGtx8dd7DJp/Rw==
-deep-equal@^2.0.1:
- version "2.0.3"
- resolved "https://registry.yarnpkg.com/deep-equal/-/deep-equal-2.0.3.tgz#cad1c15277ad78a5c01c49c2dee0f54de8a6a7b0"
- integrity sha512-Spqdl4H+ky45I9ByyJtXteOm9CaIrPmnIPmOhrkKGNYWeDgCvJ8jNYVCTjChxW4FqGuZnLHADc8EKRMX6+CgvA==
- dependencies:
- es-abstract "^1.17.5"
- es-get-iterator "^1.1.0"
- is-arguments "^1.0.4"
- is-date-object "^1.0.2"
- is-regex "^1.0.5"
- isarray "^2.0.5"
- object-is "^1.1.2"
- object-keys "^1.1.1"
- object.assign "^4.1.0"
- regexp.prototype.flags "^1.3.0"
- side-channel "^1.0.2"
- which-boxed-primitive "^1.0.1"
- which-collection "^1.0.1"
- which-typed-array "^1.1.2"
-
deep-is@~0.1.3:
version "0.1.3"
resolved "https://registry.yarnpkg.com/deep-is/-/deep-is-0.1.3.tgz#b369d6fb5dbc13eecf524f91b070feedc357cf34"
@@ -544,13 +512,6 @@ deferred-leveldown@~0.2.0:
dependencies:
abstract-leveldown "~0.12.1"
-define-properties@^1.1.2, define-properties@^1.1.3:
- version "1.1.3"
- resolved "https://registry.yarnpkg.com/define-properties/-/define-properties-1.1.3.tgz#cf88da6cbee26fe6db7094f61d870cbd84cee9f1"
- integrity sha512-3MqfYKj2lLzdMSf8ZIZE/V+Zuy+BgD6f164e8K2w7dgnpKArBDerGYpM46IYYcjnkdPNMjPk9A6VFB8+3SKlXQ==
- dependencies:
- object-keys "^1.0.12"
-
delayed-stream@~1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/delayed-stream/-/delayed-stream-1.0.0.tgz#df3ae199acadfb7d440aaae0b29e2272b24ec619"
@@ -615,63 +576,6 @@ error-ex@^1.3.1:
dependencies:
is-arrayish "^0.2.1"
-es-abstract@^1.17.0-next.1, es-abstract@^1.17.4, es-abstract@^1.17.5:
- version "1.17.6"
- resolved "https://registry.yarnpkg.com/es-abstract/-/es-abstract-1.17.6.tgz#9142071707857b2cacc7b89ecb670316c3e2d52a"
- integrity sha512-Fr89bON3WFyUi5EvAeI48QTWX0AyekGgLA8H+c+7fbfCkJwRWRMLd8CQedNEyJuoYYhmtEqY92pgte1FAhBlhw==
- dependencies:
- es-to-primitive "^1.2.1"
- function-bind "^1.1.1"
- has "^1.0.3"
- has-symbols "^1.0.1"
- is-callable "^1.2.0"
- is-regex "^1.1.0"
- object-inspect "^1.7.0"
- object-keys "^1.1.1"
- object.assign "^4.1.0"
- string.prototype.trimend "^1.0.1"
- string.prototype.trimstart "^1.0.1"
-
-es-abstract@^1.18.0-next.0:
- version "1.18.0-next.0"
- resolved "https://registry.yarnpkg.com/es-abstract/-/es-abstract-1.18.0-next.0.tgz#b302834927e624d8e5837ed48224291f2c66e6fc"
- integrity sha512-elZXTZXKn51hUBdJjSZGYRujuzilgXo8vSPQzjGYXLvSlGiCo8VO8ZGV3kjo9a0WNJJ57hENagwbtlRuHuzkcQ==
- dependencies:
- es-to-primitive "^1.2.1"
- function-bind "^1.1.1"
- has "^1.0.3"
- has-symbols "^1.0.1"
- is-callable "^1.2.0"
- is-negative-zero "^2.0.0"
- is-regex "^1.1.1"
- object-inspect "^1.8.0"
- object-keys "^1.1.1"
- object.assign "^4.1.0"
- string.prototype.trimend "^1.0.1"
- string.prototype.trimstart "^1.0.1"
-
-es-get-iterator@^1.1.0:
- version "1.1.0"
- resolved "https://registry.yarnpkg.com/es-get-iterator/-/es-get-iterator-1.1.0.tgz#bb98ad9d6d63b31aacdc8f89d5d0ee57bcb5b4c8"
- integrity sha512-UfrmHuWQlNMTs35e1ypnvikg6jCz3SK8v8ImvmDsh36fCVUR1MqoFDiyn0/k52C8NqO3YsO8Oe0azeesNuqSsQ==
- dependencies:
- es-abstract "^1.17.4"
- has-symbols "^1.0.1"
- is-arguments "^1.0.4"
- is-map "^2.0.1"
- is-set "^2.0.1"
- is-string "^1.0.5"
- isarray "^2.0.5"
-
-es-to-primitive@^1.2.1:
- version "1.2.1"
- resolved "https://registry.yarnpkg.com/es-to-primitive/-/es-to-primitive-1.2.1.tgz#e55cd4c9cdc188bcefb03b366c736323fc5c898a"
- integrity sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA==
- dependencies:
- is-callable "^1.1.4"
- is-date-object "^1.0.1"
- is-symbol "^1.0.2"
-
escape-string-regexp@^1.0.5:
version "1.0.5"
resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz#1b61c0562190a8dff6ae3bb2cf0200ca130b86d4"
@@ -762,7 +666,7 @@ fast-levenshtein@~2.0.6:
resolved "https://registry.yarnpkg.com/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz#3d8a5c66883a16a30ca8643e851f19baa7797917"
integrity sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc=
-foreach@^2.0.5, foreach@~2.0.1:
+foreach@~2.0.1:
version "2.0.5"
resolved "https://registry.yarnpkg.com/foreach/-/foreach-2.0.5.tgz#0bee005018aeb260d0a3af3ae658dd0136ec1b99"
integrity sha1-C+4AUBiusmDQo6865ljdATbsG5k=
@@ -859,11 +763,6 @@ has-flag@^4.0.0:
resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-4.0.0.tgz#944771fd9c81c81265c4d6941860da06bb59479b"
integrity sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==
-has-symbols@^1.0.0, has-symbols@^1.0.1:
- version "1.0.1"
- resolved "https://registry.yarnpkg.com/has-symbols/-/has-symbols-1.0.1.tgz#9f5214758a44196c406d9bd76cebf81ec2dd31e8"
- integrity sha512-PLcsoqu++dmEIZB+6totNFKq/7Do+Z0u4oT0zKOJNl3lYK6vGwwu2hjHs+68OEZbTjiUE9bgOABXbP/GvrS0Kg==
-
has@^1.0.3:
version "1.0.3"
resolved "https://registry.yarnpkg.com/has/-/has-1.0.3.tgz#722d7cbfc1f6aa8241f16dd814e011e1f41e8796"
@@ -956,31 +855,11 @@ ip-regex@^2.1.0:
resolved "https://registry.yarnpkg.com/ip-regex/-/ip-regex-2.1.0.tgz#fa78bf5d2e6913c911ce9f819ee5146bb6d844e9"
integrity sha1-+ni/XS5pE8kRzp+BnuUUa7bYROk=
-is-arguments@^1.0.4:
- version "1.0.4"
- resolved "https://registry.yarnpkg.com/is-arguments/-/is-arguments-1.0.4.tgz#3faf966c7cba0ff437fb31f6250082fcf0448cf3"
- integrity sha512-xPh0Rmt8NE65sNzvyUmWgI1tz3mKq74lGA0mL8LYZcoIzKOzDh6HmrYm3d18k60nHerC8A9Km8kYu87zfSFnLA==
-
is-arrayish@^0.2.1:
version "0.2.1"
resolved "https://registry.yarnpkg.com/is-arrayish/-/is-arrayish-0.2.1.tgz#77c99840527aa8ecb1a8ba697b80645a7a926a9d"
integrity sha1-d8mYQFJ6qOyxqLppe4BkWnqSap0=
-is-bigint@^1.0.0:
- version "1.0.0"
- resolved "https://registry.yarnpkg.com/is-bigint/-/is-bigint-1.0.0.tgz#73da8c33208d00f130e9b5e15d23eac9215601c4"
- integrity sha512-t5mGUXC/xRheCK431ylNiSkGGpBp8bHENBcENTkDT6ppwPzEVxNGZRvgvmOEfbWkFhA7D2GEuE2mmQTr78sl2g==
-
-is-boolean-object@^1.0.0:
- version "1.0.1"
- resolved "https://registry.yarnpkg.com/is-boolean-object/-/is-boolean-object-1.0.1.tgz#10edc0900dd127697a92f6f9807c7617d68ac48e"
- integrity sha512-TqZuVwa/sppcrhUCAYkGBk7w0yxfQQnxq28fjkO53tnK9FQXmdwz2JS5+GjsWQ6RByES1K40nI+yDic5c9/aAQ==
-
-is-callable@^1.1.4, is-callable@^1.2.0:
- version "1.2.0"
- resolved "https://registry.yarnpkg.com/is-callable/-/is-callable-1.2.0.tgz#83336560b54a38e35e3a2df7afd0454d691468bb"
- integrity sha512-pyVD9AaGLxtg6srb2Ng6ynWJqkHU9bEM087AKck0w8QwDarTfNcpIYoU8x8Hv2Icm8u6kFJM18Dag8lyqGkviw==
-
is-core-module@^2.1.0:
version "2.1.0"
resolved "https://registry.yarnpkg.com/is-core-module/-/is-core-module-2.1.0.tgz#a4cc031d9b1aca63eecbd18a650e13cb4eeab946"
@@ -988,31 +867,11 @@ is-core-module@^2.1.0:
dependencies:
has "^1.0.3"
-is-date-object@^1.0.1, is-date-object@^1.0.2:
- version "1.0.2"
- resolved "https://registry.yarnpkg.com/is-date-object/-/is-date-object-1.0.2.tgz#bda736f2cd8fd06d32844e7743bfa7494c3bfd7e"
- integrity sha512-USlDT524woQ08aoZFzh3/Z6ch9Y/EWXEHQ/AaRN0SkKq4t2Jw2R2339tSXmwuVoY7LLlBCbOIlx2myP/L5zk0g==
-
-is-map@^2.0.1:
- version "2.0.1"
- resolved "https://registry.yarnpkg.com/is-map/-/is-map-2.0.1.tgz#520dafc4307bb8ebc33b813de5ce7c9400d644a1"
- integrity sha512-T/S49scO8plUiAOA2DBTBG3JHpn1yiw0kRp6dgiZ0v2/6twi5eiB0rHtHFH9ZIrvlWc6+4O+m4zg5+Z833aXgw==
-
is-module@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/is-module/-/is-module-1.0.0.tgz#3258fb69f78c14d5b815d664336b4cffb6441591"
integrity sha1-Mlj7afeMFNW4FdZkM2tM/7ZEFZE=
-is-negative-zero@^2.0.0:
- version "2.0.0"
- resolved "https://registry.yarnpkg.com/is-negative-zero/-/is-negative-zero-2.0.0.tgz#9553b121b0fac28869da9ed459e20c7543788461"
- integrity sha1-lVOxIbD6wohp2p7UWeIMdUN4hGE=
-
-is-number-object@^1.0.3:
- version "1.0.4"
- resolved "https://registry.yarnpkg.com/is-number-object/-/is-number-object-1.0.4.tgz#36ac95e741cf18b283fc1ddf5e83da798e3ec197"
- integrity sha512-zohwelOAur+5uXtk8O3GPQ1eAcu4ZX3UwxQhUlfFFMNpUd83gXgjbhJh6HmB6LUNV/ieOLQuDwJO3dWJosUeMw==
-
is-object@~0.1.2:
version "0.1.2"
resolved "https://registry.yarnpkg.com/is-object/-/is-object-0.1.2.tgz#00efbc08816c33cfc4ac8251d132e10dc65098d7"
@@ -1030,55 +889,11 @@ is-reference@^1.2.1:
dependencies:
"@types/estree" "*"
-is-regex@^1.0.5, is-regex@^1.1.0, is-regex@^1.1.1:
- version "1.1.1"
- resolved "https://registry.yarnpkg.com/is-regex/-/is-regex-1.1.1.tgz#c6f98aacc546f6cec5468a07b7b153ab564a57b9"
- integrity sha512-1+QkEcxiLlB7VEyFtyBg94e08OAsvq7FUBgApTq/w2ymCLyKJgDPsybBENVtA7XCQEgEXxKPonG+mvYRxh/LIg==
- dependencies:
- has-symbols "^1.0.1"
-
-is-set@^2.0.1:
- version "2.0.1"
- resolved "https://registry.yarnpkg.com/is-set/-/is-set-2.0.1.tgz#d1604afdab1724986d30091575f54945da7e5f43"
- integrity sha512-eJEzOtVyenDs1TMzSQ3kU3K+E0GUS9sno+F0OBT97xsgcJsF9nXMBtkT9/kut5JEpM7oL7X/0qxR17K3mcwIAA==
-
-is-string@^1.0.4, is-string@^1.0.5:
- version "1.0.5"
- resolved "https://registry.yarnpkg.com/is-string/-/is-string-1.0.5.tgz#40493ed198ef3ff477b8c7f92f644ec82a5cd3a6"
- integrity sha512-buY6VNRjhQMiF1qWDouloZlQbRhDPCebwxSjxMjxgemYT46YMd2NR0/H+fBhEfWX4A/w9TBJ+ol+okqJKFE6vQ==
-
-is-symbol@^1.0.2:
- version "1.0.3"
- resolved "https://registry.yarnpkg.com/is-symbol/-/is-symbol-1.0.3.tgz#38e1014b9e6329be0de9d24a414fd7441ec61937"
- integrity sha512-OwijhaRSgqvhm/0ZdAcXNZt9lYdKFpcRDT5ULUuYXPoT794UNOdU+gpT6Rzo7b4V2HUl/op6GqY894AZwv9faQ==
- dependencies:
- has-symbols "^1.0.1"
-
-is-typed-array@^1.1.3:
- version "1.1.3"
- resolved "https://registry.yarnpkg.com/is-typed-array/-/is-typed-array-1.1.3.tgz#a4ff5a5e672e1a55f99c7f54e59597af5c1df04d"
- integrity sha512-BSYUBOK/HJibQ30wWkWold5txYwMUXQct9YHAQJr8fSwvZoiglcqB0pd7vEN23+Tsi9IUEjztdOSzl4qLVYGTQ==
- dependencies:
- available-typed-arrays "^1.0.0"
- es-abstract "^1.17.4"
- foreach "^2.0.5"
- has-symbols "^1.0.1"
-
is-typedarray@~1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/is-typedarray/-/is-typedarray-1.0.0.tgz#e479c80858df0c1b11ddda6940f96011fcda4a9a"
integrity sha1-5HnICFjfDBsR3dppQPlgEfzaSpo=
-is-weakmap@^2.0.1:
- version "2.0.1"
- resolved "https://registry.yarnpkg.com/is-weakmap/-/is-weakmap-2.0.1.tgz#5008b59bdc43b698201d18f62b37b2ca243e8cf2"
- integrity sha512-NSBR4kH5oVj1Uwvv970ruUkCV7O1mzgVFO4/rev2cLRda9Tm9HrL70ZPut4rOHgY0FNrUu9BCbXA2sdQ+x0chA==
-
-is-weakset@^2.0.1:
- version "2.0.1"
- resolved "https://registry.yarnpkg.com/is-weakset/-/is-weakset-2.0.1.tgz#e9a0af88dbd751589f5e50d80f4c98b780884f83"
- integrity sha512-pi4vhbhVHGLxohUw7PhGsueT4vRGFoXhP7+RGN0jKIv9+8PWYCQTqtADngrxOm2g46hoH0+g8uZZBzMrvVGDmw==
-
is@~0.2.6:
version "0.2.7"
resolved "https://registry.yarnpkg.com/is/-/is-0.2.7.tgz#3b34a2c48f359972f35042849193ae7264b63562"
@@ -1089,11 +904,6 @@ isarray@0.0.1:
resolved "https://registry.yarnpkg.com/isarray/-/isarray-0.0.1.tgz#8a18acfca9a8f4177e09abfc6038939b05d1eedf"
integrity sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=
-isarray@^2.0.5:
- version "2.0.5"
- resolved "https://registry.yarnpkg.com/isarray/-/isarray-2.0.5.tgz#8af1e4c1221244cc62459faf38940d4e644a5723"
- integrity sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==
-
isarray@~1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/isarray/-/isarray-1.0.0.tgz#bb935d48582cba168c06834957a54a3e07124f11"
@@ -1390,24 +1200,6 @@ oauth-sign@~0.9.0:
resolved "https://registry.yarnpkg.com/oauth-sign/-/oauth-sign-0.9.0.tgz#47a7b016baa68b5fa0ecf3dee08a85c679ac6455"
integrity sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ==
-object-inspect@^1.7.0, object-inspect@^1.8.0:
- version "1.8.0"
- resolved "https://registry.yarnpkg.com/object-inspect/-/object-inspect-1.8.0.tgz#df807e5ecf53a609cc6bfe93eac3cc7be5b3a9d0"
- integrity sha512-jLdtEOB112fORuypAyl/50VRVIBIdVQOSUUGQHzJ4xBSbit81zRarz7GThkEFZy1RceYrWYcPcBFPQwHyAc1gA==
-
-object-is@^1.1.2:
- version "1.1.2"
- resolved "https://registry.yarnpkg.com/object-is/-/object-is-1.1.2.tgz#c5d2e87ff9e119f78b7a088441519e2eec1573b6"
- integrity sha512-5lHCz+0uufF6wZ7CRFWJN3hp8Jqblpgve06U5CMQ3f//6iDjPr2PEo9MWCjEssDsa+UZEL4PkFpr+BMop6aKzQ==
- dependencies:
- define-properties "^1.1.3"
- es-abstract "^1.17.5"
-
-object-keys@^1.0.11, object-keys@^1.0.12, object-keys@^1.1.1:
- version "1.1.1"
- resolved "https://registry.yarnpkg.com/object-keys/-/object-keys-1.1.1.tgz#1c47f272df277f3b1daf061677d9c82e2322c60e"
- integrity sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==
-
object-keys@~0.2.0:
version "0.2.0"
resolved "https://registry.yarnpkg.com/object-keys/-/object-keys-0.2.0.tgz#cddec02998b091be42bf1035ae32e49f1cb6ea67"
@@ -1422,16 +1214,6 @@ object-keys@~0.4.0:
resolved "https://registry.yarnpkg.com/object-keys/-/object-keys-0.4.0.tgz#28a6aae7428dd2c3a92f3d95f21335dd204e0336"
integrity sha1-KKaq50KN0sOpLz2V8hM13SBOAzY=
-object.assign@^4.1.0:
- version "4.1.0"
- resolved "https://registry.yarnpkg.com/object.assign/-/object.assign-4.1.0.tgz#968bf1100d7956bb3ca086f006f846b3bc4008da"
- integrity sha512-exHJeq6kBKj58mqGyTQ9DFvrZC/eR6OwxzoM9YRoGBqrXYonaFyGiFMuc9VZrXf7DarreEwMpurG3dd+CNyW5w==
- dependencies:
- define-properties "^1.1.2"
- function-bind "^1.1.1"
- has-symbols "^1.0.0"
- object-keys "^1.0.11"
-
octal@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/octal/-/octal-1.0.0.tgz#63e7162a68efbeb9e213588d58e989d1e5c4530b"
@@ -1634,14 +1416,6 @@ readable-stream@~1.0.26, readable-stream@~1.0.26-4:
isarray "0.0.1"
string_decoder "~0.10.x"
-regexp.prototype.flags@^1.3.0:
- version "1.3.0"
- resolved "https://registry.yarnpkg.com/regexp.prototype.flags/-/regexp.prototype.flags-1.3.0.tgz#7aba89b3c13a64509dabcf3ca8d9fbb9bdf5cb75"
- integrity sha512-2+Q0C5g951OlYlJz6yu5/M33IcsESLlLfsyIaLJaG4FA2r4yP8MvVMJUUP/fVBkSpbbbZlS5gynbEWLipiiXiQ==
- dependencies:
- define-properties "^1.1.3"
- es-abstract "^1.17.0-next.1"
-
regexparam@1.3.0, regexparam@^1.3.0:
version "1.3.0"
resolved "https://registry.yarnpkg.com/regexparam/-/regexparam-1.3.0.tgz#2fe42c93e32a40eff6235d635e0ffa344b92965f"
@@ -1830,14 +1604,6 @@ shortid@^2.2.15:
dependencies:
nanoid "^2.1.0"
-side-channel@^1.0.2:
- version "1.0.3"
- resolved "https://registry.yarnpkg.com/side-channel/-/side-channel-1.0.3.tgz#cdc46b057550bbab63706210838df5d4c19519c3"
- integrity sha512-A6+ByhlLkksFoUepsGxfj5x1gTSrs+OydsRptUxeNCabQpCFUvcwIczgOigI8vhY/OJCnPnyE9rGiwgvr9cS1g==
- dependencies:
- es-abstract "^1.18.0-next.0"
- object-inspect "^1.8.0"
-
source-map-support@~0.5.19:
version "0.5.19"
resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.5.19.tgz#a98b62f86dcaf4f67399648c085291ab9e8fed61"
@@ -1886,22 +1652,6 @@ string-range@~1.2, string-range@~1.2.1:
resolved "https://registry.yarnpkg.com/string-range/-/string-range-1.2.2.tgz#a893ed347e72299bc83befbbf2a692a8d239d5dd"
integrity sha1-qJPtNH5yKZvIO++78qaSqNI51d0=
-string.prototype.trimend@^1.0.1:
- version "1.0.1"
- resolved "https://registry.yarnpkg.com/string.prototype.trimend/-/string.prototype.trimend-1.0.1.tgz#85812a6b847ac002270f5808146064c995fb6913"
- integrity sha512-LRPxFUaTtpqYsTeNKaFOw3R4bxIzWOnbQ837QfBylo8jIxtcbK/A/sMV7Q+OAV/vWo+7s25pOE10KYSjaSO06g==
- dependencies:
- define-properties "^1.1.3"
- es-abstract "^1.17.5"
-
-string.prototype.trimstart@^1.0.1:
- version "1.0.1"
- resolved "https://registry.yarnpkg.com/string.prototype.trimstart/-/string.prototype.trimstart-1.0.1.tgz#14af6d9f34b053f7cfc89b72f8f2ee14b9039a54"
- integrity sha512-XxZn+QpvrBI1FOcg6dIpxUPgWCPuNXvMD72aaRaUQv1eD4e/Qy8i/hFTe0BUmD60p/QA6bh1avmuPTfNjqVWRw==
- dependencies:
- define-properties "^1.1.3"
- es-abstract "^1.17.5"
-
string_decoder@^1.1.1:
version "1.3.0"
resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.3.0.tgz#42f114594a46cf1a8e30b0a84f56c78c3edac21e"
@@ -2102,39 +1852,6 @@ whatwg-url@^8.0.0:
tr46 "^2.0.2"
webidl-conversions "^6.1.0"
-which-boxed-primitive@^1.0.1:
- version "1.0.1"
- resolved "https://registry.yarnpkg.com/which-boxed-primitive/-/which-boxed-primitive-1.0.1.tgz#cbe8f838ebe91ba2471bb69e9edbda67ab5a5ec1"
- integrity sha512-7BT4TwISdDGBgaemWU0N0OU7FeAEJ9Oo2P1PHRm/FCWoEi2VLWC9b6xvxAA3C/NMpxg3HXVgi0sMmGbNUbNepQ==
- dependencies:
- is-bigint "^1.0.0"
- is-boolean-object "^1.0.0"
- is-number-object "^1.0.3"
- is-string "^1.0.4"
- is-symbol "^1.0.2"
-
-which-collection@^1.0.1:
- version "1.0.1"
- resolved "https://registry.yarnpkg.com/which-collection/-/which-collection-1.0.1.tgz#70eab71ebbbd2aefaf32f917082fc62cdcb70906"
- integrity sha512-W8xeTUwaln8i3K/cY1nGXzdnVZlidBcagyNFtBdD5kxnb4TvGKR7FfSIS3mYpwWS1QUCutfKz8IY8RjftB0+1A==
- dependencies:
- is-map "^2.0.1"
- is-set "^2.0.1"
- is-weakmap "^2.0.1"
- is-weakset "^2.0.1"
-
-which-typed-array@^1.1.2:
- version "1.1.2"
- resolved "https://registry.yarnpkg.com/which-typed-array/-/which-typed-array-1.1.2.tgz#e5f98e56bda93e3dac196b01d47c1156679c00b2"
- integrity sha512-KT6okrd1tE6JdZAy3o2VhMoYPh3+J6EMZLyrxBQsZflI1QCZIxMrIYLkosd8Twf+YfknVIHmYQPgJt238p8dnQ==
- dependencies:
- available-typed-arrays "^1.0.2"
- es-abstract "^1.17.5"
- foreach "^2.0.5"
- function-bind "^1.1.1"
- has-symbols "^1.0.1"
- is-typed-array "^1.1.3"
-
word-wrap@~1.2.3:
version "1.2.3"
resolved "https://registry.yarnpkg.com/word-wrap/-/word-wrap-1.2.3.tgz#610636f6b1f703891bd34771ccb17fb93b47079c"
diff --git a/packages/server/__mocks__/@sendgrid/mail.js b/packages/server/__mocks__/@sendgrid/mail.js
new file mode 100644
index 0000000000..e162237ff4
--- /dev/null
+++ b/packages/server/__mocks__/@sendgrid/mail.js
@@ -0,0 +1,18 @@
+class Email {
+ constructor() {
+ this.apiKey = null
+ }
+
+ setApiKey(apiKey) {
+ this.apiKey = apiKey
+ }
+
+ async send(msg) {
+ if (msg.to === "invalid@test.com") {
+ throw "Invalid"
+ }
+ return msg
+ }
+}
+
+module.exports = new Email()
diff --git a/packages/server/__mocks__/node-fetch.js b/packages/server/__mocks__/node-fetch.js
index 1113791ec2..3cc412b1c6 100644
--- a/packages/server/__mocks__/node-fetch.js
+++ b/packages/server/__mocks__/node-fetch.js
@@ -1,17 +1,35 @@
const fetch = jest.requireActual("node-fetch")
module.exports = async (url, opts) => {
- // mocked data based on url
- if (url.includes("api/apps")) {
+ function json(body, status = 200) {
return {
+ status,
json: async () => {
- return {
- app1: {
- url: "/app1",
- },
- }
+ return body
},
}
}
+
+ // mocked data based on url
+ if (url.includes("api/apps")) {
+ return json({
+ app1: {
+ url: "/app1",
+ },
+ })
+ } else if (url.includes("test.com")) {
+ return json({
+ body: opts.body,
+ url,
+ method: opts.method,
+ })
+ } else if (url.includes("invalid.com")) {
+ return json(
+ {
+ invalid: true,
+ },
+ 404
+ )
+ }
return fetch(url, opts)
}
diff --git a/packages/server/package.json b/packages/server/package.json
index 99763109f3..6f52f1ac36 100644
--- a/packages/server/package.json
+++ b/packages/server/package.json
@@ -1,7 +1,7 @@
{
"name": "@budibase/server",
"email": "hi@budibase.com",
- "version": "0.8.5",
+ "version": "0.8.9",
"description": "Budibase Web Server",
"main": "src/electron.js",
"repository": {
@@ -33,7 +33,7 @@
},
"scripts": {
"test": "jest --testPathIgnorePatterns=routes && npm run test:integration",
- "test:integration": "jest --runInBand --coverage",
+ "test:integration": "jest --coverage --detectOpenHandles",
"test:watch": "jest --watch",
"run:docker": "node src/index",
"dev:builder": "cross-env PORT=4001 nodemon src/index.js",
@@ -53,11 +53,16 @@
"src/**/*.js",
"!**/node_modules/**",
"!src/db/views/*.js",
- "!src/api/routes/tests/**/*.js",
"!src/api/controllers/deploy/**/*.js",
- "!src/api/controllers/static/templates/**/*",
- "!src/api/controllers/static/selfhost/**/*",
- "!src/*.js"
+ "!src/*.js",
+ "!src/api/controllers/static/**/*",
+ "!src/db/dynamoClient.js",
+ "!src/utilities/usageQuota.js",
+ "!src/api/routes/tests/**/*",
+ "!src/tests/**/*",
+ "!src/automations/tests/**/*",
+ "!src/utilities/fileProcessor.js",
+ "!src/utilities/initialiseBudibase.js"
],
"coverageReporters": [
"lcov",
@@ -71,8 +76,8 @@
"author": "Budibase",
"license": "AGPL-3.0-or-later",
"dependencies": {
- "@budibase/client": "^0.8.5",
- "@budibase/string-templates": "^0.8.5",
+ "@budibase/client": "^0.8.9",
+ "@budibase/string-templates": "^0.8.9",
"@elastic/elasticsearch": "7.10.0",
"@koa/router": "8.0.0",
"@sendgrid/mail": "7.1.1",
@@ -126,7 +131,7 @@
"zlib": "1.0.5"
},
"devDependencies": {
- "@budibase/standard-components": "^0.8.5",
+ "@budibase/standard-components": "^0.8.9",
"@jest/test-sequencer": "^24.8.0",
"cross-env": "^7.0.3",
"electron": "10.1.3",
@@ -138,5 +143,5 @@
"pouchdb-adapter-memory": "^7.2.1",
"supertest": "^4.0.2"
},
- "gitHead": "768a9d59da12cfefcd7ccaba05f975e878669504"
+ "gitHead": "1b95326b20d1352d36305910259228b96a683dc7"
}
diff --git a/packages/server/src/api/controllers/application.js b/packages/server/src/api/controllers/application.js
index cba3e6455a..9e50319d5d 100644
--- a/packages/server/src/api/controllers/application.js
+++ b/packages/server/src/api/controllers/application.js
@@ -91,7 +91,6 @@ async function getAppUrlIfNotInUse(ctx) {
async function createInstance(template) {
const appId = generateAppID()
-
const db = new CouchDB(appId)
await db.put({
_id: "_design/database",
@@ -106,10 +105,10 @@ async function createInstance(template) {
// replicate the template data to the instance DB
// this is currently very hard to test, downloading and importing template files
/* istanbul ignore next */
- if (template) {
+ if (template && template.useTemplate === "true") {
let dbDumpReadStream
- if (template.fileImportPath) {
- dbDumpReadStream = fs.createReadStream(template.fileImportPath)
+ if (template.file) {
+ dbDumpReadStream = fs.createReadStream(template.file.path)
} else {
const templatePath = await downloadTemplate(...template.key.split("/"))
dbDumpReadStream = fs.createReadStream(
@@ -162,8 +161,17 @@ exports.fetchAppPackage = async function(ctx) {
}
exports.create = async function(ctx) {
+ const { useTemplate, templateKey } = ctx.request.body
+ const instanceConfig = {
+ useTemplate,
+ key: templateKey,
+ }
+ if (ctx.request.files && ctx.request.files.templateFile) {
+ instanceConfig.file = ctx.request.files.templateFile
+ }
+ const instance = await createInstance(instanceConfig)
+
const url = await getAppUrlIfNotInUse(ctx)
- const instance = await createInstance(ctx.request.body.template)
const appId = instance._id
const version = packageJson.version
const newApplication = {
diff --git a/packages/server/src/api/controllers/static/index.js b/packages/server/src/api/controllers/static/index.js
index bf01314795..ff1ecf5b2e 100644
--- a/packages/server/src/api/controllers/static/index.js
+++ b/packages/server/src/api/controllers/static/index.js
@@ -152,26 +152,6 @@ async function processLocalFileUploads({ files, outputPath, appId }) {
return processedFiles
}
-exports.performLocalFileProcessing = async function(ctx) {
- const { files } = ctx.request.body
-
- const processedFileOutputPath = resolve(
- budibaseAppsDir(),
- ctx.user.appId,
- "attachments"
- )
-
- try {
- ctx.body = await processLocalFileUploads({
- files,
- outputPath: processedFileOutputPath,
- appId: ctx.user.appId,
- })
- } catch (err) {
- ctx.throw(500, err)
- }
-}
-
exports.serveApp = async function(ctx) {
let appId = ctx.params.appId
if (env.SELF_HOSTED) {
diff --git a/packages/server/src/api/controllers/table/index.js b/packages/server/src/api/controllers/table/index.js
index 93f4ec9f94..4cb1d16146 100644
--- a/packages/server/src/api/controllers/table/index.js
+++ b/packages/server/src/api/controllers/table/index.js
@@ -65,12 +65,14 @@ exports.save = async function(ctx) {
// Don't rename if the name is the same
let { _rename } = tableToSave
+ /* istanbul ignore next */
if (_rename && _rename.old === _rename.updated) {
_rename = null
delete tableToSave._rename
}
// rename row fields when table column is renamed
+ /* istanbul ignore next */
if (_rename && tableToSave.schema[_rename.updated].type === FieldTypes.LINK) {
ctx.throw(400, "Cannot rename a linked column.")
} else if (_rename && tableToSave.primaryDisplay === _rename.old) {
@@ -159,7 +161,7 @@ exports.destroy = async function(ctx) {
ctx.eventEmitter &&
ctx.eventEmitter.emitTable(`table:delete`, appId, tableToDelete)
ctx.status = 200
- ctx.message = `Table ${ctx.params.tableId} deleted.`
+ ctx.body = { message: `Table ${ctx.params.tableId} deleted.` }
}
exports.validateCSVSchema = async function(ctx) {
diff --git a/packages/server/src/api/controllers/table/utils.js b/packages/server/src/api/controllers/table/utils.js
index 73e6e60551..66b3651ccf 100644
--- a/packages/server/src/api/controllers/table/utils.js
+++ b/packages/server/src/api/controllers/table/utils.js
@@ -90,7 +90,8 @@ exports.handleDataImport = async (user, table, dataImport) => {
return table
}
-exports.handleSearchIndexes = async (db, table) => {
+exports.handleSearchIndexes = async (appId, table) => {
+ const db = new CouchDB(appId)
// create relevant search indexes
if (table.indexes && table.indexes.length > 0) {
const currentIndexes = await db.getIndexes()
@@ -150,6 +151,9 @@ class TableSaveFunctions {
constructor({ db, ctx, oldTable, dataImport }) {
this.db = db
this.ctx = ctx
+ if (this.ctx && this.ctx.user) {
+ this.appId = this.ctx.user.appId
+ }
this.oldTable = oldTable
this.dataImport = dataImport
// any rows that need updated
@@ -178,7 +182,7 @@ class TableSaveFunctions {
// after saving
async after(table) {
- table = await exports.handleSearchIndexes(this.db, table)
+ table = await exports.handleSearchIndexes(this.appId, table)
table = await exports.handleDataImport(
this.ctx.user,
table,
diff --git a/packages/server/src/api/controllers/view/index.js b/packages/server/src/api/controllers/view/index.js
index 05dc299754..f482f3f2a6 100644
--- a/packages/server/src/api/controllers/view/index.js
+++ b/packages/server/src/api/controllers/view/index.js
@@ -29,11 +29,13 @@ const controller = {
save: async ctx => {
const db = new CouchDB(ctx.user.appId)
const { originalName, ...viewToSave } = ctx.request.body
-
const designDoc = await db.get("_design/database")
-
const view = viewTemplate(viewToSave)
+ if (!viewToSave.name) {
+ ctx.throw(400, "Cannot create view without a name")
+ }
+
designDoc.views = {
...designDoc.views,
[viewToSave.name]: view,
@@ -60,17 +62,16 @@ const controller = {
await db.put(table)
- ctx.body = table.views[viewToSave.name]
- ctx.message = `View ${viewToSave.name} saved successfully.`
+ ctx.body = {
+ ...table.views[viewToSave.name],
+ name: viewToSave.name,
+ }
},
destroy: async ctx => {
const db = new CouchDB(ctx.user.appId)
const designDoc = await db.get("_design/database")
-
const viewName = decodeURI(ctx.params.viewName)
-
const view = designDoc.views[viewName]
-
delete designDoc.views[viewName]
await db.put(designDoc)
@@ -80,16 +81,17 @@ const controller = {
await db.put(table)
ctx.body = view
- ctx.message = `View ${ctx.params.viewName} saved successfully.`
},
exportView: async ctx => {
const db = new CouchDB(ctx.user.appId)
const designDoc = await db.get("_design/database")
-
const viewName = decodeURI(ctx.query.view)
const view = designDoc.views[viewName]
const format = ctx.query.format
+ if (!format) {
+ ctx.throw(400, "Format must be specified, either csv or json")
+ }
if (view) {
ctx.params.viewName = viewName
@@ -102,6 +104,7 @@ const controller = {
}
} else {
// table all_ view
+ /* istanbul ignore next */
ctx.params.viewName = viewName
}
diff --git a/packages/server/src/api/routes/static.js b/packages/server/src/api/routes/static.js
index c812c4d3b1..30701d578b 100644
--- a/packages/server/src/api/routes/static.js
+++ b/packages/server/src/api/routes/static.js
@@ -29,11 +29,7 @@ if (env.SELF_HOSTED) {
}
router
- .post(
- "/api/attachments/process",
- authorized(BUILDER),
- controller.performLocalFileProcessing
- )
+ .post("/api/attachments/process", authorized(BUILDER), controller.uploadFile)
.post("/api/attachments/upload", usage, controller.uploadFile)
.get("/componentlibrary", controller.serveComponentLibrary)
.get("/assets/:file*", controller.serveAppAsset)
diff --git a/packages/server/src/api/routes/tests/automation.spec.js b/packages/server/src/api/routes/tests/automation.spec.js
index 9d11219506..5654c14c17 100644
--- a/packages/server/src/api/routes/tests/automation.spec.js
+++ b/packages/server/src/api/routes/tests/automation.spec.js
@@ -3,8 +3,8 @@ const {
getAllTableRows,
clearAllAutomations,
} = require("./utilities/TestFunctions")
-const { basicAutomation } = require("./utilities/structures")
const setup = require("./utilities")
+const { basicAutomation } = setup.structures
const MAX_RETRIES = 4
diff --git a/packages/server/src/api/routes/tests/component.spec.js b/packages/server/src/api/routes/tests/component.spec.js
index 926efc51e3..cabf9f8223 100644
--- a/packages/server/src/api/routes/tests/component.spec.js
+++ b/packages/server/src/api/routes/tests/component.spec.js
@@ -17,11 +17,12 @@ describe("/component", () => {
function mock() {
const manifestFile = "manifest.json"
const appId = config.getAppId()
- const libraries = ["@budibase/standard-components"]
+ const libraries = [join("@budibase", "standard-components")]
for (let library of libraries) {
let appDirectory = resolve(budibaseAppsDir(), appId, "node_modules", library, "package")
fs.mkdirSync(appDirectory, { recursive: true })
- const file = require.resolve(library).split("dist/index.js")[0] + manifestFile
+
+ const file = require.resolve(library).split(join("dist", "index.js"))[0] + manifestFile
fs.copyFileSync(file, join(appDirectory, manifestFile))
}
}
diff --git a/packages/server/src/api/routes/tests/datasource.spec.js b/packages/server/src/api/routes/tests/datasource.spec.js
index ee1a1c47f5..c1448894b1 100644
--- a/packages/server/src/api/routes/tests/datasource.spec.js
+++ b/packages/server/src/api/routes/tests/datasource.spec.js
@@ -1,6 +1,6 @@
-let {basicDatasource} = require("./utilities/structures")
-let {checkBuilderEndpoint} = require("./utilities/TestFunctions")
let setup = require("./utilities")
+let { basicDatasource } = setup.structures
+let { checkBuilderEndpoint } = require("./utilities/TestFunctions")
describe("/datasources", () => {
let request = setup.getRequest()
diff --git a/packages/server/src/api/routes/tests/layout.spec.js b/packages/server/src/api/routes/tests/layout.spec.js
index 6b21554d71..4842b2cc8e 100644
--- a/packages/server/src/api/routes/tests/layout.spec.js
+++ b/packages/server/src/api/routes/tests/layout.spec.js
@@ -1,6 +1,6 @@
const { checkBuilderEndpoint } = require("./utilities/TestFunctions")
const setup = require("./utilities")
-const { basicLayout } = require("./utilities/structures")
+const { basicLayout } = setup.structures
describe("/layouts", () => {
let request = setup.getRequest()
diff --git a/packages/server/src/api/routes/tests/misc.spec.js b/packages/server/src/api/routes/tests/misc.spec.js
index 3d3b6047e2..2957e42d90 100644
--- a/packages/server/src/api/routes/tests/misc.spec.js
+++ b/packages/server/src/api/routes/tests/misc.spec.js
@@ -1,6 +1,7 @@
const setup = require("./utilities")
+const tableUtils = require("../../controllers/table/utils")
-describe("/analytics", () => {
+describe("run misc tests", () => {
let request = setup.getRequest()
let config = setup.getConfig()
@@ -10,29 +11,44 @@ describe("/analytics", () => {
await config.init()
})
- describe("isEnabled", () => {
- it("check if analytics enabled", async () => {
- const res = await request
- .get(`/api/analytics`)
- .set(config.defaultHeaders())
- .expect("Content-Type", /json/)
- .expect(200)
- expect(typeof res.body.enabled).toEqual("boolean")
+ describe("/analytics", () => {
+ it("check if analytics enabled", async () => {
+ const res = await request
+ .get(`/api/analytics`)
+ .set(config.defaultHeaders())
+ .expect("Content-Type", /json/)
+ .expect(200)
+ expect(typeof res.body.enabled).toEqual("boolean")
+ })
+ })
+
+ describe("/health", () => {
+ it("should confirm healthy", async () => {
+ await request.get("/health").expect(200)
})
})
-})
-describe("/health", () => {
- it("should confirm healthy", async () => {
- let config = setup.getConfig()
- await config.getRequest().get("/health").expect(200)
+ describe("/version", () => {
+ it("should confirm version", async () => {
+ const res = await request.get("/version").expect(200)
+ expect(res.text.split(".").length).toEqual(3)
+ })
})
-})
-describe("/version", () => {
- it("should confirm version", async () => {
- const config = setup.getConfig()
- const res = await config.getRequest().get("/version").expect(200)
- expect(res.text.split(".").length).toEqual(3)
+ describe("test table utilities", () => {
+ it("should be able to import a CSV", async () => {
+ const table = await config.createTable()
+ const dataImport = {
+ csvString: "a,b,c,d\n1,2,3,4"
+ }
+ await tableUtils.handleDataImport({
+ appId: config.getAppId(),
+ userId: "test",
+ }, table, dataImport)
+ const rows = await config.getRows()
+ expect(rows[0].a).toEqual("1")
+ expect(rows[0].b).toEqual("2")
+ expect(rows[0].c).toEqual("3")
+ })
})
})
\ No newline at end of file
diff --git a/packages/server/src/api/routes/tests/permissions.spec.js b/packages/server/src/api/routes/tests/permissions.spec.js
index b24fac57c0..aab5567881 100644
--- a/packages/server/src/api/routes/tests/permissions.spec.js
+++ b/packages/server/src/api/routes/tests/permissions.spec.js
@@ -1,6 +1,6 @@
const { BUILTIN_ROLE_IDS } = require("../../../utilities/security/roles")
const setup = require("./utilities")
-const { basicRow } = require("./utilities/structures")
+const { basicRow } = setup.structures
const HIGHER_ROLE_ID = BUILTIN_ROLE_IDS.BASIC
const STD_ROLE_ID = BUILTIN_ROLE_IDS.PUBLIC
diff --git a/packages/server/src/api/routes/tests/query.spec.js b/packages/server/src/api/routes/tests/query.spec.js
index aa0e5428c5..87938c6a37 100644
--- a/packages/server/src/api/routes/tests/query.spec.js
+++ b/packages/server/src/api/routes/tests/query.spec.js
@@ -1,9 +1,9 @@
// mock out postgres for this
jest.mock("pg")
-const { checkBuilderEndpoint } = require("./utilities/TestFunctions")
-const { basicQuery, basicDatasource } = require("./utilities/structures")
const setup = require("./utilities")
+const { checkBuilderEndpoint } = require("./utilities/TestFunctions")
+const { basicQuery, basicDatasource } = setup.structures
describe("/queries", () => {
let request = setup.getRequest()
diff --git a/packages/server/src/api/routes/tests/role.spec.js b/packages/server/src/api/routes/tests/role.spec.js
index 9bb38b295a..062450cf63 100644
--- a/packages/server/src/api/routes/tests/role.spec.js
+++ b/packages/server/src/api/routes/tests/role.spec.js
@@ -2,8 +2,8 @@ const { BUILTIN_ROLE_IDS } = require("../../../utilities/security/roles")
const {
BUILTIN_PERMISSION_IDS,
} = require("../../../utilities/security/permissions")
-const { basicRole } = require("./utilities/structures")
const setup = require("./utilities")
+const { basicRole } = setup.structures
describe("/roles", () => {
let request = setup.getRequest()
diff --git a/packages/server/src/api/routes/tests/routing.spec.js b/packages/server/src/api/routes/tests/routing.spec.js
index 70d1632bf3..beb1659b2a 100644
--- a/packages/server/src/api/routes/tests/routing.spec.js
+++ b/packages/server/src/api/routes/tests/routing.spec.js
@@ -1,5 +1,5 @@
const setup = require("./utilities")
-const { basicScreen } = require("./utilities/structures")
+const { basicScreen } = setup.structures
const { checkBuilderEndpoint } = require("./utilities/TestFunctions")
const { BUILTIN_ROLE_IDS } = require("../../../utilities/security/roles")
diff --git a/packages/server/src/api/routes/tests/row.spec.js b/packages/server/src/api/routes/tests/row.spec.js
index 1442e4eb75..652a17366d 100644
--- a/packages/server/src/api/routes/tests/row.spec.js
+++ b/packages/server/src/api/routes/tests/row.spec.js
@@ -1,7 +1,6 @@
const { outputProcessing } = require("../../../utilities/rowProcessor")
-const env = require("../../../environment")
-const { basicRow } = require("./utilities/structures")
const setup = require("./utilities")
+const { basicRow } = setup.structures
describe("/rows", () => {
let request = setup.getRequest()
@@ -349,7 +348,7 @@ describe("/rows", () => {
const view = await config.createView()
const row = await config.createRow()
const res = await request
- .get(`/api/views/${view._id}`)
+ .get(`/api/views/${view.name}`)
.set(config.defaultHeaders())
.expect('Content-Type', /json/)
.expect(200)
diff --git a/packages/server/src/api/routes/tests/screen.spec.js b/packages/server/src/api/routes/tests/screen.spec.js
index ae30afd29c..5533bc5e59 100644
--- a/packages/server/src/api/routes/tests/screen.spec.js
+++ b/packages/server/src/api/routes/tests/screen.spec.js
@@ -1,6 +1,6 @@
const { checkBuilderEndpoint } = require("./utilities/TestFunctions")
const setup = require("./utilities")
-const { basicScreen } = require("./utilities/structures")
+const { basicScreen } = setup.structures
describe("/screens", () => {
let request = setup.getRequest()
diff --git a/packages/server/src/api/routes/tests/table.spec.js b/packages/server/src/api/routes/tests/table.spec.js
index 1a2df624f1..df28eed0c2 100644
--- a/packages/server/src/api/routes/tests/table.spec.js
+++ b/packages/server/src/api/routes/tests/table.spec.js
@@ -1,5 +1,6 @@
-const { checkBuilderEndpoint } = require("./utilities/TestFunctions")
+const { checkBuilderEndpoint, getDB } = require("./utilities/TestFunctions")
const setup = require("./utilities")
+const { basicTable } = setup.structures
describe("/tables", () => {
let request = setup.getRequest()
@@ -12,25 +13,22 @@ describe("/tables", () => {
})
describe("create", () => {
- it("returns a success message when the table is successfully created", done => {
- request
+ it("returns a success message when the table is successfully created", async () => {
+ const res = await request
.post(`/api/tables`)
- .send({
+ .send({
name: "TestTable",
key: "name",
schema: {
- name: { type: "string" }
+ name: {type: "string"}
}
})
.set(config.defaultHeaders())
.expect('Content-Type', /json/)
.expect(200)
- .end(async (err, res) => {
- expect(res.res.statusMessage).toEqual("Table TestTable saved successfully.")
- expect(res.body.name).toEqual("TestTable")
- done()
- })
- })
+ expect(res.res.statusMessage).toEqual("Table TestTable saved successfully.")
+ expect(res.body.name).toEqual("TestTable")
+ })
it("renames all the row fields for a table when a schema key is renamed", async () => {
const testTable = await config.createTable()
@@ -46,7 +44,7 @@ describe("/tables", () => {
const updatedTable = await request
.post(`/api/tables`)
- .send({
+ .send({
_id: testTable._id,
_rev: testTable._rev,
name: "TestTable",
@@ -56,41 +54,40 @@ describe("/tables", () => {
updated: "updatedName"
},
schema: {
- updatedName: { type: "string" }
+ updatedName: {type: "string"}
}
})
.set(config.defaultHeaders())
.expect('Content-Type', /json/)
.expect(200)
+ expect(updatedTable.res.statusMessage).toEqual("Table TestTable saved successfully.")
+ expect(updatedTable.body.name).toEqual("TestTable")
- expect(updatedTable.res.statusMessage).toEqual("Table TestTable saved successfully.")
- expect(updatedTable.body.name).toEqual("TestTable")
+ const res = await request
+ .get(`/api/${testTable._id}/rows/${testRow.body._id}`)
+ .set(config.defaultHeaders())
+ .expect('Content-Type', /json/)
+ .expect(200)
- const res = await request
- .get(`/api/${testTable._id}/rows/${testRow.body._id}`)
- .set(config.defaultHeaders())
- .expect('Content-Type', /json/)
- .expect(200)
+ expect(res.body.updatedName).toEqual("test")
+ expect(res.body.name).toBeUndefined()
+ })
- expect(res.body.updatedName).toEqual("test")
- expect(res.body.name).toBeUndefined()
- })
-
- it("should apply authorization to endpoint", async () => {
- await checkBuilderEndpoint({
- config,
- method: "POST",
- url: `/api/tables`,
- body: {
- name: "TestTable",
- key: "name",
- schema: {
- name: { type: "string" }
- }
+ it("should apply authorization to endpoint", async () => {
+ await checkBuilderEndpoint({
+ config,
+ method: "POST",
+ url: `/api/tables`,
+ body: {
+ name: "TestTable",
+ key: "name",
+ schema: {
+ name: {type: "string"}
}
- })
+ }
})
})
+ })
describe("fetch", () => {
let testTable
@@ -103,28 +100,91 @@ describe("/tables", () => {
delete testTable._rev
})
- it("returns all the tables for that instance in the response body", done => {
- request
+ it("returns all the tables for that instance in the response body", async () => {
+ const res = await request
.get(`/api/tables`)
.set(config.defaultHeaders())
.expect('Content-Type', /json/)
.expect(200)
- .end(async (_, res) => {
- const fetchedTable = res.body[0]
- expect(fetchedTable.name).toEqual(testTable.name)
- expect(fetchedTable.type).toEqual("table")
- done()
- })
+ const fetchedTable = res.body[0]
+ expect(fetchedTable.name).toEqual(testTable.name)
+ expect(fetchedTable.type).toEqual("table")
})
it("should apply authorization to endpoint", async () => {
- await checkBuilderEndpoint({
- config,
- method: "GET",
- url: `/api/tables`,
- })
+ await checkBuilderEndpoint({
+ config,
+ method: "GET",
+ url: `/api/tables`,
})
})
+ })
+
+ describe("indexing", () => {
+ it("should be able to create a table with indexes", async () => {
+ const db = getDB(config)
+ const indexCount = (await db.getIndexes()).total_rows
+ const table = basicTable()
+ table.indexes = ["name"]
+ const res = await request
+ .post(`/api/tables`)
+ .send(table)
+ .set(config.defaultHeaders())
+ .expect('Content-Type', /json/)
+ .expect(200)
+ expect(res.body._id).toBeDefined()
+ expect(res.body._rev).toBeDefined()
+ expect((await db.getIndexes()).total_rows).toEqual(indexCount + 1)
+ // update index to see what happens
+ table.indexes = ["name", "description"]
+ await request
+ .post(`/api/tables`)
+ .send({
+ ...table,
+ _id: res.body._id,
+ _rev: res.body._rev,
+ })
+ .set(config.defaultHeaders())
+ .expect('Content-Type', /json/)
+ .expect(200)
+ // shouldn't have created a new index
+ expect((await db.getIndexes()).total_rows).toEqual(indexCount + 1)
+ })
+ })
+
+ describe("updating user table", () => {
+ it("should add roleId and email field when adjusting user table schema", async () => {
+ const res = await request
+ .post(`/api/tables`)
+ .send({
+ ...basicTable(),
+ _id: "ta_users",
+ })
+ .set(config.defaultHeaders())
+ .expect('Content-Type', /json/)
+ .expect(200)
+ expect(res.body.schema.email).toBeDefined()
+ expect(res.body.schema.roleId).toBeDefined()
+ })
+ })
+
+ describe("validate csv", () => {
+ it("should be able to validate a CSV layout", async () => {
+ const res = await request
+ .post(`/api/tables/csv/validate`)
+ .send({
+ csvString: "a,b,c,d\n1,2,3,4"
+ })
+ .set(config.defaultHeaders())
+ .expect('Content-Type', /json/)
+ .expect(200)
+ expect(res.body.schema).toBeDefined()
+ expect(res.body.schema.a).toEqual({
+ type: "string",
+ success: true,
+ })
+ })
+ })
describe("destroy", () => {
let testTable
@@ -137,19 +197,16 @@ describe("/tables", () => {
delete testTable._rev
})
- it("returns a success response when a table is deleted.", async done => {
- request
+ it("returns a success response when a table is deleted.", async () => {
+ const res = await request
.delete(`/api/tables/${testTable._id}/${testTable._rev}`)
.set(config.defaultHeaders())
.expect('Content-Type', /json/)
.expect(200)
- .end(async (_, res) => {
- expect(res.res.statusMessage).toEqual(`Table ${testTable._id} deleted.`)
- done()
- })
- })
+ expect(res.body.message).toEqual(`Table ${testTable._id} deleted.`)
+ })
- it("deletes linked references to the table after deletion", async done => {
+ it("deletes linked references to the table after deletion", async () => {
const linkedTable = await config.createTable({
name: "LinkedTable",
type: "table",
@@ -171,18 +228,15 @@ describe("/tables", () => {
},
})
- request
+ const res = await request
.delete(`/api/tables/${testTable._id}/${testTable._rev}`)
.set(config.defaultHeaders())
.expect('Content-Type', /json/)
.expect(200)
- .end(async (_, res) => {
- expect(res.res.statusMessage).toEqual(`Table ${testTable._id} deleted.`)
- const dependentTable = await config.getTable(linkedTable._id)
- expect(dependentTable.schema.TestTable).not.toBeDefined()
- done()
- })
- })
+ expect(res.body.message).toEqual(`Table ${testTable._id} deleted.`)
+ const dependentTable = await config.getTable(linkedTable._id)
+ expect(dependentTable.schema.TestTable).not.toBeDefined()
+ })
it("should apply authorization to endpoint", async () => {
await checkBuilderEndpoint({
@@ -191,6 +245,5 @@ describe("/tables", () => {
url: `/api/tables/${testTable._id}/${testTable._rev}`,
})
})
-
})
})
diff --git a/packages/server/src/api/routes/tests/user.spec.js b/packages/server/src/api/routes/tests/user.spec.js
index 5e7ec9e9d4..808f1a2622 100644
--- a/packages/server/src/api/routes/tests/user.spec.js
+++ b/packages/server/src/api/routes/tests/user.spec.js
@@ -1,7 +1,7 @@
const { BUILTIN_ROLE_IDS } = require("../../../utilities/security/roles")
const { checkPermissionsEndpoint } = require("./utilities/TestFunctions")
-const { basicUser } = require("./utilities/structures")
const setup = require("./utilities")
+const { basicUser } = setup.structures
describe("/users", () => {
let request = setup.getRequest()
diff --git a/packages/server/src/api/routes/tests/utilities/TestFunctions.js b/packages/server/src/api/routes/tests/utilities/TestFunctions.js
index 534119d279..313b9e63a8 100644
--- a/packages/server/src/api/routes/tests/utilities/TestFunctions.js
+++ b/packages/server/src/api/routes/tests/utilities/TestFunctions.js
@@ -1,5 +1,6 @@
const rowController = require("../../../controllers/row")
const appController = require("../../../controllers/application")
+const CouchDB = require("../../../../db")
function Request(appId, params) {
this.user = { appId }
@@ -77,3 +78,7 @@ exports.checkPermissionsEndpoint = async ({
.set(failHeader)
.expect(403)
}
+
+exports.getDB = config => {
+ return new CouchDB(config.getAppId())
+}
diff --git a/packages/server/src/api/routes/tests/utilities/controllers.js b/packages/server/src/api/routes/tests/utilities/controllers.js
deleted file mode 100644
index a4eb9ac9de..0000000000
--- a/packages/server/src/api/routes/tests/utilities/controllers.js
+++ /dev/null
@@ -1,15 +0,0 @@
-module.exports = {
- table: require("../../../controllers/table"),
- row: require("../../../controllers/row"),
- role: require("../../../controllers/role"),
- perms: require("../../../controllers/permission"),
- view: require("../../../controllers/view"),
- app: require("../../../controllers/application"),
- user: require("../../../controllers/user"),
- automation: require("../../../controllers/automation"),
- datasource: require("../../../controllers/datasource"),
- query: require("../../../controllers/query"),
- screen: require("../../../controllers/screen"),
- webhook: require("../../../controllers/webhook"),
- layout: require("../../../controllers/layout"),
-}
diff --git a/packages/server/src/api/routes/tests/utilities/index.js b/packages/server/src/api/routes/tests/utilities/index.js
index 7126f141e2..ed5c98cc48 100644
--- a/packages/server/src/api/routes/tests/utilities/index.js
+++ b/packages/server/src/api/routes/tests/utilities/index.js
@@ -1,4 +1,5 @@
-const TestConfig = require("./TestConfiguration")
+const TestConfig = require("../../../../tests/utilities/TestConfiguration")
+const structures = require("../../../../tests/utilities/structures")
const env = require("../../../../environment")
exports.delay = ms => new Promise(resolve => setTimeout(resolve, ms))
@@ -51,3 +52,5 @@ exports.switchToCloudForFunction = async func => {
throw error
}
}
+
+exports.structures = structures
diff --git a/packages/server/src/api/routes/tests/view.spec.js b/packages/server/src/api/routes/tests/view.spec.js
index a80b09d3a0..3bfbacccbe 100644
--- a/packages/server/src/api/routes/tests/view.spec.js
+++ b/packages/server/src/api/routes/tests/view.spec.js
@@ -29,9 +29,7 @@ describe("/views", () => {
.expect("Content-Type", /json/)
.expect(200)
- expect(res.res.statusMessage).toEqual(
- "View TestView saved successfully."
- )
+ expect(res.body.tableId).toBe(table._id)
})
it("updates the table row with the new view metadata", async () => {
@@ -46,10 +44,8 @@ describe("/views", () => {
.set(config.defaultHeaders())
.expect("Content-Type", /json/)
.expect(200)
+ expect(res.body.tableId).toBe(table._id)
- expect(res.res.statusMessage).toEqual(
- "View TestView saved successfully."
- )
const updatedTable = await config.getTable(table._id)
expect(updatedTable.views).toEqual({
TestView: {
@@ -173,4 +169,49 @@ describe("/views", () => {
expect(res.body).toMatchSnapshot()
})
})
+
+ describe("destroy", () => {
+ it("should be able to delete a view", async () => {
+ const table = await config.createTable()
+ const view = await config.createView()
+ const res = await request
+ .delete(`/api/views/${view.name}`)
+ .set(config.defaultHeaders())
+ .expect("Content-Type", /json/)
+ .expect(200)
+ expect(res.body.map).toBeDefined()
+ expect(res.body.meta.tableId).toEqual(table._id)
+ })
+ })
+
+ describe("exportView", () => {
+ it("should be able to delete a view", async () => {
+ await config.createTable()
+ await config.createRow()
+ const view = await config.createView()
+ let res = await request
+ .get(`/api/views/export?view=${view.name}&format=json`)
+ .set(config.defaultHeaders())
+ .expect(200)
+ let error
+ try {
+ const obj = JSON.parse(res.text)
+ expect(obj.length).toBe(1)
+ } catch (err) {
+ error = err
+ }
+ expect(error).toBeUndefined()
+ res = await request
+ .get(`/api/views/export?view=${view.name}&format=csv`)
+ .set(config.defaultHeaders())
+ .expect(200)
+ // this shouldn't be JSON
+ try {
+ JSON.parse(res.text)
+ } catch (err) {
+ error = err
+ }
+ expect(error).toBeDefined()
+ })
+ })
})
diff --git a/packages/server/src/api/routes/tests/webhook.spec.js b/packages/server/src/api/routes/tests/webhook.spec.js
index 2bf5445a09..7fb7a26fc1 100644
--- a/packages/server/src/api/routes/tests/webhook.spec.js
+++ b/packages/server/src/api/routes/tests/webhook.spec.js
@@ -1,6 +1,6 @@
const setup = require("./utilities")
const { checkBuilderEndpoint } = require("./utilities/TestFunctions")
-const { basicWebhook, basicAutomation } = require("./utilities/structures")
+const { basicWebhook, basicAutomation } = setup.structures
describe("/webhooks", () => {
let request = setup.getRequest()
diff --git a/packages/server/src/app.js b/packages/server/src/app.js
index 15e996cfe6..8bbea00474 100644
--- a/packages/server/src/app.js
+++ b/packages/server/src/app.js
@@ -9,7 +9,6 @@ const env = require("./environment")
const eventEmitter = require("./events")
const automations = require("./automations/index")
const Sentry = require("@sentry/node")
-const selfhost = require("./selfhost")
const app = new Koa()
@@ -66,11 +65,7 @@ module.exports = server.listen(env.PORT || 0, async () => {
console.log(`Budibase running on ${JSON.stringify(server.address())}`)
env._set("PORT", server.address().port)
eventEmitter.emitPort(env.PORT)
- automations.init()
- // only init the self hosting DB info in the Pouch, not needed in self hosting prod
- if (!env.CLOUD) {
- await selfhost.init()
- }
+ await automations.init()
})
process.on("uncaughtException", err => {
diff --git a/packages/server/src/automations/actions.js b/packages/server/src/automations/actions.js
index ea88c2d1d6..ee57f5a109 100644
--- a/packages/server/src/automations/actions.js
+++ b/packages/server/src/automations/actions.js
@@ -37,10 +37,12 @@ let AUTOMATION_BUCKET = env.AUTOMATION_BUCKET
let AUTOMATION_DIRECTORY = env.AUTOMATION_DIRECTORY
let MANIFEST = null
+/* istanbul ignore next */
function buildBundleName(pkgName, version) {
return `${pkgName}@${version}.min.js`
}
+/* istanbul ignore next */
async function downloadPackage(name, version, bundleName) {
await download(
`${AUTOMATION_BUCKET}/${name}/${version}/${bundleName}`,
@@ -49,6 +51,7 @@ async function downloadPackage(name, version, bundleName) {
return require(join(AUTOMATION_DIRECTORY, bundleName))
}
+/* istanbul ignore next */
module.exports.getAction = async function(actionName) {
if (BUILTIN_ACTIONS[actionName] != null) {
return BUILTIN_ACTIONS[actionName]
@@ -96,5 +99,6 @@ module.exports.init = async function() {
return MANIFEST
}
+// definitions will have downloaded ones added to it, while builtin won't
module.exports.DEFINITIONS = BUILTIN_DEFINITIONS
module.exports.BUILTIN_DEFINITIONS = BUILTIN_DEFINITIONS
diff --git a/packages/server/src/automations/index.js b/packages/server/src/automations/index.js
index a983495fb5..9aba399133 100644
--- a/packages/server/src/automations/index.js
+++ b/packages/server/src/automations/index.js
@@ -30,23 +30,22 @@ async function updateQuota(automation) {
/**
* This module is built purely to kick off the worker farm and manage the inputs/outputs
*/
-module.exports.init = function() {
- actions.init().then(() => {
- triggers.automationQueue.process(async job => {
- try {
- if (env.CLOUD && job.data.automation && !env.SELF_HOSTED) {
- job.data.automation.apiKey = await updateQuota(job.data.automation)
- }
- if (env.BUDIBASE_ENVIRONMENT === "PRODUCTION") {
- await runWorker(job)
- } else {
- await singleThread(job)
- }
- } catch (err) {
- console.error(
- `${job.data.automation.appId} automation ${job.data.automation._id} was unable to run - ${err}`
- )
+module.exports.init = async function() {
+ await actions.init()
+ triggers.automationQueue.process(async job => {
+ try {
+ if (env.CLOUD && job.data.automation && !env.SELF_HOSTED) {
+ job.data.automation.apiKey = await updateQuota(job.data.automation)
}
- })
+ if (env.BUDIBASE_ENVIRONMENT === "PRODUCTION") {
+ await runWorker(job)
+ } else {
+ await singleThread(job)
+ }
+ } catch (err) {
+ console.error(
+ `${job.data.automation.appId} automation ${job.data.automation._id} was unable to run - ${err}`
+ )
+ }
})
}
diff --git a/packages/server/src/automations/steps/createRow.js b/packages/server/src/automations/steps/createRow.js
index aeb75958f6..ef136e1131 100644
--- a/packages/server/src/automations/steps/createRow.js
+++ b/packages/server/src/automations/steps/createRow.js
@@ -59,15 +59,14 @@ module.exports.definition = {
}
module.exports.run = async function({ inputs, appId, apiKey, emitter }) {
- // TODO: better logging of when actions are missed due to missing parameters
if (inputs.row == null || inputs.row.tableId == null) {
- return
+ return {
+ success: false,
+ response: {
+ message: "Invalid inputs",
+ },
+ }
}
- inputs.row = await automationUtils.cleanUpRow(
- appId,
- inputs.row.tableId,
- inputs.row
- )
// have to clean up the row, remove the table from it
const ctx = {
params: {
@@ -81,6 +80,11 @@ module.exports.run = async function({ inputs, appId, apiKey, emitter }) {
}
try {
+ inputs.row = await automationUtils.cleanUpRow(
+ appId,
+ inputs.row.tableId,
+ inputs.row
+ )
if (env.CLOUD) {
await usage.update(apiKey, usage.Properties.ROW, 1)
}
diff --git a/packages/server/src/automations/steps/deleteRow.js b/packages/server/src/automations/steps/deleteRow.js
index 8edee38dee..ea4d60a04e 100644
--- a/packages/server/src/automations/steps/deleteRow.js
+++ b/packages/server/src/automations/steps/deleteRow.js
@@ -51,9 +51,13 @@ module.exports.definition = {
}
module.exports.run = async function({ inputs, appId, apiKey, emitter }) {
- // TODO: better logging of when actions are missed due to missing parameters
if (inputs.id == null || inputs.revision == null) {
- return
+ return {
+ success: false,
+ response: {
+ message: "Invalid inputs",
+ },
+ }
}
let ctx = {
params: {
diff --git a/packages/server/src/automations/steps/filter.js b/packages/server/src/automations/steps/filter.js
index 4286cd44e8..586e424cc4 100644
--- a/packages/server/src/automations/steps/filter.js
+++ b/packages/server/src/automations/steps/filter.js
@@ -12,6 +12,9 @@ const PrettyLogicConditions = {
[LogicConditions.LESS_THAN]: "Less than",
}
+module.exports.LogicConditions = LogicConditions
+module.exports.PrettyLogicConditions = PrettyLogicConditions
+
module.exports.definition = {
name: "Filter",
tagline: "{{inputs.field}} {{inputs.condition}} {{inputs.value}}",
@@ -64,7 +67,7 @@ module.exports.run = async function filter({ inputs }) {
value = Date.parse(value)
field = Date.parse(field)
}
- let success
+ let success = false
if (typeof field !== "object" && typeof value !== "object") {
switch (condition) {
case LogicConditions.EQUAL:
@@ -79,8 +82,6 @@ module.exports.run = async function filter({ inputs }) {
case LogicConditions.LESS_THAN:
success = field < value
break
- default:
- return
}
} else {
success = false
diff --git a/packages/server/src/automations/steps/outgoingWebhook.js b/packages/server/src/automations/steps/outgoingWebhook.js
index 817ec424b2..ab8c747c58 100644
--- a/packages/server/src/automations/steps/outgoingWebhook.js
+++ b/packages/server/src/automations/steps/outgoingWebhook.js
@@ -87,6 +87,7 @@ module.exports.run = async function({ inputs }) {
success: response.status === 200,
}
} catch (err) {
+ /* istanbul ignore next */
return {
success: false,
response: err,
diff --git a/packages/server/src/automations/steps/updateRow.js b/packages/server/src/automations/steps/updateRow.js
index 3b83f961f5..a545662cf8 100644
--- a/packages/server/src/automations/steps/updateRow.js
+++ b/packages/server/src/automations/steps/updateRow.js
@@ -55,14 +55,14 @@ module.exports.definition = {
module.exports.run = async function({ inputs, appId, emitter }) {
if (inputs.rowId == null || inputs.row == null) {
- return
+ return {
+ success: false,
+ response: {
+ message: "Invalid inputs",
+ },
+ }
}
- inputs.row = await automationUtils.cleanUpRowById(
- appId,
- inputs.rowId,
- inputs.row
- )
// clear any falsy properties so that they aren't updated
for (let propKey of Object.keys(inputs.row)) {
if (!inputs.row[propKey] || inputs.row[propKey] === "") {
@@ -73,7 +73,7 @@ module.exports.run = async function({ inputs, appId, emitter }) {
// have to clean up the row, remove the table from it
const ctx = {
params: {
- id: inputs.rowId,
+ rowId: inputs.rowId,
},
request: {
body: inputs.row,
@@ -83,6 +83,11 @@ module.exports.run = async function({ inputs, appId, emitter }) {
}
try {
+ inputs.row = await automationUtils.cleanUpRowById(
+ appId,
+ inputs.rowId,
+ inputs.row
+ )
await rowController.patch(ctx)
return {
row: ctx.body,
diff --git a/packages/server/src/automations/tests/automation.spec.js b/packages/server/src/automations/tests/automation.spec.js
new file mode 100644
index 0000000000..f4d3b4c865
--- /dev/null
+++ b/packages/server/src/automations/tests/automation.spec.js
@@ -0,0 +1,152 @@
+const automation = require("../index")
+const usageQuota = require("../../utilities/usageQuota")
+const thread = require("../thread")
+const triggers = require("../triggers")
+const { basicAutomation, basicTable } = require("../../tests/utilities/structures")
+const { wait } = require("../../utilities")
+const env = require("../../environment")
+const { makePartial } = require("../../tests/utilities")
+const { cleanInputValues } = require("../automationUtils")
+const setup = require("./utilities")
+
+let workerJob
+
+jest.mock("../../utilities/usageQuota")
+usageQuota.getAPIKey.mockReturnValue({ apiKey: "test" })
+jest.mock("../thread")
+jest.spyOn(global.console, "error")
+jest.mock("worker-farm", () => {
+ return () => {
+ const value = jest
+ .fn()
+ .mockReturnValueOnce(undefined)
+ .mockReturnValueOnce("Error")
+ return (input, callback) => {
+ workerJob = input
+ if (callback) {
+ callback(value())
+ }
+ }
+ }
+})
+
+describe("Run through some parts of the automations system", () => {
+ let config = setup.getConfig()
+
+ beforeEach(async () => {
+ await automation.init()
+ await config.init()
+ })
+
+ afterAll(setup.afterAll)
+
+ it("should be able to init in builder", async () => {
+ await triggers.externalTrigger(basicAutomation(), { a: 1 })
+ await wait(100)
+ expect(workerJob).toBeUndefined()
+ expect(thread).toHaveBeenCalled()
+ })
+
+ it("should be able to init in cloud", async () => {
+ env.CLOUD = true
+ env.BUDIBASE_ENVIRONMENT = "PRODUCTION"
+ await triggers.externalTrigger(basicAutomation(), { a: 1 })
+ await wait(100)
+ // haven't added a mock implementation so getAPIKey of usageQuota just returns undefined
+ expect(usageQuota.update).toHaveBeenCalledWith("test", "automationRuns", 1)
+ expect(workerJob).toBeDefined()
+ env.BUDIBASE_ENVIRONMENT = "JEST"
+ env.CLOUD = false
+ })
+
+ it("try error scenario", async () => {
+ env.CLOUD = true
+ env.BUDIBASE_ENVIRONMENT = "PRODUCTION"
+ // the second call will throw an error
+ await triggers.externalTrigger(basicAutomation(), { a: 1 })
+ await wait(100)
+ expect(console.error).toHaveBeenCalled()
+ env.BUDIBASE_ENVIRONMENT = "JEST"
+ env.CLOUD = false
+ })
+
+ it("should be able to check triggering row filling", async () => {
+ const automation = basicAutomation()
+ let table = basicTable()
+ table.schema.boolean = {
+ type: "boolean",
+ constraints: {
+ type: "boolean",
+ },
+ }
+ table.schema.number = {
+ type: "number",
+ constraints: {
+ type: "number",
+ },
+ }
+ table.schema.datetime = {
+ type: "datetime",
+ constraints: {
+ type: "datetime",
+ },
+ }
+ table = await config.createTable(table)
+ automation.definition.trigger.inputs.tableId = table._id
+ const params = await triggers.fillRowOutput(automation, { appId: config.getAppId() })
+ expect(params.row).toBeDefined()
+ const date = new Date(params.row.datetime)
+ expect(typeof params.row.name).toBe("string")
+ expect(typeof params.row.boolean).toBe("boolean")
+ expect(typeof params.row.number).toBe("number")
+ expect(date.getFullYear()).toBe(1970)
+ })
+
+ it("should check coercion", async () => {
+ const table = await config.createTable()
+ const automation = basicAutomation()
+ automation.definition.trigger.inputs.tableId = table._id
+ automation.definition.trigger.stepId = "APP"
+ automation.definition.trigger.inputs.fields = { a: "number" }
+ await triggers.externalTrigger(automation, {
+ appId: config.getAppId(),
+ fields: {
+ a: "1"
+ }
+ })
+ await wait(100)
+ expect(thread).toHaveBeenCalledWith(makePartial({
+ data: {
+ event: {
+ fields: {
+ a: 1
+ }
+ }
+ }
+ }))
+ })
+
+ it("should be able to clean inputs with the utilities", () => {
+ // can't clean without a schema
+ let output = cleanInputValues({a: "1"})
+ expect(output.a).toBe("1")
+ output = cleanInputValues({a: "1", b: "true", c: "false", d: 1, e: "help"}, {
+ properties: {
+ a: {
+ type: "number",
+ },
+ b: {
+ type: "boolean",
+ },
+ c: {
+ type: "boolean",
+ }
+ }
+ })
+ expect(output.a).toBe(1)
+ expect(output.b).toBe(true)
+ expect(output.c).toBe(false)
+ expect(output.d).toBe(1)
+ expect(output.e).toBe("help")
+ })
+})
\ No newline at end of file
diff --git a/packages/server/src/automations/tests/createRow.spec.js b/packages/server/src/automations/tests/createRow.spec.js
new file mode 100644
index 0000000000..0be2803e47
--- /dev/null
+++ b/packages/server/src/automations/tests/createRow.spec.js
@@ -0,0 +1,57 @@
+const usageQuota = require("../../utilities/usageQuota")
+const env = require("../../environment")
+const setup = require("./utilities")
+
+jest.mock("../../utilities/usageQuota")
+
+describe("test the create row action", () => {
+ let table, row
+ let config = setup.getConfig()
+
+ beforeEach(async () => {
+ await config.init()
+ table = await config.createTable()
+ row = {
+ tableId: table._id,
+ name: "test",
+ description: "test",
+ }
+ })
+
+ afterAll(setup.afterAll)
+
+ it("should be able to run the action", async () => {
+ const res = await setup.runStep(setup.actions.CREATE_ROW.stepId, {
+ row,
+ })
+ expect(res.id).toBeDefined()
+ expect(res.revision).toBeDefined()
+ const gottenRow = await config.getRow(table._id, res.id)
+ expect(gottenRow.name).toEqual("test")
+ expect(gottenRow.description).toEqual("test")
+ })
+
+ it("should return an error (not throw) when bad info provided", async () => {
+ const res = await setup.runStep(setup.actions.CREATE_ROW.stepId, {
+ row: {
+ tableId: "invalid",
+ invalid: "invalid",
+ }
+ })
+ expect(res.success).toEqual(false)
+ })
+
+ it("check usage quota attempts", async () => {
+ env.CLOUD = true
+ await setup.runStep(setup.actions.CREATE_ROW.stepId, {
+ row
+ })
+ expect(usageQuota.update).toHaveBeenCalledWith(setup.apiKey, "rows", 1)
+ env.CLOUD = false
+ })
+
+ it("should check invalid inputs return an error", async () => {
+ const res = await setup.runStep(setup.actions.CREATE_ROW.stepId, {})
+ expect(res.success).toEqual(false)
+ })
+})
diff --git a/packages/server/src/automations/tests/createUser.spec.js b/packages/server/src/automations/tests/createUser.spec.js
new file mode 100644
index 0000000000..5f65e260a9
--- /dev/null
+++ b/packages/server/src/automations/tests/createUser.spec.js
@@ -0,0 +1,43 @@
+const usageQuota = require("../../utilities/usageQuota")
+const env = require("../../environment")
+const setup = require("./utilities")
+const { BUILTIN_ROLE_IDS } = require("../../utilities/security/roles")
+const { ViewNames } = require("../../db/utils")
+
+jest.mock("../../utilities/usageQuota")
+
+describe("test the create user action", () => {
+ let config = setup.getConfig()
+ let user
+
+ beforeEach(async () => {
+ await config.init()
+ user = {
+ email: "test@test.com",
+ password: "password",
+ roleId: BUILTIN_ROLE_IDS.POWER
+ }
+ })
+
+ afterAll(setup.afterAll)
+
+ it("should be able to run the action", async () => {
+ const res = await setup.runStep(setup.actions.CREATE_USER.stepId, user)
+ expect(res.id).toBeDefined()
+ expect(res.revision).toBeDefined()
+ const userDoc = await config.getRow(ViewNames.USERS, res.id)
+ expect(userDoc.email).toEqual(user.email)
+ })
+
+ it("should return an error if no inputs provided", async () => {
+ const res = await setup.runStep(setup.actions.CREATE_USER.stepId, {})
+ expect(res.success).toEqual(false)
+ })
+
+ it("check usage quota attempts", async () => {
+ env.CLOUD = true
+ await setup.runStep(setup.actions.CREATE_USER.stepId, user)
+ expect(usageQuota.update).toHaveBeenCalledWith(setup.apiKey, "users", 1)
+ env.CLOUD = false
+ })
+})
diff --git a/packages/server/src/automations/tests/delay.spec.js b/packages/server/src/automations/tests/delay.spec.js
new file mode 100644
index 0000000000..99046e8171
--- /dev/null
+++ b/packages/server/src/automations/tests/delay.spec.js
@@ -0,0 +1,12 @@
+const setup = require("./utilities")
+
+describe("test the delay logic", () => {
+ it("should be able to run the delay", async () => {
+ const time = 100
+ const before = Date.now()
+ await setup.runStep(setup.logic.DELAY.stepId, { time: time })
+ const now = Date.now()
+ // divide by two just so that test will always pass as long as there was some sort of delay
+ expect(now - before).toBeGreaterThanOrEqual(time / 2)
+ })
+})
\ No newline at end of file
diff --git a/packages/server/src/automations/tests/deleteRow.spec.js b/packages/server/src/automations/tests/deleteRow.spec.js
new file mode 100644
index 0000000000..0d5ff47ed8
--- /dev/null
+++ b/packages/server/src/automations/tests/deleteRow.spec.js
@@ -0,0 +1,58 @@
+const usageQuota = require("../../utilities/usageQuota")
+const env = require("../../environment")
+const setup = require("./utilities")
+
+jest.mock("../../utilities/usageQuota")
+
+describe("test the delete row action", () => {
+ let table, row, inputs
+ let config = setup.getConfig()
+
+ beforeEach(async () => {
+ await config.init()
+ table = await config.createTable()
+ row = await config.createRow()
+ inputs = {
+ tableId: table._id,
+ id: row._id,
+ revision: row._rev,
+ }
+ })
+
+ afterAll(setup.afterAll)
+
+ it("should be able to run the action", async () => {
+ const res = await setup.runStep(setup.actions.DELETE_ROW.stepId, inputs)
+ expect(res.success).toEqual(true)
+ expect(res.response).toBeDefined()
+ expect(res.row._id).toEqual(row._id)
+ let error
+ try {
+ await config.getRow(table._id, res.id)
+ } catch (err) {
+ error = err
+ }
+ expect(error).toBeDefined()
+ })
+
+ it("check usage quota attempts", async () => {
+ env.CLOUD = true
+ await setup.runStep(setup.actions.DELETE_ROW.stepId, inputs)
+ expect(usageQuota.update).toHaveBeenCalledWith(setup.apiKey, "rows", -1)
+ env.CLOUD = false
+ })
+
+ it("should check invalid inputs return an error", async () => {
+ const res = await setup.runStep(setup.actions.DELETE_ROW.stepId, {})
+ expect(res.success).toEqual(false)
+ })
+
+ it("should return an error when table doesn't exist", async () => {
+ const res = await setup.runStep(setup.actions.DELETE_ROW.stepId, {
+ tableId: "invalid",
+ id: "invalid",
+ revision: "invalid",
+ })
+ expect(res.success).toEqual(false)
+ })
+})
diff --git a/packages/server/src/automations/tests/filter.spec.js b/packages/server/src/automations/tests/filter.spec.js
new file mode 100644
index 0000000000..05361f43ed
--- /dev/null
+++ b/packages/server/src/automations/tests/filter.spec.js
@@ -0,0 +1,48 @@
+const setup = require("./utilities")
+const { LogicConditions } = require("../steps/filter")
+
+describe("test the filter logic", () => {
+ async function checkFilter(field, condition, value, pass = true) {
+ let res = await setup.runStep(setup.logic.FILTER.stepId,
+ { field, condition, value }
+ )
+ expect(res.success).toEqual(pass)
+ }
+
+ it("should be able test equality", async () => {
+ await checkFilter("hello", LogicConditions.EQUAL, "hello", true)
+ await checkFilter("hello", LogicConditions.EQUAL, "no", false)
+ })
+
+ it("should be able to test greater than", async () => {
+ await checkFilter(10, LogicConditions.GREATER_THAN, 5, true)
+ await checkFilter(10, LogicConditions.GREATER_THAN, 15, false)
+ })
+
+ it("should be able to test less than", async () => {
+ await checkFilter(5, LogicConditions.LESS_THAN, 10, true)
+ await checkFilter(15, LogicConditions.LESS_THAN, 10, false)
+ })
+
+ it("should be able to in-equality", async () => {
+ await checkFilter("hello", LogicConditions.NOT_EQUAL, "no", true)
+ await checkFilter(10, LogicConditions.NOT_EQUAL, 10, false)
+ })
+
+ it("check number coercion", async () => {
+ await checkFilter("10", LogicConditions.GREATER_THAN, "5", true)
+ })
+
+ it("check date coercion", async () => {
+ await checkFilter(
+ (new Date()).toISOString(),
+ LogicConditions.GREATER_THAN,
+ (new Date(-10000)).toISOString(),
+ true
+ )
+ })
+
+ it("check objects always false", async () => {
+ await checkFilter({}, LogicConditions.EQUAL, {}, false)
+ })
+})
\ No newline at end of file
diff --git a/packages/server/src/automations/tests/outgoingWebhook.spec.js b/packages/server/src/automations/tests/outgoingWebhook.spec.js
new file mode 100644
index 0000000000..f1d8d25ba8
--- /dev/null
+++ b/packages/server/src/automations/tests/outgoingWebhook.spec.js
@@ -0,0 +1,39 @@
+const setup = require("./utilities")
+const fetch = require("node-fetch")
+
+jest.mock("node-fetch")
+
+describe("test the outgoing webhook action", () => {
+ let inputs
+ let config = setup.getConfig()
+
+ beforeEach(async () => {
+ await config.init()
+ inputs = {
+ requestMethod: "POST",
+ url: "www.test.com",
+ requestBody: JSON.stringify({
+ a: 1,
+ }),
+ }
+ })
+
+ afterAll(setup.afterAll)
+
+ it("should be able to run the action", async () => {
+ const res = await setup.runStep(setup.actions.OUTGOING_WEBHOOK.stepId, inputs)
+ expect(res.success).toEqual(true)
+ expect(res.response.url).toEqual("http://www.test.com")
+ expect(res.response.method).toEqual("POST")
+ expect(res.response.body.a).toEqual(1)
+ })
+
+ it("should return an error if something goes wrong in fetch", async () => {
+ const res = await setup.runStep(setup.actions.OUTGOING_WEBHOOK.stepId, {
+ requestMethod: "GET",
+ url: "www.invalid.com"
+ })
+ expect(res.success).toEqual(false)
+ })
+
+})
diff --git a/packages/server/src/automations/tests/sendEmail.spec.js b/packages/server/src/automations/tests/sendEmail.spec.js
new file mode 100644
index 0000000000..5f3aabfff8
--- /dev/null
+++ b/packages/server/src/automations/tests/sendEmail.spec.js
@@ -0,0 +1,36 @@
+const setup = require("./utilities")
+
+jest.mock("@sendgrid/mail")
+
+describe("test the send email action", () => {
+ let inputs
+ let config = setup.getConfig()
+
+ beforeEach(async () => {
+ await config.init()
+ inputs = {
+ to: "me@test.com",
+ from: "budibase@test.com",
+ subject: "Testing",
+ text: "Email contents",
+ }
+ })
+
+ afterAll(setup.afterAll)
+
+ it("should be able to run the action", async () => {
+ const res = await setup.runStep(setup.actions.SEND_EMAIL.stepId, inputs)
+ expect(res.success).toEqual(true)
+ // the mocked module throws back the input
+ expect(res.response.to).toEqual("me@test.com")
+ })
+
+ it("should return an error if input an invalid email address", async () => {
+ const res = await setup.runStep(setup.actions.SEND_EMAIL.stepId, {
+ ...inputs,
+ to: "invalid@test.com",
+ })
+ expect(res.success).toEqual(false)
+ })
+
+})
diff --git a/packages/server/src/automations/tests/updateRow.spec.js b/packages/server/src/automations/tests/updateRow.spec.js
new file mode 100644
index 0000000000..79c998459b
--- /dev/null
+++ b/packages/server/src/automations/tests/updateRow.spec.js
@@ -0,0 +1,45 @@
+const env = require("../../environment")
+const setup = require("./utilities")
+
+describe("test the update row action", () => {
+ let table, row, inputs
+ let config = setup.getConfig()
+
+ beforeEach(async () => {
+ await config.init()
+ table = await config.createTable()
+ row = await config.createRow()
+ inputs = {
+ rowId: row._id,
+ row: {
+ ...row,
+ name: "Updated name",
+ // put a falsy option in to be removed
+ description: "",
+ }
+ }
+ })
+
+ afterAll(setup.afterAll)
+
+ it("should be able to run the action", async () => {
+ const res = await setup.runStep(setup.actions.UPDATE_ROW.stepId, inputs)
+ expect(res.success).toEqual(true)
+ const updatedRow = await config.getRow(table._id, res.id)
+ expect(updatedRow.name).toEqual("Updated name")
+ expect(updatedRow.description).not.toEqual("")
+ })
+
+ it("should check invalid inputs return an error", async () => {
+ const res = await setup.runStep(setup.actions.UPDATE_ROW.stepId, {})
+ expect(res.success).toEqual(false)
+ })
+
+ it("should return an error when table doesn't exist", async () => {
+ const res = await setup.runStep(setup.actions.UPDATE_ROW.stepId, {
+ row: { _id: "invalid" },
+ rowId: "invalid",
+ })
+ expect(res.success).toEqual(false)
+ })
+})
diff --git a/packages/server/src/automations/tests/utilities/index.js b/packages/server/src/automations/tests/utilities/index.js
new file mode 100644
index 0000000000..ad149d6bde
--- /dev/null
+++ b/packages/server/src/automations/tests/utilities/index.js
@@ -0,0 +1,43 @@
+const TestConfig = require("../../../tests/utilities/TestConfiguration")
+const actions = require("../../actions")
+const logic = require("../../logic")
+const emitter = require("../../../events/index")
+
+let config
+
+exports.getConfig = () => {
+ if (!config) {
+ config = new TestConfig(false)
+ }
+ return config
+}
+
+exports.afterAll = () => {
+ config.end()
+}
+
+exports.runStep = async function runStep(stepId, inputs) {
+ let step
+ if (
+ Object.values(exports.actions)
+ .map(action => action.stepId)
+ .includes(stepId)
+ ) {
+ step = await actions.getAction(stepId)
+ } else {
+ step = logic.getLogic(stepId)
+ }
+ expect(step).toBeDefined()
+ return step({
+ inputs,
+ appId: config ? config.getAppId() : null,
+ // don't really need an API key, mocked out usage quota, not being tested here
+ apiKey: exports.apiKey,
+ emitter,
+ })
+}
+
+exports.apiKey = "test"
+
+exports.actions = actions.BUILTIN_DEFINITIONS
+exports.logic = logic.BUILTIN_DEFINITIONS
diff --git a/packages/server/src/automations/triggers.js b/packages/server/src/automations/triggers.js
index 73ce9edeed..7e50e5ee74 100644
--- a/packages/server/src/automations/triggers.js
+++ b/packages/server/src/automations/triggers.js
@@ -225,6 +225,7 @@ async function queueRelevantRowAutomations(event, eventType) {
}
emitter.on("row:save", async function(event) {
+ /* istanbul ignore next */
if (!event || !event.row || !event.row.tableId) {
return
}
@@ -232,6 +233,7 @@ emitter.on("row:save", async function(event) {
})
emitter.on("row:update", async function(event) {
+ /* istanbul ignore next */
if (!event || !event.row || !event.row.tableId) {
return
}
@@ -239,6 +241,7 @@ emitter.on("row:update", async function(event) {
})
emitter.on("row:delete", async function(event) {
+ /* istanbul ignore next */
if (!event || !event.row || !event.row.tableId) {
return
}
@@ -272,6 +275,7 @@ async function fillRowOutput(automation, params) {
}
params.row = row
} catch (err) {
+ /* istanbul ignore next */
throw "Failed to find table for trigger"
}
return params
@@ -297,6 +301,7 @@ module.exports.externalTrigger = async function(automation, params) {
automationQueue.add({ automation, event: params })
}
+module.exports.fillRowOutput = fillRowOutput
module.exports.automationQueue = automationQueue
module.exports.BUILTIN_DEFINITIONS = BUILTIN_DEFINITIONS
diff --git a/packages/server/src/db/client.js b/packages/server/src/db/client.js
index b5edb1e877..f6dea33a40 100644
--- a/packages/server/src/db/client.js
+++ b/packages/server/src/db/client.js
@@ -30,6 +30,7 @@ const Pouch = PouchDB.defaults(POUCH_DB_DEFAULTS)
allDbs(Pouch)
// replicate your local levelDB pouch to a running HTTP compliant couch or pouchdb server.
+/* istanbul ignore next */
// eslint-disable-next-line no-unused-vars
function replicateLocal() {
Pouch.allDbs().then(dbs => {
diff --git a/packages/server/src/middleware/tests/usageQuota.spec.js b/packages/server/src/middleware/tests/usageQuota.spec.js
index c76acb47d2..395f14c1ed 100644
--- a/packages/server/src/middleware/tests/usageQuota.spec.js
+++ b/packages/server/src/middleware/tests/usageQuota.spec.js
@@ -3,7 +3,7 @@ const usageQuota = require("../../utilities/usageQuota")
const CouchDB = require("../../db")
const env = require("../../environment")
-jest.mock("../../db");
+jest.mock("../../db")
jest.mock("../../utilities/usageQuota")
jest.mock("../../environment")
diff --git a/packages/server/src/selfhost/README.md b/packages/server/src/selfhost/README.md
deleted file mode 100644
index a02743a58c..0000000000
--- a/packages/server/src/selfhost/README.md
+++ /dev/null
@@ -1,7 +0,0 @@
-### Self hosting
-This directory contains utilities that are needed for self hosted platforms to operate.
-These will mostly be utilities, necessary to the operation of the server e.g. storing self
-hosting specific options and attributes to CouchDB.
-
-All the internal operations should be exposed through the `index.js` so importing
-the self host directory should give you everything you need.
\ No newline at end of file
diff --git a/packages/server/src/selfhost/index.js b/packages/server/src/selfhost/index.js
deleted file mode 100644
index f77d1f0b6c..0000000000
--- a/packages/server/src/selfhost/index.js
+++ /dev/null
@@ -1,44 +0,0 @@
-const CouchDB = require("../db")
-const env = require("../environment")
-const newid = require("../db/newid")
-
-const SELF_HOST_DB = "self-host-db"
-const SELF_HOST_DOC = "self-host-info"
-
-async function createSelfHostDB(db) {
- await db.put({
- _id: "_design/database",
- views: {},
- })
- const selfHostInfo = {
- _id: SELF_HOST_DOC,
- apiKeyId: newid(),
- }
- await db.put(selfHostInfo)
- return selfHostInfo
-}
-
-exports.init = async () => {
- if (!env.SELF_HOSTED) {
- return
- }
- const db = new CouchDB(SELF_HOST_DB)
- try {
- await db.get(SELF_HOST_DOC)
- } catch (err) {
- // failed to retrieve
- if (err.status === 404) {
- await createSelfHostDB(db)
- }
- }
-}
-
-exports.getSelfHostInfo = async () => {
- const db = new CouchDB(SELF_HOST_DB)
- return db.get(SELF_HOST_DOC)
-}
-
-exports.getSelfHostAPIKey = async () => {
- const info = await exports.getSelfHostInfo()
- return info ? info.apiKeyId : null
-}
diff --git a/packages/server/src/api/routes/tests/utilities/TestConfiguration.js b/packages/server/src/tests/utilities/TestConfiguration.js
similarity index 88%
rename from packages/server/src/api/routes/tests/utilities/TestConfiguration.js
rename to packages/server/src/tests/utilities/TestConfiguration.js
index 0ff742293d..b36b45186a 100644
--- a/packages/server/src/api/routes/tests/utilities/TestConfiguration.js
+++ b/packages/server/src/tests/utilities/TestConfiguration.js
@@ -1,6 +1,6 @@
-const { BUILTIN_ROLE_IDS } = require("../../../../utilities/security/roles")
+const { BUILTIN_ROLE_IDS } = require("../../utilities/security/roles")
const jwt = require("jsonwebtoken")
-const env = require("../../../../environment")
+const env = require("../../environment")
const {
basicTable,
basicRow,
@@ -15,18 +15,20 @@ const {
const controllers = require("./controllers")
const supertest = require("supertest")
const fs = require("fs")
-const { budibaseAppsDir } = require("../../../../utilities/budibaseDir")
+const { budibaseAppsDir } = require("../../utilities/budibaseDir")
const { join } = require("path")
const EMAIL = "babs@babs.com"
const PASSWORD = "babs_password"
class TestConfiguration {
- constructor() {
- env.PORT = 4002
- this.server = require("../../../../app")
- // we need the request for logging in, involves cookies, hard to fake
- this.request = supertest(this.server)
+ constructor(openServer = true) {
+ if (openServer) {
+ env.PORT = 4002
+ this.server = require("../../app")
+ // we need the request for logging in, involves cookies, hard to fake
+ this.request = supertest(this.server)
+ }
this.appId = null
this.allApps = []
}
@@ -61,7 +63,9 @@ class TestConfiguration {
}
end() {
- this.server.close()
+ if (this.server) {
+ this.server.close()
+ }
const appDir = budibaseAppsDir()
const files = fs.readdirSync(appDir)
for (let file of files) {
@@ -163,6 +167,17 @@ class TestConfiguration {
return this._req(config, { tableId: this.table._id }, controllers.row.save)
}
+ async getRow(tableId, rowId) {
+ return this._req(null, { tableId, rowId }, controllers.row.find)
+ }
+
+ async getRows(tableId) {
+ if (!tableId && this.table) {
+ tableId = this.table._id
+ }
+ return this._req(null, { tableId }, controllers.row.fetchTableRows)
+ }
+
async createRole(config = null) {
config = config || basicRole()
return this._req(config, null, controllers.role.save)
@@ -187,6 +202,7 @@ class TestConfiguration {
const view = config || {
map: "function(doc) { emit(doc[doc.key], doc._id); } ",
tableId: this.table._id,
+ name: "ViewTest",
}
return this._req(view, null, controllers.view.save)
}
@@ -285,6 +301,9 @@ class TestConfiguration {
}
async login(email, password) {
+ if (!this.request) {
+ throw "Server has not been opened, cannot login."
+ }
if (!email || !password) {
await this.createUser()
email = EMAIL
diff --git a/packages/server/src/tests/utilities/controllers.js b/packages/server/src/tests/utilities/controllers.js
new file mode 100644
index 0000000000..b07754038f
--- /dev/null
+++ b/packages/server/src/tests/utilities/controllers.js
@@ -0,0 +1,15 @@
+module.exports = {
+ table: require("../../api/controllers/table"),
+ row: require("../../api/controllers/row"),
+ role: require("../../api/controllers/role"),
+ perms: require("../../api/controllers/permission"),
+ view: require("../../api/controllers/view"),
+ app: require("../../api/controllers/application"),
+ user: require("../../api/controllers/user"),
+ automation: require("../../api/controllers/automation"),
+ datasource: require("../../api/controllers/datasource"),
+ query: require("../../api/controllers/query"),
+ screen: require("../../api/controllers/screen"),
+ webhook: require("../../api/controllers/webhook"),
+ layout: require("../../api/controllers/layout"),
+}
diff --git a/packages/server/src/tests/utilities/index.js b/packages/server/src/tests/utilities/index.js
new file mode 100644
index 0000000000..aa8039ce2f
--- /dev/null
+++ b/packages/server/src/tests/utilities/index.js
@@ -0,0 +1,11 @@
+exports.makePartial = obj => {
+ const newObj = {}
+ for (let key of Object.keys(obj)) {
+ if (typeof obj[key] === "object") {
+ newObj[key] = exports.makePartial(obj[key])
+ } else {
+ newObj[key] = obj[key]
+ }
+ }
+ return expect.objectContaining(newObj)
+}
diff --git a/packages/server/src/api/routes/tests/utilities/structures.js b/packages/server/src/tests/utilities/structures.js
similarity index 86%
rename from packages/server/src/api/routes/tests/utilities/structures.js
rename to packages/server/src/tests/utilities/structures.js
index ff3a239211..e6489f0903 100644
--- a/packages/server/src/api/routes/tests/utilities/structures.js
+++ b/packages/server/src/tests/utilities/structures.js
@@ -1,9 +1,9 @@
-const { BUILTIN_ROLE_IDS } = require("../../../../utilities/security/roles")
+const { BUILTIN_ROLE_IDS } = require("../../utilities/security/roles")
const {
BUILTIN_PERMISSION_IDS,
-} = require("../../../../utilities/security/permissions")
-const { createHomeScreen } = require("../../../../constants/screens")
-const { EMPTY_LAYOUT } = require("../../../../constants/layouts")
+} = require("../../utilities/security/permissions")
+const { createHomeScreen } = require("../../constants/screens")
+const { EMPTY_LAYOUT } = require("../../constants/layouts")
const { cloneDeep } = require("lodash/fp")
exports.basicTable = () => {
diff --git a/packages/server/src/utilities/createAppPackage.js b/packages/server/src/utilities/createAppPackage.js
index a62e8c96df..9500554227 100644
--- a/packages/server/src/utilities/createAppPackage.js
+++ b/packages/server/src/utilities/createAppPackage.js
@@ -7,6 +7,8 @@ const packageJson = require("../../package.json")
const streamPipeline = promisify(stream.pipeline)
+// can't really test this due to the downloading nature of it, wouldn't be a great test case
+/* istanbul ignore next */
exports.downloadExtractComponentLibraries = async appFolder => {
const LIBRARIES = ["standard-components"]
diff --git a/packages/server/src/utilities/exceptions.js b/packages/server/src/utilities/exceptions.js
deleted file mode 100644
index e02c88eec3..0000000000
--- a/packages/server/src/utilities/exceptions.js
+++ /dev/null
@@ -1,16 +0,0 @@
-const statusCodes = require("./statusCodes")
-
-const errorWithStatus = (message, statusCode) => {
- const e = new Error(message)
- e.statusCode = statusCode
- return e
-}
-
-module.exports.unauthorized = message =>
- errorWithStatus(message, statusCodes.UNAUTHORIZED)
-
-module.exports.forbidden = message =>
- errorWithStatus(message, statusCodes.FORBIDDEN)
-
-module.exports.notfound = message =>
- errorWithStatus(message, statusCodes.NOT_FOUND)
diff --git a/packages/server/src/utilities/routing/index.js b/packages/server/src/utilities/routing/index.js
index f4af585dc6..541733dcc4 100644
--- a/packages/server/src/utilities/routing/index.js
+++ b/packages/server/src/utilities/routing/index.js
@@ -12,6 +12,7 @@ exports.getRoutingInfo = async appId => {
return allRouting.rows.map(row => row.value)
} catch (err) {
// check if the view doesn't exist, it should for all new instances
+ /* istanbul ignore next */
if (err != null && err.name === "not_found") {
await createRoutingView(appId)
return exports.getRoutingInfo(appId)
diff --git a/packages/server/src/utilities/security/apikey.js b/packages/server/src/utilities/security/apikey.js
index c8965cee43..3d5f428bb7 100644
--- a/packages/server/src/utilities/security/apikey.js
+++ b/packages/server/src/utilities/security/apikey.js
@@ -1,6 +1,5 @@
const { apiKeyTable } = require("../../db/dynamoClient")
const env = require("../../environment")
-const { getSelfHostAPIKey } = require("../../selfhost")
/**
* This file purely exists so that we can centralise all logic pertaining to API keys, as their usage differs
@@ -8,16 +7,13 @@ const { getSelfHostAPIKey } = require("../../selfhost")
*/
exports.isAPIKeyValid = async apiKeyId => {
- if (env.CLOUD && !env.SELF_HOSTED) {
+ if (!env.SELF_HOSTED) {
let apiKeyInfo = await apiKeyTable.get({
primary: apiKeyId,
})
return apiKeyInfo != null
- }
- if (env.SELF_HOSTED) {
- const selfHostKey = await getSelfHostAPIKey()
+ } else {
// if the api key supplied is correct then return structure similar
- return apiKeyId === selfHostKey ? { pk: apiKeyId } : null
+ return apiKeyId === env.HOSTING_KEY
}
- return false
}
diff --git a/packages/standard-components/package.json b/packages/standard-components/package.json
index 8bce8b2f0b..3e937cff44 100644
--- a/packages/standard-components/package.json
+++ b/packages/standard-components/package.json
@@ -35,9 +35,9 @@
"keywords": [
"svelte"
],
- "version": "0.8.5",
+ "version": "0.8.9",
"license": "MIT",
- "gitHead": "768a9d59da12cfefcd7ccaba05f975e878669504",
+ "gitHead": "1b95326b20d1352d36305910259228b96a683dc7",
"dependencies": {
"@adobe/spectrum-css-workflow-icons": "^1.1.0",
"@budibase/bbui": "^1.58.13",
diff --git a/packages/string-templates/package.json b/packages/string-templates/package.json
index ec7858da74..6e0b573656 100644
--- a/packages/string-templates/package.json
+++ b/packages/string-templates/package.json
@@ -1,6 +1,6 @@
{
"name": "@budibase/string-templates",
- "version": "0.8.5",
+ "version": "0.8.9",
"description": "Handlebars wrapper for Budibase templating.",
"main": "src/index.js",
"module": "src/index.js",
@@ -33,5 +33,5 @@
"rollup-plugin-terser": "^7.0.2",
"typescript": "^4.1.3"
},
- "gitHead": "768a9d59da12cfefcd7ccaba05f975e878669504"
+ "gitHead": "1b95326b20d1352d36305910259228b96a683dc7"
}
diff --git a/packages/worker/package.json b/packages/worker/package.json
index 9f12a0ac1a..1670fd729e 100644
--- a/packages/worker/package.json
+++ b/packages/worker/package.json
@@ -1,7 +1,7 @@
{
"name": "@budibase/deployment",
"email": "hi@budibase.com",
- "version": "0.8.5",
+ "version": "0.8.9",
"description": "Budibase Deployment Server",
"main": "src/index.js",
"repository": {
@@ -34,5 +34,5 @@
"pouchdb-all-dbs": "^1.0.2",
"server-destroy": "^1.0.1"
},
- "gitHead": "768a9d59da12cfefcd7ccaba05f975e878669504"
+ "gitHead": "1b95326b20d1352d36305910259228b96a683dc7"
}