Merge branch 'next' into fix/route-preservation

This commit is contained in:
Keviin Åberg Kultalahti 2021-03-18 08:41:41 +01:00
commit 0b36b98295
76 changed files with 1056 additions and 655 deletions

View File

@ -7,10 +7,12 @@ on:
branches:
- master
- develop
- next
pull_request:
branches:
- master
- develop
- next
jobs:
build:

View File

@ -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') }}

View File

@ -38,7 +38,7 @@
</a>
<img src="https://img.shields.io/badge/Contributor%20Covenant-v2.0%20adopted-ff69b4.svg" alt="Code of conduct" />
<a href="https://codecov.io/gh/Budibase/budibase">
<img src="https://codecov.io/gh/Budibase/budibase/branch/master/graph/badge.svg?token=E8W2ZFXQOH"/>
<img src="https://codecov.io/gh/Budibase/budibase/branch/next/graph/badge.svg?token=E8W2ZFXQOH"/>
</a>
</p>

View File

@ -1,5 +1,5 @@
{
"version": "0.8.5",
"version": "0.8.9",
"npmClient": "yarn",
"packages": [
"packages/*"

View File

@ -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",

View File

@ -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,
})
}

View File

@ -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 =

View File

@ -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()
}
</script>

View File

@ -1,12 +1,12 @@
<script>
import { createEventDispatcher } from 'svelte'
import { createEventDispatcher } from "svelte"
import Colorpicker from "@budibase/colorpicker"
const dispatch = createEventDispatcher();
const dispatch = createEventDispatcher()
export let value
const WAIT = 150;
const WAIT = 150
function throttle(callback, wait, immediate = false) {
let timeout = null
@ -30,9 +30,13 @@
}
}
const onChange = throttle(e => {
dispatch('change', e.detail)
}, WAIT, true)
const onChange = throttle(
e => {
dispatch("change", e.detail)
},
WAIT,
true
)
</script>
<Colorpicker value={value || '#C4C4C4'} on:change={onChange} />

View File

@ -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)
}

View File

@ -1,6 +1,5 @@
<script>
import { Label, Heading, Input } from "@budibase/bbui"
import Dropzone from "components/common/Dropzone.svelte"
const BYTES_IN_MB = 1000000
const FILE_SIZE_LIMIT = BYTES_IN_MB * 5
@ -20,8 +19,8 @@
)
return
}
file = fileArray[0]
template.fileImportPath = file.path
file = evt.target.files[0]
template.file = file
}
</script>

View File

@ -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",

View File

@ -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"
}

View File

@ -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,
}
}

View File

@ -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"

View File

@ -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()

View File

@ -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)
}

View File

@ -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"
}

View File

@ -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 = {

View File

@ -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) {

View File

@ -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) {

View File

@ -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,

View File

@ -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
}

View File

@ -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)

View File

@ -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

View File

@ -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))
}
}

View File

@ -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()

View File

@ -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()

View File

@ -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("/version", () => {
it("should confirm version", async () => {
const res = await request.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")
})
})
})
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 config = setup.getConfig()
const res = await config.getRequest().get("/version").expect(200)
expect(res.text.split(".").length).toEqual(3)
})
})

View File

@ -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

View File

@ -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()

View File

@ -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()

View File

@ -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")

View File

@ -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)

View File

@ -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()

View File

@ -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({
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()
@ -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}`,
})
})
})
})

View File

@ -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()

View File

@ -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())
}

View File

@ -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"),
}

View File

@ -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

View File

@ -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()
})
})
})

View File

@ -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()

View File

@ -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 => {

View File

@ -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

View File

@ -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}`
)
}
})
}

View File

@ -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)
}

View File

@ -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: {

View File

@ -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

View File

@ -87,6 +87,7 @@ module.exports.run = async function({ inputs }) {
success: response.status === 200,
}
} catch (err) {
/* istanbul ignore next */
return {
success: false,
response: err,

View File

@ -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,

View File

@ -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")
})
})

View File

@ -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)
})
})

View File

@ -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
})
})

View File

@ -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)
})
})

View File

@ -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)
})
})

View File

@ -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)
})
})

View File

@ -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)
})
})

View File

@ -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)
})
})

View File

@ -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)
})
})

View File

@ -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

View File

@ -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

View File

@ -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 => {

View File

@ -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")

View File

@ -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.

View File

@ -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
}

View File

@ -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

View File

@ -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"),
}

View File

@ -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)
}

View File

@ -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 = () => {

View File

@ -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"]

View File

@ -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)

View File

@ -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)

View File

@ -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
}

View File

@ -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",

View File

@ -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"
}

View File

@ -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"
}