1
0
Fork 0
mirror of synced 2024-09-15 08:47:37 +12:00
This commit is contained in:
Martin McKeaveney 2022-06-07 16:44:04 +01:00
commit 75c599ce70
101 changed files with 4330 additions and 3934 deletions

View file

@ -72,3 +72,56 @@ jobs:
env: env:
DOCKER_USER: ${{ secrets.DOCKER_USERNAME }} DOCKER_USER: ${{ secrets.DOCKER_USERNAME }}
DOCKER_PASSWORD: ${{ secrets.DOCKER_API_KEY }} DOCKER_PASSWORD: ${{ secrets.DOCKER_API_KEY }}
- name: Get the latest budibase release version
id: version
run: |
release_version=$(cat lerna.json | jq -r '.version')
echo "RELEASE_VERSION=$release_version" >> $GITHUB_ENV
- name: Tag and release Proxy service docker image
run: |
docker login -u $DOCKER_USER -p $DOCKER_PASSWORD
yarn build:docker:proxy:release
docker tag proxy-service budibase/proxy:$RELEASE_TAG
docker push budibase/proxy:$RELEASE_TAG
env:
DOCKER_USER: ${{ secrets.DOCKER_USERNAME }}
DOCKER_PASSWORD: ${{ secrets.DOCKER_API_KEY }}
RELEASE_TAG: k8s-release
- name: Pull values.yaml from budibase-infra
run: |
curl -H "Authorization: token ${{ secrets.GH_PERSONAL_TOKEN }}" \
-H 'Accept: application/vnd.github.v3.raw' \
-o values.release.yaml \
-L https://api.github.com/repos/budibase/budibase-infra/contents/kubernetes/budibase-release/values.yaml
wc -l values.release.yaml
- name: Deploy to Release Environment
uses: glopezep/helm@v1.7.1
with:
release: budibase-release
namespace: budibase
chart: charts/budibase
token: ${{ github.token }}
helm: helm3
values: |
globals:
appVersion: develop
ingress:
enabled: true
nginx: true
value-files: >-
[
"values.release.yaml"
]
env:
KUBECONFIG_FILE: '${{ secrets.RELEASE_KUBECONFIG }}'
- name: Discord Webhook Action
uses: tsickert/discord-webhook@v4.0.0
with:
webhook-url: ${{ secrets.PROD_DEPLOY_WEBHOOK_URL }}
content: "Release Env Deployment Complete: ${{ env.RELEASE_VERSION }} deployed to Budibase Release Env."
embed-title: ${{ env.RELEASE_VERSION }}

View file

@ -11,7 +11,7 @@ sources:
- https://github.com/Budibase/budibase - https://github.com/Budibase/budibase
- https://budibase.com - https://budibase.com
type: application type: application
version: 0.2.9 version: 0.2.10
appVersion: 1.0.48 appVersion: 1.0.48
dependencies: dependencies:
- name: couchdb - name: couchdb

View file

@ -78,6 +78,10 @@ spec:
value: {{ .Values.services.objectStore.url }} value: {{ .Values.services.objectStore.url }}
- name: PORT - name: PORT
value: {{ .Values.services.apps.port | quote }} value: {{ .Values.services.apps.port | quote }}
{{ if .Values.services.worker.publicApiRateLimitPerSecond }}
- name: API_REQ_LIMIT_PER_SEC
value: {{ .Values.globals.apps.publicApiRateLimitPerSecond | quote }}
{{ end }}
- name: MULTI_TENANCY - name: MULTI_TENANCY
value: {{ .Values.globals.multiTenancy | quote }} value: {{ .Values.globals.multiTenancy | quote }}
- name: LOG_LEVEL - name: LOG_LEVEL
@ -119,6 +123,12 @@ spec:
image: budibase/apps:{{ .Values.globals.appVersion }} image: budibase/apps:{{ .Values.globals.appVersion }}
imagePullPolicy: Always imagePullPolicy: Always
livenessProbe:
httpGet:
path: /health
port: {{ .Values.services.apps.port }}
initialDelaySeconds: 5
periodSeconds: 5
name: bbapps name: bbapps
ports: ports:
- containerPort: {{ .Values.services.apps.port }} - containerPort: {{ .Values.services.apps.port }}

View file

@ -119,6 +119,12 @@ spec:
value: {{ .Values.globals.google.secret | quote }} value: {{ .Values.globals.google.secret | quote }}
image: budibase/worker:{{ .Values.globals.appVersion }} image: budibase/worker:{{ .Values.globals.appVersion }}
imagePullPolicy: Always imagePullPolicy: Always
livenessProbe:
httpGet:
path: /health
port: {{ .Values.services.worker.port }}
initialDelaySeconds: 5
periodSeconds: 5
name: bbworker name: bbworker
ports: ports:
- containerPort: {{ .Values.services.worker.port }} - containerPort: {{ .Values.services.worker.port }}

View file

@ -1,5 +1,5 @@
{ {
"version": "1.0.178-alpha.0", "version": "1.0.192-alpha.5",
"npmClient": "yarn", "npmClient": "yarn",
"packages": [ "packages": [
"packages/*" "packages/*"

View file

@ -55,6 +55,7 @@
"build:docker:proxy": "docker build hosting/proxy -t proxy-service", "build:docker:proxy": "docker build hosting/proxy -t proxy-service",
"build:docker:proxy:compose": "node scripts/proxy/generateProxyConfig compose && npm run build:docker:proxy", "build:docker:proxy:compose": "node scripts/proxy/generateProxyConfig compose && npm run build:docker:proxy",
"build:docker:proxy:preprod": "node scripts/proxy/generateProxyConfig preprod && npm run build:docker:proxy", "build:docker:proxy:preprod": "node scripts/proxy/generateProxyConfig preprod && npm run build:docker:proxy",
"build:docker:proxy:release": "node scripts/proxy/generateProxyConfig release && npm run build:docker:proxy",
"build:docker:proxy:prod": "node scripts/proxy/generateProxyConfig prod && npm run build:docker:proxy", "build:docker:proxy:prod": "node scripts/proxy/generateProxyConfig prod && npm run build:docker:proxy",
"build:docker:selfhost": "lerna run build:docker && cd hosting/scripts/linux/ && ./release-to-docker-hub.sh latest && cd -", "build:docker:selfhost": "lerna run build:docker && cd hosting/scripts/linux/ && ./release-to-docker-hub.sh latest && cd -",
"build:docker:develop": "node scripts/pinVersions && lerna run build:docker && npm run build:docker:proxy:compose && cd hosting/scripts/linux/ && ./release-to-docker-hub.sh develop && cd -", "build:docker:develop": "node scripts/pinVersions && lerna run build:docker && npm run build:docker:proxy:compose && cd hosting/scripts/linux/ && ./release-to-docker-hub.sh develop && cd -",

View file

@ -0,0 +1 @@
module.exports = require("./src/logging")

View file

@ -1,6 +1,6 @@
{ {
"name": "@budibase/backend-core", "name": "@budibase/backend-core",
"version": "1.0.178-alpha.0", "version": "1.0.192-alpha.5",
"description": "Budibase backend core libraries used in server and worker", "description": "Budibase backend core libraries used in server and worker",
"main": "src/index.js", "main": "src/index.js",
"author": "Budibase", "author": "Budibase",
@ -12,7 +12,7 @@
"dependencies": { "dependencies": {
"@techpass/passport-openidconnect": "^0.3.0", "@techpass/passport-openidconnect": "^0.3.0",
"aws-sdk": "^2.901.0", "aws-sdk": "^2.901.0",
"bcryptjs": "^2.4.3", "bcrypt": "^5.0.1",
"dotenv": "^16.0.1", "dotenv": "^16.0.1",
"emitter-listener": "^1.1.2", "emitter-listener": "^1.1.2",
"ioredis": "^4.27.1", "ioredis": "^4.27.1",

View file

@ -1,4 +1,4 @@
const bcrypt = require("bcryptjs") const bcrypt = require("bcrypt")
const env = require("./environment") const env = require("./environment")
const { v4 } = require("uuid") const { v4 } = require("uuid")

View file

@ -0,0 +1,16 @@
const NonErrors = ["AccountError"]
function isSuppressed(e) {
return e && e["suppressAlert"]
}
module.exports.logAlert = (message, e = null) => {
if (e && NonErrors.includes(e.name) && isSuppressed(e)) {
return
}
let errorJson = ""
if (e) {
errorJson = ": " + JSON.stringify(e, Object.getOwnPropertyNames(e))
}
console.error(`bb-alert: ${message} ${errorJson}`)
}

View file

@ -23,7 +23,7 @@ function connectionError(timeout, err) {
if (CLOSED) { if (CLOSED) {
return return
} }
CLIENT.end() CLIENT.disconnect()
CLOSED = true CLOSED = true
// always clear this on error // always clear this on error
clearTimeout(timeout) clearTimeout(timeout)

View file

@ -15,29 +15,33 @@ function makeSessionID(userId, sessionId) {
} }
async function invalidateSessions(userId, sessionIds = null) { async function invalidateSessions(userId, sessionIds = null) {
let sessions = [] try {
let sessions = []
// If no sessionIds, get all the sessions for the user // If no sessionIds, get all the sessions for the user
if (!sessionIds) { if (!sessionIds) {
sessions = await getSessionsForUser(userId) sessions = await getSessionsForUser(userId)
sessions.forEach( sessions.forEach(
session => session =>
(session.key = makeSessionID(session.userId, session.sessionId)) (session.key = makeSessionID(session.userId, session.sessionId))
) )
} else { } else {
// use the passed array of sessionIds // use the passed array of sessionIds
sessions = Array.isArray(sessionIds) ? sessionIds : [sessionIds] sessions = Array.isArray(sessionIds) ? sessionIds : [sessionIds]
sessions = sessions.map(sessionId => ({ sessions = sessions.map(sessionId => ({
key: makeSessionID(userId, sessionId), key: makeSessionID(userId, sessionId),
})) }))
} }
const client = await redis.getSessionClient() const client = await redis.getSessionClient()
const promises = [] const promises = []
for (let session of sessions) { for (let session of sessions) {
promises.push(client.delete(session.key)) promises.push(client.delete(session.key))
}
await Promise.all(promises)
} catch (err) {
console.error(`Error invalidating sessions: ${err}`)
} }
await Promise.all(promises)
} }
exports.createASession = async (userId, session) => { exports.createASession = async (userId, session) => {
@ -76,6 +80,7 @@ exports.getSession = async (userId, sessionId) => {
return client.get(makeSessionID(userId, sessionId)) return client.get(makeSessionID(userId, sessionId))
} catch (err) { } catch (err) {
// if can't get session don't error, just don't return anything // if can't get session don't error, just don't return anything
console.error(err)
return null return null
} }
} }

View file

@ -497,6 +497,21 @@
"@types/yargs" "^15.0.0" "@types/yargs" "^15.0.0"
chalk "^4.0.0" chalk "^4.0.0"
"@mapbox/node-pre-gyp@^1.0.0":
version "1.0.9"
resolved "https://registry.yarnpkg.com/@mapbox/node-pre-gyp/-/node-pre-gyp-1.0.9.tgz#09a8781a3a036151cdebbe8719d6f8b25d4058bc"
integrity sha512-aDF3S3rK9Q2gey/WAttUlISduDItz5BU3306M9Eyv6/oS40aMprnopshtlKTykxRNIBEZuRMaZAnbrQ4QtKGyw==
dependencies:
detect-libc "^2.0.0"
https-proxy-agent "^5.0.0"
make-dir "^3.1.0"
node-fetch "^2.6.7"
nopt "^5.0.0"
npmlog "^5.0.1"
rimraf "^3.0.2"
semver "^7.3.5"
tar "^6.1.11"
"@sinonjs/commons@^1.7.0": "@sinonjs/commons@^1.7.0":
version "1.8.3" version "1.8.3"
resolved "https://registry.yarnpkg.com/@sinonjs/commons/-/commons-1.8.3.tgz#3802ddd21a50a949b6721ddd72da36e67e7f1b2d" resolved "https://registry.yarnpkg.com/@sinonjs/commons/-/commons-1.8.3.tgz#3802ddd21a50a949b6721ddd72da36e67e7f1b2d"
@ -623,6 +638,11 @@ abab@^2.0.3, abab@^2.0.5:
resolved "https://registry.yarnpkg.com/abab/-/abab-2.0.5.tgz#c0b678fb32d60fc1219c784d6a826fe385aeb79a" resolved "https://registry.yarnpkg.com/abab/-/abab-2.0.5.tgz#c0b678fb32d60fc1219c784d6a826fe385aeb79a"
integrity sha512-9IK9EadsbHo6jLWIpxpR6pL0sazTXV6+SQv25ZB+F7Bj9mJNaOc4nCRabwd5M/JwmUa8idz6Eci6eKfJryPs6Q== integrity sha512-9IK9EadsbHo6jLWIpxpR6pL0sazTXV6+SQv25ZB+F7Bj9mJNaOc4nCRabwd5M/JwmUa8idz6Eci6eKfJryPs6Q==
abbrev@1:
version "1.1.1"
resolved "https://registry.yarnpkg.com/abbrev/-/abbrev-1.1.1.tgz#f8f2c887ad10bf67f634f005b6987fed3179aac8"
integrity sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==
abort-controller@3.0.0: abort-controller@3.0.0:
version "3.0.0" version "3.0.0"
resolved "https://registry.yarnpkg.com/abort-controller/-/abort-controller-3.0.0.tgz#eaf54d53b62bae4138e809ca225c8439a6efb392" resolved "https://registry.yarnpkg.com/abort-controller/-/abort-controller-3.0.0.tgz#eaf54d53b62bae4138e809ca225c8439a6efb392"
@ -738,6 +758,19 @@ anymatch@^3.0.3:
normalize-path "^3.0.0" normalize-path "^3.0.0"
picomatch "^2.0.4" picomatch "^2.0.4"
"aproba@^1.0.3 || ^2.0.0":
version "2.0.0"
resolved "https://registry.yarnpkg.com/aproba/-/aproba-2.0.0.tgz#52520b8ae5b569215b354efc0caa3fe1e45a8adc"
integrity sha512-lYe4Gx7QT+MKGbDsA+Z+he/Wtef0BiwDOlK/XkBrdfsh9J/jPPXbX0tE9x9cl27Tmu5gg3QUbUrQYa/y+KOHPQ==
are-we-there-yet@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/are-we-there-yet/-/are-we-there-yet-2.0.0.tgz#372e0e7bd279d8e94c653aaa1f67200884bf3e1c"
integrity sha512-Ci/qENmwHnsYo9xKIcUJN5LeDKdJ6R1Z1j9V/J5wyq8nh/mYPEpIKJbBZXtZjG04HiK7zV/p6Vs9952MrMeUIw==
dependencies:
delegates "^1.0.0"
readable-stream "^3.6.0"
argparse@^1.0.7: argparse@^1.0.7:
version "1.0.10" version "1.0.10"
resolved "https://registry.yarnpkg.com/argparse/-/argparse-1.0.10.tgz#bcd6791ea5ae09725e17e5ad988134cd40b3d911" resolved "https://registry.yarnpkg.com/argparse/-/argparse-1.0.10.tgz#bcd6791ea5ae09725e17e5ad988134cd40b3d911"
@ -958,10 +991,13 @@ bcrypt-pbkdf@^1.0.0:
dependencies: dependencies:
tweetnacl "^0.14.3" tweetnacl "^0.14.3"
bcryptjs@^2.4.3: bcrypt@^5.0.1:
version "2.4.3" version "5.0.1"
resolved "https://registry.yarnpkg.com/bcryptjs/-/bcryptjs-2.4.3.tgz#9ab5627b93e60621ff7cdac5da9733027df1d0cb" resolved "https://registry.yarnpkg.com/bcrypt/-/bcrypt-5.0.1.tgz#f1a2c20f208e2ccdceea4433df0c8b2c54ecdf71"
integrity sha1-mrVie5PmBiH/fNrF2pczAn3x0Ms= integrity sha512-9BTgmrhZM2t1bNuDtrtIMVSmmxZBrJ71n8Wg+YgdjHuIWYF7SjjmCPZFB+/5i/o/PIeRpwVJR3P+NrpIItUjqw==
dependencies:
"@mapbox/node-pre-gyp" "^1.0.0"
node-addon-api "^3.1.0"
bl@^4.0.3: bl@^4.0.3:
version "4.1.0" version "4.1.0"
@ -1144,6 +1180,11 @@ chownr@^1.1.1:
resolved "https://registry.yarnpkg.com/chownr/-/chownr-1.1.4.tgz#6fc9d7b42d32a583596337666e7d08084da2cc6b" resolved "https://registry.yarnpkg.com/chownr/-/chownr-1.1.4.tgz#6fc9d7b42d32a583596337666e7d08084da2cc6b"
integrity sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg== integrity sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==
chownr@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/chownr/-/chownr-2.0.0.tgz#15bfbe53d2eab4cf70f18a8cd68ebe5b3cb1dece"
integrity sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==
ci-info@^2.0.0: ci-info@^2.0.0:
version "2.0.0" version "2.0.0"
resolved "https://registry.yarnpkg.com/ci-info/-/ci-info-2.0.0.tgz#67a9e964be31a51e15e5010d58e6f12834002f46" resolved "https://registry.yarnpkg.com/ci-info/-/ci-info-2.0.0.tgz#67a9e964be31a51e15e5010d58e6f12834002f46"
@ -1225,6 +1266,11 @@ color-name@~1.1.4:
resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.4.tgz#c2a09a87acbde69543de6f63fa3995c826c536a2" resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.4.tgz#c2a09a87acbde69543de6f63fa3995c826c536a2"
integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA== integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==
color-support@^1.1.2:
version "1.1.3"
resolved "https://registry.yarnpkg.com/color-support/-/color-support-1.1.3.tgz#93834379a1cc9a0c61f82f52f0d04322251bd5a2"
integrity sha512-qiBjkpbMLO/HL68y+lh4q0/O1MZFj2RX6X/KmMa3+gJD3z+WwI1ZzDHysvqHGS3mP6mznPckpXmw1nI9cJjyRg==
combined-stream@^1.0.5, combined-stream@~1.0.5: combined-stream@^1.0.5, combined-stream@~1.0.5:
version "1.0.5" version "1.0.5"
resolved "https://registry.yarnpkg.com/combined-stream/-/combined-stream-1.0.5.tgz#938370a57b4a51dea2c77c15d5c5fdf895164009" resolved "https://registry.yarnpkg.com/combined-stream/-/combined-stream-1.0.5.tgz#938370a57b4a51dea2c77c15d5c5fdf895164009"
@ -1254,6 +1300,11 @@ concat-map@0.0.1:
resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b" resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b"
integrity sha1-2Klr13/Wjfd5OnMDajug1UBdR3s= integrity sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=
console-control-strings@^1.0.0, console-control-strings@^1.1.0:
version "1.1.0"
resolved "https://registry.yarnpkg.com/console-control-strings/-/console-control-strings-1.1.0.tgz#3d7cf4464db6446ea644bf4b39507f9851008e8e"
integrity sha1-PXz0Rk22RG6mRL9LOVB/mFEAjo4=
convert-source-map@^1.4.0, convert-source-map@^1.6.0, convert-source-map@^1.7.0: convert-source-map@^1.4.0, convert-source-map@^1.6.0, convert-source-map@^1.7.0:
version "1.8.0" version "1.8.0"
resolved "https://registry.yarnpkg.com/convert-source-map/-/convert-source-map-1.8.0.tgz#f3373c32d21b4d780dd8004514684fb791ca4369" resolved "https://registry.yarnpkg.com/convert-source-map/-/convert-source-map-1.8.0.tgz#f3373c32d21b4d780dd8004514684fb791ca4369"
@ -1410,11 +1461,21 @@ delayed-stream@~1.0.0:
resolved "https://registry.yarnpkg.com/delayed-stream/-/delayed-stream-1.0.0.tgz#df3ae199acadfb7d440aaae0b29e2272b24ec619" resolved "https://registry.yarnpkg.com/delayed-stream/-/delayed-stream-1.0.0.tgz#df3ae199acadfb7d440aaae0b29e2272b24ec619"
integrity sha1-3zrhmayt+31ECqrgsp4icrJOxhk= integrity sha1-3zrhmayt+31ECqrgsp4icrJOxhk=
delegates@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/delegates/-/delegates-1.0.0.tgz#84c6e159b81904fdca59a0ef44cd870d31250f9a"
integrity sha1-hMbhWbgZBP3KWaDvRM2HDTElD5o=
denque@^1.1.0: denque@^1.1.0:
version "1.5.1" version "1.5.1"
resolved "https://registry.yarnpkg.com/denque/-/denque-1.5.1.tgz#07f670e29c9a78f8faecb2566a1e2c11929c5cbf" resolved "https://registry.yarnpkg.com/denque/-/denque-1.5.1.tgz#07f670e29c9a78f8faecb2566a1e2c11929c5cbf"
integrity sha512-XwE+iZ4D6ZUB7mfYRMb5wByE8L74HCn30FBN7sWnXksWc1LO1bPDl67pBR9o/kC4z/xSNAwkMYcGgqDV3BE3Hw== integrity sha512-XwE+iZ4D6ZUB7mfYRMb5wByE8L74HCn30FBN7sWnXksWc1LO1bPDl67pBR9o/kC4z/xSNAwkMYcGgqDV3BE3Hw==
detect-libc@^2.0.0:
version "2.0.1"
resolved "https://registry.yarnpkg.com/detect-libc/-/detect-libc-2.0.1.tgz#e1897aa88fa6ad197862937fbc0441ef352ee0cd"
integrity sha512-463v3ZeIrcWtdgIg6vI6XUncguvr2TnGl4SzDXinkt9mSLpBJKXT3mW6xT3VQdDN11+WVs29pgvivTc4Lp8v+w==
detect-newline@^3.0.0: detect-newline@^3.0.0:
version "3.1.0" version "3.1.0"
resolved "https://registry.yarnpkg.com/detect-newline/-/detect-newline-3.1.0.tgz#576f5dfc63ae1a192ff192d8ad3af6308991b651" resolved "https://registry.yarnpkg.com/detect-newline/-/detect-newline-3.1.0.tgz#576f5dfc63ae1a192ff192d8ad3af6308991b651"
@ -1812,6 +1873,13 @@ fs-constants@^1.0.0:
resolved "https://registry.yarnpkg.com/fs-constants/-/fs-constants-1.0.0.tgz#6be0de9be998ce16af8afc24497b9ee9b7ccd9ad" resolved "https://registry.yarnpkg.com/fs-constants/-/fs-constants-1.0.0.tgz#6be0de9be998ce16af8afc24497b9ee9b7ccd9ad"
integrity sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow== integrity sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==
fs-minipass@^2.0.0:
version "2.1.0"
resolved "https://registry.yarnpkg.com/fs-minipass/-/fs-minipass-2.1.0.tgz#7f5036fdbf12c63c169190cbe4199c852271f9fb"
integrity sha512-V/JgOLFCS+R6Vcq0slCuaeWEdNC3ouDlJMNIsacH2VtALiu9mV4LPrHc5cDl8k5aw6J8jwgWWpiTo5RYhmIzvg==
dependencies:
minipass "^3.0.0"
fs.realpath@^1.0.0: fs.realpath@^1.0.0:
version "1.0.0" version "1.0.0"
resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f" resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f"
@ -1832,6 +1900,21 @@ functional-red-black-tree@^1.0.1:
resolved "https://registry.yarnpkg.com/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz#1b0ab3bd553b2a0d6399d29c0e3ea0b252078327" resolved "https://registry.yarnpkg.com/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz#1b0ab3bd553b2a0d6399d29c0e3ea0b252078327"
integrity sha1-GwqzvVU7Kg1jmdKcDj6gslIHgyc= integrity sha1-GwqzvVU7Kg1jmdKcDj6gslIHgyc=
gauge@^3.0.0:
version "3.0.2"
resolved "https://registry.yarnpkg.com/gauge/-/gauge-3.0.2.tgz#03bf4441c044383908bcfa0656ad91803259b395"
integrity sha512-+5J6MS/5XksCuXq++uFRsnUd7Ovu1XenbeuIuNRJxYWjgQbPuFhT14lAvsWfqfAmnwluf1OwMjz39HjfLPci0Q==
dependencies:
aproba "^1.0.3 || ^2.0.0"
color-support "^1.1.2"
console-control-strings "^1.0.0"
has-unicode "^2.0.1"
object-assign "^4.1.1"
signal-exit "^3.0.0"
string-width "^4.2.3"
strip-ansi "^6.0.1"
wide-align "^1.1.2"
gensync@^1.0.0-beta.2: gensync@^1.0.0-beta.2:
version "1.0.0-beta.2" version "1.0.0-beta.2"
resolved "https://registry.yarnpkg.com/gensync/-/gensync-1.0.0-beta.2.tgz#32a6ee76c3d7f52d46b2b1ae5d93fea8580a25e0" resolved "https://registry.yarnpkg.com/gensync/-/gensync-1.0.0-beta.2.tgz#32a6ee76c3d7f52d46b2b1ae5d93fea8580a25e0"
@ -1972,6 +2055,11 @@ has-flag@^4.0.0:
resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-4.0.0.tgz#944771fd9c81c81265c4d6941860da06bb59479b" resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-4.0.0.tgz#944771fd9c81c81265c4d6941860da06bb59479b"
integrity sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ== integrity sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==
has-unicode@^2.0.1:
version "2.0.1"
resolved "https://registry.yarnpkg.com/has-unicode/-/has-unicode-2.0.1.tgz#e0e6fe6a28cf51138855e086d1691e771de2a8b9"
integrity sha1-4Ob+aijPUROIVeCG0Wkedx3iqLk=
has-value@^0.3.1: has-value@^0.3.1:
version "0.3.1" version "0.3.1"
resolved "https://registry.yarnpkg.com/has-value/-/has-value-0.3.1.tgz#7b1f58bada62ca827ec0a2078025654845995e1f" resolved "https://registry.yarnpkg.com/has-value/-/has-value-0.3.1.tgz#7b1f58bada62ca827ec0a2078025654845995e1f"
@ -3180,7 +3268,7 @@ ltgt@2.2.1, ltgt@^2.1.2, ltgt@~2.2.0:
resolved "https://registry.yarnpkg.com/ltgt/-/ltgt-2.2.1.tgz#f35ca91c493f7b73da0e07495304f17b31f87ee5" resolved "https://registry.yarnpkg.com/ltgt/-/ltgt-2.2.1.tgz#f35ca91c493f7b73da0e07495304f17b31f87ee5"
integrity sha1-81ypHEk/e3PaDgdJUwTxezH4fuU= integrity sha1-81ypHEk/e3PaDgdJUwTxezH4fuU=
make-dir@^3.0.0: make-dir@^3.0.0, make-dir@^3.1.0:
version "3.1.0" version "3.1.0"
resolved "https://registry.yarnpkg.com/make-dir/-/make-dir-3.1.0.tgz#415e967046b3a7f1d185277d84aa58203726a13f" resolved "https://registry.yarnpkg.com/make-dir/-/make-dir-3.1.0.tgz#415e967046b3a7f1d185277d84aa58203726a13f"
integrity sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw== integrity sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==
@ -3305,6 +3393,21 @@ minimist@^1.1.1, minimist@^1.2.0, minimist@^1.2.5:
resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.6.tgz#8637a5b759ea0d6e98702cfb3a9283323c93af44" resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.6.tgz#8637a5b759ea0d6e98702cfb3a9283323c93af44"
integrity sha512-Jsjnk4bw3YJqYzbdyBiNsPWHPfO++UGG749Cxs6peCu5Xg4nrena6OVxOYxrQTqww0Jmwt+Ref8rggumkTLz9Q== integrity sha512-Jsjnk4bw3YJqYzbdyBiNsPWHPfO++UGG749Cxs6peCu5Xg4nrena6OVxOYxrQTqww0Jmwt+Ref8rggumkTLz9Q==
minipass@^3.0.0:
version "3.1.6"
resolved "https://registry.yarnpkg.com/minipass/-/minipass-3.1.6.tgz#3b8150aa688a711a1521af5e8779c1d3bb4f45ee"
integrity sha512-rty5kpw9/z8SX9dmxblFA6edItUmwJgMeYDZRrwlIVN27i8gysGbznJwUggw2V/FVqFSDdWy040ZPS811DYAqQ==
dependencies:
yallist "^4.0.0"
minizlib@^2.1.1:
version "2.1.2"
resolved "https://registry.yarnpkg.com/minizlib/-/minizlib-2.1.2.tgz#e90d3466ba209b932451508a11ce3d3632145931"
integrity sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg==
dependencies:
minipass "^3.0.0"
yallist "^4.0.0"
mixin-deep@^1.2.0: mixin-deep@^1.2.0:
version "1.3.2" version "1.3.2"
resolved "https://registry.yarnpkg.com/mixin-deep/-/mixin-deep-1.3.2.tgz#1120b43dc359a785dce65b55b82e257ccf479566" resolved "https://registry.yarnpkg.com/mixin-deep/-/mixin-deep-1.3.2.tgz#1120b43dc359a785dce65b55b82e257ccf479566"
@ -3318,6 +3421,11 @@ mkdirp-classic@^0.5.2:
resolved "https://registry.yarnpkg.com/mkdirp-classic/-/mkdirp-classic-0.5.3.tgz#fa10c9115cc6d8865be221ba47ee9bed78601113" resolved "https://registry.yarnpkg.com/mkdirp-classic/-/mkdirp-classic-0.5.3.tgz#fa10c9115cc6d8865be221ba47ee9bed78601113"
integrity sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A== integrity sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A==
mkdirp@^1.0.3:
version "1.0.4"
resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-1.0.4.tgz#3eb5ed62622756d79a5f0e2a221dfebad75c2f7e"
integrity sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==
ms@2.0.0: ms@2.0.0:
version "2.0.0" version "2.0.0"
resolved "https://registry.yarnpkg.com/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8" resolved "https://registry.yarnpkg.com/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8"
@ -3375,12 +3483,17 @@ nice-try@^1.0.4:
resolved "https://registry.yarnpkg.com/nice-try/-/nice-try-1.0.5.tgz#a3378a7696ce7d223e88fc9b764bd7ef1089e366" resolved "https://registry.yarnpkg.com/nice-try/-/nice-try-1.0.5.tgz#a3378a7696ce7d223e88fc9b764bd7ef1089e366"
integrity sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ== integrity sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ==
node-addon-api@^3.1.0:
version "3.2.1"
resolved "https://registry.yarnpkg.com/node-addon-api/-/node-addon-api-3.2.1.tgz#81325e0a2117789c0128dab65e7e38f07ceba161"
integrity sha512-mmcei9JghVNDYydghQmeDX8KoAm0FAiYyIcUt/N4nhyAipB17pllZQDOJD2fotxABnt4Mdz+dKTO7eftLg4d0A==
node-fetch@2.6.0: node-fetch@2.6.0:
version "2.6.0" version "2.6.0"
resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.6.0.tgz#e633456386d4aa55863f676a7ab0daa8fdecb0fd" resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.6.0.tgz#e633456386d4aa55863f676a7ab0daa8fdecb0fd"
integrity sha512-8dG4H5ujfvFiqDmVu9fQ5bOHUC15JMjMY/Zumv26oOvvVJjM67KF8koCWIabKQ1GJIa9r2mMZscBq/TbdOcmNA== integrity sha512-8dG4H5ujfvFiqDmVu9fQ5bOHUC15JMjMY/Zumv26oOvvVJjM67KF8koCWIabKQ1GJIa9r2mMZscBq/TbdOcmNA==
node-fetch@2.6.7, node-fetch@^2.6.1: node-fetch@2.6.7, node-fetch@^2.6.1, node-fetch@^2.6.7:
version "2.6.7" version "2.6.7"
resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.6.7.tgz#24de9fba827e3b4ae44dc8b20256a379160052ad" resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.6.7.tgz#24de9fba827e3b4ae44dc8b20256a379160052ad"
integrity sha512-ZjMPFEfVx5j+y2yF35Kzx5sF7kDzxuDj6ziH4FFbOp87zKDZNx8yExJIb05OGF4Nlt9IHFIMBkRl41VdvcNdbQ== integrity sha512-ZjMPFEfVx5j+y2yF35Kzx5sF7kDzxuDj6ziH4FFbOp87zKDZNx8yExJIb05OGF4Nlt9IHFIMBkRl41VdvcNdbQ==
@ -3424,6 +3537,13 @@ node-releases@^2.0.1:
resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-2.0.1.tgz#3d1d395f204f1f2f29a54358b9fb678765ad2fc5" resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-2.0.1.tgz#3d1d395f204f1f2f29a54358b9fb678765ad2fc5"
integrity sha512-CqyzN6z7Q6aMeF/ktcMVTzhAHCEpf8SOarwpzpf8pNBY2k5/oM34UHldUwp8VKI7uxct2HxSRdJjBaZeESzcxA== integrity sha512-CqyzN6z7Q6aMeF/ktcMVTzhAHCEpf8SOarwpzpf8pNBY2k5/oM34UHldUwp8VKI7uxct2HxSRdJjBaZeESzcxA==
nopt@^5.0.0:
version "5.0.0"
resolved "https://registry.yarnpkg.com/nopt/-/nopt-5.0.0.tgz#530942bb58a512fccafe53fe210f13a25355dc88"
integrity sha512-Tbj67rffqceeLpcRXrT7vKAN8CwfPeIBgM7E6iBkmKLV7bEMwpGgYLGv0jACUsECaa/vuxP0IjEont6umdMgtQ==
dependencies:
abbrev "1"
normalize-package-data@^2.5.0: normalize-package-data@^2.5.0:
version "2.5.0" version "2.5.0"
resolved "https://registry.yarnpkg.com/normalize-package-data/-/normalize-package-data-2.5.0.tgz#e66db1838b200c1dfc233225d12cb36520e234a8" resolved "https://registry.yarnpkg.com/normalize-package-data/-/normalize-package-data-2.5.0.tgz#e66db1838b200c1dfc233225d12cb36520e234a8"
@ -3460,6 +3580,16 @@ npm-run-path@^4.0.0:
dependencies: dependencies:
path-key "^3.0.0" path-key "^3.0.0"
npmlog@^5.0.1:
version "5.0.1"
resolved "https://registry.yarnpkg.com/npmlog/-/npmlog-5.0.1.tgz#f06678e80e29419ad67ab964e0fa69959c1eb8b0"
integrity sha512-AqZtDUWOMKs1G/8lwylVjrdYgqA4d9nu8hc+0gzRxlDb1I10+FHBGMXs6aiQHFdCUUlqH99MUMuLfzWDNDtfxw==
dependencies:
are-we-there-yet "^2.0.0"
console-control-strings "^1.1.0"
gauge "^3.0.0"
set-blocking "^2.0.0"
nwsapi@^2.2.0: nwsapi@^2.2.0:
version "2.2.0" version "2.2.0"
resolved "https://registry.yarnpkg.com/nwsapi/-/nwsapi-2.2.0.tgz#204879a9e3d068ff2a55139c2c772780681a38b7" resolved "https://registry.yarnpkg.com/nwsapi/-/nwsapi-2.2.0.tgz#204879a9e3d068ff2a55139c2c772780681a38b7"
@ -3480,6 +3610,11 @@ oauth@0.9.x, oauth@^0.9.15:
resolved "https://registry.yarnpkg.com/oauth/-/oauth-0.9.15.tgz#bd1fefaf686c96b75475aed5196412ff60cfb9c1" resolved "https://registry.yarnpkg.com/oauth/-/oauth-0.9.15.tgz#bd1fefaf686c96b75475aed5196412ff60cfb9c1"
integrity sha1-vR/vr2hslrdUda7VGWQS/2DPucE= integrity sha1-vR/vr2hslrdUda7VGWQS/2DPucE=
object-assign@^4.1.1:
version "4.1.1"
resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863"
integrity sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=
object-copy@^0.1.0: object-copy@^0.1.0:
version "0.1.0" version "0.1.0"
resolved "https://registry.yarnpkg.com/object-copy/-/object-copy-0.1.0.tgz#7e7d858b781bd7c991a41ba975ed3812754e998c" resolved "https://registry.yarnpkg.com/object-copy/-/object-copy-0.1.0.tgz#7e7d858b781bd7c991a41ba975ed3812754e998c"
@ -4066,7 +4201,7 @@ readable-stream@1.1.14, readable-stream@^1.0.27-1:
isarray "0.0.1" isarray "0.0.1"
string_decoder "~0.10.x" string_decoder "~0.10.x"
"readable-stream@2 || 3", readable-stream@^3.1.1, readable-stream@^3.4.0: "readable-stream@2 || 3", readable-stream@^3.1.1, readable-stream@^3.4.0, readable-stream@^3.6.0:
version "3.6.0" version "3.6.0"
resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-3.6.0.tgz#337bbda3adc0706bd3e024426a286d4b4b2c9198" resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-3.6.0.tgz#337bbda3adc0706bd3e024426a286d4b4b2c9198"
integrity sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA== integrity sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==
@ -4242,7 +4377,7 @@ ret@~0.1.10:
resolved "https://registry.yarnpkg.com/ret/-/ret-0.1.15.tgz#b8a4825d5bdb1fc3f6f53c2bc33f81388681c7bc" resolved "https://registry.yarnpkg.com/ret/-/ret-0.1.15.tgz#b8a4825d5bdb1fc3f6f53c2bc33f81388681c7bc"
integrity sha512-TTlYpa+OL+vMMNG24xSlQGEJ3B/RzEfUlLct7b5G/ytav+wPrplCpVMFuwzXbkecJrb6IYo1iFb0S9v37754mg== integrity sha512-TTlYpa+OL+vMMNG24xSlQGEJ3B/RzEfUlLct7b5G/ytav+wPrplCpVMFuwzXbkecJrb6IYo1iFb0S9v37754mg==
rimraf@^3.0.0: rimraf@^3.0.0, rimraf@^3.0.2:
version "3.0.2" version "3.0.2"
resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-3.0.2.tgz#f1a5402ba6220ad52cc1282bac1ae3aa49fd061a" resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-3.0.2.tgz#f1a5402ba6220ad52cc1282bac1ae3aa49fd061a"
integrity sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA== integrity sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==
@ -4330,6 +4465,13 @@ semver@^7.3.2:
dependencies: dependencies:
lru-cache "^6.0.0" lru-cache "^6.0.0"
semver@^7.3.5:
version "7.3.7"
resolved "https://registry.yarnpkg.com/semver/-/semver-7.3.7.tgz#12c5b649afdbf9049707796e22a4028814ce523f"
integrity sha512-QlYTucUYOews+WeEujDoEGziz4K6c47V/Bd+LjSSYcA94p+DmINdf7ncaUinThfvZyu13lN9OY1XDxt8C0Tw0g==
dependencies:
lru-cache "^6.0.0"
set-blocking@^2.0.0: set-blocking@^2.0.0:
version "2.0.0" version "2.0.0"
resolved "https://registry.yarnpkg.com/set-blocking/-/set-blocking-2.0.0.tgz#045f9782d011ae9a6803ddd382b24392b3d890f7" resolved "https://registry.yarnpkg.com/set-blocking/-/set-blocking-2.0.0.tgz#045f9782d011ae9a6803ddd382b24392b3d890f7"
@ -4584,7 +4726,7 @@ string-template@~1.0.0:
resolved "https://registry.yarnpkg.com/string-template/-/string-template-1.0.0.tgz#9e9f2233dc00f218718ec379a28a5673ecca8b96" resolved "https://registry.yarnpkg.com/string-template/-/string-template-1.0.0.tgz#9e9f2233dc00f218718ec379a28a5673ecca8b96"
integrity sha1-np8iM9wA8hhxjsN5oopWc+zKi5Y= integrity sha1-np8iM9wA8hhxjsN5oopWc+zKi5Y=
string-width@^4.1.0, string-width@^4.2.0: "string-width@^1.0.2 || 2 || 3 || 4", string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3:
version "4.2.3" version "4.2.3"
resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010" resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010"
integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==
@ -4697,6 +4839,18 @@ tar-stream@^2.1.4:
inherits "^2.0.3" inherits "^2.0.3"
readable-stream "^3.1.1" readable-stream "^3.1.1"
tar@^6.1.11:
version "6.1.11"
resolved "https://registry.yarnpkg.com/tar/-/tar-6.1.11.tgz#6760a38f003afa1b2ffd0ffe9e9abbd0eab3d621"
integrity sha512-an/KZQzQUkZCkuoAA64hM92X0Urb6VpRhAFllDzz44U2mcD5scmT3zBc4VgVpkugF580+DQn8eAFSyoQt0tznA==
dependencies:
chownr "^2.0.0"
fs-minipass "^2.0.0"
minipass "^3.0.0"
minizlib "^2.1.1"
mkdirp "^1.0.3"
yallist "^4.0.0"
terminal-link@^2.0.0: terminal-link@^2.0.0:
version "2.1.1" version "2.1.1"
resolved "https://registry.yarnpkg.com/terminal-link/-/terminal-link-2.1.1.tgz#14a64a27ab3c0df933ea546fba55f2d078edc994" resolved "https://registry.yarnpkg.com/terminal-link/-/terminal-link-2.1.1.tgz#14a64a27ab3c0df933ea546fba55f2d078edc994"
@ -5075,6 +5229,13 @@ which@^2.0.1, which@^2.0.2:
dependencies: dependencies:
isexe "^2.0.0" isexe "^2.0.0"
wide-align@^1.1.2:
version "1.1.5"
resolved "https://registry.yarnpkg.com/wide-align/-/wide-align-1.1.5.tgz#df1d4c206854369ecf3c9a4898f1b23fbd9d15d3"
integrity sha512-eDMORYaPNZ4sQIuuYPDHdQvf4gyCF9rEEV/yPxGfwPkRodwEgiMUUXTx/dex+Me0wxx53S+NgUHaP7y3MGlDmg==
dependencies:
string-width "^1.0.2 || 2 || 3 || 4"
word-wrap@~1.2.3: word-wrap@~1.2.3:
version "1.2.3" version "1.2.3"
resolved "https://registry.yarnpkg.com/word-wrap/-/word-wrap-1.2.3.tgz#610636f6b1f703891bd34771ccb17fb93b47079c" resolved "https://registry.yarnpkg.com/word-wrap/-/word-wrap-1.2.3.tgz#610636f6b1f703891bd34771ccb17fb93b47079c"

View file

@ -1,7 +1,7 @@
{ {
"name": "@budibase/bbui", "name": "@budibase/bbui",
"description": "A UI solution used in the different Budibase projects.", "description": "A UI solution used in the different Budibase projects.",
"version": "1.0.178-alpha.0", "version": "1.0.192-alpha.5",
"license": "MPL-2.0", "license": "MPL-2.0",
"svelte": "src/index.js", "svelte": "src/index.js",
"module": "dist/bbui.es.js", "module": "dist/bbui.es.js",
@ -38,7 +38,7 @@
], ],
"dependencies": { "dependencies": {
"@adobe/spectrum-css-workflow-icons": "^1.2.1", "@adobe/spectrum-css-workflow-icons": "^1.2.1",
"@budibase/string-templates": "^1.0.178-alpha.0", "@budibase/string-templates": "^1.0.192-alpha.5",
"@spectrum-css/actionbutton": "^1.0.1", "@spectrum-css/actionbutton": "^1.0.1",
"@spectrum-css/actiongroup": "^1.0.1", "@spectrum-css/actiongroup": "^1.0.1",
"@spectrum-css/avatar": "^3.0.2", "@spectrum-css/avatar": "^3.0.2",

View file

@ -18,6 +18,7 @@
export let fileSizeLimit = BYTES_IN_MB * 20 export let fileSizeLimit = BYTES_IN_MB * 20
export let processFiles = null export let processFiles = null
export let handleFileTooLarge = null export let handleFileTooLarge = null
export let handleTooManyFiles = null
export let gallery = true export let gallery = true
export let error = null export let error = null
export let fileTags = [] export let fileTags = []
@ -71,6 +72,13 @@
handleFileTooLarge(fileSizeLimit, value) handleFileTooLarge(fileSizeLimit, value)
return return
} }
const fileCount = fileList.length + value.length
if (handleTooManyFiles && maximum && fileCount > maximum) {
handleTooManyFiles(maximum)
return
}
if (processFiles) { if (processFiles) {
const processedFiles = await processFiles(fileList) const processedFiles = await processFiles(fileList)
const newValue = [...value, ...processedFiles] const newValue = [...value, ...processedFiles]

View file

@ -11,6 +11,7 @@
export let fileSizeLimit = undefined export let fileSizeLimit = undefined
export let processFiles = undefined export let processFiles = undefined
export let handleFileTooLarge = undefined export let handleFileTooLarge = undefined
export let handleTooManyFiles = undefined
export let gallery = true export let gallery = true
export let fileTags = [] export let fileTags = []
export let maximum = undefined export let maximum = undefined
@ -30,6 +31,7 @@
{fileSizeLimit} {fileSizeLimit}
{processFiles} {processFiles}
{handleFileTooLarge} {handleFileTooLarge}
{handleTooManyFiles}
{gallery} {gallery}
{fileTags} {fileTags}
{maximum} {maximum}

View file

@ -41,7 +41,7 @@ filterTests(['all'], () => {
} }
// Check items have been selected // Check items have been selected
cy.getComponent(componentId) cy.getComponent(componentId)
.find(interact.SPECTRUM_Picker_LABEL) .find(interact.SPECTRUM_PICKER_LABEL)
.contains("(5)") .contains("(5)")
}) })
}) })

View file

@ -132,22 +132,36 @@ filterTests(['all'], () => {
}) })
}) })
it("Should allow the editing of the application icon", () => { it("Should allow the editing of the application icon and colour", () => {
cy.visit(`${Cypress.config().baseUrl}/builder`) cy.visit(`${Cypress.config().baseUrl}/builder`)
cy.get(".appTable", { timeout: 2000})
cy.get(".appTable .name").eq(0).click() .within(() => {
cy.get(".app-row-actions-icon").eq(0).click()
cy.get(".app-logo .edit-hover").should("exist").invoke("show").click() })
cy.get(".spectrum-Menu").contains("Edit icon").click()
cy.customiseAppIcon() // Select random icon
cy.get(".grid").within(() => {
cy.get(".app-logo") cy.get(".icon-item").eq(Math.floor(Math.random() * 23) + 1).click()
.within(() => {
cy.get('[aria-label]').eq(0).children()
.should('have.attr', 'xlink:href').and('not.contain', '#spectrum-icon-18-Apps')
cy.get(".app-icon")
.should('have.attr', 'style').and('contains', 'color')
}) })
// Select random colour
cy.get(".fill").click()
cy.get(".colors").within(() => {
cy.get(".color").eq(Math.floor(Math.random() * 33) + 1).click()
})
cy.intercept('**/applications/**').as('iconChange')
cy.get(".spectrum-Button").contains("Save").click({ force: true })
cy.wait("@iconChange")
cy.get("@iconChange").its('response.statusCode')
.should('eq', 200)
// Confirm icon has changed from default
// Confirm colour has been applied
cy.get(".appTable", { timeout: 2000})
.within(() => {
cy.get('[aria-label]').eq(0).children()
.should('have.attr', 'xlink:href').and('not.contain', '#spectrum-icon-18-Apps')
cy.get(".title").children().children()
.should('have.attr', 'style').and('contains', 'color')
})
}) })
it("Should reflect the last time the application was edited", () => { it("Should reflect the last time the application was edited", () => {
@ -259,6 +273,7 @@ filterTests(['all'], () => {
}); });
cy.visit(`${Cypress.config().baseUrl}/builder`) cy.visit(`${Cypress.config().baseUrl}/builder`)
cy.wait(1000)
cy.get(".appTable .name").eq(0).click() cy.get(".appTable .name").eq(0).click()
cy.get(".spectrum-Tabs-item").contains("Settings").click() cy.get(".spectrum-Tabs-item").contains("Settings").click()
cy.get(".spectrum-Tabs-item.is-selected").contains("Settings") cy.get(".spectrum-Tabs-item.is-selected").contains("Settings")
@ -269,7 +284,7 @@ filterTests(['all'], () => {
}) })
it("Should allow copying of the published application Id", () => { xit("Should allow copying of the published application Id", () => {
cy.visit(`${Cypress.config().baseUrl}/builder`) cy.visit(`${Cypress.config().baseUrl}/builder`)
cy.get(".appTable .app-row-actions").eq(0) cy.get(".appTable .app-row-actions").eq(0)
.within(() => { .within(() => {

View file

@ -1,4 +1,6 @@
import filterTests from "../support/filterTests" import filterTests from "../support/filterTests"
import { APP_TABLE_APP_NAME, DEPLOY_SUCCESS_MODAL } from "../support/interact";
const interact = require('../support/interact')
filterTests(['all'], () => { filterTests(['all'], () => {
context("Publish Application Workflow", () => { context("Publish Application Workflow", () => {
@ -11,87 +13,94 @@ filterTests(['all'], () => {
cy.visit(`${Cypress.config().baseUrl}/builder`) cy.visit(`${Cypress.config().baseUrl}/builder`)
cy.wait(1000) cy.wait(1000)
cy.get(".appTable .app-status").eq(0) cy.get(interact.APP_TABLE_STATUS).eq(0)
.within(() => { .within(() => {
cy.contains("Unpublished") cy.contains("Unpublished")
cy.get("svg[aria-label='GlobeStrike']").should("exist") cy.get(interact.GLOBESTRIKE).should("exist")
}) })
cy.get(".appTable .app-row-actions").eq(0) cy.get(interact.APP_TABLE_ROW_ACTION).eq(0)
.within(() => { .within(() => {
cy.get(".spectrum-Button").contains("View") cy.get(interact.SPECTRUM_BUTTON_TEMPLATE).contains("Edit").click({ force: true })
cy.get(".spectrum-Button").contains("Edit").click({ force: true })
}) })
cy.get(".deployment-top-nav svg[aria-label='GlobeStrike']").should("exist") cy.get(interact.DEPLOYMENT_TOP_NAV_GLOBESTRIKE).should("exist")
cy.get(".deployment-top-nav svg[aria-label='Globe']").should("not.exist") cy.get(interact.DEPLOYMENT_TOP_GLOBE).should("not.exist")
}) })
it("Should publish an application and correctly reflect that", () => { it("Should publish an application and correctly reflect that", () => {
//Assuming the previous test was run and the unpublished app is open in edit mode. //Assuming the previous test was run and the unpublished app is open in edit mode.
cy.get(interact.TOPRIGHTNAV_BUTTON_SPECTRUM).contains("Publish").click({ force : true })
cy.publishApp("cypress-tests") cy.get(interact.DEPLOY_APP_MODAL).should("be.visible")
.within(() => {
cy.get(interact.SPECTRUM_BUTTON).contains("Publish").click({ force : true })
cy.wait(1000)
});
//Verify that the app url is presented correctly to the user
cy.get(interact.DEPLOY_SUCCESS_MODAL)
.should("be.visible")
.within(() => {
let appUrl = Cypress.config().baseUrl + '/app/cypress-tests'
cy.get(interact.DEPLOY_APP_URL_INPUT).should('have.value', appUrl)
cy.get(interact.SPECTRUM_BUTTON).contains("Done").click({ force: true })
})
cy.visit(`${Cypress.config().baseUrl}/builder`) cy.visit(`${Cypress.config().baseUrl}/builder`)
cy.wait(1000) cy.wait(1000)
cy.get(".appTable .app-status").eq(0) cy.get(interact.APP_TABLE_STATUS).eq(0)
.within(() => { .within(() => {
cy.contains("Published") cy.contains("Published")
cy.get("svg[aria-label='Globe']").should("exist") cy.get(interact.GLOBE).should("exist")
}) })
cy.get(".appTable .app-row-actions").eq(0) cy.get(interact.APP_TABLE_ROW_ACTION).eq(0)
.within(() => { .within(() => {
cy.get(".spectrum-Button").contains("View") cy.get(interact.SPECTRUM_BUTTON).contains("View")
cy.get(".spectrum-Button").contains("Edit").click({ force: true }) cy.get(interact.SPECTRUM_BUTTON).contains("Edit").click({ force: true })
}) })
cy.get(".deployment-top-nav svg[aria-label='Globe']").should("exist").click({ force: true }) cy.get(interact.DEPLOYMENT_TOP_GLOBE).should("exist").click({ force: true })
cy.get("[data-cy='publish-popover-menu']").should("be.visible") cy.get(interact.PUBLISH_POPOVER_MENU).should("be.visible")
.within(() => { .within(() => {
cy.get("[data-cy='publish-popover-action']").should("exist") cy.get(interact.PUBLISH_POPOVER_ACTION).should("exist")
cy.get("button").contains("View").should("exist") cy.get("button").contains("View app").should("exist")
cy.get(".publish-popover-message").should("have.text", "Last published a few seconds ago") cy.get(interact.PUBLISH_POPOVER_MESSAGE).should("have.text", "Last published a few seconds ago")
}) })
}) })
it("Should unpublish an application from the top navigation and reflect the status change", () => { it("Should unpublish an application using the link and reflect the status change", () => {
//Assuming the previous test app exists and is published //Assuming the previous test app exists and is published
cy.visit(`${Cypress.config().baseUrl}/builder`) cy.visit(`${Cypress.config().baseUrl}/builder`)
cy.get(".appTable .app-status").eq(0) cy.get(interact.APP_TABLE_STATUS).eq(0)
.within(() => { .within(() => {
cy.contains("Published") cy.contains("Published")
cy.get("svg[aria-label='Globe']").should("exist") cy.get("svg[aria-label='Globe']").should("exist")
}) })
cy.get(".appTable .app-row-actions").eq(0) cy.get(interact.APP_TABLE_ROW_ACTION).eq(0)
.within(() => { .within(() => {
cy.get(".spectrum-Button").contains("View") cy.get(interact.SPECTRUM_BUTTON).contains("View")
cy.get(".spectrum-Button").contains("Edit").click({ force: true }) cy.get(interact.APP_TABLE_APP_NAME).click({ force: true })
}) })
//The published status cy.get(interact.SPECTRUM_LINK).contains('Unpublish').click();
cy.get(".deployment-top-nav svg[aria-label='Globe']").should("exist")
.click({ force: true })
cy.get("[data-cy='publish-popover-menu']").should("be.visible") cy.get(interact.UNPUBLISH_MODAL).should("be.visible")
cy.get("[data-cy='publish-popover-menu'] [data-cy='publish-popover-action']")
.click({ force : true })
cy.get("[data-cy='unpublish-modal']").should("be.visible")
.within(() => { .within(() => {
cy.get(".confirm-wrap button").click({ force: true } cy.get(interact.CONFIRM_WRAP_BUTTON).click({ force: true }
)}) )})
cy.get(".deployment-top-nav svg[aria-label='GlobeStrike']").should("exist") cy.get(interact.DEPLOYMENT_TOP_NAV_GLOBESTRIKE).should("exist")
cy.visit(`${Cypress.config().baseUrl}/builder`) cy.visit(`${Cypress.config().baseUrl}/builder`)
cy.get(".appTable .app-status").eq(0).contains("Unpublished") cy.get(interact.APP_TABLE_STATUS).eq(0).contains("Unpublished")
}) })
}) })

View file

@ -1,4 +1,5 @@
import filterTests from "../support/filterTests" import filterTests from "../support/filterTests"
const interact = require('../support/interact')
filterTests(['smoke', 'all'], () => { filterTests(['smoke', 'all'], () => {
context("Auto Screens UI", () => { context("Auto Screens UI", () => {
@ -12,10 +13,10 @@ filterTests(['smoke', 'all'], () => {
cy.closeModal(); cy.closeModal();
cy.contains("Design").click() cy.contains("Design").click()
cy.get("[aria-label=AddCircle]").click() cy.get(interact.LABEL_ADD_CIRCLE).click()
cy.get(".spectrum-Modal").within(() => { cy.get(interact.SPECTRUM_MODAL).within(() => {
cy.get(".item.disabled").contains("Autogenerated screens") cy.get(interact.ITEM_DISABLED).contains("Autogenerated screens")
cy.get(".confirm-wrap .spectrum-Button").should('be.disabled') cy.get(interact.CONFIRM_WRAP_SPE_BUTTON).should('be.disabled')
}) })
cy.deleteAllApps() cy.deleteAllApps()
@ -26,14 +27,14 @@ filterTests(['smoke', 'all'], () => {
cy.selectExternalDatasource("REST") cy.selectExternalDatasource("REST")
cy.selectExternalDatasource("S3") cy.selectExternalDatasource("S3")
cy.get(".spectrum-Modal").within(() => { cy.get(interact.SPECTRUM_MODAL).within(() => {
cy.get(".spectrum-Button").contains("Save and continue to query").click({ force : true }) cy.get(interact.SPECTRUM_BUTTON).contains("Save and continue to query").click({ force : true })
}) })
cy.navigateToAutogeneratedModal() cy.navigateToAutogeneratedModal()
cy.get('.data-source-entry').should('have.length', 1) cy.get(interact.DATA_SOURCE_ENTRY).should('have.length', 1)
cy.get('.data-source-entry') cy.get(interact.DATA_SOURCE_ENTRY)
cy.deleteAllApps() cy.deleteAllApps()
}); });
@ -43,8 +44,8 @@ filterTests(['smoke', 'all'], () => {
// Create Autogenerated screens from the internal table // Create Autogenerated screens from the internal table
cy.createDatasourceScreen(["Cypress Tests"]) cy.createDatasourceScreen(["Cypress Tests"])
// Confirm screens have been auto generated // Confirm screens have been auto generated
cy.get(".nav-items-container").contains("cypress-tests").click({ force: true }) cy.get(interact.NAV_ITEMS_CONTAINER).contains("cypress-tests").click({ force: true })
cy.get(".nav-items-container").should('contain', 'cypress-tests/:id') cy.get(interact.NAV_ITEMS_CONTAINER).should('contain', 'cypress-tests/:id')
.and('contain', 'cypress-tests/new/row') .and('contain', 'cypress-tests/new/row')
}) })
@ -56,12 +57,12 @@ filterTests(['smoke', 'all'], () => {
// Create Autogenerated screens from the internal tables // Create Autogenerated screens from the internal tables
cy.createDatasourceScreen([initialTable, secondTable]) cy.createDatasourceScreen([initialTable, secondTable])
// Confirm screens have been auto generated // Confirm screens have been auto generated
cy.get(".nav-items-container").contains("cypress-tests").click({ force: true }) cy.get(interact.NAV_ITEMS_CONTAINER).contains("cypress-tests").click({ force: true })
// Previously generated tables are suffixed with numbers - as expected // Previously generated tables are suffixed with numbers - as expected
cy.get(".nav-items-container").should('contain', 'cypress-tests-2/:id') cy.get(interact.NAV_ITEMS_CONTAINER).should('contain', 'cypress-tests-2/:id')
.and('contain', 'cypress-tests-2/new/row') .and('contain', 'cypress-tests-2/new/row')
cy.get(".nav-items-container").contains("table-two").click() cy.get(interact.NAV_ITEMS_CONTAINER).contains("table-two").click()
cy.get(".nav-items-container").should('contain', 'table-two/:id') cy.get(interact.NAV_ITEMS_CONTAINER).should('contain', 'table-two/:id')
.and('contain', 'table-two/new/row') .and('contain', 'table-two/new/row')
}) })
@ -71,17 +72,17 @@ filterTests(['smoke', 'all'], () => {
cy.createTable("Table Four") cy.createTable("Table Four")
cy.createDatasourceScreen(["Table Three", "Table Four"], "Admin") cy.createDatasourceScreen(["Table Three", "Table Four"], "Admin")
cy.get(".nav-items-container").contains("table-three").click() cy.get(interact.NAV_ITEMS_CONTAINER).contains("table-three").click()
cy.get(".nav-items-container").should('contain', 'table-three/:id') cy.get(interact.NAV_ITEMS_CONTAINER).should('contain', 'table-three/:id')
.and('contain', 'table-three/new/row') .and('contain', 'table-three/new/row')
cy.get(".nav-items-container").contains("table-four").click() cy.get(interact.NAV_ITEMS_CONTAINER).contains("table-four").click()
cy.get(".nav-items-container").should('contain', 'table-four/:id') cy.get(interact.NAV_ITEMS_CONTAINER).should('contain', 'table-four/:id')
.and('contain', 'table-four/new/row') .and('contain', 'table-four/new/row')
//The access level should now be set to admin. Previous screens should be filtered. //The access level should now be set to admin. Previous screens should be filtered.
cy.get(".nav-items-container").contains("table-two").should('not.exist') cy.get(interact.NAV_ITEMS_CONTAINER).contains("table-two").should('not.exist')
cy.get(".nav-items-container").contains("cypress-tests").should('not.exist') cy.get(interact.NAV_ITEMS_CONTAINER).contains("cypress-tests").should('not.exist')
}) })
if (Cypress.env("TEST_ENV")) { if (Cypress.env("TEST_ENV")) {
@ -94,8 +95,8 @@ filterTests(['smoke', 'all'], () => {
// Create Autogenerated screens from a MySQL table - MySQL contains books table // Create Autogenerated screens from a MySQL table - MySQL contains books table
cy.createDatasourceScreen(["books"]) cy.createDatasourceScreen(["books"])
cy.get(".nav-items-container").contains("books").click() cy.get(interact.NAV_ITEMS_CONTAINER).contains("books").click()
cy.get(".nav-items-container").should('contain', 'books/:id') cy.get(interact.NAV_ITEMS_CONTAINER).should('contain', 'books/:id')
.and('contain', 'books/new/row') .and('contain', 'books/new/row')
}) })
} }

View file

@ -1,44 +0,0 @@
import filterTests from "../support/filterTests"
filterTests(['all'], () => {
context("Change Application Icon and Colour", () => {
before(() => {
cy.login()
})
it("should change the icon and colour for an application", () => {
// Search for test application
cy.applicationInAppTable("Cypress Tests")
cy.get(".appTable")
.within(() => {
cy.get(".app-row-actions-icon").eq(0).click()
})
cy.get(".spectrum-Menu").contains("Edit icon").click()
// Select random icon
cy.get(".grid").within(() => {
cy.get(".icon-item").eq(Math.floor(Math.random() * 23) + 1).click()
})
// Select random colour
cy.get(".fill").click()
cy.get(".colors").within(() => {
cy.get(".color").eq(Math.floor(Math.random() * 33) + 1).click()
})
cy.intercept('**/applications/**').as('iconChange')
cy.get(".spectrum-Button").contains("Save").click({ force: true })
cy.wait("@iconChange")
cy.get("@iconChange").its('response.statusCode')
.should('eq', 200)
cy.wait(1000)
// Confirm icon has changed from default
// Confirm colour has been applied - There is no default colour
cy.get(".appTable")
.within(() => {
cy.get('[aria-label]').eq(0).children()
.should('have.attr', 'xlink:href').and('not.contain', '#spectrum-icon-18-Apps')
cy.get(".title").children().children()
.should('have.attr', 'style').and('contains', 'color')
})
cy.deleteAllApps()
})
})
})

View file

@ -30,7 +30,7 @@ filterTests(['smoke', 'all'], () => {
.its("body") .its("body")
.then(val => { .then(val => {
if (val.length > 0) { if (val.length > 0) {
cy.get(interact.SPECTRUM_BUTTON_TEMPLATE).contains("Templates").click({force: true}) cy.get(interact.SPECTRUM_BUTTON).contains("Templates").click({force: true})
} }
}) })

View file

@ -1,6 +1,7 @@
// TODO for now components are skipped, might not be good to keep doing this // TODO for now components are skipped, might not be good to keep doing this
import filterTests from "../support/filterTests" import filterTests from "../support/filterTests"
const interact = require('../support/interact')
filterTests(['all'], () => { filterTests(['all'], () => {
xcontext("Create Components", () => { xcontext("Create Components", () => {
@ -31,32 +32,32 @@ filterTests(['all'], () => {
it("should change the text of the headline", () => { it("should change the text of the headline", () => {
const text = "Lorem ipsum dolor sit amet." const text = "Lorem ipsum dolor sit amet."
cy.get("[data-cy=Settings]").click() cy.get(interact.SETTINGS).click()
cy.get("[data-cy=setting-text] input") cy.get(interact.SETTINGS_INPUT)
.type(text) .type(text)
.blur() .blur()
cy.getComponent(headlineId).should("have.text", text) cy.getComponent(headlineId).should("have.text", text)
}) })
it("should change the size of the headline", () => { it("should change the size of the headline", () => {
cy.get("[data-cy=Design]").click() cy.get(interact.DESIGN).click()
cy.contains("Typography").click() cy.contains("Typography").click()
cy.get("[data-cy=font-size-prop-control]").click() cy.get(interact.FONT_SIZE_PROP_CONTROL).click()
cy.contains("60px").click() cy.contains("60px").click()
cy.getComponent(headlineId).should("have.css", "font-size", "60px") cy.getComponent(headlineId).should("have.css", "font-size", "60px")
}) })
it("should create a form and reset to match schema", () => { it("should create a form and reset to match schema", () => {
cy.addComponent("Form", "Form").then(() => { cy.addComponent("Form", "Form").then(() => {
cy.get("[data-cy=Settings]").click() cy.get(interact.SETTINGS).click()
cy.get("[data-cy=setting-dataSource]") cy.get(interact.DATA_CY_DATASOURCE)
.contains("Choose option") .contains("Choose option")
.click() .click()
cy.get(".dropdown") cy.get(interact.DROPDOWN)
.contains("dog") .contains("dog")
.click() .click()
cy.addComponent("Form", "Field Group").then(fieldGroupId => { cy.addComponent("Form", "Field Group").then(fieldGroupId => {
cy.get("[data-cy=Settings]").click() cy.get(interact.SETTINGS).click()
cy.contains("Update Form Fields").click() cy.contains("Update Form Fields").click()
cy.get(".modal") cy.get(".modal")
.get("button.primary") .get("button.primary")
@ -70,7 +71,7 @@ filterTests(['all'], () => {
.find("input") .find("input")
.should("have.length", 2) .should("have.length", 2)
cy.getComponent(fieldGroupId) cy.getComponent(fieldGroupId)
.find(".spectrum-Picker") .find(interact.SPECTRUM_PICKER)
.should("have.length", 1) .should("have.length", 1)
}) })
}) })
@ -84,7 +85,7 @@ filterTests(['all'], () => {
cy.get(".ui-nav ul .nav-item.selected .ri-more-line").click({ cy.get(".ui-nav ul .nav-item.selected .ri-more-line").click({
force: true, force: true,
}) })
cy.get(".dropdown-container") cy.get(interact.DROPDOWN_CONTAINER)
.contains("Delete") .contains("Delete")
.click() .click()
cy.get(".modal") cy.get(".modal")

View file

@ -1,56 +0,0 @@
import filterTests from "../../../support/filterTests"
filterTests(["all"], () => {
context("Verify HR Template Details", () => {
before(() => {
cy.login()
// Template navigation
cy.visit(`${Cypress.config().baseUrl}/builder/portal/apps/templates`)
// Filter HR Templates
cy.get(".template-category-filters").within(() => {
cy.get('[data-cy="HR"]').click()
})
})
it("should verify the details option for HR templates", () => {
cy.get(".template-grid").find(".template-card").its('length')
.then(len => {
for (let i = 0; i < len; i++) {
cy.get(".template-card").eq(i).within(() => {
const templateName = cy.get(".template-thumbnail-text")
templateName.invoke('text')
.then(templateNameText => {
const templateNameParsed = templateNameText.toLowerCase().replace(/\s+/g, '-')
if (templateNameText == "Job Application Tracker") {
// Template name should include 'applicant-tracking-system'
cy.get('a')
.should('have.attr', 'href').and('contain', 'applicant-tracking-system')
}
else if (templateNameText == "Job Portal App") {
// Template name should include 'job-portal'
const templateNameSplit = templateNameParsed.split('-app')[0]
cy.get('a')
.should('have.attr', 'href').and('contain', templateNameSplit)
}
else {
cy.get('a')
.should('have.attr', 'href').and('contain', templateNameParsed)
}
})
// Verify correct status from Details link - 200
cy.get('a')
.then(link => {
cy.request(link.prop('href'))
.its('status')
.should('eq', 200)
})
})
}
})
})
})
})

View file

@ -1,222 +0,0 @@
import filterTests from "../../../support/filterTests"
filterTests(["all"], () => {
context("Job Application Tracker Template Functionality", () => {
const templateName = "Job Application Tracker"
const templateNameParsed = templateName.toLowerCase().replace(/\s+/g, '-')
before(() => {
cy.login()
cy.deleteApp(templateName)
// Template navigation
cy.visit(`${Cypress.config().baseUrl}/builder/portal/apps/templates`, {
onBeforeLoad(win) {
cy.stub(win, 'open')
}
})
cy.wait(2000)
})
it("should create and publish app with Job Application Tracker template", () => {
// Select Job Application Tracker template
cy.get(".template-thumbnail-text")
.contains(templateName).parentsUntil(".template-grid").within(() => {
cy.get(".spectrum-Button").contains("Use template").click({ force: true })
})
// Confirm URL matches template name
const appUrl = cy.get(".app-server")
appUrl.invoke('text').then(appUrlText => {
expect(appUrlText).to.equal(`${Cypress.config().baseUrl}/app/` + templateNameParsed)
})
// Create App
cy.get(".spectrum-Dialog-grid").within(() => {
cy.get(".spectrum-Button").contains("Create app").click({ force: true })
})
// Publish App & Verify it opened
cy.wait(2000) // Wait for app to generate
cy.publishApp(true)
cy.window().its('open').should('be.calledOnce')
})
it("should add active/inactive vacancies", () => {
// Visit published app
cy.visit(`${Cypress.config().baseUrl}/app/` + templateNameParsed)
// loop for active/inactive vacancies
for (let i = 0; i < 2; i++) {
// Vacancies section
cy.get(".links").contains("Vacancies").click({ force: true })
cy.get(".spectrum-Button").contains("Create New").click()
// Add inactive vacancy
// Title
cy.get('[data-name="Title"]').within(() => {
cy.get(".spectrum-Textfield").type("Tester")
})
// Closing Date
cy.get('[data-name="Closing date"]').within(() => {
cy.get('[aria-label=Calendar]').click({ force: true })
})
cy.get("[aria-current=date]").click()
// Department
cy.get('[data-name="Department"]').within(() => {
cy.get(".spectrum-Picker-label").click()
})
cy.get(".spectrum-Menu").find('li').its('length').then(len => {
cy.get(".spectrum-Menu-item").eq(Math.floor(Math.random() * len)).click()
})
// Employment Type
cy.get('[data-name="Employment type"]').within(() => {
cy.get(".spectrum-Picker-label").click()
})
cy.get(".spectrum-Menu").find('li').its('length').then(len => {
cy.get(".spectrum-Menu-item").eq(Math.floor(Math.random() * len)).click()
})
// Salary
cy.get('[data-name="Salary ($)"]').within(() => {
cy.get(".spectrum-Textfield").type(40000)
})
// Description
cy.get('[data-name="Description"]').within(() => {
cy.get(".spectrum-Textfield").type("description")
})
// Responsibilities
cy.get('[data-name="Responsibilities"]').within(() => {
cy.get(".spectrum-Textfield").type("Responsibilities")
})
// Requirements
cy.get('[data-name="Requirements"]').within(() => {
cy.get(".spectrum-Textfield").type("Requirements")
})
// Hiring manager
cy.get('[data-name="Hiring manager"]').within(() => {
cy.get(".spectrum-Picker-label").click()
})
cy.get(".spectrum-Menu").find('li').its('length').then(len => {
cy.get(".spectrum-Menu-item").eq(Math.floor(Math.random() * len)).click()
})
// Active
if (i == 0) {
cy.get('[data-name="Active"]').within(() => {
cy.get(".spectrum-Checkbox-box").click({ force: true })
})
}
// Location
cy.get('[data-name="Location"]').within(() => {
cy.get(".spectrum-Picker-label").click()
})
cy.get(".spectrum-Menu").find('li').its('length').then(len => {
cy.get(".spectrum-Menu-item").eq(Math.floor(Math.random() * len)).click()
})
// Save vacancy
cy.get(".spectrum-Button").contains("Save").click({ force: true })
cy.wait(1000)
// Check table was updated
cy.get('[data-name="Vacancies Table"]').eq(i).should('contain', 'Tester')
}
})
xit("should filter applications by stage", () => {
// Visit published app
cy.visit(`${Cypress.config().baseUrl}/app/` + templateNameParsed)
cy.wait(1000)
// Applications section
cy.get(".links").contains("Applications").click({ force: true })
cy.wait(1000)
// Filter by stage - Confirm table updates
cy.get(".spectrum-Picker").contains("Filter by stage").click({ force: true })
cy.get(".spectrum-Menu").find('li').its('length').then(len => {
for (let i = 1; i < len; i++) {
cy.get(".spectrum-Menu-item").eq(i).click()
const stage = cy.get(".spectrum-Picker-label")
stage.invoke('text').then(stageText => {
if (stageText == "1st interview") {
cy.get(".placeholder").should('contain', 'No rows found')
}
else {
cy.get(".spectrum-Table-row").should('contain', stageText)
}
cy.get(".spectrum-Picker").contains(stageText).click({ force: true })
})
}
})
})
xit("should edit an application", () => {
// Switch application from not hired to hired
// Visit published app
cy.visit(`${Cypress.config().baseUrl}/app/` + templateNameParsed)
cy.wait(1000)
// Not Hired section
cy.get(".links").contains("Not hired").click({ force: true })
cy.wait(500)
// View application
cy.get(".spectrum-Table").within(() => {
cy.get(".spectrum-Button").contains("View").click({ force: true })
cy.wait(500)
})
// Update value for 'Staged'
cy.get('[data-name="Stage"]').within(() => {
cy.get(".spectrum-Picker-label").click()
})
cy.get(".spectrum-Menu").within(() => {
cy.get(".spectrum-Menu-item").contains("Hired").click()
})
// Save application
cy.get(".spectrum-Button").contains("Save").click({ force: true })
cy.wait(500)
// Hired section
cy.get(".links").contains("Hired").click({ force: true })
cy.wait(500)
// Verify Table size - Total rows = 2
cy.get(".spectrum-Table").find(".spectrum-Table-row").its('length').then((len => {
expect(len).to.eq(2)
}))
})
xit("should delete an application", () => {
// Visit published app
cy.visit(`${Cypress.config().baseUrl}/app/` + templateNameParsed)
cy.wait(1000)
// Hired section
cy.get(".links").contains("Hired").click({ force: true })
cy.wait(500)
// View first application
cy.get(".spectrum-Table-row").eq(0).within(() => {
cy.get(".spectrum-Button").contains("View").click({ force: true })
cy.wait(500)
})
// Delete application
cy.get(".spectrum-Button").contains("Delete").click({ force: true })
cy.get(".spectrum-Dialog-grid").within(() => {
cy.get(".spectrum-Button").contains("Confirm").click()
})
})
})
})

View file

@ -1,60 +0,0 @@
import filterTests from "../../../support/filterTests"
filterTests(["all"], () => {
context("Verify IT Template Details", () => {
before(() => {
cy.login()
// Template navigation
cy.visit(`${Cypress.config().baseUrl}/builder/portal/apps/templates`)
// Filter IT Templates
cy.get(".template-category-filters").within(() => {
cy.get('[data-cy="IT"]').click()
})
})
it("should verify the details option for IT templates", () => {
cy.get(".template-grid").find(".template-card").its('length')
.then(len => {
// Verify template name is within details link
for (let i = 0; i < len; i++) {
cy.get(".template-card").eq(i).within(() => {
const templateName = cy.get(".template-thumbnail-text")
templateName.invoke('text')
.then(templateNameText => {
const templateNameParsed = templateNameText.toLowerCase().replace(/\s+/g, '-')
if (templateNameText == "Hashicorp Scorecard Template") {
const templateNameSplit = templateNameParsed.split('-template')[0]
cy.get('a')
.should('have.attr', 'href').and('contain', templateNameSplit)
}
else if (templateNameText == "IT Ticketing System") {
const templateNameSplit = templateNameParsed.split('it-')[1]
cy.get('a')
.should('have.attr', 'href').and('contain', templateNameSplit)
}
else if (templateNameText == "IT Incident Report Form") {
const templateNameSplit = templateNameParsed.split('-form')[0]
cy.get('a')
.should('have.attr', 'href').and('contain', templateNameSplit)
}
else {
cy.get('a').should('have.attr', 'href').and('contain', templateNameParsed)
}
})
// Verify correct status from Details link - 200
cy.get('a')
.then(link => {
cy.request(link.prop('href'))
.its('status')
.should('eq', 200)
})
})
}
})
})
})
})

View file

@ -1,72 +0,0 @@
import filterTests from "../../../support/filterTests"
filterTests(["all"], () => {
context("IT Ticketing System Template Functionality", () => {
const templateName = "IT Ticketing System"
const templateNameParsed = templateName.toLowerCase().replace(/\s+/g, '-')
before(() => {
cy.login()
cy.deleteApp(templateName)
// Template navigation
cy.visit(`${Cypress.config().baseUrl}/builder/portal/apps/templates`, {
onBeforeLoad(win) {
cy.stub(win, 'open')
}
})
cy.wait(2000)
})
it("should create and publish app with IT Ticketing System template", () => {
// Select IT Ticketing System template
cy.get(".template-thumbnail-text")
.contains(templateName).parentsUntil(".template-grid").within(() => {
cy.get(".spectrum-Button").contains("Use template").click({ force: true })
})
// Confirm URL matches template name
const appUrl = cy.get(".app-server")
appUrl.invoke('text').then(appUrlText => {
expect(appUrlText).to.equal(`${Cypress.config().baseUrl}/app/` + templateNameParsed)
})
// Create App
cy.get(".spectrum-Dialog-grid").within(() => {
cy.get(".spectrum-Button").contains("Create app").click({ force: true })
})
// Publish App & Verify it opened
cy.wait(2000) // Wait for app to generate
cy.publishApp(true)
cy.window().its('open').should('be.calledOnce')
})
xit("should filter tickets by status", () => {
// Visit published app
cy.visit(`${Cypress.config().baseUrl}/app/` + templateNameParsed)
cy.wait(1000)
// Tickets section
cy.get(".links").contains("Tickets").click({ force: true })
cy.wait(1000)
// Filter by stage - Confirm table updates
cy.get(".spectrum-Picker").contains("Filter by status").click({ force: true })
cy.get(".spectrum-Menu").find('li').its('length').then(len => {
for (let i = 1; i < len; i++) {
cy.get(".spectrum-Menu-item").eq(i).click()
const stage = cy.get(".spectrum-Picker-label")
stage.invoke('text').then(stageText => {
if (stageText == "In progress" || stageText == "On hold" || stageText == "Triaged") {
cy.get(".placeholder").should('contain', 'No rows found')
}
else {
cy.get(".spectrum-Table-row").should('contain', stageText)
}
cy.get(".spectrum-Picker").contains(stageText).click({ force: true })
})
}
})
})
})
})

View file

@ -1,42 +0,0 @@
import filterTests from "../../../support/filterTests"
filterTests(["all"], () => {
context("Verify Admin Panel Template Details", () => {
before(() => {
cy.login()
// Template navigation
cy.visit(`${Cypress.config().baseUrl}/builder/portal/apps/templates`)
// Filter Admin Panels Templates
cy.get(".template-category-filters").within(() => {
cy.get('[data-cy="Admin Panels"]').click()
})
})
it("should verify the details option for Admin Panels templates", () => {
cy.get(".template-grid").find(".template-card").its('length')
.then(len => {
// Verify template name is within details link
for (let i = 0; i < len; i++) {
cy.get(".template-card").eq(i).within(() => {
const templateName = cy.get(".template-thumbnail-text")
templateName.invoke('text')
.then(templateNameText => {
const templateNameParsed = templateNameText.toLowerCase().replace(/\s+/g, '-')
cy.get('a').should('have.attr', 'href').and('contain', templateNameParsed)
})
// Verify correct status from Details link - 200
cy.get('a')
.then(link => {
cy.request(link.prop('href'))
.its('status')
.should('eq', 200)
})
})
}
})
})
})
})

View file

@ -1,51 +0,0 @@
import filterTests from "../../../support/filterTests"
filterTests(["all"], () => {
context("Verify Aproval Apps Template Details", () => {
before(() => {
cy.login()
// Template navigation
cy.visit(`${Cypress.config().baseUrl}/builder/portal/apps/templates`)
// Filter Approval Apps Templates
cy.get(".template-category-filters").within(() => {
cy.get('[data-cy="Approval Apps"]').click()
})
})
it("should verify the details option for Approval Apps templates", () => {
cy.get(".template-grid").find(".template-card").its('length')
.then(len => {
// Verify template name is within details link
for (let i = 0; i < len; i++) {
cy.get(".template-card").eq(i).within(() => {
const templateName = cy.get(".template-thumbnail-text")
templateName.invoke('text')
.then(templateNameText => {
const templateNameParsed = templateNameText.toLowerCase().replace(/\s+/g, '-')
if (templateNameText == "Content Approval System") {
// Template name should include 'content-approval'
const templateNameSplit = templateNameParsed.split('-system')[0]
cy.get('a')
.should('have.attr', 'href').and('contain', templateNameSplit)
}
else {
cy.get('a').should('have.attr', 'href').and('contain', templateNameParsed)
}
})
// Verify correct status from Details link - 200
cy.get('a')
.then(link => {
cy.request(link.prop('href'))
.its('status')
.should('eq', 200)
})
})
}
})
})
})
})

View file

@ -1,51 +0,0 @@
import filterTests from "../../../support/filterTests"
filterTests(["all"], () => {
context("Verify Business Apps Template Details", () => {
before(() => {
cy.login()
// Template navigation
cy.visit(`${Cypress.config().baseUrl}/builder/portal/apps/templates`)
// Filter Business Apps Templates
cy.get(".template-category-filters").within(() => {
cy.get('[data-cy="Business Apps"]').click()
})
})
it("should verify the details option for Business Apps templates", () => {
cy.get(".template-grid").find(".template-card").its('length')
.then(len => {
// Verify template name is within details link
for (let i = 0; i < len; i++) {
cy.get(".template-card").eq(i).within(() => {
const templateName = cy.get(".template-thumbnail-text")
templateName.invoke('text')
.then(templateNameText => {
const templateNameParsed = templateNameText.toLowerCase().replace(/\s+/g, '-')
if (templateNameText == "Employee Check-in/Check-Out Template") {
// Remove / from template name
const templateNameReplace = templateNameParsed.replace(/\//g, "-")
cy.get('a')
.should('have.attr', 'href').and('contain', templateNameReplace)
}
else {
cy.get('a').should('have.attr', 'href').and('contain', templateNameParsed)
}
})
// Verify correct status from Details link - 200
cy.get('a')
.then(link => {
cy.request(link.prop('href'))
.its('status')
.should('eq', 200)
})
})
}
})
})
})
})

View file

@ -1,44 +0,0 @@
import filterTests from "../../../support/filterTests"
filterTests(["all"], () => {
context("Verify Directories Template Details", () => {
before(() => {
cy.login()
// Template navigation
cy.visit(`${Cypress.config().baseUrl}/builder/portal/apps/templates`)
// Filter Directories Templates
cy.get(".template-category-filters").within(() => {
cy.get('[data-cy="Directories"]').click()
})
})
it("should verify the details option for Directories templates", () => {
cy.get(".template-grid").find(".template-card").its('length')
.then(len => {
// Verify template name is within details link
for (let i = 0; i < len; i++) {
cy.get(".template-card").eq(i).within(() => {
const templateName = cy.get(".template-thumbnail-text")
templateName.invoke('text')
.then(templateNameText => {
const templateNameParsed = templateNameText.toLowerCase().replace(/\s+/g, '-')
const templateNameSplit = templateNameParsed.split('-template')[0]
cy.get('a')
.should('have.attr', 'href').and('contain', templateNameSplit)
})
// Verify correct status from Details link - 200
cy.get('a')
.then(link => {
cy.request(link.prop('href'))
.its('status')
.should('eq', 200)
})
})
}
})
})
})
})

View file

@ -1,42 +0,0 @@
import filterTests from "../../../support/filterTests"
filterTests(["all"], () => {
context("Verify Forms Template Details", () => {
before(() => {
cy.login()
// Template navigation
cy.visit(`${Cypress.config().baseUrl}/builder/portal/apps/templates`)
// Filter Forms Templates
cy.get(".template-category-filters").within(() => {
cy.get('[data-cy="Forms"]').click()
})
})
it("should verify the details option for Forms templates", () => {
cy.get(".template-grid").find(".template-card").its('length')
.then(len => {
// Verify template name is within details link
for (let i = 0; i < len; i++) {
cy.get(".template-card").eq(i).within(() => {
const templateName = cy.get(".template-thumbnail-text")
templateName.invoke('text')
.then(templateNameText => {
const templateNameParsed = templateNameText.toLowerCase().replace(/\s+/g, '-')
cy.get('a').should('have.attr', 'href').and('contain', templateNameParsed)
})
// Verify correct status from Details link - 200
cy.get('a')
.then(link => {
cy.request(link.prop('href'))
.its('status')
.should('eq', 200)
})
})
}
})
})
})
})

View file

@ -1,43 +0,0 @@
import filterTests from "../../../support/filterTests"
filterTests(["all"], () => {
context("Verify Healthcare Template Details", () => {
before(() => {
cy.login()
// Template navigation
cy.visit(`${Cypress.config().baseUrl}/builder/portal/apps/templates`)
// Filter Healthcare Templates
cy.get(".template-category-filters").within(() => {
cy.get('[data-cy="Healthcare"]').click()
})
})
it("should verify the details option for Healthcare templates", () => {
cy.get(".template-grid").find(".template-card").its('length')
.then(len => {
// Verify template name is within details link
for (let i = 0; i < len; i++) {
cy.get(".template-card").eq(i).within(() => {
const templateName = cy.get(".template-thumbnail-text")
templateName.invoke('text')
.then(templateNameText => {
const templateNameParsed = templateNameText.toLowerCase().replace(/\s+/g, '-')
cy.get('a').should('have.attr', 'href').and('contain', templateNameParsed)
})
// Verify correct status from Details link - 200
cy.get('a')
.then(link => {
cy.request(link.prop('href'))
.its('status')
.should('eq', 200)
})
})
}
})
})
})
})

View file

@ -1,42 +0,0 @@
import filterTests from "../../../support/filterTests"
filterTests(["all"], () => {
context("Verify Legal Template Details", () => {
before(() => {
cy.login()
// Template navigation
cy.visit(`${Cypress.config().baseUrl}/builder/portal/apps/templates`)
// Filter Legal Templates
cy.get(".template-category-filters").within(() => {
cy.get('[data-cy="Legal"]').click()
})
})
it("should verify the details option for Legal templates", () => {
cy.get(".template-grid").find(".template-card").its('length')
.then(len => {
// Verify template name is within details link
for (let i = 0; i < len; i++) {
cy.get(".template-card").eq(i).within(() => {
const templateName = cy.get(".template-thumbnail-text")
templateName.invoke('text')
.then(templateNameText => {
const templateNameParsed = templateNameText.toLowerCase().replace(/\s+/g, '-')
cy.get('a').should('have.attr', 'href').and('contain', templateNameParsed)
})
// Verify correct status from Details link - 200
cy.get('a')
.then(link => {
cy.request(link.prop('href'))
.its('status')
.should('eq', 200)
})
})
}
})
})
})
})

View file

@ -1,42 +0,0 @@
import filterTests from "../../../support/filterTests"
filterTests(["all"], () => {
context("Verify Logistics Template Details", () => {
before(() => {
cy.login()
// Template navigation
cy.visit(`${Cypress.config().baseUrl}/builder/portal/apps/templates`)
// Filter Logistics Templates
cy.get(".template-category-filters").within(() => {
cy.get('[data-cy="Logistics"]').click()
})
})
it("should verify the details option for Logistics templates", () => {
cy.get(".template-grid").find(".template-card").its('length')
.then(len => {
// Verify template name is within details link
for (let i = 0; i < len; i++) {
cy.get(".template-card").eq(i).within(() => {
const templateName = cy.get(".template-thumbnail-text")
templateName.invoke('text')
.then(templateNameText => {
const templateNameParsed = templateNameText.toLowerCase().replace(/\s+/g, '-')
cy.get('a').should('have.attr', 'href').and('contain', templateNameParsed)
})
// Verify correct status from Details link - 200
cy.get('a')
.then(link => {
cy.request(link.prop('href'))
.its('status')
.should('eq', 200)
})
})
}
})
})
})
})

View file

@ -1,42 +0,0 @@
import filterTests from "../../../support/filterTests"
filterTests(["all"], () => {
context("Verify Manufacturing Template Details", () => {
before(() => {
cy.login()
// Template navigation
cy.visit(`${Cypress.config().baseUrl}/builder/portal/apps/templates`)
// Filter Manufacturing Templates
cy.get(".template-category-filters").within(() => {
cy.get('[data-cy="Manufacturing"]').click()
})
})
it("should verify the details option for Manufacturing templates", () => {
cy.get(".template-grid").find(".template-card").its('length')
.then(len => {
// Verify template name is within details link
for (let i = 0; i < len; i++) {
cy.get(".template-card").eq(i).within(() => {
const templateName = cy.get(".template-thumbnail-text")
templateName.invoke('text')
.then(templateNameText => {
const templateNameParsed = templateNameText.toLowerCase().replace(/\s+/g, '-')
cy.get('a').should('have.attr', 'href').and('contain', templateNameParsed)
})
// Verify correct status from Details link - 200
cy.get('a')
.then(link => {
cy.request(link.prop('href'))
.its('status')
.should('eq', 200)
})
})
}
})
})
})
})

View file

@ -1,44 +0,0 @@
import filterTests from "../../../support/filterTests"
filterTests(["all"], () => {
context("Lead Generation Form Template Functionality", () => {
const templateName = "Lead Generation Form"
const templateNameParsed = templateName.toLowerCase().replace(/\s+/g, '-')
before(() => {
cy.login()
cy.deleteApp(templateName)
// Template navigation
cy.visit(`${Cypress.config().baseUrl}/builder/portal/apps/templates`, {
onBeforeLoad(win) {
cy.stub(win, 'open')
}
})
cy.wait(2000)
})
it("should create and publish app with Lead Generation Form template", () => {
// Select Lead Generation Form template
cy.get(".template-thumbnail-text")
.contains(templateName).parentsUntil(".template-grid").within(() => {
cy.get(".spectrum-Button").contains("Use template").click({ force: true })
})
// Confirm URL matches template name
const appUrl = cy.get(".app-server")
appUrl.invoke('text').then(appUrlText => {
expect(appUrlText).to.equal(`${Cypress.config().baseUrl}/app/` + templateNameParsed)
})
// Create App
cy.get(".spectrum-Dialog-grid").within(() => {
cy.get(".spectrum-Button").contains("Create app").click({ force: true })
})
// Publish App & Verify it opened
cy.wait(2000) // Wait for app to generate
cy.publishApp(true)
cy.window().its('open').should('be.calledOnce')
})
})
})

View file

@ -1,51 +0,0 @@
import filterTests from "../../../support/filterTests"
filterTests(["all"], () => {
context("Verify Marketing Template Details", () => {
before(() => {
cy.login()
// Template navigation
cy.visit(`${Cypress.config().baseUrl}/builder/portal/apps/templates`)
// Filter Marketing Templates
cy.get(".template-category-filters").within(() => {
cy.get('[data-cy="Marketing"]').click()
})
})
it("should verify the details option for Marketing templates", () => {
cy.get(".template-grid").find(".template-card").its('length')
.then(len => {
// Verify template name is within details link
for (let i = 0; i < len; i++) {
cy.get(".template-card").eq(i).within(() => {
const templateName = cy.get(".template-thumbnail-text")
templateName.invoke('text')
.then(templateNameText => {
const templateNameParsed = templateNameText.toLowerCase().replace(/\s+/g, '-')
if (templateNameText == "Lead Generation Form") {
// Multi-step lead form
// Template name includes 'multi-step-lead-form'
cy.get('a')
.should('have.attr', 'href').and('contain', 'multi-step-lead-form')
}
else {
cy.get('a').should('have.attr', 'href').and('contain', templateNameParsed)
}
})
// Verify correct status from Details link - 200
cy.get('a')
.then(link => {
cy.request(link.prop('href'))
.its('status')
.should('eq', 200)
})
})
}
})
})
})
})

View file

@ -1,42 +0,0 @@
import filterTests from "../../../support/filterTests"
filterTests(["all"], () => {
context("Verify Operations Template Details", () => {
before(() => {
cy.login()
// Template navigation
cy.visit(`${Cypress.config().baseUrl}/builder/portal/apps/templates`)
// Filter Operations Templates
cy.get(".template-category-filters").within(() => {
cy.get('[data-cy="Operations"]').click()
})
})
it("should verify the details option for Operations templates", () => {
cy.get(".template-grid").find(".template-card").its('length')
.then(len => {
// Verify template name is within details link
for (let i = 0; i < len; i++) {
cy.get(".template-card").eq(i).within(() => {
const templateName = cy.get(".template-thumbnail-text")
templateName.invoke('text')
.then(templateNameText => {
const templateNameParsed = templateNameText.toLowerCase().replace(/\s+/g, '-')
cy.get('a').should('have.attr', 'href').and('contain', templateNameParsed)
})
// Verify correct status from Details link - 200
cy.get('a')
.then(link => {
cy.request(link.prop('href'))
.its('status')
.should('eq', 200)
})
})
}
})
})
})
})

View file

@ -1,71 +0,0 @@
import filterTests from "../../../support/filterTests"
filterTests(["all"], () => {
context("Verify Portals Template Details", () => {
before(() => {
cy.login()
// Template navigation
cy.visit(`${Cypress.config().baseUrl}/builder/portal/apps/templates`)
// Filter Portal Templates
cy.get(".template-category-filters").within(() => {
cy.get('[data-cy="Portal"]').click()
})
})
it("should verify the details option for Portal templates", () => {
cy.get(".template-grid").find(".template-card").its('length')
.then(len => {
for (let i = 0; i < len; i++) {
cy.get(".template-card").eq(i).within(() => {
const templateName = cy.get(".template-thumbnail-text")
templateName.invoke('text')
.then(templateNameText => {
const templateNameParsed = templateNameText.toLowerCase().replace(/\s+/g, '-')
cy.get('a')
.should('have.attr', 'href').and('contain', templateNameParsed)
})
// Verify correct status from Details link - 200
cy.get('a')
.then(link => {
cy.request(link.prop('href'))
.its('status')
.should('eq', 200)
})
})
}
})
})
it("should verify the details option for Portals templates", () => {
// Filter Portals Templates
cy.get(".template-category-filters").within(() => {
cy.get('[data-cy="Portals"]').click()
})
cy.get(".template-grid").find(".template-card").its('length')
.then(len => {
for (let i = 0; i < len; i++) {
cy.get(".template-card").eq(i).within(() => {
const templateName = cy.get(".template-thumbnail-text")
templateName.invoke('text')
.then(templateNameText => {
const templateNameParsed = templateNameText.toLowerCase().replace(/\s+/g, '-')
cy.get('a')
.should('have.attr', 'href').and('contain', templateNameParsed)
})
// Verify correct status from Details link - 200
cy.get('a')
.then(link => {
cy.request(link.prop('href'))
.its('status')
.should('eq', 200)
})
})
}
})
})
})
})

View file

@ -1,42 +0,0 @@
import filterTests from "../../../support/filterTests"
filterTests(["all"], () => {
context("Verify Professional Services Template Details", () => {
before(() => {
cy.login()
// Template navigation
cy.visit(`${Cypress.config().baseUrl}/builder/portal/apps/templates`)
// Filter Professional Services Templates
cy.get(".template-category-filters").within(() => {
cy.get('[data-cy="Professional Services"]').click()
})
})
it("should verify the details option for Professional Services templates", () => {
cy.get(".template-grid").find(".template-card").its('length')
.then(len => {
// Verify template name is within details link
for (let i = 0; i < len; i++) {
cy.get(".template-card").eq(i).within(() => {
const templateName = cy.get(".template-thumbnail-text")
templateName.invoke('text')
.then(templateNameText => {
const templateNameParsed = templateNameText.toLowerCase().replace(/\s+/g, '-')
cy.get('a').should('have.attr', 'href').and('contain', templateNameParsed)
})
// Verify correct status from Details link - 200
cy.get('a')
.then(link => {
cy.request(link.prop('href'))
.its('status')
.should('eq', 200)
})
})
}
})
})
})
})

View file

@ -1,14 +1,8 @@
// ***********************************************
// For more comprehensive examples of custom
// commands please read more here:
// https://on.cypress.io/custom-commands
// ***********************************************
//
Cypress.on("uncaught:exception", () => { Cypress.on("uncaught:exception", () => {
return false return false
}) })
// ACCOUNTS & USERS
Cypress.Commands.add("login", () => { Cypress.Commands.add("login", () => {
cy.visit(`${Cypress.config().baseUrl}/builder`) cy.visit(`${Cypress.config().baseUrl}/builder`)
cy.wait(2000) cy.wait(2000)
@ -41,43 +35,20 @@ Cypress.Commands.add("logOut", () => {
cy.wait(2000) cy.wait(2000)
}) })
Cypress.Commands.add("closeModal", () => { Cypress.Commands.add("createUser", email => {
cy.get(".spectrum-Modal").within(() => { // quick hacky recorded way to create a user
cy.get(".close-icon").click() cy.contains("Users").click()
cy.wait(1000) // Wait for modal to close cy.get(`[data-cy="add-user"]`).click()
}) cy.get(".spectrum-Picker-label").click()
}) cy.get(".spectrum-Menu-item:nth-child(2) > .spectrum-Menu-itemLabel").click()
Cypress.Commands.add("importApp", (exportFilePath, name) => { //Onboarding type selector
cy.visit(`${Cypress.config().baseUrl}/builder`) cy.get(
":nth-child(2) > .spectrum-Form-itemField > .spectrum-Textfield > .spectrum-Textfield-input"
cy.request(`${Cypress.config().baseUrl}/api/applications?status=all`) )
.its("body") .first()
.then(val => { .type(email, { force: true })
if (val.length > 0) { cy.get(".spectrum-Button--cta").click({ force: true })
cy.get(`[data-cy="create-app-btn"]`).click({ force: true })
cy.wait(500)
}
cy.get(`[data-cy="import-app-btn"]`).click({ force: true })
})
cy.get(".spectrum-Modal").within(() => {
cy.get("input").eq(1).should("have.focus")
cy.get(".spectrum-Dropzone").selectFile(exportFilePath, {
action: "drag-drop",
})
cy.get(".gallery .filename").contains("exported-app.txt")
if (name && name != "") {
cy.get("input").eq(0).type(name).should("have.value", name).blur()
}
cy.get(".confirm-wrap button")
.should("not.be.disabled")
.click({ force: true })
cy.wait(5000)
})
}) })
Cypress.Commands.add("updateUserInformation", (firstName, lastName) => { Cypress.Commands.add("updateUserInformation", (firstName, lastName) => {
@ -113,6 +84,13 @@ Cypress.Commands.add("updateUserInformation", (firstName, lastName) => {
}) })
}) })
// APPLICATIONS
Cypress.Commands.add("createTestApp", () => {
const appName = "Cypress Tests"
cy.deleteApp(appName)
cy.createApp(appName, "This app is used for Cypress testing.")
})
Cypress.Commands.add("createApp", (name, addDefaultTable) => { Cypress.Commands.add("createApp", (name, addDefaultTable) => {
const shouldCreateDefaultTable = const shouldCreateDefaultTable =
typeof addDefaultTable != "boolean" ? true : addDefaultTable typeof addDefaultTable != "boolean" ? true : addDefaultTable
@ -218,57 +196,6 @@ Cypress.Commands.add("deleteAllApps", () => {
}) })
}) })
Cypress.Commands.add("customiseAppIcon", () => {
// Select random icon
cy.get(".grid").within(() => {
cy.get(".icon-item")
.eq(Math.floor(Math.random() * 23) + 1)
.click()
})
// Select random colour
cy.get(".fill").click()
cy.get(".colors").within(() => {
cy.get(".color")
.eq(Math.floor(Math.random() * 33) + 1)
.click()
})
cy.intercept("**/applications/**").as("iconChange")
cy.get(".spectrum-Button").contains("Save").click({ force: true })
cy.wait("@iconChange")
cy.get("@iconChange").its("response.statusCode").should("eq", 200)
cy.wait(1000)
})
Cypress.Commands.add("alterAppVersion", (appId, version) => {
return cy
.request("put", `${Cypress.config().baseUrl}/api/applications/${appId}`, {
version: version || "0.0.1-alpha.0",
})
.then(resp => {
expect(resp.status).to.eq(200)
})
})
Cypress.Commands.add("updateAppName", (changedName, noName) => {
cy.get(".spectrum-Modal").within(() => {
if (noName == true) {
cy.get("input").clear()
cy.get(".spectrum-Dialog-grid")
.click()
.contains("App name must be letters, numbers and spaces only")
return cy
}
cy.get("input").clear()
cy.get("input")
.eq(0)
.type(changedName)
.should("have.value", changedName)
.blur()
cy.get(".spectrum-ButtonGroup").contains("Save").click({ force: true })
cy.wait(500)
})
})
Cypress.Commands.add("unlockApp", unlock_config => { Cypress.Commands.add("unlockApp", unlock_config => {
let config = { ...unlock_config } let config = { ...unlock_config }
@ -298,6 +225,26 @@ Cypress.Commands.add("unlockApp", unlock_config => {
}) })
}) })
Cypress.Commands.add("updateAppName", (changedName, noName) => {
cy.get(".spectrum-Modal").within(() => {
if (noName == true) {
cy.get("input").clear()
cy.get(".spectrum-Dialog-grid")
.click()
.contains("App name must be letters, numbers and spaces only")
return cy
}
cy.get("input").clear()
cy.get("input")
.eq(0)
.type(changedName)
.should("have.value", changedName)
.blur()
cy.get(".spectrum-ButtonGroup").contains("Save").click({ force: true })
cy.wait(500)
})
})
Cypress.Commands.add("publishApp", resolvedAppPath => { Cypress.Commands.add("publishApp", resolvedAppPath => {
//Assumes you have navigated to an application first //Assumes you have navigated to an application first
cy.get(".toprightnav button.spectrum-Button") cy.get(".toprightnav button.spectrum-Button")
@ -321,34 +268,96 @@ Cypress.Commands.add("publishApp", resolvedAppPath => {
}) })
}) })
Cypress.Commands.add("createTestApp", () => { Cypress.Commands.add("alterAppVersion", (appId, version) => {
const appName = "Cypress Tests" return cy
cy.deleteApp(appName) .request("put", `${Cypress.config().baseUrl}/api/applications/${appId}`, {
cy.createApp(appName, "This app is used for Cypress testing.") version: version || "0.0.1-alpha.0",
//cy.createScreen("home") })
.then(resp => {
expect(resp.status).to.eq(200)
})
}) })
Cypress.Commands.add("createTestTableWithData", () => { Cypress.Commands.add("importApp", (exportFilePath, name) => {
cy.createTable("dog") cy.visit(`${Cypress.config().baseUrl}/builder`)
cy.addColumn("dog", "name", "Text")
cy.addColumn("dog", "age", "Number")
})
Cypress.Commands.add("publishApp", (viewApp = false) => { cy.request(`${Cypress.config().baseUrl}/api/applications?status=all`)
cy.get(".toprightnav").contains("Publish").click({ force: true }) .its("body")
cy.get(".spectrum-Dialog-grid").within(() => { .then(val => {
cy.get(".spectrum-Button").contains("Publish").click({ force: true }) if (val.length > 0) {
}) cy.get(`[data-cy="create-app-btn"]`).click({ force: true })
cy.wait(2000) // Wait for App to publish and modal to appear cy.wait(500)
cy.get(".spectrum-Dialog-grid").within(() => { }
if (viewApp) { cy.get(`[data-cy="import-app-btn"]`).click({ force: true })
cy.get(".spectrum-Button").contains("View App").click({ force: true }) })
} else {
cy.get(".spectrum-Button").contains("Done").click({ force: true }) cy.get(".spectrum-Modal").within(() => {
cy.get("input").eq(1).should("have.focus")
cy.get(".spectrum-Dropzone").selectFile(exportFilePath, {
action: "drag-drop",
})
cy.get(".gallery .filename").contains("exported-app.txt")
if (name && name != "") {
cy.get("input").eq(0).type(name).should("have.value", name).blur()
} }
cy.get(".confirm-wrap button")
.should("not.be.disabled")
.click({ force: true })
cy.wait(5000)
}) })
}) })
// Filters visible with 1 or more
Cypress.Commands.add("searchForApplication", appName => {
cy.visit(`${Cypress.config().baseUrl}/builder`)
cy.wait(2000)
// No app filter functionality if only 1 app exists
cy.request(`${Cypress.config().baseUrl}/api/applications?status=all`)
.its("body")
.then(val => {
if (val.length < 2) {
return
} else {
// Searches for the app
cy.get(".filter").then(() => {
cy.get(".spectrum-Textfield").within(() => {
cy.get("input").eq(0).clear()
cy.get("input").eq(0).type(appName)
})
})
}
})
})
// Assumes there are no others
Cypress.Commands.add("applicationInAppTable", appName => {
cy.get(".appTable").within(() => {
cy.get(".title").contains(appName).should("exist")
})
})
Cypress.Commands.add("createAppFromScratch", appName => {
cy.get(`[data-cy="create-app-btn"]`)
.contains("Start from scratch")
.click({ force: true })
cy.get(".spectrum-Modal").within(() => {
cy.get("input")
.eq(0)
.clear()
.type(appName)
.should("have.value", appName)
.blur()
cy.get(".spectrum-ButtonGroup").contains("Create app").click()
cy.wait(10000)
})
cy.createTable("Cypress Tests", true)
})
// TABLES
Cypress.Commands.add("createTable", (tableName, initialTable) => { Cypress.Commands.add("createTable", (tableName, initialTable) => {
if (!initialTable) { if (!initialTable) {
cy.navigateToDataSection() cy.navigateToDataSection()
@ -369,6 +378,12 @@ Cypress.Commands.add("createTable", (tableName, initialTable) => {
cy.contains(tableName).should("be.visible") cy.contains(tableName).should("be.visible")
}) })
Cypress.Commands.add("createTestTableWithData", () => {
cy.createTable("dog")
cy.addColumn("dog", "name", "Text")
cy.addColumn("dog", "age", "Number")
})
Cypress.Commands.add( Cypress.Commands.add(
"addColumn", "addColumn",
(tableName, columnName, type, multiOptions = null) => { (tableName, columnName, type, multiOptions = null) => {
@ -423,22 +438,33 @@ Cypress.Commands.add("addRowMultiValue", values => {
}) })
}) })
Cypress.Commands.add("createUser", email => { Cypress.Commands.add("selectTable", tableName => {
// quick hacky recorded way to create a user cy.expandBudibaseConnection()
cy.contains("Users").click() cy.contains(".nav-item", tableName).click()
cy.get(`[data-cy="add-user"]`).click()
cy.get(".spectrum-Picker-label").click()
cy.get(".spectrum-Menu-item:nth-child(2) > .spectrum-Menu-itemLabel").click()
//Onboarding type selector
cy.get(
":nth-child(2) > .spectrum-Form-itemField > .spectrum-Textfield > .spectrum-Textfield-input"
)
.first()
.type(email, { force: true })
cy.get(".spectrum-Button--cta").click({ force: true })
}) })
Cypress.Commands.add("addCustomSourceOptions", totalOptions => {
cy.get(".spectrum-ActionButton")
.contains("Define Options")
.click()
.then(() => {
for (let i = 0; i < totalOptions; i++) {
// Add radio button options
cy.get(".spectrum-Button")
.contains("Add Option")
.click({ force: true })
.then(() => {
cy.wait(500)
cy.get("[placeholder='Label']").eq(i).type(i)
cy.get("[placeholder='Value']").eq(i).type(i)
})
}
// Save options
cy.get(".spectrum-Button").contains("Save").click({ force: true })
})
})
// DESIGN AREA
Cypress.Commands.add("addComponent", (category, component) => { Cypress.Commands.add("addComponent", (category, component) => {
if (category) { if (category) {
cy.get(`[data-cy="category-${category}"]`).click({ force: true }) cy.get(`[data-cy="category-${category}"]`).click({ force: true })
@ -466,22 +492,8 @@ Cypress.Commands.add("getComponent", componentId => {
.find(`[data-id=${componentId}]`) .find(`[data-id=${componentId}]`)
}) })
Cypress.Commands.add("navigateToFrontend", () => {
// Clicks on Design tab and then the Home nav item
cy.wait(1000)
cy.contains("Design").click()
cy.get(".spectrum-Search").type("/")
cy.get(".nav-item").contains("home").click()
})
Cypress.Commands.add("navigateToDataSection", () => {
// Clicks on the Data tab
cy.wait(500)
cy.contains("Data").click()
})
//Blank
Cypress.Commands.add("createScreen", (route, accessLevelLabel) => { Cypress.Commands.add("createScreen", (route, accessLevelLabel) => {
// Blank Screen
cy.contains("Design").click() cy.contains("Design").click()
cy.get("[aria-label=AddCircle]").click() cy.get("[aria-label=AddCircle]").click()
cy.get(".spectrum-Modal").within(() => { cy.get(".spectrum-Modal").within(() => {
@ -541,17 +553,6 @@ Cypress.Commands.add(
} }
) )
Cypress.Commands.add("navigateToAutogeneratedModal", () => {
// Screen name must already exist within data source
cy.contains("Design").click()
cy.get("[aria-label=AddCircle]").click()
cy.get(".spectrum-Modal").within(() => {
cy.get(".item").contains("Autogenerated screens").click()
cy.get(".spectrum-Button").contains("Continue").click({ force: true })
cy.wait(500)
})
})
Cypress.Commands.add( Cypress.Commands.add(
"createAutogeneratedScreens", "createAutogeneratedScreens",
(screenNames, accessLevelLabel) => { (screenNames, accessLevelLabel) => {
@ -573,96 +574,33 @@ Cypress.Commands.add(
} }
) )
Cypress.Commands.add("addRow", values => { // NAVIGATION
cy.contains("Create row").click() Cypress.Commands.add("navigateToFrontend", () => {
// Clicks on Design tab and then the Home nav item
cy.wait(1000)
cy.contains("Design").click()
cy.get(".spectrum-Search").type("/")
cy.get(".nav-item").contains("home").click()
})
Cypress.Commands.add("navigateToDataSection", () => {
// Clicks on the Data tab
cy.wait(500)
cy.contains("Data").click()
})
Cypress.Commands.add("navigateToAutogeneratedModal", () => {
// Screen name must already exist within data source
cy.contains("Design").click()
cy.get("[aria-label=AddCircle]").click()
cy.get(".spectrum-Modal").within(() => { cy.get(".spectrum-Modal").within(() => {
for (let i = 0; i < values.length; i++) { cy.get(".item").contains("Autogenerated screens").click()
cy.get("input").eq(i).type(values[i]).blur() cy.get(".spectrum-Button").contains("Continue").click({ force: true })
} cy.wait(500)
cy.get(".spectrum-ButtonGroup").contains("Create").click()
}) })
}) })
Cypress.Commands.add("expandBudibaseConnection", () => { // DATASOURCES
if (Cypress.$(".nav-item > .content > .opened").length === 0) {
// expand the Budibase DB connection string
cy.get(".icon.arrow").eq(0).click()
}
})
Cypress.Commands.add("selectTable", tableName => {
cy.expandBudibaseConnection()
cy.contains(".nav-item", tableName).click()
})
Cypress.Commands.add("addCustomSourceOptions", totalOptions => {
cy.get(".spectrum-ActionButton")
.contains("Define Options")
.click()
.then(() => {
for (let i = 0; i < totalOptions; i++) {
// Add radio button options
cy.get(".spectrum-Button")
.contains("Add Option")
.click({ force: true })
.then(() => {
cy.wait(500)
cy.get("[placeholder='Label']").eq(i).type(i)
cy.get("[placeholder='Value']").eq(i).type(i)
})
}
// Save options
cy.get(".spectrum-Button").contains("Save").click({ force: true })
})
})
//Filters visible with 1 or more
Cypress.Commands.add("searchForApplication", appName => {
cy.visit(`${Cypress.config().baseUrl}/builder`)
cy.wait(2000)
// No app filter functionality if only 1 app exists
cy.request(`${Cypress.config().baseUrl}/api/applications?status=all`)
.its("body")
.then(val => {
if (val.length < 2) {
return
} else {
// Searches for the app
cy.get(".filter").then(() => {
cy.get(".spectrum-Textfield").within(() => {
cy.get("input").eq(0).clear()
cy.get("input").eq(0).type(appName)
})
})
}
})
})
//Assumes there are no others
Cypress.Commands.add("applicationInAppTable", appName => {
cy.get(".appTable").within(() => {
cy.get(".title").contains(appName).should("exist")
})
})
Cypress.Commands.add("createAppFromScratch", appName => {
cy.get(`[data-cy="create-app-btn"]`)
.contains("Start from scratch")
.click({ force: true })
cy.get(".spectrum-Modal").within(() => {
cy.get("input")
.eq(0)
.clear()
.type(appName)
.should("have.value", appName)
.blur()
cy.get(".spectrum-ButtonGroup").contains("Create app").click()
cy.wait(10000)
})
cy.createTable("Cypress Tests", true)
})
Cypress.Commands.add("selectExternalDatasource", datasourceName => { Cypress.Commands.add("selectExternalDatasource", datasourceName => {
// Navigates to Data Section // Navigates to Data Section
cy.navigateToDataSection() cy.navigateToDataSection()
@ -798,3 +736,18 @@ Cypress.Commands.add("createRestQuery", (method, restUrl, queryPrettyName) => {
.should("contain", method) .should("contain", method)
.and("contain", queryPrettyName) .and("contain", queryPrettyName)
}) })
// MISC
Cypress.Commands.add("closeModal", () => {
cy.get(".spectrum-Modal").within(() => {
cy.get(".close-icon").click()
cy.wait(1000) // Wait for modal to close
})
})
Cypress.Commands.add("expandBudibaseConnection", () => {
if (Cypress.$(".nav-item > .content > .opened").length === 0) {
// expand the Budibase DB connection string
cy.get(".icon.arrow").eq(0).click()
}
})

View file

@ -17,10 +17,48 @@ export const CATEGORY_DATA = '[data-cy="category-Data"]'
export const COMPONENT_DATA_PROVIDER = '[data-cy="component-Data Provider"]' export const COMPONENT_DATA_PROVIDER = '[data-cy="component-Data Provider"]'
export const DATASOURCE_PROP_CONTROL = '[data-cy="dataSource-prop-control"]' export const DATASOURCE_PROP_CONTROL = '[data-cy="dataSource-prop-control"]'
export const DROPDOWN = ".dropdown" export const DROPDOWN = ".dropdown"
export const SPECTRUM_Picker_LABEL = ".spectrum-Picker-label" export const SPECTRUM_PICKER_LABEL = ".spectrum-Picker-label"
export const DATASOURCE_FIELD_CONTROL = '[data-cy="field-prop-control"]' export const DATASOURCE_FIELD_CONTROL = '[data-cy="field-prop-control"]'
export const OPTION_TYPE_PROP_CONTROL = '[data-cy="optionsType-prop-control' export const OPTION_TYPE_PROP_CONTROL = '[data-cy="optionsType-prop-control'
//AddRadioButtons //AddRadioButtons
export const SPECTRUM_POPOVER = ".spectrum-Popover" export const SPECTRUM_POPOVER = ".spectrum-Popover"
export const OPTION_SOURCE_PROP_CONROL = '[data-cy="optionsSource-prop-control' export const OPTION_SOURCE_PROP_CONROL = '[data-cy="optionsSource-prop-control'
export const APP_TABLE_STATUS = ".appTable .app-status"
export const APP_TABLE_ROW_ACTION = ".appTable .app-row-actions"
export const APP_TABLE_APP_NAME = '[data-cy="app-name-link"]'
export const DEPLOYMENT_TOP_NAV_GLOBESTRIKE =
".deployment-top-nav svg[aria-label=GlobeStrike]"
export const DEPLOYMENT_TOP_GLOBE = ".deployment-top-nav svg[aria-label=Globe]"
export const PUBLISH_POPOVER_MENU = '[data-cy="publish-popover-menu"]'
export const PUBLISH_POPOVER_ACTION = '[data-cy="publish-popover-action"]'
export const PUBLISH_POPOVER_MESSAGE = ".publish-popover-message"
export const SPECTRUM_BUTTON = ".spectrum-Button"
export const SPECTRUM_LINK = ".spectrum-Link"
export const TOPRIGHTNAV_BUTTON_SPECTRUM = ".toprightnav button.spectrum-Button"
//createComponents
export const SETTINGS = "[data-cy=Settings]"
export const SETTINGS_INPUT = "[data-cy=setting-text] input"
export const DESIGN = "[data-cy=Design]"
export const FONT_SIZE_PROP_CONTROL = "[data-cy=font-size-prop-control]"
export const DATA_CY_DATASOURCE = "[data-cy=setting-dataSource]"
export const DROPDOWN_CONTAINER = ".dropdown-container"
export const SPECTRUM_PICKER = ".spectrum-Picker"
//autoScreens
export const LABEL_ADD_CIRCLE = "[aria-label=AddCircle]"
export const ITEM_DISABLED = ".item.disabled"
export const CONFIRM_WRAP_SPE_BUTTON = ".confirm-wrap .spectrum-Button"
export const DATA_SOURCE_ENTRY = ".data-source-entry"
export const NAV_ITEMS_CONTAINER = ".nav-items-container"
//publishWorkFlow
export const DEPLOY_APP_MODAL = ".spectrum-Modal [data-cy=deploy-app-modal]"
export const DEPLOY_SUCCESS_MODAL =
".spectrum-Modal [data-cy=deploy-app-success-modal]"
export const DEPLOY_APP_URL_INPUT = "[data-cy=deployed-app-url] input"
export const GLOBESTRIKE = "svg[aria-label=GlobeStrike]"
export const GLOBE = "svg[aria-label=Globe]"
export const UNPUBLISH_MODAL = "[data-cy=unpublish-modal]"
export const CONFIRM_WRAP_BUTTON = ".confirm-wrap button"

View file

@ -1,6 +1,6 @@
{ {
"name": "@budibase/builder", "name": "@budibase/builder",
"version": "1.0.178-alpha.0", "version": "1.0.192-alpha.5",
"license": "GPL-3.0", "license": "GPL-3.0",
"private": true, "private": true,
"scripts": { "scripts": {
@ -69,10 +69,10 @@
} }
}, },
"dependencies": { "dependencies": {
"@budibase/bbui": "^1.0.178-alpha.0", "@budibase/bbui": "^1.0.192-alpha.5",
"@budibase/client": "^1.0.178-alpha.0", "@budibase/client": "^1.0.192-alpha.5",
"@budibase/frontend-core": "^1.0.178-alpha.0", "@budibase/frontend-core": "^1.0.192-alpha.5",
"@budibase/string-templates": "^1.0.178-alpha.0", "@budibase/string-templates": "^1.0.192-alpha.5",
"@sentry/browser": "5.19.1", "@sentry/browser": "5.19.1",
"@spectrum-css/page": "^3.0.1", "@spectrum-css/page": "^3.0.1",
"@spectrum-css/vars": "^3.0.1", "@spectrum-css/vars": "^3.0.1",

View file

@ -25,7 +25,6 @@
let blockComplete let blockComplete
let showLooping = false let showLooping = false
$: rowControl = $automationStore.selectedAutomation.automation.rowControl
$: showBindingPicker = $: showBindingPicker =
block.stepId === "CREATE_ROW" || block.stepId === "UPDATE_ROW" block.stepId === "CREATE_ROW" || block.stepId === "UPDATE_ROW"
@ -194,7 +193,7 @@
on:change={toggleFieldControl} on:change={toggleFieldControl}
defaultValue="Use values" defaultValue="Use values"
autoWidth autoWidth
value={rowControl ? "Use bindings" : "Use values"} value={block.rowControl ? "Use bindings" : "Use values"}
options={["Use values", "Use bindings"]} options={["Use values", "Use bindings"]}
placeholder={null} placeholder={null}
/> />

View file

@ -304,7 +304,9 @@
) )
} }
const newError = {} const newError = {}
if (PROHIBITED_COLUMN_NAMES.some(name => fieldInfo.name === name)) { if (!external && fieldInfo.name?.startsWith("_")) {
newError.name = `Column name cannot start with an underscore.`
} else if (PROHIBITED_COLUMN_NAMES.some(name => fieldInfo.name === name)) {
newError.name = `${PROHIBITED_COLUMN_NAMES.join( newError.name = `${PROHIBITED_COLUMN_NAMES.join(
", " ", "
)} are not allowed as column names` )} are not allowed as column names`

View file

@ -0,0 +1,50 @@
<script>
export let width = "100"
export let height = "100"
</script>
<svg
{width}
{height}
viewBox="-2 -2 48 48"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<g id="Page-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
<g
id="Group"
transform="translate(-0.002027, 0.531250)"
fill="#29B5E8"
fill-rule="nonzero"
>
<path
d="M37.2637465,33.128906 L28.0879655,27.828125 C26.7989025,27.085938 25.1504655,27.527344 24.4043715,28.816406 C24.1153085,29.324219 24.0020275,29.882812 24.0567155,30.425781 L24.0567155,40.785156 C24.0567155,42.265625 25.2598395,43.46875 26.7442155,43.46875 C28.2246835,43.46875 29.4278085,42.265625 29.4278085,40.785156 L29.4278085,34.828125 L34.5684335,37.796875 C35.8574965,38.542969 37.5098395,38.097656 38.2520275,36.808594 C38.9981215,35.519531 38.5567155,33.871094 37.2637465,33.128906"
id="Path"
/>
<path
d="M14.4434335,21.769531 C14.4590585,20.8125 13.9551525,19.921875 13.1270275,19.441406 L3.95124649,14.144531 C3.55280849,13.914062 3.09577749,13.792969 2.63874649,13.792969 C1.69733949,13.792969 0.822339495,14.296875 0.353589495,15.109375 C-0.372972505,16.367188 0.060621495,17.980469 1.31843349,18.707031 L6.60749649,21.757812 L1.31843349,24.8125 C0.709058495,25.164062 0.271558495,25.730469 0.091871495,26.410156 C-0.091722505,27.089844 0.00202749496,27.800781 0.353589495,28.410156 C0.822339495,29.222656 1.69733949,29.726562 2.63483949,29.726562 C3.09577749,29.726562 3.55280849,29.605469 3.95124649,29.375 L13.1270275,24.078125 C13.9473395,23.601562 14.4512465,22.71875 14.4434335,21.769531"
id="Path2"
/>
<path
d="M6.03327749,10.390625 L15.2090585,15.6875 C16.2793715,16.308594 17.5996835,16.105469 18.4434335,15.28125 C18.9785895,14.789062 19.3106215,14.085938 19.3106215,13.304688 L19.3106215,2.6875 C19.3106215,1.203125 18.1074965,0 16.6270275,0 C15.1426525,0 13.9395275,1.203125 13.9395275,2.6875 L13.9395275,8.730469 L8.72858949,5.722656 C7.43952749,4.976562 5.79108949,5.417969 5.04499649,6.707031 C4.29890249,7.996094 4.74421549,9.644531 6.03327749,10.390625"
id="Path3"
/>
<path
d="M26.6660895,22.199219 C26.6660895,22.402344 26.5489025,22.683594 26.4043715,22.832031 L22.7676525,26.46875 C22.6231215,26.613281 22.3379655,26.730469 22.1348395,26.730469 L21.2090585,26.730469 C21.0059335,26.730469 20.7207775,26.613281 20.5762465,26.46875 L16.9356215,22.832031 C16.7910895,22.683594 16.6739025,22.402344 16.6739025,22.199219 L16.6739025,21.273438 C16.6739025,21.066406 16.7910895,20.785156 16.9356215,20.640625 L20.5762465,17 C20.7207775,16.855469 21.0059335,16.738281 21.2090585,16.738281 L22.1348395,16.738281 C22.3379655,16.738281 22.6231215,16.855469 22.7676525,17 L26.4043715,20.640625 C26.5489025,20.785156 26.6660895,21.066406 26.6660895,21.273438 L26.6660895,22.199219 Z M23.4199965,21.753906 L23.4199965,21.714844 C23.4199965,21.566406 23.3340585,21.359375 23.2285895,21.25 L22.1543715,20.179688 C22.0489025,20.070312 21.8418715,19.984375 21.6895275,19.984375 L21.6504655,19.984375 C21.5020275,19.984375 21.2949965,20.070312 21.1856215,20.179688 L20.1153085,21.25 C20.0098395,21.355469 19.9239025,21.5625 19.9239025,21.714844 L19.9239025,21.753906 C19.9239025,21.90625 20.0098395,22.113281 20.1153085,22.21875 L21.1856215,23.292969 C21.2949965,23.398438 21.5020275,23.484375 21.6504655,23.484375 L21.6895275,23.484375 C21.8418715,23.484375 22.0489025,23.398438 22.1543715,23.292969 L23.2285895,22.21875 C23.3340585,22.113281 23.4199965,21.90625 23.4199965,21.753906 Z"
id="Combined-Shape"
/>
<path
d="M28.0879655,15.6875 L37.2637465,10.390625 C38.5528085,9.648438 38.9981215,7.996094 38.2520275,6.707031 C37.5059335,5.417969 35.8574965,4.976562 34.5684335,5.722656 L29.4278085,8.691406 L29.4278085,2.6875 C29.4278085,1.203125 28.2246835,-5.68434189e-14 26.7442155,-5.68434189e-14 C25.2598395,-5.68434189e-14 24.0567155,1.203125 24.0567155,2.6875 L24.0567155,13.09375 C24.0059335,13.632812 24.1114025,14.195312 24.4043715,14.703125 C25.1504655,15.992188 26.7989025,16.433594 28.0879655,15.6875"
id="Path4"
/>
<path
d="M17.0489025,27.515625 C16.4395275,27.398438 15.7871835,27.496094 15.2090585,27.828125 L6.03327749,33.128906 C4.74421549,33.871094 4.29890249,35.519531 5.04499649,36.808594 C5.79108949,38.101562 7.43952749,38.542969 8.72858949,37.796875 L13.9395275,34.789062 L13.9395275,40.785156 C13.9395275,42.265625 15.1426525,43.46875 16.6270275,43.46875 C18.1074965,43.46875 19.3106215,42.265625 19.3106215,40.785156 L19.3106215,30.167969 C19.3106215,28.828125 18.3301525,27.71875 17.0489025,27.515625"
id="Path5"
/>
<path
d="M42.9981215,15.078125 C42.2559335,13.785156 40.6035895,13.34375 39.3145275,14.089844 L30.1387465,19.386719 C29.2598395,19.894531 28.7754655,20.824219 28.7910895,21.769531 C28.7832775,22.710938 29.2676525,23.628906 30.1387465,24.128906 L39.3145275,29.429688 C40.6035895,30.171875 42.2520275,29.730469 42.9981215,28.441406 C43.7442155,27.152344 43.2989025,25.503906 42.0098395,24.757812 L36.8145275,21.757812 L42.0098395,18.757812 C43.3028085,18.015625 43.7442155,16.367188 42.9981215,15.078125"
id="Path6"
/>
</g>
</g>
</svg>

View file

@ -14,6 +14,7 @@ import Oracle from "./Oracle.svelte"
import GoogleSheets from "./GoogleSheets.svelte" import GoogleSheets from "./GoogleSheets.svelte"
import Firebase from "./Firebase.svelte" import Firebase from "./Firebase.svelte"
import Redis from "./Redis.svelte" import Redis from "./Redis.svelte"
import Snowflake from "./Snowflake.svelte"
export default { export default {
BUDIBASE: Budibase, BUDIBASE: Budibase,
@ -32,4 +33,5 @@ export default {
GOOGLE_SHEETS: GoogleSheets, GOOGLE_SHEETS: GoogleSheets,
FIREBASE: Firebase, FIREBASE: Firebase,
REDIS: Redis, REDIS: Redis,
SNOWFLAKE: Snowflake,
} }

View file

@ -45,6 +45,8 @@
name, name,
schema: addAutoColumns(name, dataImport.schema || {}), schema: addAutoColumns(name, dataImport.schema || {}),
dataImport, dataImport,
type: "internal",
sourceId: "bb_internal",
} }
// Only set primary display if defined // Only set primary display if defined

View file

@ -101,7 +101,7 @@
confirmText="Done" confirmText="Done"
cancelText="View App" cancelText="View App"
onCancel={viewApp} onCancel={viewApp}
dataCy={"deploy-app-success-modal"} dataCy="deploy-app-success-modal"
> >
<div slot="header" class="app-published-header"> <div slot="header" class="app-published-header">
<svg <svg

View file

@ -20,7 +20,7 @@
<div class="app-icon" style="color: {app.icon?.color || ''}"> <div class="app-icon" style="color: {app.icon?.color || ''}">
<Icon size="XL" name={app.icon?.name || "Apps"} /> <Icon size="XL" name={app.icon?.name || "Apps"} />
</div> </div>
<div class="name" on:click={() => appOverview(app)}> <div class="name" data-cy="app-name-link" on:click={() => appOverview(app)}>
<Heading size="XS"> <Heading size="XS">
{app.name} {app.name}
</Heading> </Heading>

View file

@ -0,0 +1,16 @@
<script>
import { ModalContent, Toggle } from "@budibase/bbui"
export let app
let excludeRows = false
const exportApp = () => {
const id = app.deployed ? app.prodId : app.devId
const appName = encodeURIComponent(app.name)
window.location = `/api/backups/export?appId=${id}&appname=${appName}&excludeRows=${excludeRows}`
}
</script>
<ModalContent title={"Export"} confirmText={"Export"} onConfirm={exportApp}>
<Toggle text="Exclude Rows" bind:value={excludeRows} />
</ModalContent>

View file

@ -184,6 +184,7 @@ export const IntegrationTypes = {
GOOGLE_SHEETS: "GOOGLE_SHEETS", GOOGLE_SHEETS: "GOOGLE_SHEETS",
FIREBASE: "FIREBASE", FIREBASE: "FIREBASE",
REDIS: "REDIS", REDIS: "REDIS",
SNOWFLAKE: "SNOWFLAKE",
} }
export const IntegrationNames = { export const IntegrationNames = {
@ -203,6 +204,7 @@ export const IntegrationNames = {
[IntegrationTypes.GOOGLE_SHEETS]: "Google Sheets", [IntegrationTypes.GOOGLE_SHEETS]: "Google Sheets",
[IntegrationTypes.FIREBASE]: "Firebase", [IntegrationTypes.FIREBASE]: "Firebase",
[IntegrationTypes.REDIS]: "Redis", [IntegrationTypes.REDIS]: "Redis",
[IntegrationTypes.SNOWFLAKE]: "Snowflake",
} }
export const SchemaTypeOptions = [ export const SchemaTypeOptions = [

View file

@ -174,6 +174,8 @@
if (!$isActive("./navigation")) { if (!$isActive("./navigation")) {
$goto("./navigation") $goto("./navigation")
} }
} else if (type === "request-add-component") {
$goto("./components/new")
} else { } else {
console.warn(`Client sent unknown event type: ${type}`) console.warn(`Client sent unknown event type: ${type}`)
} }

View file

@ -76,11 +76,9 @@
return [] return []
} }
// Split list into either components or blocks initially // Remove blocks if there is no search string
if (section === "components") { if (!search) {
structure = structure.filter(category => category.name !== "Blocks") structure = structure.filter(category => category.name !== "Blocks")
} else {
structure = structure.filter(category => category.name === "Blocks")
} }
// Return only items which match the search string // Return only items which match the search string
@ -159,33 +157,31 @@
borderRight borderRight
> >
<Layout paddingX="L" paddingY="XL" gap="S"> <Layout paddingX="L" paddingY="XL" gap="S">
<ActionGroup compact justified> <Search
<ActionButton placeholder="Search"
fullWidth value={searchString}
selected={section === "components"} on:change={e => (searchString = e.detail)}
on:click={() => (section = "components")}>Components</ActionButton bind:inputRef={searchRef}
> />
<ActionButton {#if !searchString}
fullWidth <ActionGroup compact justified>
selected={section === "blocks"} <ActionButton
on:click={() => (section = "blocks")}>Blocks</ActionButton fullWidth
> selected={section === "components"}
</ActionGroup> on:click={() => (section = "components")}>Components</ActionButton
>
<ActionButton
fullWidth
selected={section === "blocks"}
on:click={() => (section = "blocks")}>Blocks</ActionButton
>
</ActionGroup>
{/if}
</Layout> </Layout>
<div> <div>
<Divider noMargin noGrid /> <Divider noMargin noGrid />
</div> </div>
{#if section === "components"} {#if searchString || section === "components"}
<Layout paddingX="L" paddingY="XL">
<Search
placeholder="Search"
value={searchString}
on:change={e => (searchString = e.detail)}
bind:inputRef={searchRef}
/>
</Layout>
{/if}
{#if section === "components"}
{#each filteredStructure as category} {#each filteredStructure as category}
<DetailSummary name={category.name} collapsible={false}> <DetailSummary name={category.name} collapsible={false}>
<div class="component-grid"> <div class="component-grid">

View file

@ -17,6 +17,7 @@
import CreateAppModal from "components/start/CreateAppModal.svelte" import CreateAppModal from "components/start/CreateAppModal.svelte"
import UpdateAppModal from "components/start/UpdateAppModal.svelte" import UpdateAppModal from "components/start/UpdateAppModal.svelte"
import ChooseIconModal from "components/start/ChooseIconModal.svelte" import ChooseIconModal from "components/start/ChooseIconModal.svelte"
import ExportAppModal from "components/start/ExportAppModal.svelte"
import { store, automationStore } from "builderStore" import { store, automationStore } from "builderStore"
import { API } from "api" import { API } from "api"
@ -37,6 +38,7 @@
let updatingModal let updatingModal
let deletionModal let deletionModal
let unpublishModal let unpublishModal
let exportModal
let iconModal let iconModal
let creatingApp = false let creatingApp = false
let loaded = $apps?.length || $templates?.length let loaded = $apps?.length || $templates?.length
@ -200,9 +202,8 @@
} }
const exportApp = app => { const exportApp = app => {
const id = app.deployed ? app.prodId : app.devId exportModal.show()
const appName = encodeURIComponent(app.name) selectedApp = app
window.location = `/api/backups/export?appId=${id}&appname=${appName}`
} }
const unpublishApp = app => { const unpublishApp = app => {
@ -457,6 +458,10 @@
<UpdateAppModal app={selectedApp} /> <UpdateAppModal app={selectedApp} />
</Modal> </Modal>
<Modal bind:this={exportModal} padding={false} width="600px">
<ExportAppModal app={selectedApp} />
</Modal>
<ConfirmDialog <ConfirmDialog
bind:this={deletionModal} bind:this={deletionModal}
title="Confirm deletion" title="Confirm deletion"

View file

@ -274,12 +274,6 @@
Export Export
</MenuItem> </MenuItem>
{#if isPublished} {#if isPublished}
<MenuItem
on:click={() => unpublishApp(selectedApp)}
icon="GlobeRemove"
>
Unpublish
</MenuItem>
<MenuItem on:click={() => copyAppId(selectedApp)} icon="Copy"> <MenuItem on:click={() => copyAppId(selectedApp)} icon="Copy">
Copy App ID Copy App ID
</MenuItem> </MenuItem>
@ -306,6 +300,7 @@
app={selectedApp} app={selectedApp}
deployments={latestDeployments} deployments={latestDeployments}
navigateTab={handleTabChange} navigateTab={handleTabChange}
on:unpublish={e => unpublishApp(e.detail)}
/> />
</Tab> </Tab>
{#if false} {#if false}

View file

@ -13,10 +13,12 @@
import clientPackage from "@budibase/client/package.json" import clientPackage from "@budibase/client/package.json"
import { processStringSync } from "@budibase/string-templates" import { processStringSync } from "@budibase/string-templates"
import { users, auth } from "stores/portal" import { users, auth } from "stores/portal"
import { createEventDispatcher } from "svelte"
export let app export let app
export let deployments export let deployments
export let navigateTab export let navigateTab
const dispatch = createEventDispatcher()
const userInit = async () => { const userInit = async () => {
try { try {
@ -26,6 +28,10 @@
} }
} }
const unpublishApp = () => {
dispatch("unpublish", app)
}
let userPromise = userInit() let userPromise = userInit()
$: updateAvailable = clientPackage.version !== $store.version $: updateAvailable = clientPackage.version !== $store.version
@ -72,6 +78,9 @@
new Date(deployments[0].updatedAt).getTime(), new Date(deployments[0].updatedAt).getTime(),
} }
)} )}
{#if isPublished}
- <Link on:click={unpublishApp}>Unpublish</Link>
{/if}
{/if} {/if}
{#if !deployments?.length} {#if !deployments?.length}
- -

View file

@ -1,6 +1,6 @@
{ {
"name": "@budibase/cli", "name": "@budibase/cli",
"version": "1.0.178-alpha.0", "version": "1.0.192-alpha.5",
"description": "Budibase CLI, for developers, self hosting and migrations.", "description": "Budibase CLI, for developers, self hosting and migrations.",
"main": "src/index.js", "main": "src/index.js",
"bin": { "bin": {

View file

@ -2791,6 +2791,12 @@
"label": "Extensions", "label": "Extensions",
"key": "extensions" "key": "extensions"
}, },
{
"type": "number",
"label": "No. of attachment",
"key": "maximum",
"min": 1
},
{ {
"type": "event", "type": "event",
"label": "On Change", "label": "On Change",
@ -3048,6 +3054,7 @@
"illegalChildren": ["section"], "illegalChildren": ["section"],
"hasChildren": true, "hasChildren": true,
"showEmptyState": false, "showEmptyState": false,
"info": "Row selection is only compatible with internal or SQL tables",
"settings": [ "settings": [
{ {
"type": "dataProvider", "type": "dataProvider",
@ -3336,6 +3343,7 @@
{ {
"section": true, "section": true,
"name": "Table", "name": "Table",
"info": "Row selection is only compatible with internal or SQL tables",
"settings": [ "settings": [
{ {
"type": "number", "type": "number",

View file

@ -1,6 +1,6 @@
{ {
"name": "@budibase/client", "name": "@budibase/client",
"version": "1.0.178-alpha.0", "version": "1.0.192-alpha.5",
"license": "MPL-2.0", "license": "MPL-2.0",
"module": "dist/budibase-client.js", "module": "dist/budibase-client.js",
"main": "dist/budibase-client.js", "main": "dist/budibase-client.js",
@ -19,9 +19,9 @@
"dev:builder": "rollup -cw" "dev:builder": "rollup -cw"
}, },
"dependencies": { "dependencies": {
"@budibase/bbui": "^1.0.178-alpha.0", "@budibase/bbui": "^1.0.192-alpha.5",
"@budibase/frontend-core": "^1.0.178-alpha.0", "@budibase/frontend-core": "^1.0.192-alpha.5",
"@budibase/string-templates": "^1.0.178-alpha.0", "@budibase/string-templates": "^1.0.192-alpha.5",
"@spectrum-css/button": "^3.0.3", "@spectrum-css/button": "^3.0.3",
"@spectrum-css/card": "^3.0.3", "@spectrum-css/card": "^3.0.3",
"@spectrum-css/divider": "^1.0.3", "@spectrum-css/divider": "^1.0.3",

View file

@ -22,6 +22,7 @@
import { Helpers } from "@budibase/bbui" import { Helpers } from "@budibase/bbui"
import { getActiveConditions, reduceConditionActions } from "utils/conditions" import { getActiveConditions, reduceConditionActions } from "utils/conditions"
import Placeholder from "components/app/Placeholder.svelte" import Placeholder from "components/app/Placeholder.svelte"
import ScreenPlaceholder from "components/app/ScreenPlaceholder.svelte"
export let instance = {} export let instance = {}
export let isLayout = false export let isLayout = false
@ -428,7 +429,11 @@
<svelte:self instance={child} /> <svelte:self instance={child} />
{/each} {/each}
{:else if emptyState} {:else if emptyState}
<Placeholder /> {#if isScreen}
<ScreenPlaceholder />
{:else}
<Placeholder />
{/if}
{:else if isBlock} {:else if isBlock}
<slot /> <slot />
{/if} {/if}

View file

@ -0,0 +1,30 @@
<script>
import { getContext } from "svelte"
import { Heading, Body, Button } from "@budibase/bbui"
const { builderStore } = getContext("sdk")
</script>
{#if $builderStore.inBuilder}
<div class="placeholder">
<Heading size="L">Your screen is empty</Heading>
<Body>Bring your app to life by adding some components!</Body>
<Button cta icon="Add" on:click={builderStore.actions.requestAddComponent}
>Add component</Button
>
</div>
{/if}
<style>
.placeholder {
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
gap: var(--spacing-s);
flex: 1 1 auto;
}
.placeholder :global(.spectrum-Button) {
margin-top: var(--spacing-m);
}
</style>

View file

@ -9,6 +9,7 @@
export let validation export let validation
export let extensions export let extensions
export let onChange export let onChange
export let maximum = undefined
let fieldState let fieldState
let fieldApi let fieldApi
@ -25,6 +26,12 @@
) )
} }
const handleTooManyFiles = fileLimit => {
notificationStore.actions.warning(
`Please select a maximum of ${fileLimit} files.`
)
}
const processFiles = async fileList => { const processFiles = async fileList => {
let data = new FormData() let data = new FormData()
for (let i = 0; i < fileList.length; i++) { for (let i = 0; i < fileList.length; i++) {
@ -66,6 +73,8 @@
on:change={handleChange} on:change={handleChange}
{processFiles} {processFiles}
{handleFileTooLarge} {handleFileTooLarge}
{handleTooManyFiles}
{maximum}
{extensions} {extensions}
/> />
{/if} {/if}

View file

@ -39,6 +39,8 @@
dataProvider?.id, dataProvider?.id,
ActionTypes.SetDataProviderSorting ActionTypes.SetDataProviderSorting
) )
$: table = dataProvider?.datasource?.type === "table"
$: { $: {
rowSelectionStore.actions.updateSelection( rowSelectionStore.actions.updateSelection(
$component.id, $component.id,
@ -142,7 +144,7 @@
{quiet} {quiet}
{compact} {compact}
{customRenderers} {customRenderers}
allowSelectRows={!!allowSelectRows} allowSelectRows={allowSelectRows && table}
bind:selectedRows bind:selectedRows
allowEditRows={false} allowEditRows={false}
allowEditColumns={false} allowEditColumns={false}

View file

@ -78,6 +78,9 @@ const createBuilderStore = () => {
clickNav: () => { clickNav: () => {
dispatchEvent("click-nav") dispatchEvent("click-nav")
}, },
requestAddComponent: () => {
dispatchEvent("request-add-component")
},
} }
return { return {
...store, ...store,

View file

@ -1,5 +1,4 @@
import { API } from "api" import { API } from "api"
import { JSONUtils } from "@budibase/frontend-core"
import TableFetch from "@budibase/frontend-core/src/fetch/TableFetch.js" import TableFetch from "@budibase/frontend-core/src/fetch/TableFetch.js"
import ViewFetch from "@budibase/frontend-core/src/fetch/ViewFetch.js" import ViewFetch from "@budibase/frontend-core/src/fetch/ViewFetch.js"
import QueryFetch from "@budibase/frontend-core/src/fetch/QueryFetch.js" import QueryFetch from "@budibase/frontend-core/src/fetch/QueryFetch.js"
@ -40,44 +39,41 @@ export const fetchDatasourceSchema = async (
return null return null
} }
// Check for any JSON fields so we can add any top level properties // Enrich schema with relationships if required
let jsonAdditions = {} if (definition?.sql && options?.enrichRelationships) {
Object.keys(schema).forEach(fieldKey => { const relationshipAdditions = await getRelationshipSchemaAdditions(schema)
schema = {
...schema,
...relationshipAdditions,
}
}
// Ensure schema is in the correct structure
return instance.enrichSchema(schema)
}
/**
* Fetches the schema of relationship fields for a SQL table schema
* @param schema the schema to enrich
*/
export const getRelationshipSchemaAdditions = async schema => {
if (!schema) {
return null
}
let relationshipAdditions = {}
for (let fieldKey of Object.keys(schema)) {
const fieldSchema = schema[fieldKey] const fieldSchema = schema[fieldKey]
if (fieldSchema?.type === "json") { if (fieldSchema?.type === "link") {
const jsonSchema = JSONUtils.convertJSONSchemaToTableSchema(fieldSchema, { const linkSchema = await fetchDatasourceSchema({
squashObjects: true, type: "table",
tableId: fieldSchema?.tableId,
}) })
Object.keys(jsonSchema).forEach(jsonKey => { Object.keys(linkSchema || {}).forEach(linkKey => {
jsonAdditions[`${fieldKey}.${jsonKey}`] = { relationshipAdditions[`${fieldKey}.${linkKey}`] = {
type: jsonSchema[jsonKey].type, type: linkSchema[linkKey].type,
nestedJSON: true,
} }
}) })
} }
})
schema = { ...schema, ...jsonAdditions }
// Check for any relationship fields if required
if (options?.enrichRelationships && definition.sql) {
let relationshipAdditions = {}
for (let fieldKey of Object.keys(schema)) {
const fieldSchema = schema[fieldKey]
if (fieldSchema?.type === "link") {
const linkSchema = await fetchDatasourceSchema({
type: "table",
tableId: fieldSchema?.tableId,
})
Object.keys(linkSchema || {}).forEach(linkKey => {
relationshipAdditions[`${fieldKey}.${linkKey}`] = {
type: linkSchema[linkKey].type,
}
})
}
}
schema = { ...schema, ...relationshipAdditions }
} }
return relationshipAdditions
// Ensure schema structure is correct
return instance.enrichSchema(schema)
} }

View file

@ -1,12 +1,12 @@
{ {
"name": "@budibase/frontend-core", "name": "@budibase/frontend-core",
"version": "1.0.178-alpha.0", "version": "1.0.192-alpha.5",
"description": "Budibase frontend core libraries used in builder and client", "description": "Budibase frontend core libraries used in builder and client",
"author": "Budibase", "author": "Budibase",
"license": "MPL-2.0", "license": "MPL-2.0",
"svelte": "src/index.js", "svelte": "src/index.js",
"dependencies": { "dependencies": {
"@budibase/bbui": "^1.0.178-alpha.0", "@budibase/bbui": "^1.0.192-alpha.5",
"lodash": "^4.17.21", "lodash": "^4.17.21",
"svelte": "^3.46.2" "svelte": "^3.46.2"
} }

View file

@ -6,6 +6,7 @@ import {
runLuceneQuery, runLuceneQuery,
luceneSort, luceneSort,
} from "../utils/lucene" } from "../utils/lucene"
import { convertJSONSchemaToTableSchema } from "../utils/json"
/** /**
* Parent class which handles the implementation of fetching data from an * Parent class which handles the implementation of fetching data from an
@ -248,7 +249,8 @@ export default class DataFetch {
} }
/** /**
* Enriches the schema and ensures that entries are objects with names * Enriches a datasource schema with nested fields and ensures the structure
* is correct.
* @param schema the datasource schema * @param schema the datasource schema
* @return {object} the enriched datasource schema * @return {object} the enriched datasource schema
*/ */
@ -256,6 +258,26 @@ export default class DataFetch {
if (schema == null) { if (schema == null) {
return null return null
} }
// Check for any JSON fields so we can add any top level properties
let jsonAdditions = {}
Object.keys(schema).forEach(fieldKey => {
const fieldSchema = schema[fieldKey]
if (fieldSchema?.type === "json") {
const jsonSchema = convertJSONSchemaToTableSchema(fieldSchema, {
squashObjects: true,
})
Object.keys(jsonSchema).forEach(jsonKey => {
jsonAdditions[`${fieldKey}.${jsonKey}`] = {
type: jsonSchema[jsonKey].type,
nestedJSON: true,
}
})
}
})
schema = { ...schema, ...jsonAdditions }
// Ensure schema is in the correct structure
let enrichedSchema = {} let enrichedSchema = {}
Object.entries(schema).forEach(([fieldName, fieldSchema]) => { Object.entries(schema).forEach(([fieldName, fieldSchema]) => {
if (typeof fieldSchema === "string") { if (typeof fieldSchema === "string") {
@ -270,6 +292,7 @@ export default class DataFetch {
} }
} }
}) })
return enrichedSchema return enrichedSchema
} }

View file

@ -1,7 +1,5 @@
FROM node:14-slim FROM node:14-slim
RUN apt-get update
LABEL com.centurylinklabs.watchtower.lifecycle.pre-check="scripts/watchtower-hooks/pre-check.sh" LABEL com.centurylinklabs.watchtower.lifecycle.pre-check="scripts/watchtower-hooks/pre-check.sh"
LABEL com.centurylinklabs.watchtower.lifecycle.pre-update="scripts/watchtower-hooks/pre-update.sh" LABEL com.centurylinklabs.watchtower.lifecycle.pre-update="scripts/watchtower-hooks/pre-update.sh"
LABEL com.centurylinklabs.watchtower.lifecycle.post-update="scripts/watchtower-hooks/post-update.sh" LABEL com.centurylinklabs.watchtower.lifecycle.post-update="scripts/watchtower-hooks/post-update.sh"
@ -15,7 +13,13 @@ ENV BUDIBASE_ENVIRONMENT=PRODUCTION
# copy files and install dependencies # copy files and install dependencies
COPY . ./ COPY . ./
RUN yarn # handle node-gyp
RUN apt-get update \
&& apt-get install -y --no-install-recommends g++ make python \
&& yarn \
&& yarn cache clean \
&& apt-get remove -y --purge --auto-remove g++ make python \
&& rm -rf /tmp/* /root/.node-gyp /usr/local/lib/node_modules/npm/node_modules/node-gyp
RUN yarn global add pm2 RUN yarn global add pm2
RUN yarn build RUN yarn build

View file

@ -1,3 +1,5 @@
#!/usr/bin/env bash
if [ -z $CLUSTER_MODE ]; then if [ -z $CLUSTER_MODE ]; then
yarn run:docker yarn run:docker
else else

View file

@ -1,7 +1,7 @@
{ {
"name": "@budibase/server", "name": "@budibase/server",
"email": "hi@budibase.com", "email": "hi@budibase.com",
"version": "1.0.178-alpha.0", "version": "1.0.192-alpha.5",
"description": "Budibase Web Server", "description": "Budibase Web Server",
"main": "src/index.ts", "main": "src/index.ts",
"repository": { "repository": {
@ -70,10 +70,10 @@
"license": "GPL-3.0", "license": "GPL-3.0",
"dependencies": { "dependencies": {
"@apidevtools/swagger-parser": "^10.0.3", "@apidevtools/swagger-parser": "^10.0.3",
"@budibase/backend-core": "^1.0.178-alpha.0", "@budibase/backend-core": "^1.0.192-alpha.5",
"@budibase/client": "^1.0.178-alpha.0", "@budibase/client": "^1.0.192-alpha.5",
"@budibase/pro": "1.0.178-alpha.0", "@budibase/pro": "1.0.192-alpha.5",
"@budibase/string-templates": "^1.0.178-alpha.0", "@budibase/string-templates": "^1.0.192-alpha.5",
"@bull-board/api": "^3.7.0", "@bull-board/api": "^3.7.0",
"@bull-board/koa": "^3.7.0", "@bull-board/koa": "^3.7.0",
"@elastic/elasticsearch": "7.10.0", "@elastic/elasticsearch": "7.10.0",
@ -130,6 +130,7 @@
"pouchdb-replication-stream": "1.2.9", "pouchdb-replication-stream": "1.2.9",
"redis": "4", "redis": "4",
"server-destroy": "1.0.1", "server-destroy": "1.0.1",
"snowflake-promise": "^4.5.0",
"svelte": "^3.38.2", "svelte": "^3.38.2",
"swagger-parser": "^10.0.3", "swagger-parser": "^10.0.3",
"to-json-schema": "0.2.5", "to-json-schema": "0.2.5",

View file

@ -1,9 +1,11 @@
const { streamBackup } = require("../../utilities/fileSystem") const { streamBackup } = require("../../utilities/fileSystem")
exports.exportAppDump = async function (ctx) { exports.exportAppDump = async function (ctx) {
const { appId } = ctx.query let { appId, excludeRows } = ctx.query
const appName = decodeURI(ctx.query.appname) const appName = decodeURI(ctx.query.appname)
excludeRows = excludeRows === "true"
const backupIdentifier = `${appName}-export-${new Date().getTime()}.txt` const backupIdentifier = `${appName}-export-${new Date().getTime()}.txt`
ctx.attachment(backupIdentifier) ctx.attachment(backupIdentifier)
ctx.body = await streamBackup(appId)
ctx.body = await streamBackup(appId, excludeRows)
} }

View file

@ -64,6 +64,9 @@ exports.validate = async ({ tableId, row, table }) => {
// Validate.js doesn't seem to handle array // Validate.js doesn't seem to handle array
if (type === FieldTypes.ARRAY && row[fieldName]) { if (type === FieldTypes.ARRAY && row[fieldName]) {
if (row[fieldName].length) { if (row[fieldName].length) {
if (!Array.isArray(row[fieldName])) {
row[fieldName] = row[fieldName].split(",")
}
row[fieldName].map(val => { row[fieldName].map(val => {
if ( if (
!constraints.inclusion.includes(val) && !constraints.inclusion.includes(val) &&

View file

@ -12,6 +12,7 @@ const { mainRoutes, staticRoutes, publicRoutes } = require("./routes")
const pkg = require("../../package.json") const pkg = require("../../package.json")
const env = require("../environment") const env = require("../environment")
const { middleware: pro } = require("@budibase/pro") const { middleware: pro } = require("@budibase/pro")
const { shutdown } = require("./routes/public")
const router = new Router() const router = new Router()
@ -90,4 +91,5 @@ router.use(publicRoutes.allowedMethods())
router.use(staticRoutes.routes()) router.use(staticRoutes.routes())
router.use(staticRoutes.allowedMethods()) router.use(staticRoutes.allowedMethods())
module.exports = router module.exports.router = router
module.exports.shutdown = shutdown

View file

@ -29,6 +29,7 @@ function getApiLimitPerSecond(): number {
return parseInt(env.API_REQ_LIMIT_PER_SEC) return parseInt(env.API_REQ_LIMIT_PER_SEC)
} }
let rateLimitStore: any = null
if (!env.isTest()) { if (!env.isTest()) {
const REDIS_OPTS = getRedisOptions() const REDIS_OPTS = getRedisOptions()
let options let options
@ -47,8 +48,9 @@ if (!env.isTest()) {
database: 1, database: 1,
} }
} }
rateLimitStore = new Stores.Redis(options)
RateLimit.defaultOptions({ RateLimit.defaultOptions({
store: new Stores.Redis(options), store: rateLimitStore,
}) })
} }
// rate limiting, allows for 2 requests per second // rate limiting, allows for 2 requests per second
@ -128,3 +130,10 @@ applyRoutes(queryEndpoints, PermissionTypes.QUERY, "queryId")
applyRoutes(rowEndpoints, PermissionTypes.TABLE, "tableId", "rowId") applyRoutes(rowEndpoints, PermissionTypes.TABLE, "tableId", "rowId")
export default publicRouter export default publicRouter
export const shutdown = () => {
if (rateLimitStore) {
rateLimitStore.client.disconnect()
rateLimitStore = null
}
}

View file

@ -14,6 +14,8 @@ const automations = require("./automations/index")
const Sentry = require("@sentry/node") const Sentry = require("@sentry/node")
const fileSystem = require("./utilities/fileSystem") const fileSystem = require("./utilities/fileSystem")
const bullboard = require("./automations/bullboard") const bullboard = require("./automations/bullboard")
const { logAlert } = require("@budibase/backend-core/logging")
const { Thread } = require("./threads")
import redis from "./utilities/redis" import redis from "./utilities/redis"
import * as migrations from "./migrations" import * as migrations from "./migrations"
@ -49,7 +51,7 @@ app.context.eventEmitter = eventEmitter
app.context.auth = {} app.context.auth = {}
// api routes // api routes
app.use(api.routes()) app.use(api.router.routes())
if (env.isProd()) { if (env.isProd()) {
env._set("NODE_ENV", "production") env._set("NODE_ENV", "production")
@ -68,11 +70,24 @@ if (env.isProd()) {
const server = http.createServer(app.callback()) const server = http.createServer(app.callback())
destroyable(server) destroyable(server)
let shuttingDown = false,
errCode = 0
server.on("close", async () => { server.on("close", async () => {
if (env.NODE_ENV !== "jest") { // already in process
if (shuttingDown) {
return
}
shuttingDown = true
if (!env.isTest()) {
console.log("Server Closed") console.log("Server Closed")
} }
await automations.shutdown()
await redis.shutdown() await redis.shutdown()
await Thread.shutdown()
api.shutdown()
if (!env.isTest()) {
process.exit(errCode)
}
}) })
module.exports = server.listen(env.PORT || 0, async () => { module.exports = server.listen(env.PORT || 0, async () => {
@ -90,7 +105,13 @@ const shutdown = () => {
} }
process.on("uncaughtException", err => { process.on("uncaughtException", err => {
console.error(err) // @ts-ignore
// don't worry about this error, comes from zlib isn't important
if (err && err["code"] === "ERR_INVALID_CHAR") {
return
}
errCode = -1
logAlert("Uncaught exception.", err)
shutdown() shutdown()
}) })
@ -102,7 +123,7 @@ process.on("SIGTERM", () => {
// not recommended in a clustered environment // not recommended in a clustered environment
if (!env.HTTP_MIGRATIONS) { if (!env.HTTP_MIGRATIONS) {
migrations.migrate().catch(err => { migrations.migrate().catch(err => {
console.error("Error performing migrations. Exiting.\n", err) logAlert("Error performing migrations. Exiting.", err)
shutdown() shutdown()
}) })
} }

View file

@ -45,4 +45,12 @@ exports.init = () => {
return serverAdapter.registerPlugin() return serverAdapter.registerPlugin()
} }
exports.shutdown = async () => {
if (automationQueue) {
clearInterval(cleanupInternal)
await automationQueue.close()
automationQueue = null
}
}
exports.queue = automationQueue exports.queue = automationQueue

View file

@ -1,5 +1,5 @@
const { processEvent } = require("./utils") const { processEvent } = require("./utils")
const { queue } = require("./bullboard") const { queue, shutdown } = require("./bullboard")
/** /**
* This module is built purely to kick off the worker farm and manage the inputs/outputs * This module is built purely to kick off the worker farm and manage the inputs/outputs
@ -14,4 +14,9 @@ exports.init = function () {
exports.getQueues = () => { exports.getQueues = () => {
return [queue] return [queue]
} }
exports.shutdown = () => {
return shutdown()
}
exports.queue = queue exports.queue = queue

View file

@ -53,6 +53,7 @@ exports.run = async function ({ inputs }) {
if (!contents) { if (!contents) {
contents = "<h1>No content</h1>" contents = "<h1>No content</h1>"
} }
to = to || undefined
try { try {
let response = await sendSmtpEmail(to, from, subject, contents, true) let response = await sendSmtpEmail(to, from, subject, contents, true)
return { return {

View file

@ -74,6 +74,7 @@ exports.isDevAppID = isDevAppID
exports.isProdAppID = isProdAppID exports.isProdAppID = isProdAppID
exports.USER_METDATA_PREFIX = `${DocumentTypes.ROW}${SEPARATOR}${InternalTables.USER_METADATA}${SEPARATOR}` exports.USER_METDATA_PREFIX = `${DocumentTypes.ROW}${SEPARATOR}${InternalTables.USER_METADATA}${SEPARATOR}`
exports.LINK_USER_METADATA_PREFIX = `${DocumentTypes.LINK}${SEPARATOR}${InternalTables.USER_METADATA}${SEPARATOR}` exports.LINK_USER_METADATA_PREFIX = `${DocumentTypes.LINK}${SEPARATOR}${InternalTables.USER_METADATA}${SEPARATOR}`
exports.TABLE_ROW_PREFIX = `${DocumentTypes.ROW}${SEPARATOR}${DocumentTypes.TABLE}`
exports.ViewNames = ViewNames exports.ViewNames = ViewNames
exports.InternalTables = InternalTables exports.InternalTables = InternalTables
exports.DocumentTypes = DocumentTypes exports.DocumentTypes = DocumentTypes

View file

@ -50,6 +50,7 @@ export enum SourceNames {
GOOGLE_SHEETS = "GOOGLE_SHEETS", GOOGLE_SHEETS = "GOOGLE_SHEETS",
FIREBASE = "FIREBASE", FIREBASE = "FIREBASE",
REDIS = "REDIS", REDIS = "REDIS",
SNOWFLAKE = "SNOWFLAKE",
} }
export enum IncludeRelationships { export enum IncludeRelationships {

View file

@ -12,6 +12,7 @@ const rest = require("./rest")
const googlesheets = require("./googlesheets") const googlesheets = require("./googlesheets")
const firebase = require("./firebase") const firebase = require("./firebase")
const redis = require("./redis") const redis = require("./redis")
const snowflake = require("./snowflake")
const { SourceNames } = require("../definitions/datasource") const { SourceNames } = require("../definitions/datasource")
const environment = require("../environment") const environment = require("../environment")
@ -29,6 +30,7 @@ const DEFINITIONS = {
[SourceNames.REST]: rest.schema, [SourceNames.REST]: rest.schema,
[SourceNames.FIREBASE]: firebase.schema, [SourceNames.FIREBASE]: firebase.schema,
[SourceNames.REDIS]: redis.schema, [SourceNames.REDIS]: redis.schema,
[SourceNames.SNOWFLAKE]: snowflake.schema,
} }
const INTEGRATIONS = { const INTEGRATIONS = {
@ -47,6 +49,7 @@ const INTEGRATIONS = {
[SourceNames.GOOGLE_SHEETS]: googlesheets.integration, [SourceNames.GOOGLE_SHEETS]: googlesheets.integration,
[SourceNames.REDIS]: redis.integration, [SourceNames.REDIS]: redis.integration,
[SourceNames.FIREBASE]: firebase.integration, [SourceNames.FIREBASE]: firebase.integration,
[SourceNames.SNOWFLAKE]: snowflake.integration,
} }
// optionally add oracle integration if the oracle binary can be installed // optionally add oracle integration if the oracle binary can be installed

View file

@ -0,0 +1,98 @@
import { Integration, QueryTypes, SqlQuery } from "../definitions/datasource"
import { Snowflake } from "snowflake-promise"
module SnowflakeModule {
interface SnowflakeConfig {
account: string
username: string
password: string
warehouse: string
database: string
schema: string
}
const SCHEMA: Integration = {
docs: "https://developers.snowflake.com/",
description:
"Snowflake is a solution for data warehousing, data lakes, data engineering, data science, data application development, and securely sharing and consuming shared data.",
friendlyName: "Snowflake",
datasource: {
account: {
type: "string",
required: true,
},
username: {
type: "string",
required: true,
},
password: {
type: "password",
required: true,
},
warehouse: {
type: "string",
required: true,
},
database: {
type: "string",
required: true,
},
schema: {
type: "string",
required: true,
},
},
query: {
create: {
type: QueryTypes.SQL,
},
read: {
type: QueryTypes.SQL,
},
update: {
type: QueryTypes.SQL,
},
delete: {
type: QueryTypes.SQL,
},
},
}
class SnowflakeIntegration {
private client: Snowflake
constructor(config: SnowflakeConfig) {
this.client = new Snowflake(config)
}
async internalQuery(query: SqlQuery) {
await this.client.connect()
try {
return await this.client.execute(query.sql)
} catch (err: any) {
throw err?.message.split(":")[1] || err?.message
}
}
async create(query: SqlQuery) {
return this.internalQuery(query)
}
async read(query: SqlQuery) {
return this.internalQuery(query)
}
async update(query: SqlQuery) {
return this.internalQuery(query)
}
async delete(query: SqlQuery) {
return this.internalQuery(query)
}
}
module.exports = {
schema: SCHEMA,
integration: SnowflakeIntegration,
}
}

View file

@ -219,7 +219,7 @@ class Orchestrator {
} }
if ( if (
index === parseInt(env.AUTOMATION_MAX_ITERATIONS) || index === parseInt(env.AUTOMATION_MAX_ITERATIONS) ||
index === loopStep.inputs.iterations index === parseInt(loopStep.inputs.iterations)
) { ) {
this.updateContextAndOutput(loopStepNumber, step, tempOutput, { this.updateContextAndOutput(loopStepNumber, step, tempOutput, {
status: AutomationErrors.MAX_ITERATIONS, status: AutomationErrors.MAX_ITERATIONS,

View file

@ -28,6 +28,8 @@ export class Thread {
workers: any workers: any
timeoutMs: any timeoutMs: any
static workerRefs: any[] = []
constructor(type: any, opts: any = { timeoutMs: null, count: 1 }) { constructor(type: any, opts: any = { timeoutMs: null, count: 1 }) {
this.type = type this.type = type
this.count = opts.count ? opts.count : 1 this.count = opts.count ? opts.count : 1
@ -46,6 +48,7 @@ export class Thread {
workerOpts.maxCallTime = opts.timeoutMs workerOpts.maxCallTime = opts.timeoutMs
} }
this.workers = workerFarm(workerOpts, typeToFile(type)) this.workers = workerFarm(workerOpts, typeToFile(type))
Thread.workerRefs.push(this.workers)
} }
} }
@ -73,4 +76,23 @@ export class Thread {
}) })
}) })
} }
static shutdown() {
return new Promise<void>(resolve => {
if (Thread.workerRefs.length === 0) {
resolve()
}
let count = 0
function complete() {
count++
if (count >= Thread.workerRefs.length) {
resolve()
}
}
for (let worker of Thread.workerRefs) {
workerFarm.end(worker, complete)
}
Thread.workerRefs = []
})
}
} }

View file

@ -1,15 +0,0 @@
// TODO: REMOVE
const bcrypt = require("bcryptjs")
const env = require("../environment")
const SALT_ROUNDS = env.SALT_ROUNDS || 10
exports.hash = async data => {
const salt = await bcrypt.genSalt(SALT_ROUNDS)
const result = await bcrypt.hash(data, salt)
return result
}
exports.compare = async (data, encrypted) =>
await bcrypt.compare(data, encrypted)

View file

@ -21,6 +21,7 @@ const env = require("../../environment")
const { const {
USER_METDATA_PREFIX, USER_METDATA_PREFIX,
LINK_USER_METADATA_PREFIX, LINK_USER_METADATA_PREFIX,
TABLE_ROW_PREFIX,
} = require("../../db/utils") } = require("../../db/utils")
const MemoryStream = require("memorystream") const MemoryStream = require("memorystream")
const { getAppId } = require("@budibase/backend-core/context") const { getAppId } = require("@budibase/backend-core/context")
@ -109,6 +110,23 @@ exports.apiFileReturn = contents => {
return fs.createReadStream(path) return fs.createReadStream(path)
} }
exports.defineFilter = excludeRows => {
if (excludeRows) {
return doc =>
!(
doc._id.includes(USER_METDATA_PREFIX) ||
doc._id.includes(LINK_USER_METADATA_PREFIX) ||
doc._id.includes(TABLE_ROW_PREFIX)
)
} else if (!excludeRows) {
return doc =>
!(
doc._id.includes(USER_METDATA_PREFIX) ||
doc._id.includes(LINK_USER_METADATA_PREFIX)
)
}
}
/** /**
* Local utility to back up the database state for an app, excluding global user * Local utility to back up the database state for an app, excluding global user
* data or user relationships. * data or user relationships.
@ -116,14 +134,10 @@ exports.apiFileReturn = contents => {
* @param {object} config Config to send to export DB * @param {object} config Config to send to export DB
* @returns {*} either a string or a stream of the backup * @returns {*} either a string or a stream of the backup
*/ */
const backupAppData = async (appId, config) => { const backupAppData = async (appId, config, includeRows) => {
return await exports.exportDB(appId, { return await exports.exportDB(appId, {
...config, ...config,
filter: doc => filter: exports.defineFilter(includeRows),
!(
doc._id.includes(USER_METDATA_PREFIX) ||
doc._id.includes(LINK_USER_METADATA_PREFIX)
),
}) })
} }
@ -142,8 +156,8 @@ exports.performBackup = async (appId, backupName) => {
* @param {string} appId The ID of the app which is to be backed up. * @param {string} appId The ID of the app which is to be backed up.
* @returns {*} a readable stream of the backup which is written in real time * @returns {*} a readable stream of the backup which is written in real time
*/ */
exports.streamBackup = async appId => { exports.streamBackup = async (appId, includeRows) => {
return await backupAppData(appId, { stream: true }) return await backupAppData(appId, { stream: true }, includeRows)
} }
/** /**

View file

@ -75,6 +75,13 @@ class InMemoryQueue {
this._emitter.emit("message") this._emitter.emit("message")
} }
/**
* replicating the close function from bull, which waits for jobs to finish.
*/
async close() {
return []
}
/** /**
* This removes a cron which has been implemented, this is part of Bull API. * This removes a cron which has been implemented, this is part of Bull API.
* @param {string} cronJobId The cron which is to be removed. * @param {string} cronJobId The cron which is to be removed.

File diff suppressed because it is too large Load diff

View file

@ -1,6 +1,6 @@
{ {
"name": "@budibase/string-templates", "name": "@budibase/string-templates",
"version": "1.0.178-alpha.0", "version": "1.0.192-alpha.5",
"description": "Handlebars wrapper for Budibase templating.", "description": "Handlebars wrapper for Budibase templating.",
"main": "src/index.cjs", "main": "src/index.cjs",
"module": "dist/bundle.mjs", "module": "dist/bundle.mjs",

View file

@ -9,7 +9,9 @@ WORKDIR /app
# copy files and install dependencies # copy files and install dependencies
COPY . ./ COPY . ./
RUN yarn # handle node-gyp
RUN apk add --no-cache --virtual .gyp python3 make g++ \
&& yarn && apk del .gyp
RUN yarn global add pm2 RUN yarn global add pm2
EXPOSE 4001 EXPOSE 4001

View file

@ -1,3 +1,5 @@
#!/bin/sh
if [[ -z $CLUSTER_MODE ]]; then if [[ -z $CLUSTER_MODE ]]; then
yarn run:docker yarn run:docker
else else

View file

@ -1,7 +1,7 @@
{ {
"name": "@budibase/worker", "name": "@budibase/worker",
"email": "hi@budibase.com", "email": "hi@budibase.com",
"version": "1.0.178-alpha.0", "version": "1.0.192-alpha.5",
"description": "Budibase background service", "description": "Budibase background service",
"main": "src/index.ts", "main": "src/index.ts",
"repository": { "repository": {
@ -32,9 +32,9 @@
"author": "Budibase", "author": "Budibase",
"license": "GPL-3.0", "license": "GPL-3.0",
"dependencies": { "dependencies": {
"@budibase/backend-core": "^1.0.178-alpha.0", "@budibase/backend-core": "^1.0.192-alpha.5",
"@budibase/pro": "1.0.178-alpha.0", "@budibase/pro": "1.0.192-alpha.5",
"@budibase/string-templates": "^1.0.178-alpha.0", "@budibase/string-templates": "^1.0.192-alpha.5",
"@koa/router": "^8.0.0", "@koa/router": "^8.0.0",
"@sentry/node": "6.17.7", "@sentry/node": "6.17.7",
"@techpass/passport-openidconnect": "^0.3.0", "@techpass/passport-openidconnect": "^0.3.0",

View file

@ -0,0 +1,172 @@
const fetch = require("node-fetch")
const MAX_RUNTIME_SEC = 600
const HOST = "http://localhost:10000"
const TENANT_ID = "default"
const RATE_MS = 500
let API_KEY = process.argv[2]
let STATS = {
iterations: 0,
error: 0,
success: 0,
}
if (!API_KEY) {
console.error("Must specify API key as first run command!")
process.exit(-1)
}
const USERS = [
{
email: "loadtest1@test.com",
password: "test",
},
{
email: "loadtest2@test.com",
password: "test",
},
{
email: "loadtest3@test.com",
password: "test",
},
{
email: "loadtest4@test.com",
password: "test",
},
{
email: "loadtest5@test.com",
password: "test",
},
{
email: "loadtest6@test.com",
password: "test",
},
{
email: "loadtest7@test.com",
password: "test",
},
]
const REQUESTS = [
{
endpoint: `/api/global/self`,
method: "GET",
},
]
function timeout() {
return new Promise(resolve => {
setTimeout(() => {
resolve()
}, MAX_RUNTIME_SEC * 1000)
})
}
async function preTest() {
// check if the user exists or not
const response = await fetch(`${HOST}/api/global/users`, {
method: "GET",
headers: {
"x-budibase-api-key": API_KEY,
},
})
if (response.status !== 200) {
throw new Error("Unable to retrieve users")
}
const users = await response.json()
for (let user of USERS) {
if (users.find(u => u.email === user.email)) {
continue
}
const response = await fetch(`${HOST}/api/global/users`, {
method: "POST",
headers: {
"x-budibase-api-key": API_KEY,
"Content-Type": "application/json",
},
body: JSON.stringify({
...user,
roles: {},
status: "active",
}),
})
if (response.status !== 200) {
throw new Error(
`Unable to create user ${user.email}, reason: ${await response.text()}`
)
}
}
}
async function requests(user) {
let response = await fetch(`${HOST}/api/global/auth/${TENANT_ID}/login`, {
method: "POST",
body: JSON.stringify({
username: user.email,
password: user.password,
}),
headers: {
"Content-Type": "application/json",
},
})
// unable to login
if (response.status !== 200) {
STATS.error++
return
} else {
STATS.success++
}
const cookie = response.headers.get("set-cookie")
let promises = []
for (let request of REQUESTS) {
const headers = {
cookie,
}
if (request.body) {
headers["Content-Type"] = "application/json"
}
promises.push(
fetch(`${HOST}${request.endpoint}`, {
method: request.method,
headers: {
cookie,
},
})
)
}
const responses = await Promise.all(promises)
for (let resp of responses) {
if (resp.status !== 200) {
console.error(await resp.text())
STATS.error++
} else {
STATS.success++
}
}
}
async function run() {
await preTest()
setInterval(async () => {
let promises = []
for (let user of USERS) {
promises.push(requests(user))
}
await Promise.all(promises)
console.log(
`Iteration ${STATS.iterations++} - errors: ${STATS.error}, success: ${
STATS.success
}`
)
}, RATE_MS)
await timeout()
console.log(
`Max runtime of ${MAX_RUNTIME_SEC} seconds has been reached - stopping.`
)
process.exit(0)
}
run().catch(err => {
console.error("Failed to run - ", err)
})

View file

@ -12,6 +12,7 @@ const {
getTenantId, getTenantId,
getTenantUser, getTenantUser,
doesTenantExist, doesTenantExist,
doInTenant,
} = require("@budibase/backend-core/tenancy") } = require("@budibase/backend-core/tenancy")
const { removeUserFromInfoDB } = require("@budibase/backend-core/deprovision") const { removeUserFromInfoDB } = require("@budibase/backend-core/deprovision")
const { errors } = require("@budibase/backend-core") const { errors } = require("@budibase/backend-core")
@ -41,70 +42,73 @@ const parseBooleanParam = (param: any) => {
export const adminUser = async (ctx: any) => { export const adminUser = async (ctx: any) => {
const { email, password, tenantId } = ctx.request.body const { email, password, tenantId } = ctx.request.body
await doInTenant(tenantId, async () => {
// account portal sends a pre-hashed password - honour param to prevent double hashing
const hashPassword = parseBooleanParam(ctx.request.query.hashPassword)
// account portal sends no password for SSO users
const requirePassword = parseBooleanParam(ctx.request.query.requirePassword)
// account portal sends a pre-hashed password - honour param to prevent double hashing if (await doesTenantExist(tenantId)) {
const hashPassword = parseBooleanParam(ctx.request.query.hashPassword) ctx.throw(403, "Organisation already exists.")
// account portal sends no password for SSO users
const requirePassword = parseBooleanParam(ctx.request.query.requirePassword)
if (await doesTenantExist(tenantId)) {
ctx.throw(403, "Organisation already exists.")
}
const response = await doWithGlobalDB(tenantId, async (db: any) => {
const response = await db.allDocs(
getGlobalUserParams(null, {
include_docs: true,
})
)
// write usage quotas for cloud
if (!env.SELF_HOSTED) {
// could be a scenario where it exists, make sure its clean
try {
const usageQuota = await db.get(StaticDatabases.GLOBAL.docs.usageQuota)
if (usageQuota) {
await db.remove(usageQuota._id, usageQuota._rev)
}
} catch (err) {
// don't worry about errors
}
await db.put(quotas.generateNewQuotaUsage())
} }
return response
})
if (response.rows.some((row: any) => row.doc.admin)) { const response = await doWithGlobalDB(tenantId, async (db: any) => {
ctx.throw( const response = await db.allDocs(
403, getGlobalUserParams(null, {
"You cannot initialise once an global user has been created." include_docs: true,
) })
} )
// write usage quotas for cloud
if (!env.SELF_HOSTED) {
// could be a scenario where it exists, make sure its clean
try {
const usageQuota = await db.get(
StaticDatabases.GLOBAL.docs.usageQuota
)
if (usageQuota) {
await db.remove(usageQuota._id, usageQuota._rev)
}
} catch (err) {
// don't worry about errors
}
await db.put(quotas.generateNewQuotaUsage())
}
return response
})
const user = { if (response.rows.some((row: any) => row.doc.admin)) {
email: email, ctx.throw(
password: password, 403,
createdAt: Date.now(), "You cannot initialise once an global user has been created."
roles: {}, )
builder: { }
global: true,
}, const user = {
admin: { email: email,
global: true, password: password,
}, createdAt: Date.now(),
tenantId, roles: {},
} builder: {
try { global: true,
const finalUser = await users.save( },
user, admin: {
global: true,
},
tenantId, tenantId,
hashPassword, }
requirePassword try {
) const finalUser = await users.save(
await bustCache(CacheKeys.CHECKLIST) user,
ctx.body = finalUser tenantId,
} catch (err: any) { hashPassword,
ctx.throw(err.status || 400, err) requirePassword
} )
await bustCache(CacheKeys.CHECKLIST)
ctx.body = finalUser
} catch (err: any) {
ctx.throw(err.status || 400, err)
}
})
} }
export const destroy = async (ctx: any) => { export const destroy = async (ctx: any) => {

View file

@ -12,6 +12,7 @@ const destroyable = require("server-destroy")
const koaBody = require("koa-body") const koaBody = require("koa-body")
const koaSession = require("koa-session") const koaSession = require("koa-session")
const { passport } = require("@budibase/backend-core/auth") const { passport } = require("@budibase/backend-core/auth")
const { logAlert } = require("@budibase/backend-core/logging")
const logger = require("koa-pino-logger") const logger = require("koa-pino-logger")
const http = require("http") const http = require("http")
const api = require("./api") const api = require("./api")
@ -28,7 +29,6 @@ app.keys = ["secret", "key"]
// set up top level koa middleware // set up top level koa middleware
app.use(koaBody({ multipart: true })) app.use(koaBody({ multipart: true }))
app.use(koaSession(app)) app.use(koaSession(app))
app.use( app.use(
logger({ logger({
prettyPrint: { prettyPrint: {
@ -62,25 +62,38 @@ if (env.isProd()) {
const server = http.createServer(app.callback()) const server = http.createServer(app.callback())
destroyable(server) destroyable(server)
let shuttingDown = false,
errCode = 0
server.on("close", async () => { server.on("close", async () => {
if (env.isProd()) { if (shuttingDown) {
return
}
shuttingDown = true
if (!env.isTest()) {
console.log("Server Closed") console.log("Server Closed")
} }
await redis.shutdown() await redis.shutdown()
if (!env.isTest()) {
process.exit(errCode)
}
}) })
const shutdown = () => {
server.close()
server.destroy()
}
module.exports = server.listen(parseInt(env.PORT || 4002), async () => { module.exports = server.listen(parseInt(env.PORT || 4002), async () => {
console.log(`Worker running on ${JSON.stringify(server.address())}`) console.log(`Worker running on ${JSON.stringify(server.address())}`)
await redis.init() await redis.init()
}) })
process.on("uncaughtException", err => { process.on("uncaughtException", err => {
console.error(err) errCode = -1
server.close() logAlert("Uncaught exception.", err)
server.destroy() shutdown()
}) })
process.on("SIGTERM", () => { process.on("SIGTERM", () => {
server.close() shutdown()
server.destroy()
}) })

File diff suppressed because it is too large Load diff

View file

@ -51,7 +51,7 @@ cd -
lerna publish $VERSION --yes --force-publish --dist-tag $TAG lerna publish $VERSION --yes --force-publish --dist-tag $TAG
############################################# #############################################
# POST-PUBLISH - PRO # # POST-PUBLISH - PRO #
############################################# #############################################
# Revert build changes on packages/pro/package.json # Revert build changes on packages/pro/package.json

Some files were not shown because too many files have changed in this diff Show more