merge
This commit is contained in:
commit
75c599ce70
|
@ -72,3 +72,56 @@ jobs:
|
|||
env:
|
||||
DOCKER_USER: ${{ secrets.DOCKER_USERNAME }}
|
||||
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 }}
|
|
@ -11,7 +11,7 @@ sources:
|
|||
- https://github.com/Budibase/budibase
|
||||
- https://budibase.com
|
||||
type: application
|
||||
version: 0.2.9
|
||||
version: 0.2.10
|
||||
appVersion: 1.0.48
|
||||
dependencies:
|
||||
- name: couchdb
|
||||
|
|
|
@ -78,6 +78,10 @@ spec:
|
|||
value: {{ .Values.services.objectStore.url }}
|
||||
- name: PORT
|
||||
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
|
||||
value: {{ .Values.globals.multiTenancy | quote }}
|
||||
- name: LOG_LEVEL
|
||||
|
@ -119,6 +123,12 @@ spec:
|
|||
|
||||
image: budibase/apps:{{ .Values.globals.appVersion }}
|
||||
imagePullPolicy: Always
|
||||
livenessProbe:
|
||||
httpGet:
|
||||
path: /health
|
||||
port: {{ .Values.services.apps.port }}
|
||||
initialDelaySeconds: 5
|
||||
periodSeconds: 5
|
||||
name: bbapps
|
||||
ports:
|
||||
- containerPort: {{ .Values.services.apps.port }}
|
||||
|
|
|
@ -119,6 +119,12 @@ spec:
|
|||
value: {{ .Values.globals.google.secret | quote }}
|
||||
image: budibase/worker:{{ .Values.globals.appVersion }}
|
||||
imagePullPolicy: Always
|
||||
livenessProbe:
|
||||
httpGet:
|
||||
path: /health
|
||||
port: {{ .Values.services.worker.port }}
|
||||
initialDelaySeconds: 5
|
||||
periodSeconds: 5
|
||||
name: bbworker
|
||||
ports:
|
||||
- containerPort: {{ .Values.services.worker.port }}
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
{
|
||||
"version": "1.0.178-alpha.0",
|
||||
"version": "1.0.192-alpha.5",
|
||||
"npmClient": "yarn",
|
||||
"packages": [
|
||||
"packages/*"
|
||||
|
|
|
@ -55,6 +55,7 @@
|
|||
"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: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: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 -",
|
||||
|
|
|
@ -0,0 +1 @@
|
|||
module.exports = require("./src/logging")
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"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",
|
||||
"main": "src/index.js",
|
||||
"author": "Budibase",
|
||||
|
@ -12,7 +12,7 @@
|
|||
"dependencies": {
|
||||
"@techpass/passport-openidconnect": "^0.3.0",
|
||||
"aws-sdk": "^2.901.0",
|
||||
"bcryptjs": "^2.4.3",
|
||||
"bcrypt": "^5.0.1",
|
||||
"dotenv": "^16.0.1",
|
||||
"emitter-listener": "^1.1.2",
|
||||
"ioredis": "^4.27.1",
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
const bcrypt = require("bcryptjs")
|
||||
const bcrypt = require("bcrypt")
|
||||
const env = require("./environment")
|
||||
const { v4 } = require("uuid")
|
||||
|
||||
|
|
|
@ -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}`)
|
||||
}
|
|
@ -23,7 +23,7 @@ function connectionError(timeout, err) {
|
|||
if (CLOSED) {
|
||||
return
|
||||
}
|
||||
CLIENT.end()
|
||||
CLIENT.disconnect()
|
||||
CLOSED = true
|
||||
// always clear this on error
|
||||
clearTimeout(timeout)
|
||||
|
|
|
@ -15,29 +15,33 @@ function makeSessionID(userId, sessionId) {
|
|||
}
|
||||
|
||||
async function invalidateSessions(userId, sessionIds = null) {
|
||||
let sessions = []
|
||||
try {
|
||||
let sessions = []
|
||||
|
||||
// If no sessionIds, get all the sessions for the user
|
||||
if (!sessionIds) {
|
||||
sessions = await getSessionsForUser(userId)
|
||||
sessions.forEach(
|
||||
session =>
|
||||
(session.key = makeSessionID(session.userId, session.sessionId))
|
||||
)
|
||||
} else {
|
||||
// use the passed array of sessionIds
|
||||
sessions = Array.isArray(sessionIds) ? sessionIds : [sessionIds]
|
||||
sessions = sessions.map(sessionId => ({
|
||||
key: makeSessionID(userId, sessionId),
|
||||
}))
|
||||
}
|
||||
// If no sessionIds, get all the sessions for the user
|
||||
if (!sessionIds) {
|
||||
sessions = await getSessionsForUser(userId)
|
||||
sessions.forEach(
|
||||
session =>
|
||||
(session.key = makeSessionID(session.userId, session.sessionId))
|
||||
)
|
||||
} else {
|
||||
// use the passed array of sessionIds
|
||||
sessions = Array.isArray(sessionIds) ? sessionIds : [sessionIds]
|
||||
sessions = sessions.map(sessionId => ({
|
||||
key: makeSessionID(userId, sessionId),
|
||||
}))
|
||||
}
|
||||
|
||||
const client = await redis.getSessionClient()
|
||||
const promises = []
|
||||
for (let session of sessions) {
|
||||
promises.push(client.delete(session.key))
|
||||
const client = await redis.getSessionClient()
|
||||
const promises = []
|
||||
for (let session of sessions) {
|
||||
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) => {
|
||||
|
@ -76,6 +80,7 @@ exports.getSession = async (userId, sessionId) => {
|
|||
return client.get(makeSessionID(userId, sessionId))
|
||||
} catch (err) {
|
||||
// if can't get session don't error, just don't return anything
|
||||
console.error(err)
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
|
|
@ -497,6 +497,21 @@
|
|||
"@types/yargs" "^15.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":
|
||||
version "1.8.3"
|
||||
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"
|
||||
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:
|
||||
version "3.0.0"
|
||||
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"
|
||||
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:
|
||||
version "1.0.10"
|
||||
resolved "https://registry.yarnpkg.com/argparse/-/argparse-1.0.10.tgz#bcd6791ea5ae09725e17e5ad988134cd40b3d911"
|
||||
|
@ -958,10 +991,13 @@ bcrypt-pbkdf@^1.0.0:
|
|||
dependencies:
|
||||
tweetnacl "^0.14.3"
|
||||
|
||||
bcryptjs@^2.4.3:
|
||||
version "2.4.3"
|
||||
resolved "https://registry.yarnpkg.com/bcryptjs/-/bcryptjs-2.4.3.tgz#9ab5627b93e60621ff7cdac5da9733027df1d0cb"
|
||||
integrity sha1-mrVie5PmBiH/fNrF2pczAn3x0Ms=
|
||||
bcrypt@^5.0.1:
|
||||
version "5.0.1"
|
||||
resolved "https://registry.yarnpkg.com/bcrypt/-/bcrypt-5.0.1.tgz#f1a2c20f208e2ccdceea4433df0c8b2c54ecdf71"
|
||||
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:
|
||||
version "4.1.0"
|
||||
|
@ -1144,6 +1180,11 @@ chownr@^1.1.1:
|
|||
resolved "https://registry.yarnpkg.com/chownr/-/chownr-1.1.4.tgz#6fc9d7b42d32a583596337666e7d08084da2cc6b"
|
||||
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:
|
||||
version "2.0.0"
|
||||
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"
|
||||
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:
|
||||
version "1.0.5"
|
||||
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"
|
||||
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:
|
||||
version "1.8.0"
|
||||
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"
|
||||
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:
|
||||
version "1.5.1"
|
||||
resolved "https://registry.yarnpkg.com/denque/-/denque-1.5.1.tgz#07f670e29c9a78f8faecb2566a1e2c11929c5cbf"
|
||||
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:
|
||||
version "3.1.0"
|
||||
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"
|
||||
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:
|
||||
version "1.0.0"
|
||||
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"
|
||||
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:
|
||||
version "1.0.0-beta.2"
|
||||
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"
|
||||
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:
|
||||
version "0.3.1"
|
||||
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"
|
||||
integrity sha1-81ypHEk/e3PaDgdJUwTxezH4fuU=
|
||||
|
||||
make-dir@^3.0.0:
|
||||
make-dir@^3.0.0, make-dir@^3.1.0:
|
||||
version "3.1.0"
|
||||
resolved "https://registry.yarnpkg.com/make-dir/-/make-dir-3.1.0.tgz#415e967046b3a7f1d185277d84aa58203726a13f"
|
||||
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"
|
||||
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:
|
||||
version "1.3.2"
|
||||
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"
|
||||
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:
|
||||
version "2.0.0"
|
||||
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"
|
||||
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:
|
||||
version "2.6.0"
|
||||
resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.6.0.tgz#e633456386d4aa55863f676a7ab0daa8fdecb0fd"
|
||||
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"
|
||||
resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.6.7.tgz#24de9fba827e3b4ae44dc8b20256a379160052ad"
|
||||
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"
|
||||
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:
|
||||
version "2.5.0"
|
||||
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:
|
||||
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:
|
||||
version "2.2.0"
|
||||
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"
|
||||
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:
|
||||
version "0.1.0"
|
||||
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"
|
||||
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"
|
||||
resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-3.6.0.tgz#337bbda3adc0706bd3e024426a286d4b4b2c9198"
|
||||
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"
|
||||
integrity sha512-TTlYpa+OL+vMMNG24xSlQGEJ3B/RzEfUlLct7b5G/ytav+wPrplCpVMFuwzXbkecJrb6IYo1iFb0S9v37754mg==
|
||||
|
||||
rimraf@^3.0.0:
|
||||
rimraf@^3.0.0, rimraf@^3.0.2:
|
||||
version "3.0.2"
|
||||
resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-3.0.2.tgz#f1a5402ba6220ad52cc1282bac1ae3aa49fd061a"
|
||||
integrity sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==
|
||||
|
@ -4330,6 +4465,13 @@ semver@^7.3.2:
|
|||
dependencies:
|
||||
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:
|
||||
version "2.0.0"
|
||||
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"
|
||||
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"
|
||||
resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010"
|
||||
integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==
|
||||
|
@ -4697,6 +4839,18 @@ tar-stream@^2.1.4:
|
|||
inherits "^2.0.3"
|
||||
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:
|
||||
version "2.1.1"
|
||||
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:
|
||||
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:
|
||||
version "1.2.3"
|
||||
resolved "https://registry.yarnpkg.com/word-wrap/-/word-wrap-1.2.3.tgz#610636f6b1f703891bd34771ccb17fb93b47079c"
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"name": "@budibase/bbui",
|
||||
"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",
|
||||
"svelte": "src/index.js",
|
||||
"module": "dist/bbui.es.js",
|
||||
|
@ -38,7 +38,7 @@
|
|||
],
|
||||
"dependencies": {
|
||||
"@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/actiongroup": "^1.0.1",
|
||||
"@spectrum-css/avatar": "^3.0.2",
|
||||
|
|
|
@ -18,6 +18,7 @@
|
|||
export let fileSizeLimit = BYTES_IN_MB * 20
|
||||
export let processFiles = null
|
||||
export let handleFileTooLarge = null
|
||||
export let handleTooManyFiles = null
|
||||
export let gallery = true
|
||||
export let error = null
|
||||
export let fileTags = []
|
||||
|
@ -71,6 +72,13 @@
|
|||
handleFileTooLarge(fileSizeLimit, value)
|
||||
return
|
||||
}
|
||||
|
||||
const fileCount = fileList.length + value.length
|
||||
if (handleTooManyFiles && maximum && fileCount > maximum) {
|
||||
handleTooManyFiles(maximum)
|
||||
return
|
||||
}
|
||||
|
||||
if (processFiles) {
|
||||
const processedFiles = await processFiles(fileList)
|
||||
const newValue = [...value, ...processedFiles]
|
||||
|
|
|
@ -11,6 +11,7 @@
|
|||
export let fileSizeLimit = undefined
|
||||
export let processFiles = undefined
|
||||
export let handleFileTooLarge = undefined
|
||||
export let handleTooManyFiles = undefined
|
||||
export let gallery = true
|
||||
export let fileTags = []
|
||||
export let maximum = undefined
|
||||
|
@ -30,6 +31,7 @@
|
|||
{fileSizeLimit}
|
||||
{processFiles}
|
||||
{handleFileTooLarge}
|
||||
{handleTooManyFiles}
|
||||
{gallery}
|
||||
{fileTags}
|
||||
{maximum}
|
||||
|
|
|
@ -41,7 +41,7 @@ filterTests(['all'], () => {
|
|||
}
|
||||
// Check items have been selected
|
||||
cy.getComponent(componentId)
|
||||
.find(interact.SPECTRUM_Picker_LABEL)
|
||||
.find(interact.SPECTRUM_PICKER_LABEL)
|
||||
.contains("(5)")
|
||||
})
|
||||
})
|
||||
|
|
|
@ -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.get(".appTable .name").eq(0).click()
|
||||
|
||||
cy.get(".app-logo .edit-hover").should("exist").invoke("show").click()
|
||||
|
||||
cy.customiseAppIcon()
|
||||
|
||||
cy.get(".app-logo")
|
||||
.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')
|
||||
cy.get(".appTable", { timeout: 2000})
|
||||
.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)
|
||||
// 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", () => {
|
||||
|
@ -259,6 +273,7 @@ filterTests(['all'], () => {
|
|||
});
|
||||
|
||||
cy.visit(`${Cypress.config().baseUrl}/builder`)
|
||||
cy.wait(1000)
|
||||
cy.get(".appTable .name").eq(0).click()
|
||||
cy.get(".spectrum-Tabs-item").contains("Settings").click()
|
||||
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.get(".appTable .app-row-actions").eq(0)
|
||||
.within(() => {
|
||||
|
|
|
@ -1,4 +1,6 @@
|
|||
import filterTests from "../support/filterTests"
|
||||
import { APP_TABLE_APP_NAME, DEPLOY_SUCCESS_MODAL } from "../support/interact";
|
||||
const interact = require('../support/interact')
|
||||
|
||||
filterTests(['all'], () => {
|
||||
context("Publish Application Workflow", () => {
|
||||
|
@ -11,87 +13,94 @@ filterTests(['all'], () => {
|
|||
cy.visit(`${Cypress.config().baseUrl}/builder`)
|
||||
cy.wait(1000)
|
||||
|
||||
cy.get(".appTable .app-status").eq(0)
|
||||
cy.get(interact.APP_TABLE_STATUS).eq(0)
|
||||
.within(() => {
|
||||
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(() => {
|
||||
cy.get(".spectrum-Button").contains("View")
|
||||
cy.get(".spectrum-Button").contains("Edit").click({ force: true })
|
||||
cy.get(interact.SPECTRUM_BUTTON_TEMPLATE).contains("Edit").click({ force: true })
|
||||
})
|
||||
|
||||
cy.get(".deployment-top-nav svg[aria-label='GlobeStrike']").should("exist")
|
||||
cy.get(".deployment-top-nav svg[aria-label='Globe']").should("not.exist")
|
||||
|
||||
cy.get(interact.DEPLOYMENT_TOP_NAV_GLOBESTRIKE).should("exist")
|
||||
cy.get(interact.DEPLOYMENT_TOP_GLOBE).should("not.exist")
|
||||
})
|
||||
|
||||
it("Should publish an application and correctly reflect that", () => {
|
||||
//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.wait(1000)
|
||||
|
||||
cy.get(".appTable .app-status").eq(0)
|
||||
cy.get(interact.APP_TABLE_STATUS).eq(0)
|
||||
.within(() => {
|
||||
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(() => {
|
||||
cy.get(".spectrum-Button").contains("View")
|
||||
cy.get(".spectrum-Button").contains("Edit").click({ force: true })
|
||||
cy.get(interact.SPECTRUM_BUTTON).contains("View")
|
||||
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("[data-cy='publish-popover-menu']").should("be.visible")
|
||||
cy.get(interact.DEPLOYMENT_TOP_GLOBE).should("exist").click({ force: true })
|
||||
|
||||
cy.get(interact.PUBLISH_POPOVER_MENU).should("be.visible")
|
||||
.within(() => {
|
||||
cy.get("[data-cy='publish-popover-action']").should("exist")
|
||||
cy.get("button").contains("View").should("exist")
|
||||
cy.get(".publish-popover-message").should("have.text", "Last published a few seconds ago")
|
||||
cy.get(interact.PUBLISH_POPOVER_ACTION).should("exist")
|
||||
cy.get("button").contains("View app").should("exist")
|
||||
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
|
||||
|
||||
|
||||
cy.visit(`${Cypress.config().baseUrl}/builder`)
|
||||
|
||||
cy.get(".appTable .app-status").eq(0)
|
||||
cy.get(interact.APP_TABLE_STATUS).eq(0)
|
||||
.within(() => {
|
||||
cy.contains("Published")
|
||||
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(() => {
|
||||
cy.get(".spectrum-Button").contains("View")
|
||||
cy.get(".spectrum-Button").contains("Edit").click({ force: true })
|
||||
cy.get(interact.SPECTRUM_BUTTON).contains("View")
|
||||
cy.get(interact.APP_TABLE_APP_NAME).click({ force: true })
|
||||
})
|
||||
|
||||
//The published status
|
||||
cy.get(".deployment-top-nav svg[aria-label='Globe']").should("exist")
|
||||
.click({ force: true })
|
||||
cy.get(interact.SPECTRUM_LINK).contains('Unpublish').click();
|
||||
|
||||
cy.get("[data-cy='publish-popover-menu']").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")
|
||||
cy.get(interact.UNPUBLISH_MODAL).should("be.visible")
|
||||
.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.get(".appTable .app-status").eq(0).contains("Unpublished")
|
||||
cy.get(interact.APP_TABLE_STATUS).eq(0).contains("Unpublished")
|
||||
|
||||
})
|
||||
})
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
import filterTests from "../support/filterTests"
|
||||
const interact = require('../support/interact')
|
||||
|
||||
filterTests(['smoke', 'all'], () => {
|
||||
context("Auto Screens UI", () => {
|
||||
|
@ -12,10 +13,10 @@ filterTests(['smoke', 'all'], () => {
|
|||
cy.closeModal();
|
||||
|
||||
cy.contains("Design").click()
|
||||
cy.get("[aria-label=AddCircle]").click()
|
||||
cy.get(".spectrum-Modal").within(() => {
|
||||
cy.get(".item.disabled").contains("Autogenerated screens")
|
||||
cy.get(".confirm-wrap .spectrum-Button").should('be.disabled')
|
||||
cy.get(interact.LABEL_ADD_CIRCLE).click()
|
||||
cy.get(interact.SPECTRUM_MODAL).within(() => {
|
||||
cy.get(interact.ITEM_DISABLED).contains("Autogenerated screens")
|
||||
cy.get(interact.CONFIRM_WRAP_SPE_BUTTON).should('be.disabled')
|
||||
})
|
||||
|
||||
cy.deleteAllApps()
|
||||
|
@ -26,14 +27,14 @@ filterTests(['smoke', 'all'], () => {
|
|||
|
||||
cy.selectExternalDatasource("REST")
|
||||
cy.selectExternalDatasource("S3")
|
||||
cy.get(".spectrum-Modal").within(() => {
|
||||
cy.get(".spectrum-Button").contains("Save and continue to query").click({ force : true })
|
||||
cy.get(interact.SPECTRUM_MODAL).within(() => {
|
||||
cy.get(interact.SPECTRUM_BUTTON).contains("Save and continue to query").click({ force : true })
|
||||
})
|
||||
|
||||
cy.navigateToAutogeneratedModal()
|
||||
|
||||
cy.get('.data-source-entry').should('have.length', 1)
|
||||
cy.get('.data-source-entry')
|
||||
cy.get(interact.DATA_SOURCE_ENTRY).should('have.length', 1)
|
||||
cy.get(interact.DATA_SOURCE_ENTRY)
|
||||
|
||||
cy.deleteAllApps()
|
||||
});
|
||||
|
@ -43,8 +44,8 @@ filterTests(['smoke', 'all'], () => {
|
|||
// Create Autogenerated screens from the internal table
|
||||
cy.createDatasourceScreen(["Cypress Tests"])
|
||||
// Confirm screens have been auto generated
|
||||
cy.get(".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).contains("cypress-tests").click({ force: true })
|
||||
cy.get(interact.NAV_ITEMS_CONTAINER).should('contain', 'cypress-tests/:id')
|
||||
.and('contain', 'cypress-tests/new/row')
|
||||
})
|
||||
|
||||
|
@ -56,12 +57,12 @@ filterTests(['smoke', 'all'], () => {
|
|||
// Create Autogenerated screens from the internal tables
|
||||
cy.createDatasourceScreen([initialTable, secondTable])
|
||||
// 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
|
||||
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')
|
||||
cy.get(".nav-items-container").contains("table-two").click()
|
||||
cy.get(".nav-items-container").should('contain', 'table-two/:id')
|
||||
cy.get(interact.NAV_ITEMS_CONTAINER).contains("table-two").click()
|
||||
cy.get(interact.NAV_ITEMS_CONTAINER).should('contain', 'table-two/:id')
|
||||
.and('contain', 'table-two/new/row')
|
||||
})
|
||||
|
||||
|
@ -71,17 +72,17 @@ filterTests(['smoke', 'all'], () => {
|
|||
cy.createTable("Table Four")
|
||||
cy.createDatasourceScreen(["Table Three", "Table Four"], "Admin")
|
||||
|
||||
cy.get(".nav-items-container").contains("table-three").click()
|
||||
cy.get(".nav-items-container").should('contain', 'table-three/:id')
|
||||
cy.get(interact.NAV_ITEMS_CONTAINER).contains("table-three").click()
|
||||
cy.get(interact.NAV_ITEMS_CONTAINER).should('contain', 'table-three/:id')
|
||||
.and('contain', 'table-three/new/row')
|
||||
|
||||
cy.get(".nav-items-container").contains("table-four").click()
|
||||
cy.get(".nav-items-container").should('contain', 'table-four/:id')
|
||||
cy.get(interact.NAV_ITEMS_CONTAINER).contains("table-four").click()
|
||||
cy.get(interact.NAV_ITEMS_CONTAINER).should('contain', 'table-four/:id')
|
||||
.and('contain', 'table-four/new/row')
|
||||
|
||||
//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(".nav-items-container").contains("cypress-tests").should('not.exist')
|
||||
cy.get(interact.NAV_ITEMS_CONTAINER).contains("table-two").should('not.exist')
|
||||
cy.get(interact.NAV_ITEMS_CONTAINER).contains("cypress-tests").should('not.exist')
|
||||
})
|
||||
|
||||
if (Cypress.env("TEST_ENV")) {
|
||||
|
@ -94,8 +95,8 @@ filterTests(['smoke', 'all'], () => {
|
|||
// Create Autogenerated screens from a MySQL table - MySQL contains books table
|
||||
cy.createDatasourceScreen(["books"])
|
||||
|
||||
cy.get(".nav-items-container").contains("books").click()
|
||||
cy.get(".nav-items-container").should('contain', 'books/:id')
|
||||
cy.get(interact.NAV_ITEMS_CONTAINER).contains("books").click()
|
||||
cy.get(interact.NAV_ITEMS_CONTAINER).should('contain', 'books/:id')
|
||||
.and('contain', 'books/new/row')
|
||||
})
|
||||
}
|
||||
|
|
|
@ -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()
|
||||
})
|
||||
})
|
||||
})
|
|
@ -30,7 +30,7 @@ filterTests(['smoke', 'all'], () => {
|
|||
.its("body")
|
||||
.then(val => {
|
||||
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})
|
||||
}
|
||||
})
|
||||
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
// TODO for now components are skipped, might not be good to keep doing this
|
||||
|
||||
import filterTests from "../support/filterTests"
|
||||
const interact = require('../support/interact')
|
||||
|
||||
filterTests(['all'], () => {
|
||||
xcontext("Create Components", () => {
|
||||
|
@ -31,32 +32,32 @@ filterTests(['all'], () => {
|
|||
|
||||
it("should change the text of the headline", () => {
|
||||
const text = "Lorem ipsum dolor sit amet."
|
||||
cy.get("[data-cy=Settings]").click()
|
||||
cy.get("[data-cy=setting-text] input")
|
||||
cy.get(interact.SETTINGS).click()
|
||||
cy.get(interact.SETTINGS_INPUT)
|
||||
.type(text)
|
||||
.blur()
|
||||
cy.getComponent(headlineId).should("have.text", text)
|
||||
})
|
||||
|
||||
it("should change the size of the headline", () => {
|
||||
cy.get("[data-cy=Design]").click()
|
||||
cy.get(interact.DESIGN).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.getComponent(headlineId).should("have.css", "font-size", "60px")
|
||||
})
|
||||
|
||||
it("should create a form and reset to match schema", () => {
|
||||
cy.addComponent("Form", "Form").then(() => {
|
||||
cy.get("[data-cy=Settings]").click()
|
||||
cy.get("[data-cy=setting-dataSource]")
|
||||
cy.get(interact.SETTINGS).click()
|
||||
cy.get(interact.DATA_CY_DATASOURCE)
|
||||
.contains("Choose option")
|
||||
.click()
|
||||
cy.get(".dropdown")
|
||||
cy.get(interact.DROPDOWN)
|
||||
.contains("dog")
|
||||
.click()
|
||||
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.get(".modal")
|
||||
.get("button.primary")
|
||||
|
@ -70,7 +71,7 @@ filterTests(['all'], () => {
|
|||
.find("input")
|
||||
.should("have.length", 2)
|
||||
cy.getComponent(fieldGroupId)
|
||||
.find(".spectrum-Picker")
|
||||
.find(interact.SPECTRUM_PICKER)
|
||||
.should("have.length", 1)
|
||||
})
|
||||
})
|
||||
|
@ -84,7 +85,7 @@ filterTests(['all'], () => {
|
|||
cy.get(".ui-nav ul .nav-item.selected .ri-more-line").click({
|
||||
force: true,
|
||||
})
|
||||
cy.get(".dropdown-container")
|
||||
cy.get(interact.DROPDOWN_CONTAINER)
|
||||
.contains("Delete")
|
||||
.click()
|
||||
cy.get(".modal")
|
||||
|
|
|
@ -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)
|
||||
})
|
||||
})
|
||||
}
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
|
@ -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()
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
|
@ -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)
|
||||
})
|
||||
})
|
||||
}
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
|
@ -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 })
|
||||
})
|
||||
}
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
|
@ -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)
|
||||
})
|
||||
})
|
||||
}
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
|
@ -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)
|
||||
})
|
||||
})
|
||||
}
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
|
@ -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)
|
||||
})
|
||||
})
|
||||
}
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
|
@ -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)
|
||||
})
|
||||
})
|
||||
}
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
|
@ -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)
|
||||
})
|
||||
})
|
||||
}
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
|
@ -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)
|
||||
})
|
||||
})
|
||||
}
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
|
@ -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)
|
||||
})
|
||||
})
|
||||
}
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
|
@ -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)
|
||||
})
|
||||
})
|
||||
}
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
|
@ -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)
|
||||
})
|
||||
})
|
||||
}
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
|
@ -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')
|
||||
})
|
||||
})
|
||||
})
|
|
@ -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)
|
||||
})
|
||||
})
|
||||
}
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
|
@ -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)
|
||||
})
|
||||
})
|
||||
}
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
|
@ -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)
|
||||
})
|
||||
})
|
||||
}
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
|
@ -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)
|
||||
})
|
||||
})
|
||||
}
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
|
@ -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", () => {
|
||||
return false
|
||||
})
|
||||
|
||||
// ACCOUNTS & USERS
|
||||
Cypress.Commands.add("login", () => {
|
||||
cy.visit(`${Cypress.config().baseUrl}/builder`)
|
||||
cy.wait(2000)
|
||||
|
@ -41,43 +35,20 @@ Cypress.Commands.add("logOut", () => {
|
|||
cy.wait(2000)
|
||||
})
|
||||
|
||||
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("createUser", email => {
|
||||
// quick hacky recorded way to create a user
|
||||
cy.contains("Users").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()
|
||||
|
||||
Cypress.Commands.add("importApp", (exportFilePath, name) => {
|
||||
cy.visit(`${Cypress.config().baseUrl}/builder`)
|
||||
|
||||
cy.request(`${Cypress.config().baseUrl}/api/applications?status=all`)
|
||||
.its("body")
|
||||
.then(val => {
|
||||
if (val.length > 0) {
|
||||
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)
|
||||
})
|
||||
//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("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) => {
|
||||
const shouldCreateDefaultTable =
|
||||
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 => {
|
||||
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 => {
|
||||
//Assumes you have navigated to an application first
|
||||
cy.get(".toprightnav button.spectrum-Button")
|
||||
|
@ -321,34 +268,96 @@ Cypress.Commands.add("publishApp", resolvedAppPath => {
|
|||
})
|
||||
})
|
||||
|
||||
Cypress.Commands.add("createTestApp", () => {
|
||||
const appName = "Cypress Tests"
|
||||
cy.deleteApp(appName)
|
||||
cy.createApp(appName, "This app is used for Cypress testing.")
|
||||
//cy.createScreen("home")
|
||||
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("createTestTableWithData", () => {
|
||||
cy.createTable("dog")
|
||||
cy.addColumn("dog", "name", "Text")
|
||||
cy.addColumn("dog", "age", "Number")
|
||||
})
|
||||
Cypress.Commands.add("importApp", (exportFilePath, name) => {
|
||||
cy.visit(`${Cypress.config().baseUrl}/builder`)
|
||||
|
||||
Cypress.Commands.add("publishApp", (viewApp = false) => {
|
||||
cy.get(".toprightnav").contains("Publish").click({ force: true })
|
||||
cy.get(".spectrum-Dialog-grid").within(() => {
|
||||
cy.get(".spectrum-Button").contains("Publish").click({ force: true })
|
||||
})
|
||||
cy.wait(2000) // Wait for App to publish and modal to appear
|
||||
cy.get(".spectrum-Dialog-grid").within(() => {
|
||||
if (viewApp) {
|
||||
cy.get(".spectrum-Button").contains("View App").click({ force: true })
|
||||
} else {
|
||||
cy.get(".spectrum-Button").contains("Done").click({ force: true })
|
||||
cy.request(`${Cypress.config().baseUrl}/api/applications?status=all`)
|
||||
.its("body")
|
||||
.then(val => {
|
||||
if (val.length > 0) {
|
||||
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)
|
||||
})
|
||||
})
|
||||
|
||||
// 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) => {
|
||||
if (!initialTable) {
|
||||
cy.navigateToDataSection()
|
||||
|
@ -369,6 +378,12 @@ Cypress.Commands.add("createTable", (tableName, initialTable) => {
|
|||
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(
|
||||
"addColumn",
|
||||
(tableName, columnName, type, multiOptions = null) => {
|
||||
|
@ -423,22 +438,33 @@ Cypress.Commands.add("addRowMultiValue", values => {
|
|||
})
|
||||
})
|
||||
|
||||
Cypress.Commands.add("createUser", email => {
|
||||
// quick hacky recorded way to create a user
|
||||
cy.contains("Users").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("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 })
|
||||
})
|
||||
})
|
||||
|
||||
// DESIGN AREA
|
||||
Cypress.Commands.add("addComponent", (category, component) => {
|
||||
if (category) {
|
||||
cy.get(`[data-cy="category-${category}"]`).click({ force: true })
|
||||
|
@ -466,22 +492,8 @@ Cypress.Commands.add("getComponent", 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) => {
|
||||
// Blank Screen
|
||||
cy.contains("Design").click()
|
||||
cy.get("[aria-label=AddCircle]").click()
|
||||
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(
|
||||
"createAutogeneratedScreens",
|
||||
(screenNames, accessLevelLabel) => {
|
||||
|
@ -573,96 +574,33 @@ Cypress.Commands.add(
|
|||
}
|
||||
)
|
||||
|
||||
Cypress.Commands.add("addRow", values => {
|
||||
cy.contains("Create row").click()
|
||||
// NAVIGATION
|
||||
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(() => {
|
||||
for (let i = 0; i < values.length; i++) {
|
||||
cy.get("input").eq(i).type(values[i]).blur()
|
||||
}
|
||||
cy.get(".spectrum-ButtonGroup").contains("Create").click()
|
||||
cy.get(".item").contains("Autogenerated screens").click()
|
||||
cy.get(".spectrum-Button").contains("Continue").click({ force: true })
|
||||
cy.wait(500)
|
||||
})
|
||||
})
|
||||
|
||||
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()
|
||||
}
|
||||
})
|
||||
|
||||
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)
|
||||
})
|
||||
|
||||
// DATASOURCES
|
||||
Cypress.Commands.add("selectExternalDatasource", datasourceName => {
|
||||
// Navigates to Data Section
|
||||
cy.navigateToDataSection()
|
||||
|
@ -798,3 +736,18 @@ Cypress.Commands.add("createRestQuery", (method, restUrl, queryPrettyName) => {
|
|||
.should("contain", method)
|
||||
.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()
|
||||
}
|
||||
})
|
||||
|
|
|
@ -17,10 +17,48 @@ export const CATEGORY_DATA = '[data-cy="category-Data"]'
|
|||
export const COMPONENT_DATA_PROVIDER = '[data-cy="component-Data Provider"]'
|
||||
export const DATASOURCE_PROP_CONTROL = '[data-cy="dataSource-prop-control"]'
|
||||
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 OPTION_TYPE_PROP_CONTROL = '[data-cy="optionsType-prop-control'
|
||||
|
||||
//AddRadioButtons
|
||||
export const SPECTRUM_POPOVER = ".spectrum-Popover"
|
||||
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"
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "@budibase/builder",
|
||||
"version": "1.0.178-alpha.0",
|
||||
"version": "1.0.192-alpha.5",
|
||||
"license": "GPL-3.0",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
|
@ -69,10 +69,10 @@
|
|||
}
|
||||
},
|
||||
"dependencies": {
|
||||
"@budibase/bbui": "^1.0.178-alpha.0",
|
||||
"@budibase/client": "^1.0.178-alpha.0",
|
||||
"@budibase/frontend-core": "^1.0.178-alpha.0",
|
||||
"@budibase/string-templates": "^1.0.178-alpha.0",
|
||||
"@budibase/bbui": "^1.0.192-alpha.5",
|
||||
"@budibase/client": "^1.0.192-alpha.5",
|
||||
"@budibase/frontend-core": "^1.0.192-alpha.5",
|
||||
"@budibase/string-templates": "^1.0.192-alpha.5",
|
||||
"@sentry/browser": "5.19.1",
|
||||
"@spectrum-css/page": "^3.0.1",
|
||||
"@spectrum-css/vars": "^3.0.1",
|
||||
|
|
|
@ -25,7 +25,6 @@
|
|||
let blockComplete
|
||||
let showLooping = false
|
||||
|
||||
$: rowControl = $automationStore.selectedAutomation.automation.rowControl
|
||||
$: showBindingPicker =
|
||||
block.stepId === "CREATE_ROW" || block.stepId === "UPDATE_ROW"
|
||||
|
||||
|
@ -194,7 +193,7 @@
|
|||
on:change={toggleFieldControl}
|
||||
defaultValue="Use values"
|
||||
autoWidth
|
||||
value={rowControl ? "Use bindings" : "Use values"}
|
||||
value={block.rowControl ? "Use bindings" : "Use values"}
|
||||
options={["Use values", "Use bindings"]}
|
||||
placeholder={null}
|
||||
/>
|
||||
|
|
|
@ -304,7 +304,9 @@
|
|||
)
|
||||
}
|
||||
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(
|
||||
", "
|
||||
)} are not allowed as column names`
|
||||
|
|
|
@ -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>
|
|
@ -14,6 +14,7 @@ import Oracle from "./Oracle.svelte"
|
|||
import GoogleSheets from "./GoogleSheets.svelte"
|
||||
import Firebase from "./Firebase.svelte"
|
||||
import Redis from "./Redis.svelte"
|
||||
import Snowflake from "./Snowflake.svelte"
|
||||
|
||||
export default {
|
||||
BUDIBASE: Budibase,
|
||||
|
@ -32,4 +33,5 @@ export default {
|
|||
GOOGLE_SHEETS: GoogleSheets,
|
||||
FIREBASE: Firebase,
|
||||
REDIS: Redis,
|
||||
SNOWFLAKE: Snowflake,
|
||||
}
|
||||
|
|
|
@ -45,6 +45,8 @@
|
|||
name,
|
||||
schema: addAutoColumns(name, dataImport.schema || {}),
|
||||
dataImport,
|
||||
type: "internal",
|
||||
sourceId: "bb_internal",
|
||||
}
|
||||
|
||||
// Only set primary display if defined
|
||||
|
|
|
@ -101,7 +101,7 @@
|
|||
confirmText="Done"
|
||||
cancelText="View App"
|
||||
onCancel={viewApp}
|
||||
dataCy={"deploy-app-success-modal"}
|
||||
dataCy="deploy-app-success-modal"
|
||||
>
|
||||
<div slot="header" class="app-published-header">
|
||||
<svg
|
||||
|
|
|
@ -20,7 +20,7 @@
|
|||
<div class="app-icon" style="color: {app.icon?.color || ''}">
|
||||
<Icon size="XL" name={app.icon?.name || "Apps"} />
|
||||
</div>
|
||||
<div class="name" on:click={() => appOverview(app)}>
|
||||
<div class="name" data-cy="app-name-link" on:click={() => appOverview(app)}>
|
||||
<Heading size="XS">
|
||||
{app.name}
|
||||
</Heading>
|
||||
|
|
|
@ -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>
|
|
@ -184,6 +184,7 @@ export const IntegrationTypes = {
|
|||
GOOGLE_SHEETS: "GOOGLE_SHEETS",
|
||||
FIREBASE: "FIREBASE",
|
||||
REDIS: "REDIS",
|
||||
SNOWFLAKE: "SNOWFLAKE",
|
||||
}
|
||||
|
||||
export const IntegrationNames = {
|
||||
|
@ -203,6 +204,7 @@ export const IntegrationNames = {
|
|||
[IntegrationTypes.GOOGLE_SHEETS]: "Google Sheets",
|
||||
[IntegrationTypes.FIREBASE]: "Firebase",
|
||||
[IntegrationTypes.REDIS]: "Redis",
|
||||
[IntegrationTypes.SNOWFLAKE]: "Snowflake",
|
||||
}
|
||||
|
||||
export const SchemaTypeOptions = [
|
||||
|
|
|
@ -174,6 +174,8 @@
|
|||
if (!$isActive("./navigation")) {
|
||||
$goto("./navigation")
|
||||
}
|
||||
} else if (type === "request-add-component") {
|
||||
$goto("./components/new")
|
||||
} else {
|
||||
console.warn(`Client sent unknown event type: ${type}`)
|
||||
}
|
||||
|
|
|
@ -76,11 +76,9 @@
|
|||
return []
|
||||
}
|
||||
|
||||
// Split list into either components or blocks initially
|
||||
if (section === "components") {
|
||||
// Remove blocks if there is no search string
|
||||
if (!search) {
|
||||
structure = structure.filter(category => category.name !== "Blocks")
|
||||
} else {
|
||||
structure = structure.filter(category => category.name === "Blocks")
|
||||
}
|
||||
|
||||
// Return only items which match the search string
|
||||
|
@ -159,33 +157,31 @@
|
|||
borderRight
|
||||
>
|
||||
<Layout paddingX="L" paddingY="XL" gap="S">
|
||||
<ActionGroup compact justified>
|
||||
<ActionButton
|
||||
fullWidth
|
||||
selected={section === "components"}
|
||||
on:click={() => (section = "components")}>Components</ActionButton
|
||||
>
|
||||
<ActionButton
|
||||
fullWidth
|
||||
selected={section === "blocks"}
|
||||
on:click={() => (section = "blocks")}>Blocks</ActionButton
|
||||
>
|
||||
</ActionGroup>
|
||||
<Search
|
||||
placeholder="Search"
|
||||
value={searchString}
|
||||
on:change={e => (searchString = e.detail)}
|
||||
bind:inputRef={searchRef}
|
||||
/>
|
||||
{#if !searchString}
|
||||
<ActionGroup compact justified>
|
||||
<ActionButton
|
||||
fullWidth
|
||||
selected={section === "components"}
|
||||
on:click={() => (section = "components")}>Components</ActionButton
|
||||
>
|
||||
<ActionButton
|
||||
fullWidth
|
||||
selected={section === "blocks"}
|
||||
on:click={() => (section = "blocks")}>Blocks</ActionButton
|
||||
>
|
||||
</ActionGroup>
|
||||
{/if}
|
||||
</Layout>
|
||||
<div>
|
||||
<Divider noMargin noGrid />
|
||||
</div>
|
||||
{#if 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"}
|
||||
{#if searchString || section === "components"}
|
||||
{#each filteredStructure as category}
|
||||
<DetailSummary name={category.name} collapsible={false}>
|
||||
<div class="component-grid">
|
||||
|
|
|
@ -17,6 +17,7 @@
|
|||
import CreateAppModal from "components/start/CreateAppModal.svelte"
|
||||
import UpdateAppModal from "components/start/UpdateAppModal.svelte"
|
||||
import ChooseIconModal from "components/start/ChooseIconModal.svelte"
|
||||
import ExportAppModal from "components/start/ExportAppModal.svelte"
|
||||
|
||||
import { store, automationStore } from "builderStore"
|
||||
import { API } from "api"
|
||||
|
@ -37,6 +38,7 @@
|
|||
let updatingModal
|
||||
let deletionModal
|
||||
let unpublishModal
|
||||
let exportModal
|
||||
let iconModal
|
||||
let creatingApp = false
|
||||
let loaded = $apps?.length || $templates?.length
|
||||
|
@ -200,9 +202,8 @@
|
|||
}
|
||||
|
||||
const exportApp = app => {
|
||||
const id = app.deployed ? app.prodId : app.devId
|
||||
const appName = encodeURIComponent(app.name)
|
||||
window.location = `/api/backups/export?appId=${id}&appname=${appName}`
|
||||
exportModal.show()
|
||||
selectedApp = app
|
||||
}
|
||||
|
||||
const unpublishApp = app => {
|
||||
|
@ -457,6 +458,10 @@
|
|||
<UpdateAppModal app={selectedApp} />
|
||||
</Modal>
|
||||
|
||||
<Modal bind:this={exportModal} padding={false} width="600px">
|
||||
<ExportAppModal app={selectedApp} />
|
||||
</Modal>
|
||||
|
||||
<ConfirmDialog
|
||||
bind:this={deletionModal}
|
||||
title="Confirm deletion"
|
||||
|
|
|
@ -274,12 +274,6 @@
|
|||
Export
|
||||
</MenuItem>
|
||||
{#if isPublished}
|
||||
<MenuItem
|
||||
on:click={() => unpublishApp(selectedApp)}
|
||||
icon="GlobeRemove"
|
||||
>
|
||||
Unpublish
|
||||
</MenuItem>
|
||||
<MenuItem on:click={() => copyAppId(selectedApp)} icon="Copy">
|
||||
Copy App ID
|
||||
</MenuItem>
|
||||
|
@ -306,6 +300,7 @@
|
|||
app={selectedApp}
|
||||
deployments={latestDeployments}
|
||||
navigateTab={handleTabChange}
|
||||
on:unpublish={e => unpublishApp(e.detail)}
|
||||
/>
|
||||
</Tab>
|
||||
{#if false}
|
||||
|
|
|
@ -13,10 +13,12 @@
|
|||
import clientPackage from "@budibase/client/package.json"
|
||||
import { processStringSync } from "@budibase/string-templates"
|
||||
import { users, auth } from "stores/portal"
|
||||
import { createEventDispatcher } from "svelte"
|
||||
|
||||
export let app
|
||||
export let deployments
|
||||
export let navigateTab
|
||||
const dispatch = createEventDispatcher()
|
||||
|
||||
const userInit = async () => {
|
||||
try {
|
||||
|
@ -26,6 +28,10 @@
|
|||
}
|
||||
}
|
||||
|
||||
const unpublishApp = () => {
|
||||
dispatch("unpublish", app)
|
||||
}
|
||||
|
||||
let userPromise = userInit()
|
||||
|
||||
$: updateAvailable = clientPackage.version !== $store.version
|
||||
|
@ -72,6 +78,9 @@
|
|||
new Date(deployments[0].updatedAt).getTime(),
|
||||
}
|
||||
)}
|
||||
{#if isPublished}
|
||||
- <Link on:click={unpublishApp}>Unpublish</Link>
|
||||
{/if}
|
||||
{/if}
|
||||
{#if !deployments?.length}
|
||||
-
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "@budibase/cli",
|
||||
"version": "1.0.178-alpha.0",
|
||||
"version": "1.0.192-alpha.5",
|
||||
"description": "Budibase CLI, for developers, self hosting and migrations.",
|
||||
"main": "src/index.js",
|
||||
"bin": {
|
||||
|
|
|
@ -2791,6 +2791,12 @@
|
|||
"label": "Extensions",
|
||||
"key": "extensions"
|
||||
},
|
||||
{
|
||||
"type": "number",
|
||||
"label": "No. of attachment",
|
||||
"key": "maximum",
|
||||
"min": 1
|
||||
},
|
||||
{
|
||||
"type": "event",
|
||||
"label": "On Change",
|
||||
|
@ -3048,6 +3054,7 @@
|
|||
"illegalChildren": ["section"],
|
||||
"hasChildren": true,
|
||||
"showEmptyState": false,
|
||||
"info": "Row selection is only compatible with internal or SQL tables",
|
||||
"settings": [
|
||||
{
|
||||
"type": "dataProvider",
|
||||
|
@ -3336,6 +3343,7 @@
|
|||
{
|
||||
"section": true,
|
||||
"name": "Table",
|
||||
"info": "Row selection is only compatible with internal or SQL tables",
|
||||
"settings": [
|
||||
{
|
||||
"type": "number",
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "@budibase/client",
|
||||
"version": "1.0.178-alpha.0",
|
||||
"version": "1.0.192-alpha.5",
|
||||
"license": "MPL-2.0",
|
||||
"module": "dist/budibase-client.js",
|
||||
"main": "dist/budibase-client.js",
|
||||
|
@ -19,9 +19,9 @@
|
|||
"dev:builder": "rollup -cw"
|
||||
},
|
||||
"dependencies": {
|
||||
"@budibase/bbui": "^1.0.178-alpha.0",
|
||||
"@budibase/frontend-core": "^1.0.178-alpha.0",
|
||||
"@budibase/string-templates": "^1.0.178-alpha.0",
|
||||
"@budibase/bbui": "^1.0.192-alpha.5",
|
||||
"@budibase/frontend-core": "^1.0.192-alpha.5",
|
||||
"@budibase/string-templates": "^1.0.192-alpha.5",
|
||||
"@spectrum-css/button": "^3.0.3",
|
||||
"@spectrum-css/card": "^3.0.3",
|
||||
"@spectrum-css/divider": "^1.0.3",
|
||||
|
|
|
@ -22,6 +22,7 @@
|
|||
import { Helpers } from "@budibase/bbui"
|
||||
import { getActiveConditions, reduceConditionActions } from "utils/conditions"
|
||||
import Placeholder from "components/app/Placeholder.svelte"
|
||||
import ScreenPlaceholder from "components/app/ScreenPlaceholder.svelte"
|
||||
|
||||
export let instance = {}
|
||||
export let isLayout = false
|
||||
|
@ -428,7 +429,11 @@
|
|||
<svelte:self instance={child} />
|
||||
{/each}
|
||||
{:else if emptyState}
|
||||
<Placeholder />
|
||||
{#if isScreen}
|
||||
<ScreenPlaceholder />
|
||||
{:else}
|
||||
<Placeholder />
|
||||
{/if}
|
||||
{:else if isBlock}
|
||||
<slot />
|
||||
{/if}
|
||||
|
|
|
@ -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>
|
|
@ -9,6 +9,7 @@
|
|||
export let validation
|
||||
export let extensions
|
||||
export let onChange
|
||||
export let maximum = undefined
|
||||
|
||||
let fieldState
|
||||
let fieldApi
|
||||
|
@ -25,6 +26,12 @@
|
|||
)
|
||||
}
|
||||
|
||||
const handleTooManyFiles = fileLimit => {
|
||||
notificationStore.actions.warning(
|
||||
`Please select a maximum of ${fileLimit} files.`
|
||||
)
|
||||
}
|
||||
|
||||
const processFiles = async fileList => {
|
||||
let data = new FormData()
|
||||
for (let i = 0; i < fileList.length; i++) {
|
||||
|
@ -66,6 +73,8 @@
|
|||
on:change={handleChange}
|
||||
{processFiles}
|
||||
{handleFileTooLarge}
|
||||
{handleTooManyFiles}
|
||||
{maximum}
|
||||
{extensions}
|
||||
/>
|
||||
{/if}
|
||||
|
|
|
@ -39,6 +39,8 @@
|
|||
dataProvider?.id,
|
||||
ActionTypes.SetDataProviderSorting
|
||||
)
|
||||
$: table = dataProvider?.datasource?.type === "table"
|
||||
|
||||
$: {
|
||||
rowSelectionStore.actions.updateSelection(
|
||||
$component.id,
|
||||
|
@ -142,7 +144,7 @@
|
|||
{quiet}
|
||||
{compact}
|
||||
{customRenderers}
|
||||
allowSelectRows={!!allowSelectRows}
|
||||
allowSelectRows={allowSelectRows && table}
|
||||
bind:selectedRows
|
||||
allowEditRows={false}
|
||||
allowEditColumns={false}
|
||||
|
|
|
@ -78,6 +78,9 @@ const createBuilderStore = () => {
|
|||
clickNav: () => {
|
||||
dispatchEvent("click-nav")
|
||||
},
|
||||
requestAddComponent: () => {
|
||||
dispatchEvent("request-add-component")
|
||||
},
|
||||
}
|
||||
return {
|
||||
...store,
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
import { API } from "api"
|
||||
import { JSONUtils } from "@budibase/frontend-core"
|
||||
import TableFetch from "@budibase/frontend-core/src/fetch/TableFetch.js"
|
||||
import ViewFetch from "@budibase/frontend-core/src/fetch/ViewFetch.js"
|
||||
import QueryFetch from "@budibase/frontend-core/src/fetch/QueryFetch.js"
|
||||
|
@ -40,44 +39,41 @@ export const fetchDatasourceSchema = async (
|
|||
return null
|
||||
}
|
||||
|
||||
// Check for any JSON fields so we can add any top level properties
|
||||
let jsonAdditions = {}
|
||||
Object.keys(schema).forEach(fieldKey => {
|
||||
// Enrich schema with relationships if required
|
||||
if (definition?.sql && options?.enrichRelationships) {
|
||||
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]
|
||||
if (fieldSchema?.type === "json") {
|
||||
const jsonSchema = JSONUtils.convertJSONSchemaToTableSchema(fieldSchema, {
|
||||
squashObjects: true,
|
||||
if (fieldSchema?.type === "link") {
|
||||
const linkSchema = await fetchDatasourceSchema({
|
||||
type: "table",
|
||||
tableId: fieldSchema?.tableId,
|
||||
})
|
||||
Object.keys(jsonSchema).forEach(jsonKey => {
|
||||
jsonAdditions[`${fieldKey}.${jsonKey}`] = {
|
||||
type: jsonSchema[jsonKey].type,
|
||||
nestedJSON: true,
|
||||
Object.keys(linkSchema || {}).forEach(linkKey => {
|
||||
relationshipAdditions[`${fieldKey}.${linkKey}`] = {
|
||||
type: linkSchema[linkKey].type,
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
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 }
|
||||
}
|
||||
|
||||
// Ensure schema structure is correct
|
||||
return instance.enrichSchema(schema)
|
||||
return relationshipAdditions
|
||||
}
|
||||
|
|
|
@ -1,12 +1,12 @@
|
|||
{
|
||||
"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",
|
||||
"author": "Budibase",
|
||||
"license": "MPL-2.0",
|
||||
"svelte": "src/index.js",
|
||||
"dependencies": {
|
||||
"@budibase/bbui": "^1.0.178-alpha.0",
|
||||
"@budibase/bbui": "^1.0.192-alpha.5",
|
||||
"lodash": "^4.17.21",
|
||||
"svelte": "^3.46.2"
|
||||
}
|
||||
|
|
|
@ -6,6 +6,7 @@ import {
|
|||
runLuceneQuery,
|
||||
luceneSort,
|
||||
} from "../utils/lucene"
|
||||
import { convertJSONSchemaToTableSchema } from "../utils/json"
|
||||
|
||||
/**
|
||||
* 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
|
||||
* @return {object} the enriched datasource schema
|
||||
*/
|
||||
|
@ -256,6 +258,26 @@ export default class DataFetch {
|
|||
if (schema == 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 = {}
|
||||
Object.entries(schema).forEach(([fieldName, fieldSchema]) => {
|
||||
if (typeof fieldSchema === "string") {
|
||||
|
@ -270,6 +292,7 @@ export default class DataFetch {
|
|||
}
|
||||
}
|
||||
})
|
||||
|
||||
return enrichedSchema
|
||||
}
|
||||
|
||||
|
|
|
@ -1,7 +1,5 @@
|
|||
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-update="scripts/watchtower-hooks/pre-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 . ./
|
||||
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 build
|
||||
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
#!/usr/bin/env bash
|
||||
|
||||
if [ -z $CLUSTER_MODE ]; then
|
||||
yarn run:docker
|
||||
else
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"name": "@budibase/server",
|
||||
"email": "hi@budibase.com",
|
||||
"version": "1.0.178-alpha.0",
|
||||
"version": "1.0.192-alpha.5",
|
||||
"description": "Budibase Web Server",
|
||||
"main": "src/index.ts",
|
||||
"repository": {
|
||||
|
@ -70,10 +70,10 @@
|
|||
"license": "GPL-3.0",
|
||||
"dependencies": {
|
||||
"@apidevtools/swagger-parser": "^10.0.3",
|
||||
"@budibase/backend-core": "^1.0.178-alpha.0",
|
||||
"@budibase/client": "^1.0.178-alpha.0",
|
||||
"@budibase/pro": "1.0.178-alpha.0",
|
||||
"@budibase/string-templates": "^1.0.178-alpha.0",
|
||||
"@budibase/backend-core": "^1.0.192-alpha.5",
|
||||
"@budibase/client": "^1.0.192-alpha.5",
|
||||
"@budibase/pro": "1.0.192-alpha.5",
|
||||
"@budibase/string-templates": "^1.0.192-alpha.5",
|
||||
"@bull-board/api": "^3.7.0",
|
||||
"@bull-board/koa": "^3.7.0",
|
||||
"@elastic/elasticsearch": "7.10.0",
|
||||
|
@ -130,6 +130,7 @@
|
|||
"pouchdb-replication-stream": "1.2.9",
|
||||
"redis": "4",
|
||||
"server-destroy": "1.0.1",
|
||||
"snowflake-promise": "^4.5.0",
|
||||
"svelte": "^3.38.2",
|
||||
"swagger-parser": "^10.0.3",
|
||||
"to-json-schema": "0.2.5",
|
||||
|
|
|
@ -1,9 +1,11 @@
|
|||
const { streamBackup } = require("../../utilities/fileSystem")
|
||||
|
||||
exports.exportAppDump = async function (ctx) {
|
||||
const { appId } = ctx.query
|
||||
let { appId, excludeRows } = ctx.query
|
||||
const appName = decodeURI(ctx.query.appname)
|
||||
excludeRows = excludeRows === "true"
|
||||
const backupIdentifier = `${appName}-export-${new Date().getTime()}.txt`
|
||||
ctx.attachment(backupIdentifier)
|
||||
ctx.body = await streamBackup(appId)
|
||||
|
||||
ctx.body = await streamBackup(appId, excludeRows)
|
||||
}
|
||||
|
|
|
@ -64,6 +64,9 @@ exports.validate = async ({ tableId, row, table }) => {
|
|||
// Validate.js doesn't seem to handle array
|
||||
if (type === FieldTypes.ARRAY && row[fieldName]) {
|
||||
if (row[fieldName].length) {
|
||||
if (!Array.isArray(row[fieldName])) {
|
||||
row[fieldName] = row[fieldName].split(",")
|
||||
}
|
||||
row[fieldName].map(val => {
|
||||
if (
|
||||
!constraints.inclusion.includes(val) &&
|
||||
|
|
|
@ -12,6 +12,7 @@ const { mainRoutes, staticRoutes, publicRoutes } = require("./routes")
|
|||
const pkg = require("../../package.json")
|
||||
const env = require("../environment")
|
||||
const { middleware: pro } = require("@budibase/pro")
|
||||
const { shutdown } = require("./routes/public")
|
||||
|
||||
const router = new Router()
|
||||
|
||||
|
@ -90,4 +91,5 @@ router.use(publicRoutes.allowedMethods())
|
|||
router.use(staticRoutes.routes())
|
||||
router.use(staticRoutes.allowedMethods())
|
||||
|
||||
module.exports = router
|
||||
module.exports.router = router
|
||||
module.exports.shutdown = shutdown
|
||||
|
|
|
@ -29,6 +29,7 @@ function getApiLimitPerSecond(): number {
|
|||
return parseInt(env.API_REQ_LIMIT_PER_SEC)
|
||||
}
|
||||
|
||||
let rateLimitStore: any = null
|
||||
if (!env.isTest()) {
|
||||
const REDIS_OPTS = getRedisOptions()
|
||||
let options
|
||||
|
@ -47,8 +48,9 @@ if (!env.isTest()) {
|
|||
database: 1,
|
||||
}
|
||||
}
|
||||
rateLimitStore = new Stores.Redis(options)
|
||||
RateLimit.defaultOptions({
|
||||
store: new Stores.Redis(options),
|
||||
store: rateLimitStore,
|
||||
})
|
||||
}
|
||||
// rate limiting, allows for 2 requests per second
|
||||
|
@ -128,3 +130,10 @@ applyRoutes(queryEndpoints, PermissionTypes.QUERY, "queryId")
|
|||
applyRoutes(rowEndpoints, PermissionTypes.TABLE, "tableId", "rowId")
|
||||
|
||||
export default publicRouter
|
||||
|
||||
export const shutdown = () => {
|
||||
if (rateLimitStore) {
|
||||
rateLimitStore.client.disconnect()
|
||||
rateLimitStore = null
|
||||
}
|
||||
}
|
||||
|
|
|
@ -14,6 +14,8 @@ const automations = require("./automations/index")
|
|||
const Sentry = require("@sentry/node")
|
||||
const fileSystem = require("./utilities/fileSystem")
|
||||
const bullboard = require("./automations/bullboard")
|
||||
const { logAlert } = require("@budibase/backend-core/logging")
|
||||
const { Thread } = require("./threads")
|
||||
import redis from "./utilities/redis"
|
||||
import * as migrations from "./migrations"
|
||||
|
||||
|
@ -49,7 +51,7 @@ app.context.eventEmitter = eventEmitter
|
|||
app.context.auth = {}
|
||||
|
||||
// api routes
|
||||
app.use(api.routes())
|
||||
app.use(api.router.routes())
|
||||
|
||||
if (env.isProd()) {
|
||||
env._set("NODE_ENV", "production")
|
||||
|
@ -68,11 +70,24 @@ if (env.isProd()) {
|
|||
const server = http.createServer(app.callback())
|
||||
destroyable(server)
|
||||
|
||||
let shuttingDown = false,
|
||||
errCode = 0
|
||||
server.on("close", async () => {
|
||||
if (env.NODE_ENV !== "jest") {
|
||||
// already in process
|
||||
if (shuttingDown) {
|
||||
return
|
||||
}
|
||||
shuttingDown = true
|
||||
if (!env.isTest()) {
|
||||
console.log("Server Closed")
|
||||
}
|
||||
await automations.shutdown()
|
||||
await redis.shutdown()
|
||||
await Thread.shutdown()
|
||||
api.shutdown()
|
||||
if (!env.isTest()) {
|
||||
process.exit(errCode)
|
||||
}
|
||||
})
|
||||
|
||||
module.exports = server.listen(env.PORT || 0, async () => {
|
||||
|
@ -90,7 +105,13 @@ const shutdown = () => {
|
|||
}
|
||||
|
||||
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()
|
||||
})
|
||||
|
||||
|
@ -102,7 +123,7 @@ process.on("SIGTERM", () => {
|
|||
// not recommended in a clustered environment
|
||||
if (!env.HTTP_MIGRATIONS) {
|
||||
migrations.migrate().catch(err => {
|
||||
console.error("Error performing migrations. Exiting.\n", err)
|
||||
logAlert("Error performing migrations. Exiting.", err)
|
||||
shutdown()
|
||||
})
|
||||
}
|
||||
|
|
|
@ -45,4 +45,12 @@ exports.init = () => {
|
|||
return serverAdapter.registerPlugin()
|
||||
}
|
||||
|
||||
exports.shutdown = async () => {
|
||||
if (automationQueue) {
|
||||
clearInterval(cleanupInternal)
|
||||
await automationQueue.close()
|
||||
automationQueue = null
|
||||
}
|
||||
}
|
||||
|
||||
exports.queue = automationQueue
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
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
|
||||
|
@ -14,4 +14,9 @@ exports.init = function () {
|
|||
exports.getQueues = () => {
|
||||
return [queue]
|
||||
}
|
||||
|
||||
exports.shutdown = () => {
|
||||
return shutdown()
|
||||
}
|
||||
|
||||
exports.queue = queue
|
||||
|
|
|
@ -53,6 +53,7 @@ exports.run = async function ({ inputs }) {
|
|||
if (!contents) {
|
||||
contents = "<h1>No content</h1>"
|
||||
}
|
||||
to = to || undefined
|
||||
try {
|
||||
let response = await sendSmtpEmail(to, from, subject, contents, true)
|
||||
return {
|
||||
|
|
|
@ -74,6 +74,7 @@ exports.isDevAppID = isDevAppID
|
|||
exports.isProdAppID = isProdAppID
|
||||
exports.USER_METDATA_PREFIX = `${DocumentTypes.ROW}${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.InternalTables = InternalTables
|
||||
exports.DocumentTypes = DocumentTypes
|
||||
|
|
|
@ -50,6 +50,7 @@ export enum SourceNames {
|
|||
GOOGLE_SHEETS = "GOOGLE_SHEETS",
|
||||
FIREBASE = "FIREBASE",
|
||||
REDIS = "REDIS",
|
||||
SNOWFLAKE = "SNOWFLAKE",
|
||||
}
|
||||
|
||||
export enum IncludeRelationships {
|
||||
|
|
|
@ -12,6 +12,7 @@ const rest = require("./rest")
|
|||
const googlesheets = require("./googlesheets")
|
||||
const firebase = require("./firebase")
|
||||
const redis = require("./redis")
|
||||
const snowflake = require("./snowflake")
|
||||
const { SourceNames } = require("../definitions/datasource")
|
||||
const environment = require("../environment")
|
||||
|
||||
|
@ -29,6 +30,7 @@ const DEFINITIONS = {
|
|||
[SourceNames.REST]: rest.schema,
|
||||
[SourceNames.FIREBASE]: firebase.schema,
|
||||
[SourceNames.REDIS]: redis.schema,
|
||||
[SourceNames.SNOWFLAKE]: snowflake.schema,
|
||||
}
|
||||
|
||||
const INTEGRATIONS = {
|
||||
|
@ -47,6 +49,7 @@ const INTEGRATIONS = {
|
|||
[SourceNames.GOOGLE_SHEETS]: googlesheets.integration,
|
||||
[SourceNames.REDIS]: redis.integration,
|
||||
[SourceNames.FIREBASE]: firebase.integration,
|
||||
[SourceNames.SNOWFLAKE]: snowflake.integration,
|
||||
}
|
||||
|
||||
// optionally add oracle integration if the oracle binary can be installed
|
||||
|
|
|
@ -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,
|
||||
}
|
||||
}
|
|
@ -219,7 +219,7 @@ class Orchestrator {
|
|||
}
|
||||
if (
|
||||
index === parseInt(env.AUTOMATION_MAX_ITERATIONS) ||
|
||||
index === loopStep.inputs.iterations
|
||||
index === parseInt(loopStep.inputs.iterations)
|
||||
) {
|
||||
this.updateContextAndOutput(loopStepNumber, step, tempOutput, {
|
||||
status: AutomationErrors.MAX_ITERATIONS,
|
||||
|
|
|
@ -28,6 +28,8 @@ export class Thread {
|
|||
workers: any
|
||||
timeoutMs: any
|
||||
|
||||
static workerRefs: any[] = []
|
||||
|
||||
constructor(type: any, opts: any = { timeoutMs: null, count: 1 }) {
|
||||
this.type = type
|
||||
this.count = opts.count ? opts.count : 1
|
||||
|
@ -46,6 +48,7 @@ export class Thread {
|
|||
workerOpts.maxCallTime = opts.timeoutMs
|
||||
}
|
||||
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 = []
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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)
|
|
@ -21,6 +21,7 @@ const env = require("../../environment")
|
|||
const {
|
||||
USER_METDATA_PREFIX,
|
||||
LINK_USER_METADATA_PREFIX,
|
||||
TABLE_ROW_PREFIX,
|
||||
} = require("../../db/utils")
|
||||
const MemoryStream = require("memorystream")
|
||||
const { getAppId } = require("@budibase/backend-core/context")
|
||||
|
@ -109,6 +110,23 @@ exports.apiFileReturn = contents => {
|
|||
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
|
||||
* data or user relationships.
|
||||
|
@ -116,14 +134,10 @@ exports.apiFileReturn = contents => {
|
|||
* @param {object} config Config to send to export DB
|
||||
* @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, {
|
||||
...config,
|
||||
filter: doc =>
|
||||
!(
|
||||
doc._id.includes(USER_METDATA_PREFIX) ||
|
||||
doc._id.includes(LINK_USER_METADATA_PREFIX)
|
||||
),
|
||||
filter: exports.defineFilter(includeRows),
|
||||
})
|
||||
}
|
||||
|
||||
|
@ -142,8 +156,8 @@ exports.performBackup = async (appId, backupName) => {
|
|||
* @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
|
||||
*/
|
||||
exports.streamBackup = async appId => {
|
||||
return await backupAppData(appId, { stream: true })
|
||||
exports.streamBackup = async (appId, includeRows) => {
|
||||
return await backupAppData(appId, { stream: true }, includeRows)
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -75,6 +75,13 @@ class InMemoryQueue {
|
|||
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.
|
||||
* @param {string} cronJobId The cron which is to be removed.
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "@budibase/string-templates",
|
||||
"version": "1.0.178-alpha.0",
|
||||
"version": "1.0.192-alpha.5",
|
||||
"description": "Handlebars wrapper for Budibase templating.",
|
||||
"main": "src/index.cjs",
|
||||
"module": "dist/bundle.mjs",
|
||||
|
|
|
@ -9,7 +9,9 @@ WORKDIR /app
|
|||
|
||||
# copy files and install dependencies
|
||||
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
|
||||
|
||||
EXPOSE 4001
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
#!/bin/sh
|
||||
|
||||
if [[ -z $CLUSTER_MODE ]]; then
|
||||
yarn run:docker
|
||||
else
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"name": "@budibase/worker",
|
||||
"email": "hi@budibase.com",
|
||||
"version": "1.0.178-alpha.0",
|
||||
"version": "1.0.192-alpha.5",
|
||||
"description": "Budibase background service",
|
||||
"main": "src/index.ts",
|
||||
"repository": {
|
||||
|
@ -32,9 +32,9 @@
|
|||
"author": "Budibase",
|
||||
"license": "GPL-3.0",
|
||||
"dependencies": {
|
||||
"@budibase/backend-core": "^1.0.178-alpha.0",
|
||||
"@budibase/pro": "1.0.178-alpha.0",
|
||||
"@budibase/string-templates": "^1.0.178-alpha.0",
|
||||
"@budibase/backend-core": "^1.0.192-alpha.5",
|
||||
"@budibase/pro": "1.0.192-alpha.5",
|
||||
"@budibase/string-templates": "^1.0.192-alpha.5",
|
||||
"@koa/router": "^8.0.0",
|
||||
"@sentry/node": "6.17.7",
|
||||
"@techpass/passport-openidconnect": "^0.3.0",
|
||||
|
|
|
@ -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)
|
||||
})
|
|
@ -12,6 +12,7 @@ const {
|
|||
getTenantId,
|
||||
getTenantUser,
|
||||
doesTenantExist,
|
||||
doInTenant,
|
||||
} = require("@budibase/backend-core/tenancy")
|
||||
const { removeUserFromInfoDB } = require("@budibase/backend-core/deprovision")
|
||||
const { errors } = require("@budibase/backend-core")
|
||||
|
@ -41,70 +42,73 @@ const parseBooleanParam = (param: any) => {
|
|||
|
||||
export const adminUser = async (ctx: any) => {
|
||||
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
|
||||
const hashPassword = parseBooleanParam(ctx.request.query.hashPassword)
|
||||
// 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())
|
||||
if (await doesTenantExist(tenantId)) {
|
||||
ctx.throw(403, "Organisation already exists.")
|
||||
}
|
||||
return response
|
||||
})
|
||||
|
||||
if (response.rows.some((row: any) => row.doc.admin)) {
|
||||
ctx.throw(
|
||||
403,
|
||||
"You cannot initialise once an global user has been created."
|
||||
)
|
||||
}
|
||||
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
|
||||
})
|
||||
|
||||
const user = {
|
||||
email: email,
|
||||
password: password,
|
||||
createdAt: Date.now(),
|
||||
roles: {},
|
||||
builder: {
|
||||
global: true,
|
||||
},
|
||||
admin: {
|
||||
global: true,
|
||||
},
|
||||
tenantId,
|
||||
}
|
||||
try {
|
||||
const finalUser = await users.save(
|
||||
user,
|
||||
if (response.rows.some((row: any) => row.doc.admin)) {
|
||||
ctx.throw(
|
||||
403,
|
||||
"You cannot initialise once an global user has been created."
|
||||
)
|
||||
}
|
||||
|
||||
const user = {
|
||||
email: email,
|
||||
password: password,
|
||||
createdAt: Date.now(),
|
||||
roles: {},
|
||||
builder: {
|
||||
global: true,
|
||||
},
|
||||
admin: {
|
||||
global: true,
|
||||
},
|
||||
tenantId,
|
||||
hashPassword,
|
||||
requirePassword
|
||||
)
|
||||
await bustCache(CacheKeys.CHECKLIST)
|
||||
ctx.body = finalUser
|
||||
} catch (err: any) {
|
||||
ctx.throw(err.status || 400, err)
|
||||
}
|
||||
}
|
||||
try {
|
||||
const finalUser = await users.save(
|
||||
user,
|
||||
tenantId,
|
||||
hashPassword,
|
||||
requirePassword
|
||||
)
|
||||
await bustCache(CacheKeys.CHECKLIST)
|
||||
ctx.body = finalUser
|
||||
} catch (err: any) {
|
||||
ctx.throw(err.status || 400, err)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
export const destroy = async (ctx: any) => {
|
||||
|
|
|
@ -12,6 +12,7 @@ const destroyable = require("server-destroy")
|
|||
const koaBody = require("koa-body")
|
||||
const koaSession = require("koa-session")
|
||||
const { passport } = require("@budibase/backend-core/auth")
|
||||
const { logAlert } = require("@budibase/backend-core/logging")
|
||||
const logger = require("koa-pino-logger")
|
||||
const http = require("http")
|
||||
const api = require("./api")
|
||||
|
@ -28,7 +29,6 @@ app.keys = ["secret", "key"]
|
|||
// set up top level koa middleware
|
||||
app.use(koaBody({ multipart: true }))
|
||||
app.use(koaSession(app))
|
||||
|
||||
app.use(
|
||||
logger({
|
||||
prettyPrint: {
|
||||
|
@ -62,25 +62,38 @@ if (env.isProd()) {
|
|||
const server = http.createServer(app.callback())
|
||||
destroyable(server)
|
||||
|
||||
let shuttingDown = false,
|
||||
errCode = 0
|
||||
server.on("close", async () => {
|
||||
if (env.isProd()) {
|
||||
if (shuttingDown) {
|
||||
return
|
||||
}
|
||||
shuttingDown = true
|
||||
if (!env.isTest()) {
|
||||
console.log("Server Closed")
|
||||
}
|
||||
await redis.shutdown()
|
||||
if (!env.isTest()) {
|
||||
process.exit(errCode)
|
||||
}
|
||||
})
|
||||
|
||||
const shutdown = () => {
|
||||
server.close()
|
||||
server.destroy()
|
||||
}
|
||||
|
||||
module.exports = server.listen(parseInt(env.PORT || 4002), async () => {
|
||||
console.log(`Worker running on ${JSON.stringify(server.address())}`)
|
||||
await redis.init()
|
||||
})
|
||||
|
||||
process.on("uncaughtException", err => {
|
||||
console.error(err)
|
||||
server.close()
|
||||
server.destroy()
|
||||
errCode = -1
|
||||
logAlert("Uncaught exception.", err)
|
||||
shutdown()
|
||||
})
|
||||
|
||||
process.on("SIGTERM", () => {
|
||||
server.close()
|
||||
server.destroy()
|
||||
shutdown()
|
||||
})
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -51,7 +51,7 @@ cd -
|
|||
lerna publish $VERSION --yes --force-publish --dist-tag $TAG
|
||||
|
||||
#############################################
|
||||
# POST-PUBLISH - PRO #
|
||||
# POST-PUBLISH - PRO #
|
||||
#############################################
|
||||
|
||||
# Revert build changes on packages/pro/package.json
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue