diff --git a/packages/builder/src/pages/[application]/workflow/_layout.svelte b/packages/builder/src/pages/[application]/workflow/_layout.svelte
deleted file mode 100644
index a011b5508c..0000000000
--- a/packages/builder/src/pages/[application]/workflow/_layout.svelte
+++ /dev/null
@@ -1,51 +0,0 @@
-
-
-
-
-
-
-
- {#if $workflowStore.selectedWorkflow}
-
- {/if}
-
-
-
diff --git a/packages/builder/src/pages/[application]/workflow/index.svelte b/packages/builder/src/pages/[application]/workflow/index.svelte
deleted file mode 100644
index 781e492a0f..0000000000
--- a/packages/builder/src/pages/[application]/workflow/index.svelte
+++ /dev/null
@@ -1,5 +0,0 @@
-
-
-
diff --git a/packages/builder/yarn.lock b/packages/builder/yarn.lock
index f640e80567..2a5ee3d275 100644
--- a/packages/builder/yarn.lock
+++ b/packages/builder/yarn.lock
@@ -8,6 +8,13 @@
dependencies:
"@babel/highlight" "^7.8.3"
+"@babel/code-frame@^7.10.4":
+ version "7.10.4"
+ resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.10.4.tgz#168da1a36e90da68ae8d49c0f1b48c7c6249213a"
+ integrity sha512-vG6SvB6oYEhvgisZNFRmRCUkLz11c7rp+tbNTynGqc6mS1d5ATd/sGyV6W0KZZnXRKMTzZDRgQT3Ou9jhpAfUg==
+ dependencies:
+ "@babel/highlight" "^7.10.4"
+
"@babel/compat-data@^7.9.6":
version "7.9.6"
resolved "https://registry.yarnpkg.com/@babel/compat-data/-/compat-data-7.9.6.tgz#3f604c40e420131affe6f2c8052e9a275ae2049b"
@@ -184,6 +191,11 @@
dependencies:
"@babel/types" "^7.8.3"
+"@babel/helper-validator-identifier@^7.10.4":
+ version "7.10.4"
+ resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.10.4.tgz#a78c7a7251e01f616512d31b10adcf52ada5e0d2"
+ integrity sha512-3U9y+43hz7ZM+rzG24Qe2mufW5KhvFg/NhnNph+i9mgCtdTCtMJuI1TMkrIUiK7Ix4PYlRF9I5dhqaLYA/ADXw==
+
"@babel/helper-validator-identifier@^7.9.0", "@babel/helper-validator-identifier@^7.9.5":
version "7.9.5"
resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.9.5.tgz#90977a8e6fbf6b431a7dc31752eee233bf052d80"
@@ -205,6 +217,15 @@
"@babel/traverse" "^7.9.6"
"@babel/types" "^7.9.6"
+"@babel/highlight@^7.10.4":
+ version "7.10.4"
+ resolved "https://registry.yarnpkg.com/@babel/highlight/-/highlight-7.10.4.tgz#7d1bdfd65753538fabe6c38596cdb76d9ac60143"
+ integrity sha512-i6rgnR/YgPEQzZZnbTHHuZdlE8qyoBNalD6F+q4vAFlcMEcqmkoG+mPqJYJCo63qPf74+Y1UZsl3l6f7/RIkmA==
+ dependencies:
+ "@babel/helper-validator-identifier" "^7.10.4"
+ chalk "^2.0.0"
+ js-tokens "^4.0.0"
+
"@babel/highlight@^7.8.3":
version "7.9.0"
resolved "https://registry.yarnpkg.com/@babel/highlight/-/highlight-7.9.0.tgz#4e9b45ccb82b79607271b2979ad82c7b68163079"
@@ -760,6 +781,7 @@
"@fortawesome/fontawesome-free@^5.14.0":
version "5.14.0"
resolved "https://registry.yarnpkg.com/@fortawesome/fontawesome-free/-/fontawesome-free-5.14.0.tgz#a371e91029ebf265015e64f81bfbf7d228c9681f"
+ integrity sha512-OfdMsF+ZQgdKHP9jUbmDcRrP0eX90XXrsXIdyjLbkmSBzmMXPABB8eobUJtivaupucYaByz6WNe1PI1JuYm3qA==
"@hapi/address@^2.1.2":
version "2.1.4"
@@ -1137,10 +1159,6 @@
version "1.1.1"
resolved "https://registry.yarnpkg.com/@types/color-name/-/color-name-1.1.1.tgz#1c1261bbeaa10a8055bbc5d8ab84b7b2afc846a0"
-"@types/estree@*":
- version "0.0.44"
- resolved "https://registry.yarnpkg.com/@types/estree/-/estree-0.0.44.tgz#980cc5a29a3ef3bea6ff1f7d021047d7ea575e21"
-
"@types/estree@0.0.39":
version "0.0.39"
resolved "https://registry.yarnpkg.com/@types/estree/-/estree-0.0.39.tgz#e177e699ee1b8c22d23174caaa7422644389509f"
@@ -1283,7 +1301,7 @@ acorn@^6.0.1:
version "6.4.1"
resolved "https://registry.yarnpkg.com/acorn/-/acorn-6.4.1.tgz#531e58ba3f51b9dacb9a6646ca4debf5b14ca474"
-acorn@^7.1.0, acorn@^7.1.1:
+acorn@^7.1.1:
version "7.2.0"
resolved "https://registry.yarnpkg.com/acorn/-/acorn-7.2.0.tgz#17ea7e40d7c8640ff54a694c889c26f31704effe"
@@ -1387,6 +1405,7 @@ array-equal@^1.0.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=
array-union@^2.1.0:
version "2.1.0"
@@ -1445,6 +1464,7 @@ atob@^2.1.2:
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"
@@ -1908,7 +1928,7 @@ combined-stream@^1.0.6, combined-stream@~1.0.6:
dependencies:
delayed-stream "~1.0.0"
-commander@2, commander@^2.19.0:
+commander@2, commander@^2.20.0:
version "2.20.3"
resolved "https://registry.yarnpkg.com/commander/-/commander-2.20.3.tgz#fd485e84c03eb4881c20722ba48035e8531aeb33"
@@ -2400,6 +2420,7 @@ decode-uri-component@^0.2.0:
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"
@@ -2588,6 +2609,7 @@ es-abstract@^1.17.0-next.1, es-abstract@^1.17.2, es-abstract@^1.17.5:
es-abstract@^1.17.4:
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"
@@ -2604,6 +2626,7 @@ es-abstract@^1.17.4:
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"
@@ -2621,6 +2644,7 @@ es-abstract@^1.18.0-next.0:
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"
@@ -3313,6 +3337,7 @@ is-accessor-descriptor@^1.0.0:
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"
@@ -3321,6 +3346,7 @@ is-arrayish@^0.2.1:
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-binary-path@~2.1.0:
version "2.1.0"
@@ -3331,6 +3357,7 @@ is-binary-path@~2.1.0:
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-buffer@^1.1.5:
version "1.1.6"
@@ -3343,6 +3370,7 @@ is-callable@^1.1.4, is-callable@^1.1.5:
is-callable@^1.2.0:
version "1.2.1"
resolved "https://registry.yarnpkg.com/is-callable/-/is-callable-1.2.1.tgz#4d1e21a4f437509d25ce55f8184350771421c96d"
+ integrity sha512-wliAfSzx6V+6WfMOmus1xy0XvSgf/dlStkvTfq7F0g4bOIW0PSUbnyse3NhDwdyYS1ozfUtAAySqTws3z9Eqgg==
is-ci@^2.0.0:
version "2.0.0"
@@ -3430,6 +3458,7 @@ is-installed-globally@^0.3.2:
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"
@@ -3438,10 +3467,12 @@ is-module@^1.0.0:
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-number@^3.0.0:
version "3.0.0"
@@ -3502,12 +3533,14 @@ 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-stream@^1.1.0:
version "1.1.0"
@@ -3520,6 +3553,7 @@ is-stream@^2.0.0:
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"
@@ -3530,6 +3564,7 @@ is-symbol@^1.0.2:
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"
@@ -3543,10 +3578,12 @@ is-typedarray@~1.0.0:
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-windows@^1.0.2:
version "1.0.2"
@@ -3571,6 +3608,7 @@ isarray@1.0.0, isarray@~1.0.0:
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==
isbuffer@~0.0.0:
version "0.0.0"
@@ -3972,13 +4010,22 @@ jest-watcher@^24.9.0:
jest-util "^24.9.0"
string-length "^2.0.0"
-jest-worker@^24.0.0, jest-worker@^24.6.0, jest-worker@^24.9.0:
+jest-worker@^24.6.0, jest-worker@^24.9.0:
version "24.9.0"
resolved "https://registry.yarnpkg.com/jest-worker/-/jest-worker-24.9.0.tgz#5dbfdb5b2d322e98567898238a9697bcce67b3e5"
dependencies:
merge-stream "^2.0.0"
supports-color "^6.1.0"
+jest-worker@^26.2.1:
+ version "26.3.0"
+ resolved "https://registry.yarnpkg.com/jest-worker/-/jest-worker-26.3.0.tgz#7c8a97e4f4364b4f05ed8bca8ca0c24de091871f"
+ integrity sha512-Vmpn2F6IASefL+DVBhPzI2J9/GJUsqzomdeN+P+dK8/jKxbh8R3BtFnx3FIta7wYlPU62cpJMJQo4kuOowcMnw==
+ dependencies:
+ "@types/node" "*"
+ merge-stream "^2.0.0"
+ supports-color "^7.0.0"
+
jest@^24.8.0:
version "24.9.0"
resolved "https://registry.yarnpkg.com/jest/-/jest-24.9.0.tgz#987d290c05a08b52c56188c1002e368edb007171"
@@ -4685,10 +4732,12 @@ 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"
@@ -5086,7 +5135,7 @@ ramda@~0.26.1:
version "0.26.1"
resolved "https://registry.yarnpkg.com/ramda/-/ramda-0.26.1.tgz#8d41351eb8111c55353617fc3bbffad8e4d35d06"
-randombytes@^2.0.0, randombytes@^2.0.1, randombytes@^2.0.5:
+randombytes@^2.0.0, randombytes@^2.0.1, randombytes@^2.0.5, randombytes@^2.1.0:
version "2.1.0"
resolved "https://registry.yarnpkg.com/randombytes/-/randombytes-2.1.0.tgz#df6f84372f0270dc65cdf6291349ab7a473d4f2a"
dependencies:
@@ -5206,6 +5255,7 @@ regex-not@^1.0.0, regex-not@^1.0.2:
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"
@@ -5213,6 +5263,7 @@ regexp.prototype.flags@^1.3.0:
regexparam@^1.3.0:
version "1.3.0"
resolved "https://registry.yarnpkg.com/regexparam/-/regexparam-1.3.0.tgz#2fe42c93e32a40eff6235d635e0ffa344b92965f"
+ integrity sha512-6IQpFBv6e5vz1QAqI+V4k8P2e/3gRrqfCJ9FI+O1FLQTO+Uz6RXZEZOPmTJ6hlGj7gkERzY5BRCv09whKP96/g==
regexpu-core@^4.7.0:
version "4.7.0"
@@ -5450,14 +5501,15 @@ rollup-plugin-svelte@^5.0.3:
rollup-pluginutils "^2.8.2"
sourcemap-codec "^1.4.8"
-rollup-plugin-terser@^4.0.4:
- version "4.0.4"
- resolved "https://registry.yarnpkg.com/rollup-plugin-terser/-/rollup-plugin-terser-4.0.4.tgz#6f661ef284fa7c27963d242601691dc3d23f994e"
+rollup-plugin-terser@^7.0.2:
+ version "7.0.2"
+ resolved "https://registry.yarnpkg.com/rollup-plugin-terser/-/rollup-plugin-terser-7.0.2.tgz#e8fbba4869981b2dc35ae7e8a502d5c6c04d324d"
+ integrity sha512-w3iIaU4OxcF52UUXiZNsNeuXIMDvFrr+ZXK6bFZ0Q60qyVfq4uLptoS4bbq3paG3x216eQllFZX7zt6TIImguQ==
dependencies:
- "@babel/code-frame" "^7.0.0"
- jest-worker "^24.0.0"
- serialize-javascript "^1.6.1"
- terser "^3.14.1"
+ "@babel/code-frame" "^7.10.4"
+ jest-worker "^26.2.1"
+ serialize-javascript "^4.0.0"
+ terser "^5.0.0"
rollup-plugin-url@^2.2.2:
version "2.2.4"
@@ -5473,13 +5525,12 @@ rollup-pluginutils@^2.3.1, rollup-pluginutils@^2.8.1, rollup-pluginutils@^2.8.2:
dependencies:
estree-walker "^0.6.1"
-rollup@^1.12.0:
- version "1.32.1"
- resolved "https://registry.yarnpkg.com/rollup/-/rollup-1.32.1.tgz#4480e52d9d9e2ae4b46ba0d9ddeaf3163940f9c4"
- dependencies:
- "@types/estree" "*"
- "@types/node" "*"
- acorn "^7.1.0"
+rollup@^2.11.2:
+ version "2.27.0"
+ resolved "https://registry.yarnpkg.com/rollup/-/rollup-2.27.0.tgz#f2b70a8dd583bc3675b36686289aa9a51e27af4f"
+ integrity sha512-1WlbhNdzhLjdhh2wsf6CDxmuBAYG+5O53fYqCcGv8aJOoX/ymCfCY6oZnvllXZzaC/Ng+lPPwq9EMbHOKc5ozA==
+ optionalDependencies:
+ fsevents "~2.1.2"
rsvp@^4.8.4:
version "4.8.5"
@@ -5563,9 +5614,12 @@ semver@~2.3.1:
version "2.3.2"
resolved "https://registry.yarnpkg.com/semver/-/semver-2.3.2.tgz#b9848f25d6cf36333073ec9ef8856d42f1233e52"
-serialize-javascript@^1.6.1:
- version "1.9.1"
- resolved "https://registry.yarnpkg.com/serialize-javascript/-/serialize-javascript-1.9.1.tgz#cfc200aef77b600c47da9bb8149c943e798c2fdb"
+serialize-javascript@^4.0.0:
+ version "4.0.0"
+ resolved "https://registry.yarnpkg.com/serialize-javascript/-/serialize-javascript-4.0.0.tgz#b525e1238489a5ecfc42afacc3fe99e666f4b1aa"
+ integrity sha512-GaNA54380uFefWghODBWEGisLZFj00nS5ACs6yHa9nLqlLpVLO8ChDGeKRjZnV4Nh4n0Qi7nhYZD/9fCPzEqkw==
+ dependencies:
+ randombytes "^2.1.0"
set-blocking@^2.0.0:
version "2.0.0"
@@ -5621,6 +5675,7 @@ shortid@^2.2.15:
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"
@@ -5701,7 +5756,7 @@ source-map-resolve@^0.5.0, source-map-resolve@^0.5.2:
source-map-url "^0.4.0"
urix "^0.1.0"
-source-map-support@^0.5.6, source-map-support@~0.5.10:
+source-map-support@^0.5.6, source-map-support@~0.5.12:
version "0.5.19"
resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.5.19.tgz#a98b62f86dcaf4f67399648c085291ab9e8fed61"
dependencies:
@@ -5955,6 +6010,13 @@ supports-color@^6.1.0:
dependencies:
has-flag "^3.0.0"
+supports-color@^7.0.0:
+ version "7.2.0"
+ resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-7.2.0.tgz#1b7dcdcb32b8138801b3e478ba6a51caa89648da"
+ integrity sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==
+ dependencies:
+ has-flag "^4.0.0"
+
supports-color@^7.1.0:
version "7.1.0"
resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-7.1.0.tgz#68e32591df73e25ad1c4b49108a2ec507962bfd1"
@@ -5985,9 +6047,10 @@ svelte-simple-modal@^0.4.2:
version "0.4.2"
resolved "https://registry.yarnpkg.com/svelte-simple-modal/-/svelte-simple-modal-0.4.2.tgz#2cfe26ec8c0760b89813d65dfee836399620d6b2"
-svelte@3.23.x:
- version "3.23.0"
- resolved "https://registry.yarnpkg.com/svelte/-/svelte-3.23.0.tgz#bbcd6887cf588c24a975b14467455abfff9acd3f"
+svelte@^3.24.1:
+ version "3.25.1"
+ resolved "https://registry.yarnpkg.com/svelte/-/svelte-3.25.1.tgz#218def1243fea5a97af6eb60f5e232315bb57ac4"
+ integrity sha512-IbrVKTmuR0BvDw4ii8/gBNy8REu7nWTRy9uhUz+Yuae5lIjWgSGwKlWtJGC2Vg95s+UnXPqDu0Kk/sUwe0t2GQ==
symbol-observable@^1.1.0:
version "1.2.0"
@@ -6001,13 +6064,14 @@ synchronous-promise@^2.0.13:
version "2.0.13"
resolved "https://registry.yarnpkg.com/synchronous-promise/-/synchronous-promise-2.0.13.tgz#9d8c165ddee69c5a6542862b405bc50095926702"
-terser@^3.14.1:
- version "3.17.0"
- resolved "https://registry.yarnpkg.com/terser/-/terser-3.17.0.tgz#f88ffbeda0deb5637f9d24b0da66f4e15ab10cb2"
+terser@^5.0.0:
+ version "5.3.1"
+ resolved "https://registry.yarnpkg.com/terser/-/terser-5.3.1.tgz#f50fe20ab48b15234fe9bdd86b10148ad5fca787"
+ integrity sha512-yD80f4hdwCWTH5mojzxe1q8bN1oJbsK/vfJGLcPZM/fl+/jItIVNKhFIHqqR71OipFWMLgj3Kc+GIp6CeIqfnA==
dependencies:
- commander "^2.19.0"
+ commander "^2.20.0"
source-map "~0.6.1"
- source-map-support "~0.5.10"
+ source-map-support "~0.5.12"
test-exclude@^5.2.3:
version "5.2.3"
@@ -6323,6 +6387,7 @@ whatwg-url@^8.0.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"
@@ -6333,6 +6398,7 @@ which-boxed-primitive@^1.0.1:
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"
@@ -6346,6 +6412,7 @@ which-module@^2.0.0:
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"
diff --git a/packages/server/package.json b/packages/server/package.json
index 520870d735..2d2deca1a9 100644
--- a/packages/server/package.json
+++ b/packages/server/package.json
@@ -50,6 +50,7 @@
"bcryptjs": "^2.4.3",
"chmodr": "^1.2.0",
"dotenv": "^8.2.0",
+ "download": "^8.0.0",
"electron-is-dev": "^1.2.0",
"electron-unhandled": "^3.0.2",
"electron-updater": "^4.3.1",
diff --git a/packages/server/src/api/controllers/automation.js b/packages/server/src/api/controllers/automation.js
new file mode 100644
index 0000000000..2c415fec8b
--- /dev/null
+++ b/packages/server/src/api/controllers/automation.js
@@ -0,0 +1,130 @@
+const CouchDB = require("../../db")
+const newid = require("../../db/newid")
+const actions = require("../../automations/actions")
+const logic = require("../../automations/logic")
+const triggers = require("../../automations/triggers")
+
+/*************************
+ * *
+ * BUILDER FUNCTIONS *
+ * *
+ *************************/
+
+function cleanAutomationInputs(automation) {
+ if (automation == null) {
+ return automation
+ }
+ let steps = automation.definition.steps
+ let trigger = automation.definition.trigger
+ let allSteps = [...steps, trigger]
+ for (let step of allSteps) {
+ if (step == null) {
+ continue
+ }
+ for (let inputName of Object.keys(step.inputs)) {
+ if (!step.inputs[inputName] || step.inputs[inputName] === "") {
+ delete step.inputs[inputName]
+ }
+ }
+ }
+ return automation
+}
+
+exports.create = async function(ctx) {
+ const db = new CouchDB(ctx.user.instanceId)
+ let automation = ctx.request.body
+
+ automation._id = newid()
+
+ automation.type = "automation"
+ automation = cleanAutomationInputs(automation)
+ const response = await db.post(automation)
+ automation._rev = response.rev
+
+ ctx.status = 200
+ ctx.body = {
+ message: "Automation created successfully",
+ automation: {
+ ...automation,
+ ...response,
+ },
+ }
+}
+
+exports.update = async function(ctx) {
+ const db = new CouchDB(ctx.user.instanceId)
+ let automation = ctx.request.body
+
+ automation = cleanAutomationInputs(automation)
+ const response = await db.put(automation)
+ automation._rev = response.rev
+
+ ctx.status = 200
+ ctx.body = {
+ message: `Automation ${automation._id} updated successfully.`,
+ automation: {
+ ...automation,
+ _rev: response.rev,
+ _id: response.id,
+ },
+ }
+}
+
+exports.fetch = async function(ctx) {
+ const db = new CouchDB(ctx.user.instanceId)
+ const response = await db.query(`database/by_type`, {
+ key: ["automation"],
+ include_docs: true,
+ })
+ ctx.body = response.rows.map(row => row.doc)
+}
+
+exports.find = async function(ctx) {
+ const db = new CouchDB(ctx.user.instanceId)
+ ctx.body = await db.get(ctx.params.id)
+}
+
+exports.destroy = async function(ctx) {
+ const db = new CouchDB(ctx.user.instanceId)
+ ctx.body = await db.remove(ctx.params.id, ctx.params.rev)
+}
+
+exports.getActionList = async function(ctx) {
+ ctx.body = actions.DEFINITIONS
+}
+
+exports.getTriggerList = async function(ctx) {
+ ctx.body = triggers.BUILTIN_DEFINITIONS
+}
+
+exports.getLogicList = async function(ctx) {
+ ctx.body = logic.BUILTIN_DEFINITIONS
+}
+
+module.exports.getDefinitionList = async function(ctx) {
+ ctx.body = {
+ logic: logic.BUILTIN_DEFINITIONS,
+ trigger: triggers.BUILTIN_DEFINITIONS,
+ action: actions.DEFINITIONS,
+ }
+}
+
+/*********************
+ * *
+ * API FUNCTIONS *
+ * *
+ *********************/
+
+exports.trigger = async function(ctx) {
+ const db = new CouchDB(ctx.user.instanceId)
+ let automation = await db.get(ctx.params.id)
+ await triggers.externalTrigger(automation, {
+ ...ctx.request.body,
+ instanceId: ctx.user.instanceId,
+ })
+ ctx.status = 200
+ ctx.body = {
+ message: `Automation ${automation._id} has been triggered.`,
+ automation,
+ }
+}
diff --git a/packages/server/src/api/controllers/instance.js b/packages/server/src/api/controllers/instance.js
index 4bc22bd650..eb7b205240 100644
--- a/packages/server/src/api/controllers/instance.js
+++ b/packages/server/src/api/controllers/instance.js
@@ -31,9 +31,9 @@ exports.create = async function(ctx) {
emit([doc.type], doc._id)
}.toString(),
},
- by_workflow_trigger: {
+ by_automation_trigger: {
map: function(doc) {
- if (doc.type === "workflow") {
+ if (doc.type === "automation") {
const trigger = doc.definition.trigger
if (trigger) {
emit([trigger.event], trigger)
diff --git a/packages/server/src/api/controllers/record.js b/packages/server/src/api/controllers/record.js
index f9cea706f5..2626667ef3 100644
--- a/packages/server/src/api/controllers/record.js
+++ b/packages/server/src/api/controllers/record.js
@@ -3,13 +3,18 @@ const validateJs = require("validate.js")
const newid = require("../../db/newid")
function emitEvent(eventType, ctx, record) {
- ctx.eventEmitter &&
- ctx.eventEmitter.emit(eventType, {
- args: {
- record,
- },
- instanceId: ctx.user.instanceId,
- })
+ let event = {
+ record,
+ instanceId: ctx.user.instanceId,
+ }
+ // add syntactic sugar for mustache later
+ if (record._id) {
+ event.id = record._id
+ }
+ if (record._rev) {
+ event.revision = record._rev
+ }
+ ctx.eventEmitter && ctx.eventEmitter.emit(eventType, event)
}
validateJs.extend(validateJs.validators.datetime, {
@@ -53,7 +58,6 @@ exports.patch = async function(ctx) {
ctx.body = record
ctx.status = 200
ctx.message = `${model.name} updated successfully.`
- return
}
exports.save = async function(ctx) {
@@ -179,10 +183,13 @@ exports.destroy = async function(ctx) {
const db = new CouchDB(ctx.user.instanceId)
const record = await db.get(ctx.params.recordId)
if (record.modelId !== ctx.params.modelId) {
- ctx.throw(400, "Supplied modelId doe not match the record's modelId")
+ ctx.throw(400, "Supplied modelId doesn't match the record's modelId")
return
}
ctx.body = await db.remove(ctx.params.recordId, ctx.params.revId)
+ ctx.status = 200
+ // for automations
+ ctx.record = record
emitEvent(`record:delete`, ctx, record)
}
diff --git a/packages/server/src/api/controllers/user.js b/packages/server/src/api/controllers/user.js
index d4525974f9..3933e4f790 100644
--- a/packages/server/src/api/controllers/user.js
+++ b/packages/server/src/api/controllers/user.js
@@ -56,6 +56,7 @@ exports.create = async function(ctx) {
ctx.status = 200
ctx.message = "User created successfully."
+ ctx.userId = response._id
ctx.body = {
_rev: response.rev,
username,
diff --git a/packages/server/src/api/controllers/view/index.js b/packages/server/src/api/controllers/view/index.js
index a45132c2af..b04b59e32b 100644
--- a/packages/server/src/api/controllers/view/index.js
+++ b/packages/server/src/api/controllers/view/index.js
@@ -12,7 +12,7 @@ const controller = {
!name.startsWith("all") &&
name !== "by_type" &&
name !== "by_username" &&
- name !== "by_workflow_trigger"
+ name !== "by_automation_trigger"
) {
response.push({
name,
diff --git a/packages/server/src/api/controllers/workflow/blockDefinitions.js b/packages/server/src/api/controllers/workflow/blockDefinitions.js
deleted file mode 100644
index f713823245..0000000000
--- a/packages/server/src/api/controllers/workflow/blockDefinitions.js
+++ /dev/null
@@ -1,113 +0,0 @@
-const ACTION = {
- SAVE_RECORD: {
- name: "Save Record",
- tagline: "
Save a
{{record.model.name}} record",
- icon: "ri-save-3-fill",
- description: "Save a record to your database.",
- params: {
- record: "record",
- },
- args: {
- record: {},
- },
- type: "ACTION",
- },
- DELETE_RECORD: {
- description: "Delete a record from your database.",
- icon: "ri-delete-bin-line",
- name: "Delete Record",
- tagline: "
Delete a
{{record.model.name}} record",
- params: {},
- args: {},
- type: "ACTION",
- },
- CREATE_USER: {
- description: "Create a new user.",
- tagline: "Create user
{{username}}",
- icon: "ri-user-add-fill",
- name: "Create User",
- params: {
- username: "string",
- password: "password",
- accessLevelId: "accessLevel",
- },
- args: {
- accessLevelId: "POWER_USER",
- },
- type: "ACTION",
- },
- SEND_EMAIL: {
- description: "Send an email.",
- tagline: "Send email to
{{to}}",
- icon: "ri-mail-open-fill",
- name: "Send Email",
- params: {
- to: "string",
- from: "string",
- subject: "longText",
- text: "longText",
- },
- type: "ACTION",
- },
-}
-
-const LOGIC = {
- FILTER: {
- name: "Filter",
- tagline: "{{filter}}
{{condition}} {{value}}",
- icon: "ri-git-branch-line",
- description: "Filter any workflows which do not meet certain conditions.",
- params: {
- filter: "string",
- condition: ["equals"],
- value: "string",
- },
- args: {
- condition: "equals",
- },
- type: "LOGIC",
- },
- DELAY: {
- name: "Delay",
- icon: "ri-time-fill",
- tagline: "Delay for
{{time}} milliseconds",
- description: "Delay the workflow until an amount of time has passed.",
- params: {
- time: "number",
- },
- type: "LOGIC",
- },
-}
-
-const TRIGGER = {
- RECORD_SAVED: {
- name: "Record Saved",
- event: "record:save",
- icon: "ri-save-line",
- tagline: "Record is added to
{{model.name}}",
- description: "Fired when a record is saved to your database.",
- params: {
- model: "model",
- },
- type: "TRIGGER",
- },
- RECORD_DELETED: {
- name: "Record Deleted",
- event: "record:delete",
- icon: "ri-delete-bin-line",
- tagline: "Record is deleted from
{{model.name}}",
- description: "Fired when a record is deleted from your database.",
- params: {
- model: "model",
- },
- type: "TRIGGER",
- },
-}
-
-// This contains the definitions for the steps and triggers that make up a workflow, a workflow comprises
-// of many steps and a single trigger
-module.exports = {
- ACTION,
- LOGIC,
- TRIGGER,
-}
diff --git a/packages/server/src/api/controllers/workflow/index.js b/packages/server/src/api/controllers/workflow/index.js
deleted file mode 100644
index efb93ad1c1..0000000000
--- a/packages/server/src/api/controllers/workflow/index.js
+++ /dev/null
@@ -1,107 +0,0 @@
-const CouchDB = require("../../../db")
-const newid = require("../../../db/newid")
-const blockDefinitions = require("./blockDefinitions")
-const triggers = require("../../../workflows/triggers")
-
-/*************************
- * *
- * BUILDER FUNCTIONS *
- * *
- *************************/
-
-exports.create = async function(ctx) {
- const db = new CouchDB(ctx.user.instanceId)
- const workflow = ctx.request.body
-
- workflow._id = newid()
-
- workflow.type = "workflow"
- const response = await db.post(workflow)
- workflow._rev = response.rev
-
- ctx.status = 200
- ctx.body = {
- message: "Workflow created successfully",
- workflow: {
- ...workflow,
- ...response,
- },
- }
-}
-
-exports.update = async function(ctx) {
- const db = new CouchDB(ctx.user.instanceId)
- const workflow = ctx.request.body
-
- const response = await db.put(workflow)
- workflow._rev = response.rev
-
- ctx.status = 200
- ctx.body = {
- message: `Workflow ${workflow._id} updated successfully.`,
- workflow: {
- ...workflow,
- _rev: response.rev,
- _id: response.id,
- },
- }
-}
-
-exports.fetch = async function(ctx) {
- const db = new CouchDB(ctx.user.instanceId)
- const response = await db.query(`database/by_type`, {
- key: ["workflow"],
- include_docs: true,
- })
- ctx.body = response.rows.map(row => row.doc)
-}
-
-exports.find = async function(ctx) {
- const db = new CouchDB(ctx.user.instanceId)
- ctx.body = await db.get(ctx.params.id)
-}
-
-exports.destroy = async function(ctx) {
- const db = new CouchDB(ctx.user.instanceId)
- ctx.body = await db.remove(ctx.params.id, ctx.params.rev)
-}
-
-exports.getActionList = async function(ctx) {
- ctx.body = blockDefinitions.ACTION
-}
-
-exports.getTriggerList = async function(ctx) {
- ctx.body = blockDefinitions.TRIGGER
-}
-
-exports.getLogicList = async function(ctx) {
- ctx.body = blockDefinitions.LOGIC
-}
-
-module.exports.getDefinitionList = async function(ctx) {
- ctx.body = {
- logic: blockDefinitions.LOGIC,
- trigger: blockDefinitions.TRIGGER,
- action: blockDefinitions.ACTION,
- }
-}
-
-/*********************
- * *
- * API FUNCTIONS *
- * *
- *********************/
-
-exports.trigger = async function(ctx) {
- const db = new CouchDB(ctx.user.instanceId)
- let workflow = await db.get(ctx.params.id)
- await triggers.externalTrigger(workflow, {
- ...ctx.request.body,
- instanceId: ctx.user.instanceId,
- })
- ctx.status = 200
- ctx.body = {
- message: `Workflow ${workflow._id} has been triggered.`,
- workflow,
- }
-}
diff --git a/packages/server/src/api/index.js b/packages/server/src/api/index.js
index 48c2a9041f..b7f156fb6a 100644
--- a/packages/server/src/api/index.js
+++ b/packages/server/src/api/index.js
@@ -16,7 +16,7 @@ const {
viewRoutes,
staticRoutes,
componentRoutes,
- workflowRoutes,
+ automationRoutes,
accesslevelRoutes,
apiKeysRoutes,
} = require("./routes")
@@ -84,8 +84,8 @@ router.use(userRoutes.allowedMethods())
router.use(instanceRoutes.routes())
router.use(instanceRoutes.allowedMethods())
-router.use(workflowRoutes.routes())
-router.use(workflowRoutes.allowedMethods())
+router.use(automationRoutes.routes())
+router.use(automationRoutes.allowedMethods())
router.use(deployRoutes.routes())
router.use(deployRoutes.allowedMethods())
diff --git a/packages/server/src/api/routes/workflow.js b/packages/server/src/api/routes/automation.js
similarity index 50%
rename from packages/server/src/api/routes/workflow.js
rename to packages/server/src/api/routes/automation.js
index e6a61ae5f5..84b429be66 100644
--- a/packages/server/src/api/routes/workflow.js
+++ b/packages/server/src/api/routes/automation.js
@@ -1,5 +1,5 @@
const Router = require("@koa/router")
-const controller = require("../controllers/workflow")
+const controller = require("../controllers/automation")
const authorized = require("../../middleware/authorized")
const joiValidator = require("../../middleware/joi-validator")
const { BUILDER } = require("../../utilities/accessLevels")
@@ -23,55 +23,57 @@ function generateStepSchema(allowStepTypes) {
}).unknown(true)
}
-// prettier-ignore
-const workflowValidator = joiValidator.body(Joi.object({
- live: Joi.bool(),
- id: Joi.string().required(),
- rev: Joi.string().required(),
- name: Joi.string().required(),
- type: Joi.string().valid("workflow").required(),
- definition: Joi.object({
- steps: Joi.array().required().items(generateStepSchema(["ACTION", "LOGIC"])),
- trigger: generateStepSchema(["TRIGGER"]).required(),
- }).required().unknown(true),
-}).unknown(true))
+function generateValidator(existing = false) {
+ // prettier-ignore
+ return joiValidator.body(Joi.object({
+ live: Joi.bool(),
+ _id: existing ? Joi.string().required() : Joi.string(),
+ _rev: existing ? Joi.string().required() : Joi.string(),
+ name: Joi.string().required(),
+ type: Joi.string().valid("automation").required(),
+ definition: Joi.object({
+ steps: Joi.array().required().items(generateStepSchema(["ACTION", "LOGIC"])),
+ trigger: generateStepSchema(["TRIGGER"]),
+ }).required().unknown(true),
+ }).unknown(true))
+}
router
.get(
- "/api/workflows/trigger/list",
+ "/api/automations/trigger/list",
authorized(BUILDER),
controller.getTriggerList
)
.get(
- "/api/workflows/action/list",
+ "/api/automations/action/list",
authorized(BUILDER),
controller.getActionList
)
.get(
- "/api/workflows/logic/list",
+ "/api/automations/logic/list",
authorized(BUILDER),
controller.getLogicList
)
.get(
- "/api/workflows/definitions/list",
+ "/api/automations/definitions/list",
authorized(BUILDER),
controller.getDefinitionList
)
- .get("/api/workflows", authorized(BUILDER), controller.fetch)
- .get("/api/workflows/:id", authorized(BUILDER), controller.find)
+ .get("/api/automations", authorized(BUILDER), controller.fetch)
+ .get("/api/automations/:id", authorized(BUILDER), controller.find)
.put(
- "/api/workflows",
+ "/api/automations",
authorized(BUILDER),
- workflowValidator,
+ generateValidator(true),
controller.update
)
.post(
- "/api/workflows",
+ "/api/automations",
authorized(BUILDER),
- workflowValidator,
+ generateValidator(false),
controller.create
)
- .post("/api/workflows/:id/trigger", controller.trigger)
- .delete("/api/workflows/:id/:rev", authorized(BUILDER), controller.destroy)
+ .post("/api/automations/:id/trigger", controller.trigger)
+ .delete("/api/automations/:id/:rev", authorized(BUILDER), controller.destroy)
module.exports = router
diff --git a/packages/server/src/api/routes/index.js b/packages/server/src/api/routes/index.js
index 36dcdcd630..a2b8d3bb6c 100644
--- a/packages/server/src/api/routes/index.js
+++ b/packages/server/src/api/routes/index.js
@@ -9,7 +9,7 @@ const recordRoutes = require("./record")
const viewRoutes = require("./view")
const staticRoutes = require("./static")
const componentRoutes = require("./component")
-const workflowRoutes = require("./workflow")
+const automationRoutes = require("./automation")
const accesslevelRoutes = require("./accesslevel")
const deployRoutes = require("./deploy")
const apiKeysRoutes = require("./apikeys")
@@ -27,7 +27,7 @@ module.exports = {
viewRoutes,
staticRoutes,
componentRoutes,
- workflowRoutes,
+ automationRoutes,
accesslevelRoutes,
apiKeysRoutes,
}
diff --git a/packages/server/src/api/routes/tests/automation.spec.js b/packages/server/src/api/routes/tests/automation.spec.js
new file mode 100644
index 0000000000..d7a7200f9a
--- /dev/null
+++ b/packages/server/src/api/routes/tests/automation.spec.js
@@ -0,0 +1,260 @@
+const {
+ createClientDatabase,
+ createApplication,
+ createInstance,
+ createModel,
+ getAllFromModel,
+ defaultHeaders,
+ supertest,
+ insertDocument,
+ destroyDocument,
+ builderEndpointShouldBlockNormalUsers
+} = require("./couchTestUtils")
+
+const { delay } = require("./testUtils")
+
+const MAX_RETRIES = 4
+const TEST_AUTOMATION = {
+ _id: "Test Automation",
+ name: "My Automation",
+ pageId: "123123123",
+ screenId: "kasdkfldsafkl",
+ live: true,
+ uiTree: {
+
+ },
+ definition: {
+ trigger: {},
+ steps: [
+ ],
+ },
+ type: "automation",
+}
+
+let ACTION_DEFINITIONS = {}
+let TRIGGER_DEFINITIONS = {}
+let LOGIC_DEFINITIONS = {}
+
+describe("/automations", () => {
+ let request
+ let server
+ let app
+ let instance
+ let automation
+ let automationId
+
+ beforeAll(async () => {
+ ({ request, server } = await supertest())
+ await createClientDatabase(request)
+ app = await createApplication(request)
+ })
+
+ beforeEach(async () => {
+ if (automation) await destroyDocument(automation.id)
+ instance = await createInstance(request, app._id)
+ })
+
+ afterAll(async () => {
+ server.close()
+ })
+
+ const createAutomation = async () => {
+ automation = await insertDocument(instance._id, {
+ type: "automation",
+ ...TEST_AUTOMATION
+ })
+ automation = { ...automation, ...TEST_AUTOMATION }
+ }
+
+ const triggerWorkflow = async (automationId) => {
+ return await request
+ .post(`/api/automations/${automationId}/trigger`)
+ .send({ name: "Test", description: "TEST" })
+ .set(defaultHeaders(app._id, instance._id))
+ .expect('Content-Type', /json/)
+ .expect(200)
+ }
+
+ describe("get definitions", () => {
+ it("returns a list of definitions for actions", async () => {
+ const res = await request
+ .get(`/api/automations/action/list`)
+ .set(defaultHeaders(app._id, instance._id))
+ .expect('Content-Type', /json/)
+ .expect(200)
+
+ expect(Object.keys(res.body).length).not.toEqual(0)
+ ACTION_DEFINITIONS = res.body
+ })
+
+ it("returns a list of definitions for triggers", async () => {
+ const res = await request
+ .get(`/api/automations/trigger/list`)
+ .set(defaultHeaders(app._id, instance._id))
+ .expect('Content-Type', /json/)
+ .expect(200)
+
+ expect(Object.keys(res.body).length).not.toEqual(0)
+ TRIGGER_DEFINITIONS = res.body
+ })
+
+ it("returns a list of definitions for actions", async () => {
+ const res = await request
+ .get(`/api/automations/logic/list`)
+ .set(defaultHeaders(app._id, instance._id))
+ .expect('Content-Type', /json/)
+ .expect(200)
+
+ expect(Object.keys(res.body).length).not.toEqual(0)
+ LOGIC_DEFINITIONS = res.body
+ })
+
+ it("returns all of the definitions in one", async () => {
+ const res = await request
+ .get(`/api/automations/definitions/list`)
+ .set(defaultHeaders(app._id, instance._id))
+ .expect('Content-Type', /json/)
+ .expect(200)
+
+ expect(Object.keys(res.body.action).length).toEqual(Object.keys(ACTION_DEFINITIONS).length)
+ expect(Object.keys(res.body.trigger).length).toEqual(Object.keys(TRIGGER_DEFINITIONS).length)
+ expect(Object.keys(res.body.logic).length).toEqual(Object.keys(LOGIC_DEFINITIONS).length)
+ })
+ })
+
+ describe("create", () => {
+ it("should setup the automation fully", () => {
+ let trigger = TRIGGER_DEFINITIONS["RECORD_SAVED"]
+ trigger.id = "wadiawdo34"
+ let saveAction = ACTION_DEFINITIONS["SAVE_RECORD"]
+ saveAction.inputs.record = {
+ name: "{{trigger.name}}",
+ description: "{{trigger.description}}"
+ }
+ saveAction.id = "awde444wk"
+
+ TEST_AUTOMATION.definition.steps.push(saveAction)
+ TEST_AUTOMATION.definition.trigger = trigger
+ })
+
+ it("returns a success message when the automation is successfully created", async () => {
+ const res = await request
+ .post(`/api/automations`)
+ .set(defaultHeaders(app._id, instance._id))
+ .send(TEST_AUTOMATION)
+ .expect('Content-Type', /json/)
+ .expect(200)
+
+ expect(res.body.message).toEqual("Automation created successfully")
+ expect(res.body.automation.name).toEqual("My Automation")
+ expect(res.body.automation._id).not.toEqual(null)
+ automationId = res.body.automation._id
+ })
+
+ it("should apply authorization to endpoint", async () => {
+ await builderEndpointShouldBlockNormalUsers({
+ request,
+ method: "POST",
+ url: `/api/automations`,
+ instanceId: instance._id,
+ appId: app._id,
+ body: TEST_AUTOMATION
+ })
+ })
+ })
+
+ describe("trigger", () => {
+ it("trigger the automation successfully", async () => {
+ let model = await createModel(request, app._id, instance._id)
+ TEST_AUTOMATION.definition.trigger.inputs.modelId = model._id
+ TEST_AUTOMATION.definition.steps[0].inputs.record.modelId = model._id
+ await createAutomation()
+ // this looks a bit mad but we don't actually have a way to wait for a response from the automation to
+ // know that it has finished all of its actions - this is currently the best way
+ // also when this runs in CI it is very temper-mental so for now trying to make run stable by repeating until it works
+ // TODO: update when workflow logs are a thing
+ for (let tries = 0; tries < MAX_RETRIES; tries++) {
+ const res = await triggerWorkflow(automation._id)
+ expect(res.body.message).toEqual(`Automation ${automation._id} has been triggered.`)
+ expect(res.body.automation.name).toEqual(TEST_AUTOMATION.name)
+ await delay(500)
+ let elements = await getAllFromModel(request, app._id, instance._id, model._id)
+ // don't test it unless there are values to test
+ if (elements.length === 1) {
+ expect(elements.length).toEqual(1)
+ expect(elements[0].name).toEqual("Test")
+ expect(elements[0].description).toEqual("TEST")
+ return
+ }
+ }
+ throw "Failed to find the records"
+ })
+ })
+
+ describe("update", () => {
+ it("updates a automations data", async () => {
+ await createAutomation()
+ automation._id = automation.id
+ automation._rev = automation.rev
+ automation.name = "Updated Name"
+ automation.type = "automation"
+
+ const res = await request
+ .put(`/api/automations`)
+ .set(defaultHeaders(app._id, instance._id))
+ .send(automation)
+ .expect('Content-Type', /json/)
+ .expect(200)
+
+ expect(res.body.message).toEqual("Automation Test Automation updated successfully.")
+ expect(res.body.automation.name).toEqual("Updated Name")
+ })
+ })
+
+ describe("fetch", () => {
+ it("return all the automations for an instance", async () => {
+ await createAutomation()
+ const res = await request
+ .get(`/api/automations`)
+ .set(defaultHeaders(app._id, instance._id))
+ .expect('Content-Type', /json/)
+ .expect(200)
+
+ expect(res.body[0]).toEqual(expect.objectContaining(TEST_AUTOMATION))
+ })
+
+ it("should apply authorization to endpoint", async () => {
+ await builderEndpointShouldBlockNormalUsers({
+ request,
+ method: "GET",
+ url: `/api/automations`,
+ instanceId: instance._id,
+ appId: app._id,
+ })
+ })
+ })
+
+ describe("destroy", () => {
+ it("deletes a automation by its ID", async () => {
+ await createAutomation()
+ const res = await request
+ .delete(`/api/automations/${automation.id}/${automation.rev}`)
+ .set(defaultHeaders(app._id, instance._id))
+ .expect('Content-Type', /json/)
+ .expect(200)
+
+ expect(res.body.id).toEqual(TEST_AUTOMATION._id)
+ })
+
+ it("should apply authorization to endpoint", async () => {
+ await createAutomation()
+ await builderEndpointShouldBlockNormalUsers({
+ request,
+ method: "DELETE",
+ url: `/api/automations/${automation.id}/${automation._rev}`,
+ instanceId: instance._id,
+ appId: app._id,
+ })
+ })
+ })
+})
diff --git a/packages/server/src/api/routes/tests/couchTestUtils.js b/packages/server/src/api/routes/tests/couchTestUtils.js
index 9183c76517..a22a2a427f 100644
--- a/packages/server/src/api/routes/tests/couchTestUtils.js
+++ b/packages/server/src/api/routes/tests/couchTestUtils.js
@@ -67,6 +67,13 @@ exports.createModel = async (request, appId, instanceId, model) => {
return res.body
}
+exports.getAllFromModel = async (request, appId, instanceId, modelId) => {
+ const res = await request
+ .get(`/api/${modelId}/records`)
+ .set(exports.defaultHeaders(appId, instanceId))
+ return res.body
+}
+
exports.createView = async (request, appId, instanceId, modelId, view) => {
view = view || {
map: "function(doc) { emit(doc[doc.key], doc._id); } ",
diff --git a/packages/server/src/api/routes/tests/testUtils.js b/packages/server/src/api/routes/tests/testUtils.js
new file mode 100644
index 0000000000..0e66b47c3d
--- /dev/null
+++ b/packages/server/src/api/routes/tests/testUtils.js
@@ -0,0 +1 @@
+module.exports.delay = ms => new Promise(resolve => setTimeout(resolve, ms))
diff --git a/packages/server/src/api/routes/tests/workflow.spec.js b/packages/server/src/api/routes/tests/workflow.spec.js
deleted file mode 100644
index 5991d06d9d..0000000000
--- a/packages/server/src/api/routes/tests/workflow.spec.js
+++ /dev/null
@@ -1,153 +0,0 @@
-const {
- createClientDatabase,
- createApplication,
- createInstance,
- defaultHeaders,
- supertest,
- insertDocument,
- destroyDocument,
- builderEndpointShouldBlockNormalUsers
-} = require("./couchTestUtils")
-
-const TEST_WORKFLOW = {
- _id: "Test Workflow",
- name: "My Workflow",
- pageId: "123123123",
- screenId: "kasdkfldsafkl",
- live: true,
- uiTree: {
-
- },
- definition: {
- triggers: [
-
- ],
- next: {
- stepId: "abc123",
- type: "SERVER",
- conditions: {
- }
- }
- }
-}
-
-describe("/workflows", () => {
- let request
- let server
- let app
- let instance
- let workflow
-
- beforeAll(async () => {
- ({ request, server } = await supertest())
- await createClientDatabase(request)
- app = await createApplication(request)
- })
-
- beforeEach(async () => {
- instance = await createInstance(request, app._id)
- if (workflow) await destroyDocument(workflow.id);
- })
-
- afterAll(async () => {
- server.close()
- })
-
- const createWorkflow = async () => {
- workflow = await insertDocument(instance._id, {
- type: "workflow",
- ...TEST_WORKFLOW
- });
- }
-
- describe("create", () => {
- it("returns a success message when the workflow is successfully created", async () => {
- const res = await request
- .post(`/api/workflows`)
- .set(defaultHeaders(app._id, instance._id))
- .send(TEST_WORKFLOW)
- .expect('Content-Type', /json/)
- .expect(200)
-
- expect(res.body.message).toEqual("Workflow created successfully");
- expect(res.body.workflow.name).toEqual("My Workflow");
- })
-
- it("should apply authorization to endpoint", async () => {
- await builderEndpointShouldBlockNormalUsers({
- request,
- method: "POST",
- url: `/api/workflows`,
- instanceId: instance._id,
- appId: app._id,
- body: TEST_WORKFLOW
- })
- })
- })
-
- describe("update", () => {
- it("updates a workflows data", async () => {
- await createWorkflow();
- workflow._id = workflow.id
- workflow._rev = workflow.rev
- workflow.name = "Updated Name";
-
- const res = await request
- .put(`/api/workflows`)
- .set(defaultHeaders(app._id, instance._id))
- .send(workflow)
- .expect('Content-Type', /json/)
- .expect(200)
-
- expect(res.body.message).toEqual("Workflow Test Workflow updated successfully.");
- expect(res.body.workflow.name).toEqual("Updated Name");
- })
- })
-
- describe("fetch", () => {
- it("return all the workflows for an instance", async () => {
- await createWorkflow();
- const res = await request
- .get(`/api/workflows`)
- .set(defaultHeaders(app._id, instance._id))
- .expect('Content-Type', /json/)
- .expect(200)
-
- expect(res.body[0]).toEqual(expect.objectContaining(TEST_WORKFLOW));
- })
-
- it("should apply authorization to endpoint", async () => {
- await builderEndpointShouldBlockNormalUsers({
- request,
- method: "GET",
- url: `/api/workflows`,
- instanceId: instance._id,
- appId: app._id,
- })
- })
- })
-
- describe("destroy", () => {
- it("deletes a workflow by its ID", async () => {
- await createWorkflow();
- const res = await request
- .delete(`/api/workflows/${workflow.id}/${workflow.rev}`)
- .set(defaultHeaders(app._id, instance._id))
- .expect('Content-Type', /json/)
- .expect(200)
-
- expect(res.body.id).toEqual(TEST_WORKFLOW._id);
- })
-
- it("should apply authorization to endpoint", async () => {
- await createWorkflow();
- await builderEndpointShouldBlockNormalUsers({
- request,
- method: "DELETE",
- url: `/api/workflows/${workflow.id}/${workflow._rev}`,
- instanceId: instance._id,
- appId: app._id,
- })
- })
- })
-});
diff --git a/packages/server/src/app.js b/packages/server/src/app.js
index 5f720ad865..7560c9cfa4 100644
--- a/packages/server/src/app.js
+++ b/packages/server/src/app.js
@@ -6,7 +6,7 @@ const http = require("http")
const api = require("./api")
const env = require("./environment")
const eventEmitter = require("./events")
-const workflows = require("./workflows/index")
+const automations = require("./automations/index")
const Sentry = require("@sentry/node")
const app = new Koa()
@@ -50,5 +50,5 @@ process.on("SIGINT", () => process.exit(1))
module.exports = server.listen(env.PORT || 4001, () => {
console.log(`Budibase running on ${JSON.stringify(server.address())}`)
- workflows.init()
+ automations.init()
})
diff --git a/packages/server/src/automations/actions.js b/packages/server/src/automations/actions.js
new file mode 100644
index 0000000000..b626919ff6
--- /dev/null
+++ b/packages/server/src/automations/actions.js
@@ -0,0 +1,91 @@
+const sendEmail = require("./steps/sendEmail")
+const saveRecord = require("./steps/saveRecord")
+const updateRecord = require("./steps/updateRecord")
+const deleteRecord = require("./steps/deleteRecord")
+const createUser = require("./steps/createUser")
+const environment = require("../environment")
+const download = require("download")
+const fetch = require("node-fetch")
+const path = require("path")
+const os = require("os")
+const fs = require("fs")
+const Sentry = require("@sentry/node")
+
+const DEFAULT_BUCKET =
+ "https://prod-budi-automations.s3-eu-west-1.amazonaws.com"
+const DEFAULT_DIRECTORY = ".budibase-automations"
+const AUTOMATION_MANIFEST = "manifest.json"
+const BUILTIN_ACTIONS = {
+ SEND_EMAIL: sendEmail.run,
+ SAVE_RECORD: saveRecord.run,
+ UPDATE_RECORD: updateRecord.run,
+ DELETE_RECORD: deleteRecord.run,
+ CREATE_USER: createUser.run,
+}
+const BUILTIN_DEFINITIONS = {
+ SEND_EMAIL: sendEmail.definition,
+ SAVE_RECORD: saveRecord.definition,
+ UPDATE_RECORD: updateRecord.definition,
+ DELETE_RECORD: deleteRecord.definition,
+ CREATE_USER: createUser.definition,
+}
+
+let AUTOMATION_BUCKET = environment.AUTOMATION_BUCKET
+let AUTOMATION_DIRECTORY = environment.AUTOMATION_DIRECTORY
+let MANIFEST = null
+
+function buildBundleName(pkgName, version) {
+ return `${pkgName}@${version}.min.js`
+}
+
+async function downloadPackage(name, version, bundleName) {
+ await download(
+ `${AUTOMATION_BUCKET}/${name}/${version}/${bundleName}`,
+ AUTOMATION_DIRECTORY
+ )
+ return require(path.join(AUTOMATION_DIRECTORY, bundleName))
+}
+
+module.exports.getAction = async function(actionName) {
+ if (BUILTIN_ACTIONS[actionName] != null) {
+ return BUILTIN_ACTIONS[actionName]
+ }
+ // env setup to get async packages
+ if (!MANIFEST || !MANIFEST.packages || !MANIFEST.packages[actionName]) {
+ return null
+ }
+ const pkg = MANIFEST.packages[actionName]
+ const bundleName = buildBundleName(pkg.stepId, pkg.version)
+ try {
+ return require(path.join(AUTOMATION_DIRECTORY, bundleName))
+ } catch (err) {
+ return downloadPackage(pkg.stepId, pkg.version, bundleName)
+ }
+}
+
+module.exports.init = async function() {
+ // set defaults
+ if (!AUTOMATION_DIRECTORY) {
+ AUTOMATION_DIRECTORY = path.join(os.homedir(), DEFAULT_DIRECTORY)
+ }
+ if (!AUTOMATION_BUCKET) {
+ AUTOMATION_BUCKET = DEFAULT_BUCKET
+ }
+ if (!fs.existsSync(AUTOMATION_DIRECTORY)) {
+ fs.mkdirSync(AUTOMATION_DIRECTORY, { recursive: true })
+ }
+ // env setup to get async packages
+ try {
+ let response = await fetch(`${AUTOMATION_BUCKET}/${AUTOMATION_MANIFEST}`)
+ MANIFEST = await response.json()
+ module.exports.DEFINITIONS =
+ MANIFEST && MANIFEST.packages
+ ? Object.assign(MANIFEST.packages, BUILTIN_DEFINITIONS)
+ : BUILTIN_DEFINITIONS
+ } catch (err) {
+ Sentry.captureException(err)
+ }
+}
+
+module.exports.DEFINITIONS = BUILTIN_DEFINITIONS
+module.exports.BUILTIN_DEFINITIONS = BUILTIN_DEFINITIONS
diff --git a/packages/server/src/automations/automationUtils.js b/packages/server/src/automations/automationUtils.js
new file mode 100644
index 0000000000..507cbf8542
--- /dev/null
+++ b/packages/server/src/automations/automationUtils.js
@@ -0,0 +1,116 @@
+const CouchDB = require("../db")
+
+/**
+ * When running mustache statements to execute on the context of the automation it possible user's may input mustache
+ * in a few different forms, some of which are invalid but are logically valid. An example of this would be the mustache
+ * statement "{{steps[0].revision}}" here it is obvious the user is attempting to access an array or object using array
+ * like operators. These are not supported by Mustache and therefore the statement will fail. This function will clean up
+ * the mustache statement so it instead reads as "{{steps.0.revision}}" which is valid and will work. It may also be expanded
+ * to include any other mustache statement cleanup that has been deemed necessary for the system.
+ *
+ * @param {string} string The string which *may* contain mustache statements, it is OK if it does not contain any.
+ * @returns {string} The string that was input with cleaned up mustache statements as required.
+ */
+module.exports.cleanMustache = string => {
+ let charToReplace = {
+ "[": ".",
+ "]": "",
+ }
+ let regex = new RegExp(/{{[^}}]*}}/g)
+ let matches = string.match(regex)
+ if (matches == null) {
+ return string
+ }
+ for (let match of matches) {
+ let baseIdx = string.indexOf(match)
+ for (let key of Object.keys(charToReplace)) {
+ let idxChar = match.indexOf(key)
+ if (idxChar !== -1) {
+ string =
+ string.slice(baseIdx, baseIdx + idxChar) +
+ charToReplace[key] +
+ string.slice(baseIdx + idxChar + 1)
+ }
+ }
+ }
+ return string
+}
+
+/**
+ * When values are input to the system generally they will be of type string as this is required for mustache. This can
+ * generate some odd scenarios as the Schema of the automation requires a number but the builder might supply a string
+ * with mustache syntax to get the number from the rest of the context. To support this the server has to make sure that
+ * the post mustache statement can be cast into the correct type, this function does this for numbers and booleans.
+ *
+ * @param {object} inputs An object of inputs, please note this will not recurse down into any objects within, it simply
+ * cleanses the top level inputs, however it can be used by recursively calling it deeper into the object structures if
+ * the schema is known.
+ * @param {object} schema The defined schema of the inputs, in the form of JSON schema. The schema definition of an
+ * automation is the likely use case of this, however validate.js syntax can be converted closely enough to use this by
+ * wrapping the schema properties in a top level "properties" object.
+ * @returns {object} The inputs object which has had all the various types supported by this function converted to their
+ * primitive types.
+ */
+module.exports.cleanInputValues = (inputs, schema) => {
+ if (schema == null) {
+ return inputs
+ }
+ for (let inputKey of Object.keys(inputs)) {
+ let input = inputs[inputKey]
+ if (typeof input !== "string") {
+ continue
+ }
+ let propSchema = schema.properties[inputKey]
+ if (!propSchema) {
+ continue
+ }
+ if (propSchema.type === "boolean") {
+ let lcInput = input.toLowerCase()
+ if (lcInput === "true") {
+ inputs[inputKey] = true
+ }
+ if (lcInput === "false") {
+ inputs[inputKey] = false
+ }
+ }
+ if (propSchema.type === "number") {
+ let floatInput = parseFloat(input)
+ if (!isNaN(floatInput)) {
+ inputs[inputKey] = floatInput
+ }
+ }
+ }
+ return inputs
+}
+
+/**
+ * Given a record input like a save or update record we need to clean the inputs against a schema that is not part of
+ * the automation but is instead part of the Table/Model. This function will get the model schema and use it to instead
+ * perform the cleanInputValues function on the input record.
+ *
+ * @param {string} instanceId The instance which the Table/Model is contained under.
+ * @param {string} modelId The ID of the Table/Model which the schema is to be retrieved for.
+ * @param {object} record The input record structure which requires clean-up after having been through mustache statements.
+ * @returns {Promise