Merge remote-tracking branch 'origin/develop' into feat/dont-export-app-rows
This commit is contained in:
commit
5a67be19fa
|
@ -7,3 +7,4 @@ packages/server/client
|
||||||
packages/builder/.routify
|
packages/builder/.routify
|
||||||
packages/builder/cypress/support/queryLevelTransformerFunction.js
|
packages/builder/cypress/support/queryLevelTransformerFunction.js
|
||||||
packages/builder/cypress/support/queryLevelTransformerFunctionWithData.js
|
packages/builder/cypress/support/queryLevelTransformerFunctionWithData.js
|
||||||
|
packages/builder/cypress/reports
|
|
@ -1,4 +1,5 @@
|
||||||
name: Budibase Release Staging
|
name: Budibase Release Staging
|
||||||
|
concurrency: release-develop
|
||||||
|
|
||||||
on:
|
on:
|
||||||
push:
|
push:
|
||||||
|
|
|
@ -87,3 +87,10 @@ jobs:
|
||||||
packages/cli/build/cli-macos
|
packages/cli/build/cli-macos
|
||||||
packages/server/specs/openapi.yaml
|
packages/server/specs/openapi.yaml
|
||||||
packages/server/specs/openapi.json
|
packages/server/specs/openapi.json
|
||||||
|
|
||||||
|
- name: Discord Webhook Action
|
||||||
|
uses: tsickert/discord-webhook@v4.0.0
|
||||||
|
with:
|
||||||
|
webhook-url: ${{ secrets.PROD_DEPLOY_WEBHOOK_URL }}
|
||||||
|
content: "Self Host Deployment Complete: ${{ env.RELEASE_VERSION }} deployed to Self Host."
|
||||||
|
embed-title: ${{ env.RELEASE_VERSION }}
|
|
@ -1,4 +1,5 @@
|
||||||
name: Budibase Release
|
name: Budibase Release
|
||||||
|
concurrency: release
|
||||||
|
|
||||||
on:
|
on:
|
||||||
push:
|
push:
|
||||||
|
|
|
@ -31,24 +31,22 @@ jobs:
|
||||||
continue-on-error: true
|
continue-on-error: true
|
||||||
uses: cypress-io/github-action@v2
|
uses: cypress-io/github-action@v2
|
||||||
with:
|
with:
|
||||||
|
record: true
|
||||||
install: false
|
install: false
|
||||||
|
tag: nightly
|
||||||
command: yarn test:e2e:ci:record
|
command: yarn test:e2e:ci:record
|
||||||
env:
|
env:
|
||||||
CYPRESS_RECORD_KEY: ${{ secrets.CYPRESS_RECORD_KEY }}
|
CYPRESS_RECORD_KEY: ${{ secrets.CYPRESS_RECORD_KEY }}
|
||||||
|
|
||||||
# TODO: upload recordings to s3
|
- uses: actions/upload-artifact@v3
|
||||||
# - name: Configure AWS Credentials
|
|
||||||
# uses: aws-actions/configure-aws-credentials@v1
|
|
||||||
# with:
|
|
||||||
# aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
|
|
||||||
# aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
|
|
||||||
# aws-region: eu-west-1
|
|
||||||
|
|
||||||
- name: Discord Webhook Action
|
|
||||||
uses: tsickert/discord-webhook@v4.0.0
|
|
||||||
with:
|
with:
|
||||||
webhook-url: ${{ secrets.BUDI_QA_WEBHOOK }}
|
name: Test Reports
|
||||||
content: "Smoke test run completed with ${{ steps.cypress.outcome }}. See results at ${{ steps.cypress.dashboardUrl }}"
|
path: packages/builder/cypress/reports/testReport.html
|
||||||
embed-title: ${{ steps.cypress.outcome }}
|
|
||||||
embed-color: ${{ steps.cypress.outcome == 'success' && '3066993' || '15548997' }}
|
|
||||||
|
|
||||||
|
- name: Cypress Discord Notify
|
||||||
|
run: yarn test:e2e:ci:notify
|
||||||
|
env:
|
||||||
|
CYPRESS_WEBHOOK_URL: ${{ secrets.BUDI_QA_WEBHOOK }}
|
||||||
|
CYPRESS_OUTCOME: ${{ steps.cypress.outcome }}
|
||||||
|
CYPRESS_DASHBOARD_URL: ${{ steps.cypress.outputs.dashboardUrl }}
|
||||||
|
GITHUB_RUN_URL: $GITHUB_SERVER_URL/$GITHUB_REPOSITORY/actions/runs/$GITHUB_RUN_ID
|
||||||
|
|
|
@ -98,4 +98,6 @@ hosting/proxy/.generated-nginx.prod.conf
|
||||||
bin/
|
bin/
|
||||||
hosting/.generated*
|
hosting/.generated*
|
||||||
packages/builder/cypress.env.json
|
packages/builder/cypress.env.json
|
||||||
|
packages/builder/cypress/reports
|
||||||
stats.html
|
stats.html
|
||||||
|
|
||||||
|
|
|
@ -34,6 +34,7 @@ spec:
|
||||||
{{ else }}
|
{{ else }}
|
||||||
value: http://{{ .Release.Name }}-svc-couchdb:{{ .Values.services.couchdb.port }}
|
value: http://{{ .Release.Name }}-svc-couchdb:{{ .Values.services.couchdb.port }}
|
||||||
{{ end }}
|
{{ end }}
|
||||||
|
{{ if .Values.services.couchdb.enabled }}
|
||||||
- name: COUCH_DB_USER
|
- name: COUCH_DB_USER
|
||||||
valueFrom:
|
valueFrom:
|
||||||
secretKeyRef:
|
secretKeyRef:
|
||||||
|
@ -44,6 +45,7 @@ spec:
|
||||||
secretKeyRef:
|
secretKeyRef:
|
||||||
name: {{ template "couchdb.fullname" . }}
|
name: {{ template "couchdb.fullname" . }}
|
||||||
key: adminPassword
|
key: adminPassword
|
||||||
|
{{ end }}
|
||||||
- name: ENABLE_ANALYTICS
|
- name: ENABLE_ANALYTICS
|
||||||
value: {{ .Values.globals.enableAnalytics | quote }}
|
value: {{ .Values.globals.enableAnalytics | quote }}
|
||||||
- name: INTERNAL_API_KEY
|
- name: INTERNAL_API_KEY
|
||||||
|
@ -112,8 +114,8 @@ spec:
|
||||||
value: {{ .Values.globals.google.secret | quote }}
|
value: {{ .Values.globals.google.secret | quote }}
|
||||||
- name: AUTOMATION_MAX_ITERATIONS
|
- name: AUTOMATION_MAX_ITERATIONS
|
||||||
value: {{ .Values.globals.automationMaxIterations | quote }}
|
value: {{ .Values.globals.automationMaxIterations | quote }}
|
||||||
- name: EXCLUDE_QUOTAS_TENANTS
|
- name: TENANT_FEATURE_FLAGS
|
||||||
value: {{ .Values.globals.excludeQuotasTenants | quote }}
|
value: {{ .Values.globals.tenantFeatureFlags | quote }}
|
||||||
|
|
||||||
image: budibase/apps:{{ .Values.globals.appVersion }}
|
image: budibase/apps:{{ .Values.globals.appVersion }}
|
||||||
imagePullPolicy: Always
|
imagePullPolicy: Always
|
||||||
|
|
|
@ -29,6 +29,7 @@ spec:
|
||||||
- env:
|
- env:
|
||||||
- name: CLUSTER_PORT
|
- name: CLUSTER_PORT
|
||||||
value: {{ .Values.services.worker.port | quote }}
|
value: {{ .Values.services.worker.port | quote }}
|
||||||
|
{{ if .Values.services.couchdb.enabled }}
|
||||||
- name: COUCH_DB_USER
|
- name: COUCH_DB_USER
|
||||||
valueFrom:
|
valueFrom:
|
||||||
secretKeyRef:
|
secretKeyRef:
|
||||||
|
@ -39,6 +40,7 @@ spec:
|
||||||
secretKeyRef:
|
secretKeyRef:
|
||||||
name: {{ template "couchdb.fullname" . }}
|
name: {{ template "couchdb.fullname" . }}
|
||||||
key: adminPassword
|
key: adminPassword
|
||||||
|
{{ end }}
|
||||||
- name: COUCH_DB_URL
|
- name: COUCH_DB_URL
|
||||||
{{ if .Values.services.couchdb.url }}
|
{{ if .Values.services.couchdb.url }}
|
||||||
value: {{ .Values.services.couchdb.url }}
|
value: {{ .Values.services.couchdb.url }}
|
||||||
|
|
|
@ -48,7 +48,7 @@ http {
|
||||||
set $csp_style "style-src 'self' 'unsafe-inline' https://cdn.jsdelivr.net https://fonts.googleapis.com https://rsms.me https://maxcdn.bootstrapcdn.com";
|
set $csp_style "style-src 'self' 'unsafe-inline' https://cdn.jsdelivr.net https://fonts.googleapis.com https://rsms.me https://maxcdn.bootstrapcdn.com";
|
||||||
set $csp_object "object-src 'none'";
|
set $csp_object "object-src 'none'";
|
||||||
set $csp_base_uri "base-uri 'self'";
|
set $csp_base_uri "base-uri 'self'";
|
||||||
set $csp_connect "connect-src 'self' https://api-iam.intercom.io https://api-iam.intercom.io https://api-ping.intercom.io https://app.posthog.com wss://nexus-websocket-a.intercom.io wss://nexus-websocket-b.intercom.io https://nexus-websocket-a.intercom.io https://nexus-websocket-b.intercom.io https://uploads.intercomcdn.com https://uploads.intercomusercontent.com https://*.s3.*.amazonaws.com";
|
set $csp_connect "connect-src 'self' https://api-iam.intercom.io https://api-iam.intercom.io https://api-ping.intercom.io https://app.posthog.com wss://nexus-websocket-a.intercom.io wss://nexus-websocket-b.intercom.io https://nexus-websocket-a.intercom.io https://nexus-websocket-b.intercom.io https://uploads.intercomcdn.com https://uploads.intercomusercontent.com https://*.s3.us-east-2.amazonaws.com https://*.s3.us-east-1.amazonaws.com https://*.s3.us-west-1.amazonaws.com https://*.s3.us-west-2.amazonaws.com https://*.s3.af-south-1.amazonaws.com https://*.s3.ap-east-1.amazonaws.com https://*.s3.ap-southeast-3.amazonaws.com https://*.s3.ap-south-1.amazonaws.com https://*.s3.ap-northeast-3.amazonaws.com https://*.s3.ap-northeast-2.amazonaws.com https://*.s3.ap-southeast-1.amazonaws.com https://*.s3.ap-southeast-2.amazonaws.com https://*.s3.ap-northeast-1.amazonaws.com https://*.s3.ca-central-1.amazonaws.com https://*.s3.cn-north-1.amazonaws.com https://*.s3.cn-northwest-1.amazonaws.com https://*.s3.eu-central-1.amazonaws.com https://*.s3.eu-west-1.amazonaws.com https://*.s3.eu-west-2.amazonaws.com https://*.s3.eu-south-1.amazonaws.com https://*.s3.eu-west-3.amazonaws.com https://*.s3.eu-north-1.amazonaws.com https://*.s3.sa-east-1.amazonaws.com https://*.s3.me-south-1.amazonaws.com https://*.s3.us-gov-east-1.amazonaws.com https://*.s3.us-gov-west-1.amazonaws.com";
|
||||||
set $csp_font "font-src 'self' data: https://cdn.jsdelivr.net https://fonts.gstatic.com https://rsms.me https://maxcdn.bootstrapcdn.com https://js.intercomcdn.com https://fonts.intercomcdn.com";
|
set $csp_font "font-src 'self' data: https://cdn.jsdelivr.net https://fonts.gstatic.com https://rsms.me https://maxcdn.bootstrapcdn.com https://js.intercomcdn.com https://fonts.intercomcdn.com";
|
||||||
set $csp_frame "frame-src 'self' https:";
|
set $csp_frame "frame-src 'self' https:";
|
||||||
set $csp_img "img-src http: https: data: blob:";
|
set $csp_img "img-src http: https: data: blob:";
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
{
|
{
|
||||||
"version": "1.0.142-alpha.0",
|
"version": "1.0.185-alpha.0",
|
||||||
"npmClient": "yarn",
|
"npmClient": "yarn",
|
||||||
"packages": [
|
"packages": [
|
||||||
"packages/*"
|
"packages/*"
|
||||||
|
|
|
@ -3,6 +3,8 @@
|
||||||
"private": true,
|
"private": true,
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@rollup/plugin-json": "^4.0.2",
|
"@rollup/plugin-json": "^4.0.2",
|
||||||
|
"@types/mongodb": "3.6.3",
|
||||||
|
"@typescript-eslint/parser": "4.28.0",
|
||||||
"babel-eslint": "^10.0.3",
|
"babel-eslint": "^10.0.3",
|
||||||
"eslint": "^7.28.0",
|
"eslint": "^7.28.0",
|
||||||
"eslint-plugin-cypress": "^2.11.3",
|
"eslint-plugin-cypress": "^2.11.3",
|
||||||
|
@ -16,7 +18,6 @@
|
||||||
"rimraf": "^3.0.2",
|
"rimraf": "^3.0.2",
|
||||||
"rollup-plugin-replace": "^2.2.0",
|
"rollup-plugin-replace": "^2.2.0",
|
||||||
"svelte": "^3.38.2",
|
"svelte": "^3.38.2",
|
||||||
"@typescript-eslint/parser": "4.28.0",
|
|
||||||
"typescript": "4.5.5"
|
"typescript": "4.5.5"
|
||||||
},
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
|
@ -48,6 +49,7 @@
|
||||||
"test:e2e": "lerna run cy:test --stream",
|
"test:e2e": "lerna run cy:test --stream",
|
||||||
"test:e2e:ci": "lerna run cy:ci --stream",
|
"test:e2e:ci": "lerna run cy:ci --stream",
|
||||||
"test:e2e:ci:record": "lerna run cy:ci:record --stream",
|
"test:e2e:ci:record": "lerna run cy:ci:record --stream",
|
||||||
|
"test:e2e:ci:notify": "lerna run cy:ci:notify",
|
||||||
"build:specs": "lerna run specs",
|
"build:specs": "lerna run specs",
|
||||||
"build:docker": "lerna run build:docker && npm run build:docker:proxy:compose && cd hosting/scripts/linux/ && ./release-to-docker-hub.sh $BUDIBASE_RELEASE_VERSION && cd -",
|
"build:docker": "lerna run build:docker && npm run build:docker:proxy:compose && cd hosting/scripts/linux/ && ./release-to-docker-hub.sh $BUDIBASE_RELEASE_VERSION && cd -",
|
||||||
"build:docker:proxy": "docker build hosting/proxy -t proxy-service",
|
"build:docker:proxy": "docker build hosting/proxy -t proxy-service",
|
||||||
|
|
|
@ -1,4 +1,7 @@
|
||||||
|
const generic = require("./src/cache/generic")
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
user: require("./src/cache/user"),
|
user: require("./src/cache/user"),
|
||||||
app: require("./src/cache/appMetadata"),
|
app: require("./src/cache/appMetadata"),
|
||||||
|
...generic,
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "@budibase/backend-core",
|
"name": "@budibase/backend-core",
|
||||||
"version": "1.0.142-alpha.0",
|
"version": "1.0.185-alpha.0",
|
||||||
"description": "Budibase backend core libraries used in server and worker",
|
"description": "Budibase backend core libraries used in server and worker",
|
||||||
"main": "src/index.js",
|
"main": "src/index.js",
|
||||||
"author": "Budibase",
|
"author": "Budibase",
|
||||||
|
@ -12,8 +12,9 @@
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@techpass/passport-openidconnect": "^0.3.0",
|
"@techpass/passport-openidconnect": "^0.3.0",
|
||||||
"aws-sdk": "^2.901.0",
|
"aws-sdk": "^2.901.0",
|
||||||
"bcryptjs": "^2.4.3",
|
"bcrypt": "^5.0.1",
|
||||||
"cls-hooked": "^4.2.2",
|
"dotenv": "^16.0.1",
|
||||||
|
"emitter-listener": "^1.1.2",
|
||||||
"ioredis": "^4.27.1",
|
"ioredis": "^4.27.1",
|
||||||
"jsonwebtoken": "^8.5.1",
|
"jsonwebtoken": "^8.5.1",
|
||||||
"koa-passport": "^4.1.4",
|
"koa-passport": "^4.1.4",
|
||||||
|
@ -41,8 +42,7 @@
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"ioredis-mock": "^5.5.5",
|
"ioredis-mock": "^5.5.5",
|
||||||
"jest": "^26.6.3",
|
"jest": "^26.6.3",
|
||||||
"pouchdb-adapter-memory": "^7.2.2",
|
"pouchdb-adapter-memory": "^7.2.2"
|
||||||
"pouchdb-all-dbs": "^1.0.2"
|
|
||||||
},
|
},
|
||||||
"gitHead": "d1836a898cab3f8ab80ee6d8f42be1a9eed7dcdc"
|
"gitHead": "d1836a898cab3f8ab80ee6d8f42be1a9eed7dcdc"
|
||||||
}
|
}
|
||||||
|
|
|
@ -29,7 +29,7 @@ passport.deserializeUser(async (user, done) => {
|
||||||
const user = await db.get(user._id)
|
const user = await db.get(user._id)
|
||||||
return done(null, user)
|
return done(null, user)
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error("User not found", err)
|
console.error(`User not found`, err)
|
||||||
return done(null, false, { message: "User not found" })
|
return done(null, false, { message: "User not found" })
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
|
@ -0,0 +1,49 @@
|
||||||
|
const redis = require("../redis/authRedis")
|
||||||
|
const env = require("../environment")
|
||||||
|
const { getTenantId } = require("../context")
|
||||||
|
|
||||||
|
exports.CacheKeys = {
|
||||||
|
CHECKLIST: "checklist",
|
||||||
|
}
|
||||||
|
|
||||||
|
exports.TTL = {
|
||||||
|
ONE_MINUTE: 600,
|
||||||
|
ONE_HOUR: 3600,
|
||||||
|
ONE_DAY: 86400,
|
||||||
|
}
|
||||||
|
|
||||||
|
function generateTenantKey(key) {
|
||||||
|
const tenantId = getTenantId()
|
||||||
|
return `${key}:${tenantId}`
|
||||||
|
}
|
||||||
|
|
||||||
|
exports.withCache = async (key, ttl, fetchFn) => {
|
||||||
|
key = generateTenantKey(key)
|
||||||
|
const client = await redis.getCacheClient()
|
||||||
|
const cachedValue = await client.get(key)
|
||||||
|
if (cachedValue) {
|
||||||
|
return cachedValue
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const fetchedValue = await fetchFn()
|
||||||
|
|
||||||
|
if (!env.isTest()) {
|
||||||
|
await client.store(key, fetchedValue, ttl)
|
||||||
|
}
|
||||||
|
return fetchedValue
|
||||||
|
} catch (err) {
|
||||||
|
console.error("Error fetching before cache - ", err)
|
||||||
|
throw err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
exports.bustCache = async key => {
|
||||||
|
const client = await redis.getCacheClient()
|
||||||
|
try {
|
||||||
|
await client.delete(generateTenantKey(key))
|
||||||
|
} catch (err) {
|
||||||
|
console.error("Error busting cache - ", err)
|
||||||
|
throw err
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,650 @@
|
||||||
|
const util = require("util")
|
||||||
|
const assert = require("assert")
|
||||||
|
const wrapEmitter = require("emitter-listener")
|
||||||
|
const async_hooks = require("async_hooks")
|
||||||
|
|
||||||
|
const CONTEXTS_SYMBOL = "cls@contexts"
|
||||||
|
const ERROR_SYMBOL = "error@context"
|
||||||
|
|
||||||
|
const DEBUG_CLS_HOOKED = process.env.DEBUG_CLS_HOOKED
|
||||||
|
|
||||||
|
let currentUid = -1
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
getNamespace: getNamespace,
|
||||||
|
createNamespace: createNamespace,
|
||||||
|
destroyNamespace: destroyNamespace,
|
||||||
|
reset: reset,
|
||||||
|
ERROR_SYMBOL: ERROR_SYMBOL,
|
||||||
|
}
|
||||||
|
|
||||||
|
function Namespace(name) {
|
||||||
|
this.name = name
|
||||||
|
// changed in 2.7: no default context
|
||||||
|
this.active = null
|
||||||
|
this._set = []
|
||||||
|
this.id = null
|
||||||
|
this._contexts = new Map()
|
||||||
|
this._indent = 0
|
||||||
|
this._hook = null
|
||||||
|
}
|
||||||
|
|
||||||
|
Namespace.prototype.set = function set(key, value) {
|
||||||
|
if (!this.active) {
|
||||||
|
throw new Error(
|
||||||
|
"No context available. ns.run() or ns.bind() must be called first."
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
this.active[key] = value
|
||||||
|
|
||||||
|
if (DEBUG_CLS_HOOKED) {
|
||||||
|
const indentStr = " ".repeat(this._indent < 0 ? 0 : this._indent)
|
||||||
|
debug2(
|
||||||
|
indentStr +
|
||||||
|
"CONTEXT-SET KEY:" +
|
||||||
|
key +
|
||||||
|
"=" +
|
||||||
|
value +
|
||||||
|
" in ns:" +
|
||||||
|
this.name +
|
||||||
|
" currentUid:" +
|
||||||
|
currentUid +
|
||||||
|
" active:" +
|
||||||
|
util.inspect(this.active, { showHidden: true, depth: 2, colors: true })
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
return value
|
||||||
|
}
|
||||||
|
|
||||||
|
Namespace.prototype.get = function get(key) {
|
||||||
|
if (!this.active) {
|
||||||
|
if (DEBUG_CLS_HOOKED) {
|
||||||
|
const asyncHooksCurrentId = async_hooks.currentId()
|
||||||
|
const triggerId = async_hooks.triggerAsyncId()
|
||||||
|
const indentStr = " ".repeat(this._indent < 0 ? 0 : this._indent)
|
||||||
|
debug2(
|
||||||
|
`${indentStr}CONTEXT-GETTING KEY NO ACTIVE NS: (${this.name}) ${key}=undefined currentUid:${currentUid} asyncHooksCurrentId:${asyncHooksCurrentId} triggerId:${triggerId} len:${this._set.length}`
|
||||||
|
)
|
||||||
|
}
|
||||||
|
return undefined
|
||||||
|
}
|
||||||
|
if (DEBUG_CLS_HOOKED) {
|
||||||
|
const asyncHooksCurrentId = async_hooks.executionAsyncId()
|
||||||
|
const triggerId = async_hooks.triggerAsyncId()
|
||||||
|
const indentStr = " ".repeat(this._indent < 0 ? 0 : this._indent)
|
||||||
|
debug2(
|
||||||
|
indentStr +
|
||||||
|
"CONTEXT-GETTING KEY:" +
|
||||||
|
key +
|
||||||
|
"=" +
|
||||||
|
this.active[key] +
|
||||||
|
" (" +
|
||||||
|
this.name +
|
||||||
|
") currentUid:" +
|
||||||
|
currentUid +
|
||||||
|
" active:" +
|
||||||
|
util.inspect(this.active, { showHidden: true, depth: 2, colors: true })
|
||||||
|
)
|
||||||
|
debug2(
|
||||||
|
`${indentStr}CONTEXT-GETTING KEY: (${this.name}) ${key}=${
|
||||||
|
this.active[key]
|
||||||
|
} currentUid:${currentUid} asyncHooksCurrentId:${asyncHooksCurrentId} triggerId:${triggerId} len:${
|
||||||
|
this._set.length
|
||||||
|
} active:${util.inspect(this.active)}`
|
||||||
|
)
|
||||||
|
}
|
||||||
|
return this.active[key]
|
||||||
|
}
|
||||||
|
|
||||||
|
Namespace.prototype.createContext = function createContext() {
|
||||||
|
// Prototype inherit existing context if created a new child context within existing context.
|
||||||
|
let context = Object.create(this.active ? this.active : Object.prototype)
|
||||||
|
context._ns_name = this.name
|
||||||
|
context.id = currentUid
|
||||||
|
|
||||||
|
if (DEBUG_CLS_HOOKED) {
|
||||||
|
const asyncHooksCurrentId = async_hooks.executionAsyncId()
|
||||||
|
const triggerId = async_hooks.triggerAsyncId()
|
||||||
|
const indentStr = " ".repeat(this._indent < 0 ? 0 : this._indent)
|
||||||
|
debug2(
|
||||||
|
`${indentStr}CONTEXT-CREATED Context: (${
|
||||||
|
this.name
|
||||||
|
}) currentUid:${currentUid} asyncHooksCurrentId:${asyncHooksCurrentId} triggerId:${triggerId} len:${
|
||||||
|
this._set.length
|
||||||
|
} context:${util.inspect(context, {
|
||||||
|
showHidden: true,
|
||||||
|
depth: 2,
|
||||||
|
colors: true,
|
||||||
|
})}`
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
return context
|
||||||
|
}
|
||||||
|
|
||||||
|
Namespace.prototype.run = function run(fn) {
|
||||||
|
let context = this.createContext()
|
||||||
|
this.enter(context)
|
||||||
|
|
||||||
|
try {
|
||||||
|
if (DEBUG_CLS_HOOKED) {
|
||||||
|
const triggerId = async_hooks.triggerAsyncId()
|
||||||
|
const asyncHooksCurrentId = async_hooks.executionAsyncId()
|
||||||
|
const indentStr = " ".repeat(this._indent < 0 ? 0 : this._indent)
|
||||||
|
debug2(
|
||||||
|
`${indentStr}CONTEXT-RUN BEGIN: (${
|
||||||
|
this.name
|
||||||
|
}) currentUid:${currentUid} triggerId:${triggerId} asyncHooksCurrentId:${asyncHooksCurrentId} len:${
|
||||||
|
this._set.length
|
||||||
|
} context:${util.inspect(context)}`
|
||||||
|
)
|
||||||
|
}
|
||||||
|
fn(context)
|
||||||
|
return context
|
||||||
|
} catch (exception) {
|
||||||
|
if (exception) {
|
||||||
|
exception[ERROR_SYMBOL] = context
|
||||||
|
}
|
||||||
|
throw exception
|
||||||
|
} finally {
|
||||||
|
if (DEBUG_CLS_HOOKED) {
|
||||||
|
const triggerId = async_hooks.triggerAsyncId()
|
||||||
|
const asyncHooksCurrentId = async_hooks.executionAsyncId()
|
||||||
|
const indentStr = " ".repeat(this._indent < 0 ? 0 : this._indent)
|
||||||
|
debug2(
|
||||||
|
`${indentStr}CONTEXT-RUN END: (${
|
||||||
|
this.name
|
||||||
|
}) currentUid:${currentUid} triggerId:${triggerId} asyncHooksCurrentId:${asyncHooksCurrentId} len:${
|
||||||
|
this._set.length
|
||||||
|
} ${util.inspect(context)}`
|
||||||
|
)
|
||||||
|
}
|
||||||
|
this.exit(context)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Namespace.prototype.runAndReturn = function runAndReturn(fn) {
|
||||||
|
let value
|
||||||
|
this.run(function (context) {
|
||||||
|
value = fn(context)
|
||||||
|
})
|
||||||
|
return value
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Uses global Promise and assumes Promise is cls friendly or wrapped already.
|
||||||
|
* @param {function} fn
|
||||||
|
* @returns {*}
|
||||||
|
*/
|
||||||
|
Namespace.prototype.runPromise = function runPromise(fn) {
|
||||||
|
let context = this.createContext()
|
||||||
|
this.enter(context)
|
||||||
|
|
||||||
|
let promise = fn(context)
|
||||||
|
if (!promise || !promise.then || !promise.catch) {
|
||||||
|
throw new Error("fn must return a promise.")
|
||||||
|
}
|
||||||
|
|
||||||
|
if (DEBUG_CLS_HOOKED) {
|
||||||
|
debug2(
|
||||||
|
"CONTEXT-runPromise BEFORE: (" +
|
||||||
|
this.name +
|
||||||
|
") currentUid:" +
|
||||||
|
currentUid +
|
||||||
|
" len:" +
|
||||||
|
this._set.length +
|
||||||
|
" " +
|
||||||
|
util.inspect(context)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
return promise
|
||||||
|
.then(result => {
|
||||||
|
if (DEBUG_CLS_HOOKED) {
|
||||||
|
debug2(
|
||||||
|
"CONTEXT-runPromise AFTER then: (" +
|
||||||
|
this.name +
|
||||||
|
") currentUid:" +
|
||||||
|
currentUid +
|
||||||
|
" len:" +
|
||||||
|
this._set.length +
|
||||||
|
" " +
|
||||||
|
util.inspect(context)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
this.exit(context)
|
||||||
|
return result
|
||||||
|
})
|
||||||
|
.catch(err => {
|
||||||
|
err[ERROR_SYMBOL] = context
|
||||||
|
if (DEBUG_CLS_HOOKED) {
|
||||||
|
debug2(
|
||||||
|
"CONTEXT-runPromise AFTER catch: (" +
|
||||||
|
this.name +
|
||||||
|
") currentUid:" +
|
||||||
|
currentUid +
|
||||||
|
" len:" +
|
||||||
|
this._set.length +
|
||||||
|
" " +
|
||||||
|
util.inspect(context)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
this.exit(context)
|
||||||
|
throw err
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
Namespace.prototype.bind = function bindFactory(fn, context) {
|
||||||
|
if (!context) {
|
||||||
|
if (!this.active) {
|
||||||
|
context = this.createContext()
|
||||||
|
} else {
|
||||||
|
context = this.active
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let self = this
|
||||||
|
return function clsBind() {
|
||||||
|
self.enter(context)
|
||||||
|
try {
|
||||||
|
return fn.apply(this, arguments)
|
||||||
|
} catch (exception) {
|
||||||
|
if (exception) {
|
||||||
|
exception[ERROR_SYMBOL] = context
|
||||||
|
}
|
||||||
|
throw exception
|
||||||
|
} finally {
|
||||||
|
self.exit(context)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Namespace.prototype.enter = function enter(context) {
|
||||||
|
assert.ok(context, "context must be provided for entering")
|
||||||
|
if (DEBUG_CLS_HOOKED) {
|
||||||
|
const asyncHooksCurrentId = async_hooks.executionAsyncId()
|
||||||
|
const triggerId = async_hooks.triggerAsyncId()
|
||||||
|
const indentStr = " ".repeat(this._indent < 0 ? 0 : this._indent)
|
||||||
|
debug2(
|
||||||
|
`${indentStr}CONTEXT-ENTER: (${
|
||||||
|
this.name
|
||||||
|
}) currentUid:${currentUid} triggerId:${triggerId} asyncHooksCurrentId:${asyncHooksCurrentId} len:${
|
||||||
|
this._set.length
|
||||||
|
} ${util.inspect(context)}`
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
this._set.push(this.active)
|
||||||
|
this.active = context
|
||||||
|
}
|
||||||
|
|
||||||
|
Namespace.prototype.exit = function exit(context) {
|
||||||
|
assert.ok(context, "context must be provided for exiting")
|
||||||
|
if (DEBUG_CLS_HOOKED) {
|
||||||
|
const asyncHooksCurrentId = async_hooks.executionAsyncId()
|
||||||
|
const triggerId = async_hooks.triggerAsyncId()
|
||||||
|
const indentStr = " ".repeat(this._indent < 0 ? 0 : this._indent)
|
||||||
|
debug2(
|
||||||
|
`${indentStr}CONTEXT-EXIT: (${
|
||||||
|
this.name
|
||||||
|
}) currentUid:${currentUid} triggerId:${triggerId} asyncHooksCurrentId:${asyncHooksCurrentId} len:${
|
||||||
|
this._set.length
|
||||||
|
} ${util.inspect(context)}`
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fast path for most exits that are at the top of the stack
|
||||||
|
if (this.active === context) {
|
||||||
|
assert.ok(this._set.length, "can't remove top context")
|
||||||
|
this.active = this._set.pop()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fast search in the stack using lastIndexOf
|
||||||
|
let index = this._set.lastIndexOf(context)
|
||||||
|
|
||||||
|
if (index < 0) {
|
||||||
|
if (DEBUG_CLS_HOOKED) {
|
||||||
|
debug2(
|
||||||
|
"??ERROR?? context exiting but not entered - ignoring: " +
|
||||||
|
util.inspect(context)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
assert.ok(
|
||||||
|
index >= 0,
|
||||||
|
"context not currently entered; can't exit. \n" +
|
||||||
|
util.inspect(this) +
|
||||||
|
"\n" +
|
||||||
|
util.inspect(context)
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
assert.ok(index, "can't remove top context")
|
||||||
|
this._set.splice(index, 1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Namespace.prototype.bindEmitter = function bindEmitter(emitter) {
|
||||||
|
assert.ok(
|
||||||
|
emitter.on && emitter.addListener && emitter.emit,
|
||||||
|
"can only bind real EEs"
|
||||||
|
)
|
||||||
|
|
||||||
|
let namespace = this
|
||||||
|
let thisSymbol = "context@" + this.name
|
||||||
|
|
||||||
|
// Capture the context active at the time the emitter is bound.
|
||||||
|
function attach(listener) {
|
||||||
|
if (!listener) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (!listener[CONTEXTS_SYMBOL]) {
|
||||||
|
listener[CONTEXTS_SYMBOL] = Object.create(null)
|
||||||
|
}
|
||||||
|
|
||||||
|
listener[CONTEXTS_SYMBOL][thisSymbol] = {
|
||||||
|
namespace: namespace,
|
||||||
|
context: namespace.active,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// At emit time, bind the listener within the correct context.
|
||||||
|
function bind(unwrapped) {
|
||||||
|
if (!(unwrapped && unwrapped[CONTEXTS_SYMBOL])) {
|
||||||
|
return unwrapped
|
||||||
|
}
|
||||||
|
|
||||||
|
let wrapped = unwrapped
|
||||||
|
let unwrappedContexts = unwrapped[CONTEXTS_SYMBOL]
|
||||||
|
Object.keys(unwrappedContexts).forEach(function (name) {
|
||||||
|
let thunk = unwrappedContexts[name]
|
||||||
|
wrapped = thunk.namespace.bind(wrapped, thunk.context)
|
||||||
|
})
|
||||||
|
return wrapped
|
||||||
|
}
|
||||||
|
|
||||||
|
wrapEmitter(emitter, attach, bind)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* If an error comes out of a namespace, it will have a context attached to it.
|
||||||
|
* This function knows how to find it.
|
||||||
|
*
|
||||||
|
* @param {Error} exception Possibly annotated error.
|
||||||
|
*/
|
||||||
|
Namespace.prototype.fromException = function fromException(exception) {
|
||||||
|
return exception[ERROR_SYMBOL]
|
||||||
|
}
|
||||||
|
|
||||||
|
function getNamespace(name) {
|
||||||
|
return process.namespaces[name]
|
||||||
|
}
|
||||||
|
|
||||||
|
function createNamespace(name) {
|
||||||
|
assert.ok(name, "namespace must be given a name.")
|
||||||
|
|
||||||
|
if (DEBUG_CLS_HOOKED) {
|
||||||
|
debug2(`NS-CREATING NAMESPACE (${name})`)
|
||||||
|
}
|
||||||
|
let namespace = new Namespace(name)
|
||||||
|
namespace.id = currentUid
|
||||||
|
|
||||||
|
const hook = async_hooks.createHook({
|
||||||
|
init(asyncId, type, triggerId, resource) {
|
||||||
|
currentUid = async_hooks.executionAsyncId()
|
||||||
|
|
||||||
|
//CHAIN Parent's Context onto child if none exists. This is needed to pass net-events.spec
|
||||||
|
// let initContext = namespace.active;
|
||||||
|
// if(!initContext && triggerId) {
|
||||||
|
// let parentContext = namespace._contexts.get(triggerId);
|
||||||
|
// if (parentContext) {
|
||||||
|
// namespace.active = parentContext;
|
||||||
|
// namespace._contexts.set(currentUid, parentContext);
|
||||||
|
// if (DEBUG_CLS_HOOKED) {
|
||||||
|
// const indentStr = ' '.repeat(namespace._indent < 0 ? 0 : namespace._indent);
|
||||||
|
// debug2(`${indentStr}INIT [${type}] (${name}) WITH PARENT CONTEXT asyncId:${asyncId} currentUid:${currentUid} triggerId:${triggerId} active:${util.inspect(namespace.active, true)} resource:${resource}`);
|
||||||
|
// }
|
||||||
|
// } else if (DEBUG_CLS_HOOKED) {
|
||||||
|
// const indentStr = ' '.repeat(namespace._indent < 0 ? 0 : namespace._indent);
|
||||||
|
// debug2(`${indentStr}INIT [${type}] (${name}) MISSING CONTEXT asyncId:${asyncId} currentUid:${currentUid} triggerId:${triggerId} active:${util.inspect(namespace.active, true)} resource:${resource}`);
|
||||||
|
// }
|
||||||
|
// }else {
|
||||||
|
// namespace._contexts.set(currentUid, namespace.active);
|
||||||
|
// if (DEBUG_CLS_HOOKED) {
|
||||||
|
// const indentStr = ' '.repeat(namespace._indent < 0 ? 0 : namespace._indent);
|
||||||
|
// debug2(`${indentStr}INIT [${type}] (${name}) asyncId:${asyncId} currentUid:${currentUid} triggerId:${triggerId} active:${util.inspect(namespace.active, true)} resource:${resource}`);
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
if (namespace.active) {
|
||||||
|
namespace._contexts.set(asyncId, namespace.active)
|
||||||
|
|
||||||
|
if (DEBUG_CLS_HOOKED) {
|
||||||
|
const indentStr = " ".repeat(
|
||||||
|
namespace._indent < 0 ? 0 : namespace._indent
|
||||||
|
)
|
||||||
|
debug2(
|
||||||
|
`${indentStr}INIT [${type}] (${name}) asyncId:${asyncId} currentUid:${currentUid} triggerId:${triggerId} active:${util.inspect(
|
||||||
|
namespace.active,
|
||||||
|
{ showHidden: true, depth: 2, colors: true }
|
||||||
|
)} resource:${resource}`
|
||||||
|
)
|
||||||
|
}
|
||||||
|
} else if (currentUid === 0) {
|
||||||
|
// CurrentId will be 0 when triggered from C++. Promise events
|
||||||
|
// https://github.com/nodejs/node/blob/master/doc/api/async_hooks.md#triggerid
|
||||||
|
const triggerId = async_hooks.triggerAsyncId()
|
||||||
|
const triggerIdContext = namespace._contexts.get(triggerId)
|
||||||
|
if (triggerIdContext) {
|
||||||
|
namespace._contexts.set(asyncId, triggerIdContext)
|
||||||
|
if (DEBUG_CLS_HOOKED) {
|
||||||
|
const indentStr = " ".repeat(
|
||||||
|
namespace._indent < 0 ? 0 : namespace._indent
|
||||||
|
)
|
||||||
|
debug2(
|
||||||
|
`${indentStr}INIT USING CONTEXT FROM TRIGGERID [${type}] (${name}) asyncId:${asyncId} currentUid:${currentUid} triggerId:${triggerId} active:${util.inspect(
|
||||||
|
namespace.active,
|
||||||
|
{ showHidden: true, depth: 2, colors: true }
|
||||||
|
)} resource:${resource}`
|
||||||
|
)
|
||||||
|
}
|
||||||
|
} else if (DEBUG_CLS_HOOKED) {
|
||||||
|
const indentStr = " ".repeat(
|
||||||
|
namespace._indent < 0 ? 0 : namespace._indent
|
||||||
|
)
|
||||||
|
debug2(
|
||||||
|
`${indentStr}INIT MISSING CONTEXT [${type}] (${name}) asyncId:${asyncId} currentUid:${currentUid} triggerId:${triggerId} active:${util.inspect(
|
||||||
|
namespace.active,
|
||||||
|
{ showHidden: true, depth: 2, colors: true }
|
||||||
|
)} resource:${resource}`
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (DEBUG_CLS_HOOKED && type === "PROMISE") {
|
||||||
|
debug2(util.inspect(resource, { showHidden: true }))
|
||||||
|
const parentId = resource.parentId
|
||||||
|
const indentStr = " ".repeat(
|
||||||
|
namespace._indent < 0 ? 0 : namespace._indent
|
||||||
|
)
|
||||||
|
debug2(
|
||||||
|
`${indentStr}INIT RESOURCE-PROMISE [${type}] (${name}) parentId:${parentId} asyncId:${asyncId} currentUid:${currentUid} triggerId:${triggerId} active:${util.inspect(
|
||||||
|
namespace.active,
|
||||||
|
{ showHidden: true, depth: 2, colors: true }
|
||||||
|
)} resource:${resource}`
|
||||||
|
)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
before(asyncId) {
|
||||||
|
currentUid = async_hooks.executionAsyncId()
|
||||||
|
let context
|
||||||
|
|
||||||
|
/*
|
||||||
|
if(currentUid === 0){
|
||||||
|
// CurrentId will be 0 when triggered from C++. Promise events
|
||||||
|
// https://github.com/nodejs/node/blob/master/doc/api/async_hooks.md#triggerid
|
||||||
|
//const triggerId = async_hooks.triggerAsyncId();
|
||||||
|
context = namespace._contexts.get(asyncId); // || namespace._contexts.get(triggerId);
|
||||||
|
}else{
|
||||||
|
context = namespace._contexts.get(currentUid);
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
|
//HACK to work with promises until they are fixed in node > 8.1.1
|
||||||
|
context =
|
||||||
|
namespace._contexts.get(asyncId) || namespace._contexts.get(currentUid)
|
||||||
|
|
||||||
|
if (context) {
|
||||||
|
if (DEBUG_CLS_HOOKED) {
|
||||||
|
const triggerId = async_hooks.triggerAsyncId()
|
||||||
|
const indentStr = " ".repeat(
|
||||||
|
namespace._indent < 0 ? 0 : namespace._indent
|
||||||
|
)
|
||||||
|
debug2(
|
||||||
|
`${indentStr}BEFORE (${name}) asyncId:${asyncId} currentUid:${currentUid} triggerId:${triggerId} active:${util.inspect(
|
||||||
|
namespace.active,
|
||||||
|
{ showHidden: true, depth: 2, colors: true }
|
||||||
|
)} context:${util.inspect(context)}`
|
||||||
|
)
|
||||||
|
namespace._indent += 2
|
||||||
|
}
|
||||||
|
|
||||||
|
namespace.enter(context)
|
||||||
|
} else if (DEBUG_CLS_HOOKED) {
|
||||||
|
const triggerId = async_hooks.triggerAsyncId()
|
||||||
|
const indentStr = " ".repeat(
|
||||||
|
namespace._indent < 0 ? 0 : namespace._indent
|
||||||
|
)
|
||||||
|
debug2(
|
||||||
|
`${indentStr}BEFORE MISSING CONTEXT (${name}) asyncId:${asyncId} currentUid:${currentUid} triggerId:${triggerId} active:${util.inspect(
|
||||||
|
namespace.active,
|
||||||
|
{ showHidden: true, depth: 2, colors: true }
|
||||||
|
)} namespace._contexts:${util.inspect(namespace._contexts, {
|
||||||
|
showHidden: true,
|
||||||
|
depth: 2,
|
||||||
|
colors: true,
|
||||||
|
})}`
|
||||||
|
)
|
||||||
|
namespace._indent += 2
|
||||||
|
}
|
||||||
|
},
|
||||||
|
after(asyncId) {
|
||||||
|
currentUid = async_hooks.executionAsyncId()
|
||||||
|
let context // = namespace._contexts.get(currentUid);
|
||||||
|
/*
|
||||||
|
if(currentUid === 0){
|
||||||
|
// CurrentId will be 0 when triggered from C++. Promise events
|
||||||
|
// https://github.com/nodejs/node/blob/master/doc/api/async_hooks.md#triggerid
|
||||||
|
//const triggerId = async_hooks.triggerAsyncId();
|
||||||
|
context = namespace._contexts.get(asyncId); // || namespace._contexts.get(triggerId);
|
||||||
|
}else{
|
||||||
|
context = namespace._contexts.get(currentUid);
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
//HACK to work with promises until they are fixed in node > 8.1.1
|
||||||
|
context =
|
||||||
|
namespace._contexts.get(asyncId) || namespace._contexts.get(currentUid)
|
||||||
|
|
||||||
|
if (context) {
|
||||||
|
if (DEBUG_CLS_HOOKED) {
|
||||||
|
const triggerId = async_hooks.triggerAsyncId()
|
||||||
|
namespace._indent -= 2
|
||||||
|
const indentStr = " ".repeat(
|
||||||
|
namespace._indent < 0 ? 0 : namespace._indent
|
||||||
|
)
|
||||||
|
debug2(
|
||||||
|
`${indentStr}AFTER (${name}) asyncId:${asyncId} currentUid:${currentUid} triggerId:${triggerId} active:${util.inspect(
|
||||||
|
namespace.active,
|
||||||
|
{ showHidden: true, depth: 2, colors: true }
|
||||||
|
)} context:${util.inspect(context)}`
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
namespace.exit(context)
|
||||||
|
} else if (DEBUG_CLS_HOOKED) {
|
||||||
|
const triggerId = async_hooks.triggerAsyncId()
|
||||||
|
namespace._indent -= 2
|
||||||
|
const indentStr = " ".repeat(
|
||||||
|
namespace._indent < 0 ? 0 : namespace._indent
|
||||||
|
)
|
||||||
|
debug2(
|
||||||
|
`${indentStr}AFTER MISSING CONTEXT (${name}) asyncId:${asyncId} currentUid:${currentUid} triggerId:${triggerId} active:${util.inspect(
|
||||||
|
namespace.active,
|
||||||
|
{ showHidden: true, depth: 2, colors: true }
|
||||||
|
)} context:${util.inspect(context)}`
|
||||||
|
)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
destroy(asyncId) {
|
||||||
|
currentUid = async_hooks.executionAsyncId()
|
||||||
|
if (DEBUG_CLS_HOOKED) {
|
||||||
|
const triggerId = async_hooks.triggerAsyncId()
|
||||||
|
const indentStr = " ".repeat(
|
||||||
|
namespace._indent < 0 ? 0 : namespace._indent
|
||||||
|
)
|
||||||
|
debug2(
|
||||||
|
`${indentStr}DESTROY (${name}) currentUid:${currentUid} asyncId:${asyncId} triggerId:${triggerId} active:${util.inspect(
|
||||||
|
namespace.active,
|
||||||
|
{ showHidden: true, depth: 2, colors: true }
|
||||||
|
)} context:${util.inspect(namespace._contexts.get(currentUid))}`
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
namespace._contexts.delete(asyncId)
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
hook.enable()
|
||||||
|
namespace._hook = hook
|
||||||
|
|
||||||
|
process.namespaces[name] = namespace
|
||||||
|
return namespace
|
||||||
|
}
|
||||||
|
|
||||||
|
function destroyNamespace(name) {
|
||||||
|
let namespace = getNamespace(name)
|
||||||
|
|
||||||
|
assert.ok(namespace, "can't delete nonexistent namespace! \"" + name + '"')
|
||||||
|
assert.ok(
|
||||||
|
namespace.id,
|
||||||
|
"don't assign to process.namespaces directly! " + util.inspect(namespace)
|
||||||
|
)
|
||||||
|
|
||||||
|
namespace._hook.disable()
|
||||||
|
namespace._contexts = null
|
||||||
|
process.namespaces[name] = null
|
||||||
|
}
|
||||||
|
|
||||||
|
function reset() {
|
||||||
|
// must unregister async listeners
|
||||||
|
if (process.namespaces) {
|
||||||
|
Object.keys(process.namespaces).forEach(function (name) {
|
||||||
|
destroyNamespace(name)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
process.namespaces = Object.create(null)
|
||||||
|
}
|
||||||
|
|
||||||
|
process.namespaces = process.namespaces || {}
|
||||||
|
|
||||||
|
//const fs = require('fs');
|
||||||
|
function debug2(...args) {
|
||||||
|
if (DEBUG_CLS_HOOKED) {
|
||||||
|
//fs.writeSync(1, `${util.format(...args)}\n`);
|
||||||
|
process._rawDebug(`${util.format(...args)}`)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*function getFunctionName(fn) {
|
||||||
|
if (!fn) {
|
||||||
|
return fn;
|
||||||
|
}
|
||||||
|
if (typeof fn === 'function') {
|
||||||
|
if (fn.name) {
|
||||||
|
return fn.name;
|
||||||
|
}
|
||||||
|
return (fn.toString().trim().match(/^function\s*([^\s(]+)/) || [])[1];
|
||||||
|
} else if (fn.constructor && fn.constructor.name) {
|
||||||
|
return fn.constructor.name;
|
||||||
|
}
|
||||||
|
}*/
|
|
@ -1,84 +1,47 @@
|
||||||
const cls = require("cls-hooked")
|
const cls = require("../clshooked")
|
||||||
const { newid } = require("../hashing")
|
const { newid } = require("../hashing")
|
||||||
|
|
||||||
const REQUEST_ID_KEY = "requestId"
|
const REQUEST_ID_KEY = "requestId"
|
||||||
|
const MAIN_CTX = cls.createNamespace("main")
|
||||||
|
|
||||||
class FunctionContext {
|
function getContextStorage(namespace) {
|
||||||
static getMiddleware(
|
if (namespace && namespace.active) {
|
||||||
updateCtxFn = null,
|
let contextData = namespace.active
|
||||||
destroyFn = null,
|
|
||||||
contextName = "session"
|
|
||||||
) {
|
|
||||||
const namespace = this.createNamespace(contextName)
|
|
||||||
|
|
||||||
return async function (ctx, next) {
|
|
||||||
await new Promise(
|
|
||||||
namespace.bind(function (resolve, reject) {
|
|
||||||
// store a contextual request ID that can be used anywhere (audit logs)
|
|
||||||
namespace.set(REQUEST_ID_KEY, newid())
|
|
||||||
namespace.bindEmitter(ctx.req)
|
|
||||||
namespace.bindEmitter(ctx.res)
|
|
||||||
|
|
||||||
if (updateCtxFn) {
|
|
||||||
updateCtxFn(ctx)
|
|
||||||
}
|
|
||||||
next()
|
|
||||||
.then(resolve)
|
|
||||||
.catch(reject)
|
|
||||||
.finally(() => {
|
|
||||||
if (destroyFn) {
|
|
||||||
return destroyFn(ctx)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
})
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static run(callback, contextName = "session") {
|
|
||||||
const namespace = this.createNamespace(contextName)
|
|
||||||
|
|
||||||
return namespace.runAndReturn(callback)
|
|
||||||
}
|
|
||||||
|
|
||||||
static setOnContext(key, value, contextName = "session") {
|
|
||||||
const namespace = this.createNamespace(contextName)
|
|
||||||
namespace.set(key, value)
|
|
||||||
}
|
|
||||||
|
|
||||||
static getContextStorage() {
|
|
||||||
if (this._namespace && this._namespace.active) {
|
|
||||||
let contextData = this._namespace.active
|
|
||||||
delete contextData.id
|
delete contextData.id
|
||||||
delete contextData._ns_name
|
delete contextData._ns_name
|
||||||
return contextData
|
return contextData
|
||||||
}
|
}
|
||||||
|
|
||||||
return {}
|
return {}
|
||||||
|
}
|
||||||
|
|
||||||
|
class FunctionContext {
|
||||||
|
static run(callback) {
|
||||||
|
return MAIN_CTX.runAndReturn(async () => {
|
||||||
|
const namespaceId = newid()
|
||||||
|
MAIN_CTX.set(REQUEST_ID_KEY, namespaceId)
|
||||||
|
const namespace = cls.createNamespace(namespaceId)
|
||||||
|
let response = await namespace.runAndReturn(callback)
|
||||||
|
cls.destroyNamespace(namespaceId)
|
||||||
|
return response
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
static setOnContext(key, value) {
|
||||||
|
const namespaceId = MAIN_CTX.get(REQUEST_ID_KEY)
|
||||||
|
const namespace = cls.getNamespace(namespaceId)
|
||||||
|
namespace.set(key, value)
|
||||||
}
|
}
|
||||||
|
|
||||||
static getFromContext(key) {
|
static getFromContext(key) {
|
||||||
const context = this.getContextStorage()
|
const namespaceId = MAIN_CTX.get(REQUEST_ID_KEY)
|
||||||
|
const namespace = cls.getNamespace(namespaceId)
|
||||||
|
const context = getContextStorage(namespace)
|
||||||
if (context) {
|
if (context) {
|
||||||
return context[key]
|
return context[key]
|
||||||
} else {
|
} else {
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static destroyNamespace(name = "session") {
|
|
||||||
if (this._namespace) {
|
|
||||||
cls.destroyNamespace(name)
|
|
||||||
this._namespace = null
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static createNamespace(name = "session") {
|
|
||||||
if (!this._namespace) {
|
|
||||||
this._namespace = cls.createNamespace(name)
|
|
||||||
}
|
|
||||||
return this._namespace
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = FunctionContext
|
module.exports = FunctionContext
|
||||||
|
|
|
@ -55,6 +55,15 @@ async function closeAppDBs() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
exports.closeTenancy = async () => {
|
||||||
|
if (env.USE_COUCH) {
|
||||||
|
await closeDB(exports.getGlobalDB())
|
||||||
|
}
|
||||||
|
// clear from context now that database is closed/task is finished
|
||||||
|
cls.setOnContext(ContextKeys.TENANT_ID, null)
|
||||||
|
cls.setOnContext(ContextKeys.GLOBAL_DB, null)
|
||||||
|
}
|
||||||
|
|
||||||
exports.isDefaultTenant = () => {
|
exports.isDefaultTenant = () => {
|
||||||
return exports.getTenantId() === exports.DEFAULT_TENANT_ID
|
return exports.getTenantId() === exports.DEFAULT_TENANT_ID
|
||||||
}
|
}
|
||||||
|
@ -64,7 +73,7 @@ exports.isMultiTenant = () => {
|
||||||
}
|
}
|
||||||
|
|
||||||
// used for automations, API endpoints should always be in context already
|
// used for automations, API endpoints should always be in context already
|
||||||
exports.doInTenant = (tenantId, task) => {
|
exports.doInTenant = (tenantId, task, { forceNew } = {}) => {
|
||||||
// the internal function is so that we can re-use an existing
|
// the internal function is so that we can re-use an existing
|
||||||
// context - don't want to close DB on a parent context
|
// context - don't want to close DB on a parent context
|
||||||
async function internal(opts = { existing: false }) {
|
async function internal(opts = { existing: false }) {
|
||||||
|
@ -82,19 +91,18 @@ exports.doInTenant = (tenantId, task) => {
|
||||||
} finally {
|
} finally {
|
||||||
const using = cls.getFromContext(ContextKeys.IN_USE)
|
const using = cls.getFromContext(ContextKeys.IN_USE)
|
||||||
if (!using || using <= 1) {
|
if (!using || using <= 1) {
|
||||||
if (env.USE_COUCH) {
|
await exports.closeTenancy()
|
||||||
await closeDB(exports.getGlobalDB())
|
|
||||||
}
|
|
||||||
// clear from context now that database is closed/task is finished
|
|
||||||
cls.setOnContext(ContextKeys.TENANT_ID, null)
|
|
||||||
cls.setOnContext(ContextKeys.GLOBAL_DB, null)
|
|
||||||
} else {
|
} else {
|
||||||
cls.setOnContext(using - 1)
|
cls.setOnContext(using - 1)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
const using = cls.getFromContext(ContextKeys.IN_USE)
|
const using = cls.getFromContext(ContextKeys.IN_USE)
|
||||||
if (using && cls.getFromContext(ContextKeys.TENANT_ID) === tenantId) {
|
if (
|
||||||
|
!forceNew &&
|
||||||
|
using &&
|
||||||
|
cls.getFromContext(ContextKeys.TENANT_ID) === tenantId
|
||||||
|
) {
|
||||||
cls.setOnContext(ContextKeys.IN_USE, using + 1)
|
cls.setOnContext(ContextKeys.IN_USE, using + 1)
|
||||||
return internal({ existing: true })
|
return internal({ existing: true })
|
||||||
} else {
|
} else {
|
||||||
|
@ -131,7 +139,7 @@ const setAppTenantId = appId => {
|
||||||
exports.updateTenantId(appTenantId)
|
exports.updateTenantId(appTenantId)
|
||||||
}
|
}
|
||||||
|
|
||||||
exports.doInAppContext = (appId, task) => {
|
exports.doInAppContext = (appId, task, { forceNew } = {}) => {
|
||||||
if (!appId) {
|
if (!appId) {
|
||||||
throw new Error("appId is required")
|
throw new Error("appId is required")
|
||||||
}
|
}
|
||||||
|
@ -158,7 +166,7 @@ exports.doInAppContext = (appId, task) => {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
const using = cls.getFromContext(ContextKeys.IN_USE)
|
const using = cls.getFromContext(ContextKeys.IN_USE)
|
||||||
if (using && cls.getFromContext(ContextKeys.APP_ID) === appId) {
|
if (!forceNew && using && cls.getFromContext(ContextKeys.APP_ID) === appId) {
|
||||||
cls.setOnContext(ContextKeys.IN_USE, using + 1)
|
cls.setOnContext(ContextKeys.IN_USE, using + 1)
|
||||||
return internal({ existing: true })
|
return internal({ existing: true })
|
||||||
} else {
|
} else {
|
||||||
|
|
|
@ -23,24 +23,30 @@ exports.isDevApp = app => {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Convert a development app ID to a deployed app ID.
|
* Generates a development app ID from a real app ID.
|
||||||
|
* @returns {string} the dev app ID which can be used for dev database.
|
||||||
*/
|
*/
|
||||||
exports.getProdAppID = appId => {
|
exports.getDevelopmentAppID = appId => {
|
||||||
// if dev, convert it
|
if (!appId || appId.startsWith(APP_DEV_PREFIX)) {
|
||||||
if (appId.startsWith(APP_DEV_PREFIX)) {
|
|
||||||
const id = appId.split(APP_DEV_PREFIX)[1]
|
|
||||||
return `${APP_PREFIX}${id}`
|
|
||||||
}
|
|
||||||
return appId
|
return appId
|
||||||
|
}
|
||||||
|
// split to take off the app_ element, then join it together incase any other app_ exist
|
||||||
|
const split = appId.split(APP_PREFIX)
|
||||||
|
split.shift()
|
||||||
|
const rest = split.join(APP_PREFIX)
|
||||||
|
return `${APP_DEV_PREFIX}${rest}`
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Convert a deployed app ID to a development app ID.
|
* Convert a development app ID to a deployed app ID.
|
||||||
*/
|
*/
|
||||||
exports.getDevelopmentAppID = appId => {
|
exports.getProdAppID = appId => {
|
||||||
if (!appId.startsWith(APP_DEV_PREFIX)) {
|
if (!appId || !appId.startsWith(APP_DEV_PREFIX)) {
|
||||||
const id = appId.split(APP_PREFIX)[1]
|
|
||||||
return `${APP_DEV_PREFIX}${id}`
|
|
||||||
}
|
|
||||||
return appId
|
return appId
|
||||||
|
}
|
||||||
|
// split to take off the app_dev element, then join it together incase any other app_ exist
|
||||||
|
const split = appId.split(APP_DEV_PREFIX)
|
||||||
|
split.shift()
|
||||||
|
const rest = split.join(APP_DEV_PREFIX)
|
||||||
|
return `${APP_PREFIX}${rest}`
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,13 +3,13 @@ const env = require("../environment")
|
||||||
|
|
||||||
let PouchDB
|
let PouchDB
|
||||||
let initialised = false
|
let initialised = false
|
||||||
|
const dbList = new Set()
|
||||||
|
|
||||||
const put =
|
const put =
|
||||||
dbPut =>
|
dbPut =>
|
||||||
async (doc, options = {}) => {
|
async (doc, options = {}) => {
|
||||||
const response = await dbPut(doc, options)
|
|
||||||
// TODO: add created / updated
|
// TODO: add created / updated
|
||||||
return response
|
return await dbPut(doc, options)
|
||||||
}
|
}
|
||||||
|
|
||||||
const checkInitialised = () => {
|
const checkInitialised = () => {
|
||||||
|
@ -28,6 +28,9 @@ exports.init = opts => {
|
||||||
// in situations that using the function doWithDB does not work
|
// in situations that using the function doWithDB does not work
|
||||||
exports.dangerousGetDB = (dbName, opts) => {
|
exports.dangerousGetDB = (dbName, opts) => {
|
||||||
checkInitialised()
|
checkInitialised()
|
||||||
|
if (env.isTest()) {
|
||||||
|
dbList.add(dbName)
|
||||||
|
}
|
||||||
const db = new PouchDB(dbName, opts)
|
const db = new PouchDB(dbName, opts)
|
||||||
const dbPut = db.put
|
const dbPut = db.put
|
||||||
db.put = put(dbPut)
|
db.put = put(dbPut)
|
||||||
|
@ -63,6 +66,9 @@ exports.doWithDB = async (dbName, cb, opts) => {
|
||||||
}
|
}
|
||||||
|
|
||||||
exports.allDbs = () => {
|
exports.allDbs = () => {
|
||||||
|
if (!env.isTest()) {
|
||||||
|
throw new Error("Cannot be used outside test environment.")
|
||||||
|
}
|
||||||
checkInitialised()
|
checkInitialised()
|
||||||
return PouchDB.allDbs()
|
return [...dbList]
|
||||||
}
|
}
|
||||||
|
|
|
@ -92,11 +92,5 @@ exports.getPouch = (opts = {}) => {
|
||||||
PouchDB.plugin(find)
|
PouchDB.plugin(find)
|
||||||
}
|
}
|
||||||
|
|
||||||
const Pouch = PouchDB.defaults(POUCH_DB_DEFAULTS)
|
return PouchDB.defaults(POUCH_DB_DEFAULTS)
|
||||||
if (opts.allDbs) {
|
|
||||||
const allDbs = require("pouchdb-all-dbs")
|
|
||||||
allDbs(Pouch)
|
|
||||||
}
|
|
||||||
|
|
||||||
return Pouch
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,194 @@
|
||||||
|
require("../../tests/utilities/dbConfig");
|
||||||
|
const {
|
||||||
|
generateAppID,
|
||||||
|
getDevelopmentAppID,
|
||||||
|
getProdAppID,
|
||||||
|
isDevAppID,
|
||||||
|
isProdAppID,
|
||||||
|
getPlatformUrl,
|
||||||
|
getScopedConfig
|
||||||
|
} = require("../utils")
|
||||||
|
const tenancy = require("../../tenancy");
|
||||||
|
const { Configs, DEFAULT_TENANT_ID } = require("../../constants");
|
||||||
|
const env = require("../../environment")
|
||||||
|
|
||||||
|
describe("utils", () => {
|
||||||
|
describe("app ID manipulation", () => {
|
||||||
|
|
||||||
|
function getID() {
|
||||||
|
const appId = generateAppID()
|
||||||
|
const split = appId.split("_")
|
||||||
|
const uuid = split[split.length - 1]
|
||||||
|
const devAppId = `app_dev_${uuid}`
|
||||||
|
return { appId, devAppId, split, uuid }
|
||||||
|
}
|
||||||
|
|
||||||
|
it("should be able to generate a new app ID", () => {
|
||||||
|
expect(generateAppID().startsWith("app_")).toEqual(true)
|
||||||
|
})
|
||||||
|
|
||||||
|
it("should be able to convert a production app ID to development", () => {
|
||||||
|
const { appId, uuid } = getID()
|
||||||
|
expect(getDevelopmentAppID(appId)).toEqual(`app_dev_${uuid}`)
|
||||||
|
})
|
||||||
|
|
||||||
|
it("should be able to convert a development app ID to development", () => {
|
||||||
|
const { devAppId, uuid } = getID()
|
||||||
|
expect(getDevelopmentAppID(devAppId)).toEqual(`app_dev_${uuid}`)
|
||||||
|
})
|
||||||
|
|
||||||
|
it("should be able to convert a development ID to a production", () => {
|
||||||
|
const { devAppId, uuid } = getID()
|
||||||
|
expect(getProdAppID(devAppId)).toEqual(`app_${uuid}`)
|
||||||
|
})
|
||||||
|
|
||||||
|
it("should be able to convert a production ID to production", () => {
|
||||||
|
const { appId, uuid } = getID()
|
||||||
|
expect(getProdAppID(appId)).toEqual(`app_${uuid}`)
|
||||||
|
})
|
||||||
|
|
||||||
|
it("should be able to confirm dev app ID is development", () => {
|
||||||
|
const { devAppId } = getID()
|
||||||
|
expect(isDevAppID(devAppId)).toEqual(true)
|
||||||
|
})
|
||||||
|
|
||||||
|
it("should be able to confirm prod app ID is not development", () => {
|
||||||
|
const { appId } = getID()
|
||||||
|
expect(isDevAppID(appId)).toEqual(false)
|
||||||
|
})
|
||||||
|
|
||||||
|
it("should be able to confirm prod app ID is prod", () => {
|
||||||
|
const { appId } = getID()
|
||||||
|
expect(isProdAppID(appId)).toEqual(true)
|
||||||
|
})
|
||||||
|
|
||||||
|
it("should be able to confirm dev app ID is not prod", () => {
|
||||||
|
const { devAppId } = getID()
|
||||||
|
expect(isProdAppID(devAppId)).toEqual(false)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
const DB_URL = "http://dburl.com"
|
||||||
|
const DEFAULT_URL = "http://localhost:10000"
|
||||||
|
const ENV_URL = "http://env.com"
|
||||||
|
|
||||||
|
const setDbPlatformUrl = async () => {
|
||||||
|
const db = tenancy.getGlobalDB()
|
||||||
|
db.put({
|
||||||
|
_id: "config_settings",
|
||||||
|
type: Configs.SETTINGS,
|
||||||
|
config: {
|
||||||
|
platformUrl: DB_URL
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const clearSettingsConfig = async () => {
|
||||||
|
await tenancy.doInTenant(DEFAULT_TENANT_ID, async () => {
|
||||||
|
const db = tenancy.getGlobalDB()
|
||||||
|
try {
|
||||||
|
const config = await db.get("config_settings")
|
||||||
|
await db.remove("config_settings", config._rev)
|
||||||
|
} catch (e) {
|
||||||
|
if (e.status !== 404) {
|
||||||
|
throw e
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
describe("getPlatformUrl", () => {
|
||||||
|
describe("self host", () => {
|
||||||
|
|
||||||
|
beforeEach(async () => {
|
||||||
|
env._set("SELF_HOST", 1)
|
||||||
|
await clearSettingsConfig()
|
||||||
|
})
|
||||||
|
|
||||||
|
it("gets the default url", async () => {
|
||||||
|
await tenancy.doInTenant(null, async () => {
|
||||||
|
const url = await getPlatformUrl()
|
||||||
|
expect(url).toBe(DEFAULT_URL)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
it("gets the platform url from the environment", async () => {
|
||||||
|
await tenancy.doInTenant(null, async () => {
|
||||||
|
env._set("PLATFORM_URL", ENV_URL)
|
||||||
|
const url = await getPlatformUrl()
|
||||||
|
expect(url).toBe(ENV_URL)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
it("gets the platform url from the database", async () => {
|
||||||
|
await tenancy.doInTenant(null, async () => {
|
||||||
|
await setDbPlatformUrl()
|
||||||
|
const url = await getPlatformUrl()
|
||||||
|
expect(url).toBe(DB_URL)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
|
describe("cloud", () => {
|
||||||
|
const TENANT_AWARE_URL = "http://default.env.com"
|
||||||
|
|
||||||
|
beforeEach(async () => {
|
||||||
|
env._set("SELF_HOSTED", 0)
|
||||||
|
env._set("MULTI_TENANCY", 1)
|
||||||
|
env._set("PLATFORM_URL", ENV_URL)
|
||||||
|
await clearSettingsConfig()
|
||||||
|
})
|
||||||
|
|
||||||
|
it("gets the platform url from the environment without tenancy", async () => {
|
||||||
|
await tenancy.doInTenant(DEFAULT_TENANT_ID, async () => {
|
||||||
|
const url = await getPlatformUrl({ tenantAware: false })
|
||||||
|
expect(url).toBe(ENV_URL)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
it("gets the platform url from the environment with tenancy", async () => {
|
||||||
|
await tenancy.doInTenant(DEFAULT_TENANT_ID, async () => {
|
||||||
|
const url = await getPlatformUrl()
|
||||||
|
expect(url).toBe(TENANT_AWARE_URL)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
it("never gets the platform url from the database", async () => {
|
||||||
|
await tenancy.doInTenant(DEFAULT_TENANT_ID, async () => {
|
||||||
|
await setDbPlatformUrl()
|
||||||
|
const url = await getPlatformUrl()
|
||||||
|
expect(url).toBe(TENANT_AWARE_URL)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe("getScopedConfig", () => {
|
||||||
|
describe("settings config", () => {
|
||||||
|
|
||||||
|
beforeEach(async () => {
|
||||||
|
env._set("SELF_HOSTED", 1)
|
||||||
|
env._set("PLATFORM_URL", "")
|
||||||
|
await clearSettingsConfig()
|
||||||
|
})
|
||||||
|
|
||||||
|
it("returns the platform url with an existing config", async () => {
|
||||||
|
await tenancy.doInTenant(DEFAULT_TENANT_ID, async () => {
|
||||||
|
await setDbPlatformUrl()
|
||||||
|
const db = tenancy.getGlobalDB()
|
||||||
|
const config = await getScopedConfig(db, { type: Configs.SETTINGS })
|
||||||
|
expect(config.platformUrl).toBe(DB_URL)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
it("returns the platform url without an existing config", async () => {
|
||||||
|
await tenancy.doInTenant(DEFAULT_TENANT_ID, async () => {
|
||||||
|
const db = tenancy.getGlobalDB()
|
||||||
|
const config = await getScopedConfig(db, { type: Configs.SETTINGS })
|
||||||
|
expect(config.platformUrl).toBe(DEFAULT_URL)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
|
@ -9,7 +9,7 @@ const {
|
||||||
APP_PREFIX,
|
APP_PREFIX,
|
||||||
APP_DEV,
|
APP_DEV,
|
||||||
} = require("./constants")
|
} = require("./constants")
|
||||||
const { getTenantId, getGlobalDBName } = require("../tenancy")
|
const { getTenantId, getGlobalDBName, getGlobalDB } = require("../tenancy")
|
||||||
const fetch = require("node-fetch")
|
const fetch = require("node-fetch")
|
||||||
const { doWithDB, allDbs } = require("./index")
|
const { doWithDB, allDbs } = require("./index")
|
||||||
const { getCouchInfo } = require("./pouch")
|
const { getCouchInfo } = require("./pouch")
|
||||||
|
@ -43,6 +43,18 @@ exports.isDevAppID = isDevAppID
|
||||||
exports.getDevelopmentAppID = getDevelopmentAppID
|
exports.getDevelopmentAppID = getDevelopmentAppID
|
||||||
exports.getProdAppID = getProdAppID
|
exports.getProdAppID = getProdAppID
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generates a new app ID.
|
||||||
|
* @returns {string} The new app ID which the app doc can be stored under.
|
||||||
|
*/
|
||||||
|
exports.generateAppID = (tenantId = null) => {
|
||||||
|
let id = APP_PREFIX
|
||||||
|
if (tenantId) {
|
||||||
|
id += `${tenantId}${SEPARATOR}`
|
||||||
|
}
|
||||||
|
return `${id}${newid()}`
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* If creating DB allDocs/query params with only a single top level ID this can be used, this
|
* If creating DB allDocs/query params with only a single top level ID this can be used, this
|
||||||
* is usually the case as most of our docs are top level e.g. tables, automations, users and so on.
|
* is usually the case as most of our docs are top level e.g. tables, automations, users and so on.
|
||||||
|
@ -380,9 +392,7 @@ const getScopedFullConfig = async function (db, { type, user, workspace }) {
|
||||||
// always provide the platform URL
|
// always provide the platform URL
|
||||||
if (type === Configs.SETTINGS) {
|
if (type === Configs.SETTINGS) {
|
||||||
if (scopedConfig && scopedConfig.doc) {
|
if (scopedConfig && scopedConfig.doc) {
|
||||||
scopedConfig.doc.config.platformUrl = await getPlatformUrl(
|
scopedConfig.doc.config.platformUrl = await getPlatformUrl()
|
||||||
scopedConfig.doc.config
|
|
||||||
)
|
|
||||||
} else {
|
} else {
|
||||||
scopedConfig = {
|
scopedConfig = {
|
||||||
doc: {
|
doc: {
|
||||||
|
@ -397,19 +407,30 @@ const getScopedFullConfig = async function (db, { type, user, workspace }) {
|
||||||
return scopedConfig && scopedConfig.doc
|
return scopedConfig && scopedConfig.doc
|
||||||
}
|
}
|
||||||
|
|
||||||
const getPlatformUrl = async settings => {
|
const getPlatformUrl = async (opts = { tenantAware: true }) => {
|
||||||
let platformUrl = env.PLATFORM_URL || "http://localhost:10000"
|
let platformUrl = env.PLATFORM_URL || "http://localhost:10000"
|
||||||
|
|
||||||
if (!env.SELF_HOSTED && env.MULTI_TENANCY) {
|
if (!env.SELF_HOSTED && env.MULTI_TENANCY && opts.tenantAware) {
|
||||||
// cloud and multi tenant - add the tenant to the default platform url
|
// cloud and multi tenant - add the tenant to the default platform url
|
||||||
const tenantId = getTenantId()
|
const tenantId = getTenantId()
|
||||||
if (!platformUrl.includes("localhost:")) {
|
if (!platformUrl.includes("localhost:")) {
|
||||||
platformUrl = platformUrl.replace("://", `://${tenantId}.`)
|
platformUrl = platformUrl.replace("://", `://${tenantId}.`)
|
||||||
}
|
}
|
||||||
} else {
|
} else if (env.SELF_HOSTED) {
|
||||||
|
const db = getGlobalDB()
|
||||||
|
// get the doc directly instead of with getScopedConfig to prevent loop
|
||||||
|
let settings
|
||||||
|
try {
|
||||||
|
settings = await db.get(generateConfigID({ type: Configs.SETTINGS }))
|
||||||
|
} catch (e) {
|
||||||
|
if (e.status !== 404) {
|
||||||
|
throw e
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// self hosted - check for platform url override
|
// self hosted - check for platform url override
|
||||||
if (settings && settings.platformUrl) {
|
if (settings && settings.config && settings.config.platformUrl) {
|
||||||
platformUrl = settings.platformUrl
|
platformUrl = settings.config.platformUrl
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -10,7 +10,15 @@ function isDev() {
|
||||||
return process.env.NODE_ENV !== "production"
|
return process.env.NODE_ENV !== "production"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let LOADED = false
|
||||||
|
if (!LOADED && isDev() && !isTest()) {
|
||||||
|
require("dotenv").config()
|
||||||
|
LOADED = true
|
||||||
|
}
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
|
isTest,
|
||||||
|
isDev,
|
||||||
JWT_SECRET: process.env.JWT_SECRET,
|
JWT_SECRET: process.env.JWT_SECRET,
|
||||||
COUCH_DB_URL: process.env.COUCH_DB_URL || "http://localhost:4005",
|
COUCH_DB_URL: process.env.COUCH_DB_URL || "http://localhost:4005",
|
||||||
COUCH_DB_USERNAME: process.env.COUCH_DB_USER,
|
COUCH_DB_USERNAME: process.env.COUCH_DB_USER,
|
||||||
|
@ -34,9 +42,15 @@ module.exports = {
|
||||||
COOKIE_DOMAIN: process.env.COOKIE_DOMAIN,
|
COOKIE_DOMAIN: process.env.COOKIE_DOMAIN,
|
||||||
PLATFORM_URL: process.env.PLATFORM_URL,
|
PLATFORM_URL: process.env.PLATFORM_URL,
|
||||||
TENANT_FEATURE_FLAGS: process.env.TENANT_FEATURE_FLAGS,
|
TENANT_FEATURE_FLAGS: process.env.TENANT_FEATURE_FLAGS,
|
||||||
|
BACKUPS_BUCKET_NAME: process.env.BACKUPS_BUCKET_NAME || "backups",
|
||||||
|
APPS_BUCKET_NAME: process.env.APPS_BUCKET_NAME || "prod-budi-app-assets",
|
||||||
|
TEMPLATES_BUCKET_NAME: process.env.TEMPLATES_BUCKET_NAME || "templates",
|
||||||
|
GLOBAL_BUCKET_NAME: process.env.GLOBAL_BUCKET_NAME || "global",
|
||||||
|
GLOBAL_CLOUD_BUCKET_NAME:
|
||||||
|
process.env.GLOBAL_CLOUD_BUCKET_NAME || "prod-budi-tenant-uploads",
|
||||||
USE_COUCH: process.env.USE_COUCH || true,
|
USE_COUCH: process.env.USE_COUCH || true,
|
||||||
isTest,
|
DISABLE_DEVELOPER_LICENSE: process.env.DISABLE_DEVELOPER_LICENSE,
|
||||||
isDev,
|
DEFAULT_LICENSE: process.env.DEFAULT_LICENSE,
|
||||||
_set(key, value) {
|
_set(key, value) {
|
||||||
process.env[key] = value
|
process.env[key] = value
|
||||||
module.exports[key] = value
|
module.exports[key] = value
|
||||||
|
|
|
@ -49,4 +49,5 @@ exports.getTenantFeatureFlags = tenantId => {
|
||||||
|
|
||||||
exports.FeatureFlag = {
|
exports.FeatureFlag = {
|
||||||
LICENSING: "LICENSING",
|
LICENSING: "LICENSING",
|
||||||
|
GOOGLE_SHEETS: "GOOGLE_SHEETS",
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
const bcrypt = require("bcryptjs")
|
const bcrypt = require("bcrypt")
|
||||||
const env = require("./environment")
|
const env = require("./environment")
|
||||||
const { v4 } = require("uuid")
|
const { v4 } = require("uuid")
|
||||||
|
|
||||||
|
|
|
@ -19,5 +19,6 @@ module.exports = {
|
||||||
env: require("./environment"),
|
env: require("./environment"),
|
||||||
accounts: require("./cloud/accounts"),
|
accounts: require("./cloud/accounts"),
|
||||||
tenancy: require("./tenancy"),
|
tenancy: require("./tenancy"),
|
||||||
|
context: require("../context"),
|
||||||
featureFlags: require("./featureFlags"),
|
featureFlags: require("./featureFlags"),
|
||||||
}
|
}
|
||||||
|
|
|
@ -21,20 +21,12 @@ async function fetchGoogleCreds() {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
async function platformUrl() {
|
|
||||||
const db = getGlobalDB()
|
|
||||||
const publicConfig = await getScopedConfig(db, {
|
|
||||||
type: Configs.SETTINGS,
|
|
||||||
})
|
|
||||||
return getPlatformUrl(publicConfig)
|
|
||||||
}
|
|
||||||
|
|
||||||
async function preAuth(passport, ctx, next) {
|
async function preAuth(passport, ctx, next) {
|
||||||
// get the relevant config
|
// get the relevant config
|
||||||
const googleConfig = await fetchGoogleCreds()
|
const googleConfig = await fetchGoogleCreds()
|
||||||
const platUrl = await platformUrl()
|
const platformUrl = await getPlatformUrl({ tenantAware: false })
|
||||||
|
|
||||||
let callbackUrl = `${platUrl}/api/global/auth/datasource/google/callback`
|
let callbackUrl = `${platformUrl}/api/global/auth/datasource/google/callback`
|
||||||
const strategy = await google.strategyFactory(googleConfig, callbackUrl)
|
const strategy = await google.strategyFactory(googleConfig, callbackUrl)
|
||||||
|
|
||||||
if (!ctx.query.appId || !ctx.query.datasourceId) {
|
if (!ctx.query.appId || !ctx.query.datasourceId) {
|
||||||
|
@ -51,9 +43,9 @@ async function preAuth(passport, ctx, next) {
|
||||||
async function postAuth(passport, ctx, next) {
|
async function postAuth(passport, ctx, next) {
|
||||||
// get the relevant config
|
// get the relevant config
|
||||||
const config = await fetchGoogleCreds()
|
const config = await fetchGoogleCreds()
|
||||||
const platUrl = await platformUrl()
|
const platformUrl = await getPlatformUrl({ tenantAware: false })
|
||||||
|
|
||||||
let callbackUrl = `${platUrl}/api/global/auth/datasource/google/callback`
|
let callbackUrl = `${platformUrl}/api/global/auth/datasource/google/callback`
|
||||||
const strategy = await google.strategyFactory(
|
const strategy = await google.strategyFactory(
|
||||||
config,
|
config,
|
||||||
callbackUrl,
|
callbackUrl,
|
||||||
|
|
|
@ -30,7 +30,7 @@ exports.authenticate = async function (ctx, email, password, done) {
|
||||||
|
|
||||||
const dbUser = await getGlobalUserByEmail(email)
|
const dbUser = await getGlobalUserByEmail(email)
|
||||||
if (dbUser == null) {
|
if (dbUser == null) {
|
||||||
return authError(done, "User not found")
|
return authError(done, `User not found: [${email}]`)
|
||||||
}
|
}
|
||||||
|
|
||||||
// check that the user is currently inactive, if this is the case throw invalid
|
// check that the user is currently inactive, if this is the case throw invalid
|
||||||
|
|
|
@ -71,7 +71,7 @@ describe("oidc", () => {
|
||||||
|
|
||||||
describe("authenticate", () => {
|
describe("authenticate", () => {
|
||||||
afterEach(() => {
|
afterEach(() => {
|
||||||
jest.clearAllMocks();
|
jest.clearAllMocks()
|
||||||
});
|
});
|
||||||
|
|
||||||
// mock third party common authentication
|
// mock third party common authentication
|
||||||
|
@ -80,10 +80,10 @@ describe("oidc", () => {
|
||||||
|
|
||||||
// mock the passport callback
|
// mock the passport callback
|
||||||
const mockDone = jest.fn()
|
const mockDone = jest.fn()
|
||||||
|
const mockSaveUserFn = jest.fn()
|
||||||
|
|
||||||
async function doAuthenticate() {
|
async function doAuthenticate() {
|
||||||
const oidc = require("../oidc")
|
const oidc = require("../oidc")
|
||||||
const mockSaveUserFn = jest.fn()
|
|
||||||
const authenticate = await oidc.buildVerifyFn(mockSaveUserFn)
|
const authenticate = await oidc.buildVerifyFn(mockSaveUserFn)
|
||||||
|
|
||||||
await authenticate(
|
await authenticate(
|
||||||
|
@ -105,11 +105,13 @@ describe("oidc", () => {
|
||||||
expect(authenticateThirdParty).toHaveBeenCalledWith(
|
expect(authenticateThirdParty).toHaveBeenCalledWith(
|
||||||
user,
|
user,
|
||||||
false,
|
false,
|
||||||
mockDone)
|
mockDone,
|
||||||
|
mockSaveUserFn,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
it("delegates authentication to third party common", async () => {
|
it("delegates authentication to third party common", async () => {
|
||||||
doTest()
|
await doTest()
|
||||||
})
|
})
|
||||||
|
|
||||||
it("uses JWT email to get email", async () => {
|
it("uses JWT email to get email", async () => {
|
||||||
|
@ -118,7 +120,7 @@ describe("oidc", () => {
|
||||||
email : "mock@budibase.com"
|
email : "mock@budibase.com"
|
||||||
}
|
}
|
||||||
|
|
||||||
doTest()
|
await doTest()
|
||||||
})
|
})
|
||||||
|
|
||||||
it("uses JWT username to get email", async () => {
|
it("uses JWT username to get email", async () => {
|
||||||
|
@ -127,7 +129,7 @@ describe("oidc", () => {
|
||||||
preferred_username : "mock@budibase.com"
|
preferred_username : "mock@budibase.com"
|
||||||
}
|
}
|
||||||
|
|
||||||
doTest()
|
await doTest()
|
||||||
})
|
})
|
||||||
|
|
||||||
it("uses JWT invalid username to get email", async () => {
|
it("uses JWT invalid username to get email", async () => {
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
const { setTenantId, setGlobalDB, getGlobalDB } = require("../tenancy")
|
const { setTenantId, setGlobalDB, closeTenancy } = require("../tenancy")
|
||||||
const { closeDB } = require("../db")
|
const cls = require("../context/FunctionContext")
|
||||||
const ContextFactory = require("../context/FunctionContext")
|
|
||||||
const { buildMatcherRegex, matches } = require("./matchers")
|
const { buildMatcherRegex, matches } = require("./matchers")
|
||||||
|
|
||||||
module.exports = (
|
module.exports = (
|
||||||
|
@ -11,17 +10,16 @@ module.exports = (
|
||||||
const allowQsOptions = buildMatcherRegex(allowQueryStringPatterns)
|
const allowQsOptions = buildMatcherRegex(allowQueryStringPatterns)
|
||||||
const noTenancyOptions = buildMatcherRegex(noTenancyPatterns)
|
const noTenancyOptions = buildMatcherRegex(noTenancyPatterns)
|
||||||
|
|
||||||
const updateCtxFn = ctx => {
|
return async function (ctx, next) {
|
||||||
|
return cls.run(async () => {
|
||||||
const allowNoTenant =
|
const allowNoTenant =
|
||||||
opts.noTenancyRequired || !!matches(ctx, noTenancyOptions)
|
opts.noTenancyRequired || !!matches(ctx, noTenancyOptions)
|
||||||
const allowQs = !!matches(ctx, allowQsOptions)
|
const allowQs = !!matches(ctx, allowQsOptions)
|
||||||
const tenantId = setTenantId(ctx, { allowQs, allowNoTenant })
|
const tenantId = setTenantId(ctx, { allowQs, allowNoTenant })
|
||||||
setGlobalDB(tenantId)
|
setGlobalDB(tenantId)
|
||||||
|
const res = await next()
|
||||||
|
await closeTenancy()
|
||||||
|
return res
|
||||||
|
})
|
||||||
}
|
}
|
||||||
const destroyFn = async () => {
|
|
||||||
const db = getGlobalDB()
|
|
||||||
await closeDB(db)
|
|
||||||
}
|
|
||||||
|
|
||||||
return ContextFactory.getMiddleware(updateCtxFn, destroyFn)
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,12 +1,13 @@
|
||||||
const { join } = require("path")
|
const { join } = require("path")
|
||||||
const { tmpdir } = require("os")
|
const { tmpdir } = require("os")
|
||||||
|
const env = require("../environment")
|
||||||
|
|
||||||
exports.ObjectStoreBuckets = {
|
exports.ObjectStoreBuckets = {
|
||||||
BACKUPS: "backups",
|
BACKUPS: env.BACKUPS_BUCKET_NAME,
|
||||||
APPS: "prod-budi-app-assets",
|
APPS: env.APPS_BUCKET_NAME,
|
||||||
TEMPLATES: "templates",
|
TEMPLATES: env.TEMPLATES_BUCKET_NAME,
|
||||||
GLOBAL: "global",
|
GLOBAL: env.GLOBAL_BUCKET_NAME,
|
||||||
GLOBAL_CLOUD: "prod-budi-tenant-uploads",
|
GLOBAL_CLOUD: env.GLOBAL_CLOUD_BUCKET_NAME,
|
||||||
}
|
}
|
||||||
|
|
||||||
exports.budibaseTempDir = function () {
|
exports.budibaseTempDir = function () {
|
||||||
|
|
|
@ -1,18 +1,20 @@
|
||||||
const Client = require("./index")
|
const Client = require("./index")
|
||||||
const utils = require("./utils")
|
const utils = require("./utils")
|
||||||
|
|
||||||
let userClient, sessionClient, appClient
|
let userClient, sessionClient, appClient, cacheClient
|
||||||
|
|
||||||
async function init() {
|
async function init() {
|
||||||
userClient = await new Client(utils.Databases.USER_CACHE).init()
|
userClient = await new Client(utils.Databases.USER_CACHE).init()
|
||||||
sessionClient = await new Client(utils.Databases.SESSIONS).init()
|
sessionClient = await new Client(utils.Databases.SESSIONS).init()
|
||||||
appClient = await new Client(utils.Databases.APP_METADATA).init()
|
appClient = await new Client(utils.Databases.APP_METADATA).init()
|
||||||
|
cacheClient = await new Client(utils.Databases.GENERIC_CACHE).init()
|
||||||
}
|
}
|
||||||
|
|
||||||
process.on("exit", async () => {
|
process.on("exit", async () => {
|
||||||
if (userClient) await userClient.finish()
|
if (userClient) await userClient.finish()
|
||||||
if (sessionClient) await sessionClient.finish()
|
if (sessionClient) await sessionClient.finish()
|
||||||
if (appClient) await appClient.finish()
|
if (appClient) await appClient.finish()
|
||||||
|
if (cacheClient) await cacheClient.finish()
|
||||||
})
|
})
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
|
@ -34,4 +36,10 @@ module.exports = {
|
||||||
}
|
}
|
||||||
return appClient
|
return appClient
|
||||||
},
|
},
|
||||||
|
getCacheClient: async () => {
|
||||||
|
if (!cacheClient) {
|
||||||
|
await init()
|
||||||
|
}
|
||||||
|
return cacheClient
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
|
@ -18,6 +18,7 @@ exports.Databases = {
|
||||||
APP_METADATA: "appMetadata",
|
APP_METADATA: "appMetadata",
|
||||||
QUERY_VARS: "queryVars",
|
QUERY_VARS: "queryVars",
|
||||||
LICENSES: "license",
|
LICENSES: "license",
|
||||||
|
GENERIC_CACHE: "data_cache",
|
||||||
}
|
}
|
||||||
|
|
||||||
exports.SEPARATOR = SEPARATOR
|
exports.SEPARATOR = SEPARATOR
|
||||||
|
|
|
@ -96,6 +96,7 @@ const BUILTIN_PERMISSIONS = {
|
||||||
new Permission(PermissionTypes.QUERY, PermissionLevels.WRITE),
|
new Permission(PermissionTypes.QUERY, PermissionLevels.WRITE),
|
||||||
new Permission(PermissionTypes.TABLE, PermissionLevels.WRITE),
|
new Permission(PermissionTypes.TABLE, PermissionLevels.WRITE),
|
||||||
new Permission(PermissionTypes.VIEW, PermissionLevels.READ),
|
new Permission(PermissionTypes.VIEW, PermissionLevels.READ),
|
||||||
|
new Permission(PermissionTypes.AUTOMATION, PermissionLevels.EXECUTE),
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
POWER: {
|
POWER: {
|
||||||
|
|
|
@ -15,6 +15,7 @@ function makeSessionID(userId, sessionId) {
|
||||||
}
|
}
|
||||||
|
|
||||||
async function invalidateSessions(userId, sessionIds = null) {
|
async function invalidateSessions(userId, sessionIds = null) {
|
||||||
|
try {
|
||||||
let sessions = []
|
let sessions = []
|
||||||
|
|
||||||
// If no sessionIds, get all the sessions for the user
|
// If no sessionIds, get all the sessions for the user
|
||||||
|
@ -38,6 +39,9 @@ async function invalidateSessions(userId, sessionIds = null) {
|
||||||
promises.push(client.delete(session.key))
|
promises.push(client.delete(session.key))
|
||||||
}
|
}
|
||||||
await Promise.all(promises)
|
await Promise.all(promises)
|
||||||
|
} catch (err) {
|
||||||
|
console.error(`Error invalidating sessions: ${err}`)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
exports.createASession = async (userId, session) => {
|
exports.createASession = async (userId, session) => {
|
||||||
|
@ -76,6 +80,7 @@ exports.getSession = async (userId, sessionId) => {
|
||||||
return client.get(makeSessionID(userId, sessionId))
|
return client.get(makeSessionID(userId, sessionId))
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
// if can't get session don't error, just don't return anything
|
// if can't get session don't error, just don't return anything
|
||||||
|
console.error(err)
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -197,11 +197,16 @@ exports.getBuildersCount = async () => {
|
||||||
return builders.length
|
return builders.length
|
||||||
}
|
}
|
||||||
|
|
||||||
exports.saveUser = async (
|
const DEFAULT_SAVE_USER = {
|
||||||
|
hashPassword: true,
|
||||||
|
requirePassword: true,
|
||||||
|
bulkCreate: false,
|
||||||
|
}
|
||||||
|
|
||||||
|
exports.internalSaveUser = async (
|
||||||
user,
|
user,
|
||||||
tenantId,
|
tenantId,
|
||||||
hashPassword = true,
|
{ hashPassword, requirePassword, bulkCreate } = DEFAULT_SAVE_USER
|
||||||
requirePassword = true
|
|
||||||
) => {
|
) => {
|
||||||
if (!tenantId) {
|
if (!tenantId) {
|
||||||
throw "No tenancy specified."
|
throw "No tenancy specified."
|
||||||
|
@ -213,7 +218,10 @@ exports.saveUser = async (
|
||||||
let { email, password, _id } = user
|
let { email, password, _id } = user
|
||||||
// make sure another user isn't using the same email
|
// make sure another user isn't using the same email
|
||||||
let dbUser
|
let dbUser
|
||||||
if (email) {
|
// user can't exist in bulk creation
|
||||||
|
if (bulkCreate) {
|
||||||
|
dbUser = null
|
||||||
|
} else if (email) {
|
||||||
// check budibase users inside the tenant
|
// check budibase users inside the tenant
|
||||||
dbUser = await exports.getGlobalUserByEmail(email)
|
dbUser = await exports.getGlobalUserByEmail(email)
|
||||||
if (dbUser != null && (dbUser._id !== _id || Array.isArray(dbUser))) {
|
if (dbUser != null && (dbUser._id !== _id || Array.isArray(dbUser))) {
|
||||||
|
@ -267,11 +275,17 @@ exports.saveUser = async (
|
||||||
user.status = UserStatus.ACTIVE
|
user.status = UserStatus.ACTIVE
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
const response = await db.put({
|
const putOpts = {
|
||||||
password: hashedPassword,
|
password: hashedPassword,
|
||||||
...user,
|
...user,
|
||||||
})
|
}
|
||||||
|
if (bulkCreate) {
|
||||||
|
return putOpts
|
||||||
|
}
|
||||||
|
const response = await db.put(putOpts)
|
||||||
|
if (env.MULTI_TENANCY) {
|
||||||
await tryAddTenant(tenantId, _id, email)
|
await tryAddTenant(tenantId, _id, email)
|
||||||
|
}
|
||||||
await userCache.invalidateUser(response.id)
|
await userCache.invalidateUser(response.id)
|
||||||
return {
|
return {
|
||||||
_id: response.id,
|
_id: response.id,
|
||||||
|
@ -288,6 +302,19 @@ exports.saveUser = async (
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// maintained for api compat, don't want to change function signature
|
||||||
|
exports.saveUser = async (
|
||||||
|
user,
|
||||||
|
tenantId,
|
||||||
|
hashPassword = true,
|
||||||
|
requirePassword = true
|
||||||
|
) => {
|
||||||
|
return exports.internalSaveUser(user, tenantId, {
|
||||||
|
hashPassword,
|
||||||
|
requirePassword,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Logs a user out from budibase. Re-used across account portal and builder.
|
* Logs a user out from budibase. Re-used across account portal and builder.
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -497,6 +497,21 @@
|
||||||
"@types/yargs" "^15.0.0"
|
"@types/yargs" "^15.0.0"
|
||||||
chalk "^4.0.0"
|
chalk "^4.0.0"
|
||||||
|
|
||||||
|
"@mapbox/node-pre-gyp@^1.0.0":
|
||||||
|
version "1.0.9"
|
||||||
|
resolved "https://registry.yarnpkg.com/@mapbox/node-pre-gyp/-/node-pre-gyp-1.0.9.tgz#09a8781a3a036151cdebbe8719d6f8b25d4058bc"
|
||||||
|
integrity sha512-aDF3S3rK9Q2gey/WAttUlISduDItz5BU3306M9Eyv6/oS40aMprnopshtlKTykxRNIBEZuRMaZAnbrQ4QtKGyw==
|
||||||
|
dependencies:
|
||||||
|
detect-libc "^2.0.0"
|
||||||
|
https-proxy-agent "^5.0.0"
|
||||||
|
make-dir "^3.1.0"
|
||||||
|
node-fetch "^2.6.7"
|
||||||
|
nopt "^5.0.0"
|
||||||
|
npmlog "^5.0.1"
|
||||||
|
rimraf "^3.0.2"
|
||||||
|
semver "^7.3.5"
|
||||||
|
tar "^6.1.11"
|
||||||
|
|
||||||
"@sinonjs/commons@^1.7.0":
|
"@sinonjs/commons@^1.7.0":
|
||||||
version "1.8.3"
|
version "1.8.3"
|
||||||
resolved "https://registry.yarnpkg.com/@sinonjs/commons/-/commons-1.8.3.tgz#3802ddd21a50a949b6721ddd72da36e67e7f1b2d"
|
resolved "https://registry.yarnpkg.com/@sinonjs/commons/-/commons-1.8.3.tgz#3802ddd21a50a949b6721ddd72da36e67e7f1b2d"
|
||||||
|
@ -623,6 +638,11 @@ abab@^2.0.3, abab@^2.0.5:
|
||||||
resolved "https://registry.yarnpkg.com/abab/-/abab-2.0.5.tgz#c0b678fb32d60fc1219c784d6a826fe385aeb79a"
|
resolved "https://registry.yarnpkg.com/abab/-/abab-2.0.5.tgz#c0b678fb32d60fc1219c784d6a826fe385aeb79a"
|
||||||
integrity sha512-9IK9EadsbHo6jLWIpxpR6pL0sazTXV6+SQv25ZB+F7Bj9mJNaOc4nCRabwd5M/JwmUa8idz6Eci6eKfJryPs6Q==
|
integrity sha512-9IK9EadsbHo6jLWIpxpR6pL0sazTXV6+SQv25ZB+F7Bj9mJNaOc4nCRabwd5M/JwmUa8idz6Eci6eKfJryPs6Q==
|
||||||
|
|
||||||
|
abbrev@1:
|
||||||
|
version "1.1.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/abbrev/-/abbrev-1.1.1.tgz#f8f2c887ad10bf67f634f005b6987fed3179aac8"
|
||||||
|
integrity sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==
|
||||||
|
|
||||||
abort-controller@3.0.0:
|
abort-controller@3.0.0:
|
||||||
version "3.0.0"
|
version "3.0.0"
|
||||||
resolved "https://registry.yarnpkg.com/abort-controller/-/abort-controller-3.0.0.tgz#eaf54d53b62bae4138e809ca225c8439a6efb392"
|
resolved "https://registry.yarnpkg.com/abort-controller/-/abort-controller-3.0.0.tgz#eaf54d53b62bae4138e809ca225c8439a6efb392"
|
||||||
|
@ -661,11 +681,6 @@ acorn-walk@^7.1.1:
|
||||||
resolved "https://registry.yarnpkg.com/acorn-walk/-/acorn-walk-7.2.0.tgz#0de889a601203909b0fbe07b8938dc21d2e967bc"
|
resolved "https://registry.yarnpkg.com/acorn-walk/-/acorn-walk-7.2.0.tgz#0de889a601203909b0fbe07b8938dc21d2e967bc"
|
||||||
integrity sha512-OPdCF6GsMIP+Az+aWfAAOEt2/+iVDKE7oy6lJ098aoe59oAmK76qV6Gw60SbZ8jHuG2wH058GF4pLFbYamYrVA==
|
integrity sha512-OPdCF6GsMIP+Az+aWfAAOEt2/+iVDKE7oy6lJ098aoe59oAmK76qV6Gw60SbZ8jHuG2wH058GF4pLFbYamYrVA==
|
||||||
|
|
||||||
acorn@^5.2.1:
|
|
||||||
version "5.7.4"
|
|
||||||
resolved "https://registry.yarnpkg.com/acorn/-/acorn-5.7.4.tgz#3e8d8a9947d0599a1796d10225d7432f4a4acf5e"
|
|
||||||
integrity sha512-1D++VG7BhrtvQpNbBzovKNc1FLGGEE/oGe7b9xJm/RFHMBeUaUGpluV9RLjZa47YFdPcDAenEYuq9pQPcMdLJg==
|
|
||||||
|
|
||||||
acorn@^7.1.1:
|
acorn@^7.1.1:
|
||||||
version "7.4.1"
|
version "7.4.1"
|
||||||
resolved "https://registry.yarnpkg.com/acorn/-/acorn-7.4.1.tgz#feaed255973d2e77555b83dbc08851a6c63520fa"
|
resolved "https://registry.yarnpkg.com/acorn/-/acorn-7.4.1.tgz#feaed255973d2e77555b83dbc08851a6c63520fa"
|
||||||
|
@ -701,11 +716,6 @@ ajv@^6.12.3:
|
||||||
json-schema-traverse "^0.4.1"
|
json-schema-traverse "^0.4.1"
|
||||||
uri-js "^4.2.2"
|
uri-js "^4.2.2"
|
||||||
|
|
||||||
amdefine@>=0.0.4:
|
|
||||||
version "1.0.1"
|
|
||||||
resolved "https://registry.yarnpkg.com/amdefine/-/amdefine-1.0.1.tgz#4a5282ac164729e93619bcfd3ad151f817ce91f5"
|
|
||||||
integrity sha1-SlKCrBZHKek2Gbz9OtFR+BfOkfU=
|
|
||||||
|
|
||||||
ansi-escapes@^4.2.1:
|
ansi-escapes@^4.2.1:
|
||||||
version "4.3.2"
|
version "4.3.2"
|
||||||
resolved "https://registry.yarnpkg.com/ansi-escapes/-/ansi-escapes-4.3.2.tgz#6b2291d1db7d98b6521d5f1efa42d0f3a9feb65e"
|
resolved "https://registry.yarnpkg.com/ansi-escapes/-/ansi-escapes-4.3.2.tgz#6b2291d1db7d98b6521d5f1efa42d0f3a9feb65e"
|
||||||
|
@ -748,6 +758,19 @@ anymatch@^3.0.3:
|
||||||
normalize-path "^3.0.0"
|
normalize-path "^3.0.0"
|
||||||
picomatch "^2.0.4"
|
picomatch "^2.0.4"
|
||||||
|
|
||||||
|
"aproba@^1.0.3 || ^2.0.0":
|
||||||
|
version "2.0.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/aproba/-/aproba-2.0.0.tgz#52520b8ae5b569215b354efc0caa3fe1e45a8adc"
|
||||||
|
integrity sha512-lYe4Gx7QT+MKGbDsA+Z+he/Wtef0BiwDOlK/XkBrdfsh9J/jPPXbX0tE9x9cl27Tmu5gg3QUbUrQYa/y+KOHPQ==
|
||||||
|
|
||||||
|
are-we-there-yet@^2.0.0:
|
||||||
|
version "2.0.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/are-we-there-yet/-/are-we-there-yet-2.0.0.tgz#372e0e7bd279d8e94c653aaa1f67200884bf3e1c"
|
||||||
|
integrity sha512-Ci/qENmwHnsYo9xKIcUJN5LeDKdJ6R1Z1j9V/J5wyq8nh/mYPEpIKJbBZXtZjG04HiK7zV/p6Vs9952MrMeUIw==
|
||||||
|
dependencies:
|
||||||
|
delegates "^1.0.0"
|
||||||
|
readable-stream "^3.6.0"
|
||||||
|
|
||||||
argparse@^1.0.7:
|
argparse@^1.0.7:
|
||||||
version "1.0.10"
|
version "1.0.10"
|
||||||
resolved "https://registry.yarnpkg.com/argparse/-/argparse-1.0.10.tgz#bcd6791ea5ae09725e17e5ad988134cd40b3d911"
|
resolved "https://registry.yarnpkg.com/argparse/-/argparse-1.0.10.tgz#bcd6791ea5ae09725e17e5ad988134cd40b3d911"
|
||||||
|
@ -800,18 +823,6 @@ assign-symbols@^1.0.0:
|
||||||
resolved "https://registry.yarnpkg.com/assign-symbols/-/assign-symbols-1.0.0.tgz#59667f41fadd4f20ccbc2bb96b8d4f7f78ec0367"
|
resolved "https://registry.yarnpkg.com/assign-symbols/-/assign-symbols-1.0.0.tgz#59667f41fadd4f20ccbc2bb96b8d4f7f78ec0367"
|
||||||
integrity sha1-WWZ/QfrdTyDMvCu5a41Pf3jsA2c=
|
integrity sha1-WWZ/QfrdTyDMvCu5a41Pf3jsA2c=
|
||||||
|
|
||||||
ast-types@0.9.6:
|
|
||||||
version "0.9.6"
|
|
||||||
resolved "https://registry.yarnpkg.com/ast-types/-/ast-types-0.9.6.tgz#102c9e9e9005d3e7e3829bf0c4fa24ee862ee9b9"
|
|
||||||
integrity sha1-ECyenpAF0+fjgpvwxPok7oYu6bk=
|
|
||||||
|
|
||||||
async-hook-jl@^1.7.6:
|
|
||||||
version "1.7.6"
|
|
||||||
resolved "https://registry.yarnpkg.com/async-hook-jl/-/async-hook-jl-1.7.6.tgz#4fd25c2f864dbaf279c610d73bf97b1b28595e68"
|
|
||||||
integrity sha512-gFaHkFfSxTjvoxDMYqDuGHlcRyUuamF8s+ZTtJdDzqjws4mCt7v0vuV79/E2Wr2/riMQgtG4/yUtXWs1gZ7JMg==
|
|
||||||
dependencies:
|
|
||||||
stack-chain "^1.3.7"
|
|
||||||
|
|
||||||
async@~2.1.4:
|
async@~2.1.4:
|
||||||
version "2.1.5"
|
version "2.1.5"
|
||||||
resolved "https://registry.yarnpkg.com/async/-/async-2.1.5.tgz#e587c68580994ac67fc56ff86d3ac56bdbe810bc"
|
resolved "https://registry.yarnpkg.com/async/-/async-2.1.5.tgz#e587c68580994ac67fc56ff86d3ac56bdbe810bc"
|
||||||
|
@ -945,11 +956,6 @@ balanced-match@^1.0.0:
|
||||||
resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.2.tgz#e83e3a7e3f300b34cb9d87f615fa0cbf357690ee"
|
resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.2.tgz#e83e3a7e3f300b34cb9d87f615fa0cbf357690ee"
|
||||||
integrity sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==
|
integrity sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==
|
||||||
|
|
||||||
base62@^1.1.0:
|
|
||||||
version "1.2.8"
|
|
||||||
resolved "https://registry.yarnpkg.com/base62/-/base62-1.2.8.tgz#1264cb0fb848d875792877479dbe8bae6bae3428"
|
|
||||||
integrity sha512-V6YHUbjLxN1ymqNLb1DPHoU1CpfdL7d2YTIp5W3U4hhoG4hhxNmsFDs66M9EXxBiSEke5Bt5dwdfMwwZF70iLA==
|
|
||||||
|
|
||||||
base64-js@^1.0.2, base64-js@^1.3.1:
|
base64-js@^1.0.2, base64-js@^1.3.1:
|
||||||
version "1.5.1"
|
version "1.5.1"
|
||||||
resolved "https://registry.yarnpkg.com/base64-js/-/base64-js-1.5.1.tgz#1b1b440160a5bf7ad40b650f095963481903930a"
|
resolved "https://registry.yarnpkg.com/base64-js/-/base64-js-1.5.1.tgz#1b1b440160a5bf7ad40b650f095963481903930a"
|
||||||
|
@ -985,10 +991,18 @@ bcrypt-pbkdf@^1.0.0:
|
||||||
dependencies:
|
dependencies:
|
||||||
tweetnacl "^0.14.3"
|
tweetnacl "^0.14.3"
|
||||||
|
|
||||||
|
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"
|
||||||
|
|
||||||
bcryptjs@^2.4.3:
|
bcryptjs@^2.4.3:
|
||||||
version "2.4.3"
|
version "2.4.3"
|
||||||
resolved "https://registry.yarnpkg.com/bcryptjs/-/bcryptjs-2.4.3.tgz#9ab5627b93e60621ff7cdac5da9733027df1d0cb"
|
resolved "https://registry.yarnpkg.com/bcryptjs/-/bcryptjs-2.4.3.tgz#9ab5627b93e60621ff7cdac5da9733027df1d0cb"
|
||||||
integrity sha1-mrVie5PmBiH/fNrF2pczAn3x0Ms=
|
integrity sha512-V/Hy/X9Vt7f3BbPJEi8BdVFMByHi+jNXrYkW3huaybV/kQ0KJg0Y6PkEMbn+zeT+i+SiKZ/HMqJGIIt4LZDqNQ==
|
||||||
|
|
||||||
bl@^4.0.3:
|
bl@^4.0.3:
|
||||||
version "4.1.0"
|
version "4.1.0"
|
||||||
|
@ -1171,6 +1185,11 @@ chownr@^1.1.1:
|
||||||
resolved "https://registry.yarnpkg.com/chownr/-/chownr-1.1.4.tgz#6fc9d7b42d32a583596337666e7d08084da2cc6b"
|
resolved "https://registry.yarnpkg.com/chownr/-/chownr-1.1.4.tgz#6fc9d7b42d32a583596337666e7d08084da2cc6b"
|
||||||
integrity sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==
|
integrity sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==
|
||||||
|
|
||||||
|
chownr@^2.0.0:
|
||||||
|
version "2.0.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/chownr/-/chownr-2.0.0.tgz#15bfbe53d2eab4cf70f18a8cd68ebe5b3cb1dece"
|
||||||
|
integrity sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==
|
||||||
|
|
||||||
ci-info@^2.0.0:
|
ci-info@^2.0.0:
|
||||||
version "2.0.0"
|
version "2.0.0"
|
||||||
resolved "https://registry.yarnpkg.com/ci-info/-/ci-info-2.0.0.tgz#67a9e964be31a51e15e5010d58e6f12834002f46"
|
resolved "https://registry.yarnpkg.com/ci-info/-/ci-info-2.0.0.tgz#67a9e964be31a51e15e5010d58e6f12834002f46"
|
||||||
|
@ -1205,15 +1224,6 @@ clone-buffer@1.0.0:
|
||||||
resolved "https://registry.yarnpkg.com/clone-buffer/-/clone-buffer-1.0.0.tgz#e3e25b207ac4e701af721e2cb5a16792cac3dc58"
|
resolved "https://registry.yarnpkg.com/clone-buffer/-/clone-buffer-1.0.0.tgz#e3e25b207ac4e701af721e2cb5a16792cac3dc58"
|
||||||
integrity sha1-4+JbIHrE5wGvch4staFnksrD3Fg=
|
integrity sha1-4+JbIHrE5wGvch4staFnksrD3Fg=
|
||||||
|
|
||||||
cls-hooked@^4.2.2:
|
|
||||||
version "4.2.2"
|
|
||||||
resolved "https://registry.yarnpkg.com/cls-hooked/-/cls-hooked-4.2.2.tgz#ad2e9a4092680cdaffeb2d3551da0e225eae1908"
|
|
||||||
integrity sha512-J4Xj5f5wq/4jAvcdgoGsL3G103BtWpZrMo8NEinRltN+xpTZdI+M38pyQqhuFU/P792xkMFvnKSf+Lm81U1bxw==
|
|
||||||
dependencies:
|
|
||||||
async-hook-jl "^1.7.6"
|
|
||||||
emitter-listener "^1.0.1"
|
|
||||||
semver "^5.4.1"
|
|
||||||
|
|
||||||
cluster-key-slot@^1.1.0:
|
cluster-key-slot@^1.1.0:
|
||||||
version "1.1.0"
|
version "1.1.0"
|
||||||
resolved "https://registry.yarnpkg.com/cluster-key-slot/-/cluster-key-slot-1.1.0.tgz#30474b2a981fb12172695833052bc0d01336d10d"
|
resolved "https://registry.yarnpkg.com/cluster-key-slot/-/cluster-key-slot-1.1.0.tgz#30474b2a981fb12172695833052bc0d01336d10d"
|
||||||
|
@ -1261,6 +1271,11 @@ color-name@~1.1.4:
|
||||||
resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.4.tgz#c2a09a87acbde69543de6f63fa3995c826c536a2"
|
resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.4.tgz#c2a09a87acbde69543de6f63fa3995c826c536a2"
|
||||||
integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==
|
integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==
|
||||||
|
|
||||||
|
color-support@^1.1.2:
|
||||||
|
version "1.1.3"
|
||||||
|
resolved "https://registry.yarnpkg.com/color-support/-/color-support-1.1.3.tgz#93834379a1cc9a0c61f82f52f0d04322251bd5a2"
|
||||||
|
integrity sha512-qiBjkpbMLO/HL68y+lh4q0/O1MZFj2RX6X/KmMa3+gJD3z+WwI1ZzDHysvqHGS3mP6mznPckpXmw1nI9cJjyRg==
|
||||||
|
|
||||||
combined-stream@^1.0.5, combined-stream@~1.0.5:
|
combined-stream@^1.0.5, combined-stream@~1.0.5:
|
||||||
version "1.0.5"
|
version "1.0.5"
|
||||||
resolved "https://registry.yarnpkg.com/combined-stream/-/combined-stream-1.0.5.tgz#938370a57b4a51dea2c77c15d5c5fdf895164009"
|
resolved "https://registry.yarnpkg.com/combined-stream/-/combined-stream-1.0.5.tgz#938370a57b4a51dea2c77c15d5c5fdf895164009"
|
||||||
|
@ -1275,26 +1290,6 @@ combined-stream@^1.0.6, combined-stream@^1.0.8, combined-stream@~1.0.6:
|
||||||
dependencies:
|
dependencies:
|
||||||
delayed-stream "~1.0.0"
|
delayed-stream "~1.0.0"
|
||||||
|
|
||||||
commander@^2.5.0:
|
|
||||||
version "2.20.3"
|
|
||||||
resolved "https://registry.yarnpkg.com/commander/-/commander-2.20.3.tgz#fd485e84c03eb4881c20722ba48035e8531aeb33"
|
|
||||||
integrity sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==
|
|
||||||
|
|
||||||
commoner@^0.10.1:
|
|
||||||
version "0.10.8"
|
|
||||||
resolved "https://registry.yarnpkg.com/commoner/-/commoner-0.10.8.tgz#34fc3672cd24393e8bb47e70caa0293811f4f2c5"
|
|
||||||
integrity sha1-NPw2cs0kOT6LtH5wyqApOBH08sU=
|
|
||||||
dependencies:
|
|
||||||
commander "^2.5.0"
|
|
||||||
detective "^4.3.1"
|
|
||||||
glob "^5.0.15"
|
|
||||||
graceful-fs "^4.1.2"
|
|
||||||
iconv-lite "^0.4.5"
|
|
||||||
mkdirp "^0.5.0"
|
|
||||||
private "^0.1.6"
|
|
||||||
q "^1.1.2"
|
|
||||||
recast "^0.11.17"
|
|
||||||
|
|
||||||
component-emitter@^1.2.1:
|
component-emitter@^1.2.1:
|
||||||
version "1.3.0"
|
version "1.3.0"
|
||||||
resolved "https://registry.yarnpkg.com/component-emitter/-/component-emitter-1.3.0.tgz#16e4070fba8ae29b679f2215853ee181ab2eabc0"
|
resolved "https://registry.yarnpkg.com/component-emitter/-/component-emitter-1.3.0.tgz#16e4070fba8ae29b679f2215853ee181ab2eabc0"
|
||||||
|
@ -1310,6 +1305,11 @@ concat-map@0.0.1:
|
||||||
resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b"
|
resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b"
|
||||||
integrity sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=
|
integrity sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=
|
||||||
|
|
||||||
|
console-control-strings@^1.0.0, console-control-strings@^1.1.0:
|
||||||
|
version "1.1.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/console-control-strings/-/console-control-strings-1.1.0.tgz#3d7cf4464db6446ea644bf4b39507f9851008e8e"
|
||||||
|
integrity sha1-PXz0Rk22RG6mRL9LOVB/mFEAjo4=
|
||||||
|
|
||||||
convert-source-map@^1.4.0, convert-source-map@^1.6.0, convert-source-map@^1.7.0:
|
convert-source-map@^1.4.0, convert-source-map@^1.6.0, convert-source-map@^1.7.0:
|
||||||
version "1.8.0"
|
version "1.8.0"
|
||||||
resolved "https://registry.yarnpkg.com/convert-source-map/-/convert-source-map-1.8.0.tgz#f3373c32d21b4d780dd8004514684fb791ca4369"
|
resolved "https://registry.yarnpkg.com/convert-source-map/-/convert-source-map-1.8.0.tgz#f3373c32d21b4d780dd8004514684fb791ca4369"
|
||||||
|
@ -1461,34 +1461,31 @@ define-property@^2.0.2:
|
||||||
is-descriptor "^1.0.2"
|
is-descriptor "^1.0.2"
|
||||||
isobject "^3.0.1"
|
isobject "^3.0.1"
|
||||||
|
|
||||||
defined@^1.0.0:
|
|
||||||
version "1.0.0"
|
|
||||||
resolved "https://registry.yarnpkg.com/defined/-/defined-1.0.0.tgz#c98d9bcef75674188e110969151199e39b1fa693"
|
|
||||||
integrity sha1-yY2bzvdWdBiOEQlpFRGZ45sfppM=
|
|
||||||
|
|
||||||
delayed-stream@~1.0.0:
|
delayed-stream@~1.0.0:
|
||||||
version "1.0.0"
|
version "1.0.0"
|
||||||
resolved "https://registry.yarnpkg.com/delayed-stream/-/delayed-stream-1.0.0.tgz#df3ae199acadfb7d440aaae0b29e2272b24ec619"
|
resolved "https://registry.yarnpkg.com/delayed-stream/-/delayed-stream-1.0.0.tgz#df3ae199acadfb7d440aaae0b29e2272b24ec619"
|
||||||
integrity sha1-3zrhmayt+31ECqrgsp4icrJOxhk=
|
integrity sha1-3zrhmayt+31ECqrgsp4icrJOxhk=
|
||||||
|
|
||||||
|
delegates@^1.0.0:
|
||||||
|
version "1.0.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/delegates/-/delegates-1.0.0.tgz#84c6e159b81904fdca59a0ef44cd870d31250f9a"
|
||||||
|
integrity sha1-hMbhWbgZBP3KWaDvRM2HDTElD5o=
|
||||||
|
|
||||||
denque@^1.1.0:
|
denque@^1.1.0:
|
||||||
version "1.5.1"
|
version "1.5.1"
|
||||||
resolved "https://registry.yarnpkg.com/denque/-/denque-1.5.1.tgz#07f670e29c9a78f8faecb2566a1e2c11929c5cbf"
|
resolved "https://registry.yarnpkg.com/denque/-/denque-1.5.1.tgz#07f670e29c9a78f8faecb2566a1e2c11929c5cbf"
|
||||||
integrity sha512-XwE+iZ4D6ZUB7mfYRMb5wByE8L74HCn30FBN7sWnXksWc1LO1bPDl67pBR9o/kC4z/xSNAwkMYcGgqDV3BE3Hw==
|
integrity sha512-XwE+iZ4D6ZUB7mfYRMb5wByE8L74HCn30FBN7sWnXksWc1LO1bPDl67pBR9o/kC4z/xSNAwkMYcGgqDV3BE3Hw==
|
||||||
|
|
||||||
|
detect-libc@^2.0.0:
|
||||||
|
version "2.0.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/detect-libc/-/detect-libc-2.0.1.tgz#e1897aa88fa6ad197862937fbc0441ef352ee0cd"
|
||||||
|
integrity sha512-463v3ZeIrcWtdgIg6vI6XUncguvr2TnGl4SzDXinkt9mSLpBJKXT3mW6xT3VQdDN11+WVs29pgvivTc4Lp8v+w==
|
||||||
|
|
||||||
detect-newline@^3.0.0:
|
detect-newline@^3.0.0:
|
||||||
version "3.1.0"
|
version "3.1.0"
|
||||||
resolved "https://registry.yarnpkg.com/detect-newline/-/detect-newline-3.1.0.tgz#576f5dfc63ae1a192ff192d8ad3af6308991b651"
|
resolved "https://registry.yarnpkg.com/detect-newline/-/detect-newline-3.1.0.tgz#576f5dfc63ae1a192ff192d8ad3af6308991b651"
|
||||||
integrity sha512-TLz+x/vEXm/Y7P7wn1EJFNLxYpUD4TgMosxY6fAVJUnJMbupHBOncxyWUG9OpTaH9EBD7uFI5LfEgmMOc54DsA==
|
integrity sha512-TLz+x/vEXm/Y7P7wn1EJFNLxYpUD4TgMosxY6fAVJUnJMbupHBOncxyWUG9OpTaH9EBD7uFI5LfEgmMOc54DsA==
|
||||||
|
|
||||||
detective@^4.3.1:
|
|
||||||
version "4.7.1"
|
|
||||||
resolved "https://registry.yarnpkg.com/detective/-/detective-4.7.1.tgz#0eca7314338442febb6d65da54c10bb1c82b246e"
|
|
||||||
integrity sha512-H6PmeeUcZloWtdt4DAkFyzFL94arpHr3NOwwmVILFiy+9Qd4JTxxXrzfyGk/lmct2qVGBwTSwSXagqu2BxmWig==
|
|
||||||
dependencies:
|
|
||||||
acorn "^5.2.1"
|
|
||||||
defined "^1.0.0"
|
|
||||||
|
|
||||||
diff-sequences@^26.6.2:
|
diff-sequences@^26.6.2:
|
||||||
version "26.6.2"
|
version "26.6.2"
|
||||||
resolved "https://registry.yarnpkg.com/diff-sequences/-/diff-sequences-26.6.2.tgz#48ba99157de1923412eed41db6b6d4aa9ca7c0b1"
|
resolved "https://registry.yarnpkg.com/diff-sequences/-/diff-sequences-26.6.2.tgz#48ba99157de1923412eed41db6b6d4aa9ca7c0b1"
|
||||||
|
@ -1501,6 +1498,11 @@ domexception@^2.0.1:
|
||||||
dependencies:
|
dependencies:
|
||||||
webidl-conversions "^5.0.0"
|
webidl-conversions "^5.0.0"
|
||||||
|
|
||||||
|
dotenv@^16.0.1:
|
||||||
|
version "16.0.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/dotenv/-/dotenv-16.0.1.tgz#8f8f9d94876c35dac989876a5d3a82a267fdce1d"
|
||||||
|
integrity sha512-1K6hR6wtk2FviQ4kEiSjFiH5rpzEVi8WW0x96aztHVMhEspNpc4DVOUTEHtEva5VThQ8IaBX1Pe4gSzpVVUsKQ==
|
||||||
|
|
||||||
double-ended-queue@2.1.0-0:
|
double-ended-queue@2.1.0-0:
|
||||||
version "2.1.0-0"
|
version "2.1.0-0"
|
||||||
resolved "https://registry.yarnpkg.com/double-ended-queue/-/double-ended-queue-2.1.0-0.tgz#103d3527fd31528f40188130c841efdd78264e5c"
|
resolved "https://registry.yarnpkg.com/double-ended-queue/-/double-ended-queue-2.1.0-0.tgz#103d3527fd31528f40188130c841efdd78264e5c"
|
||||||
|
@ -1533,7 +1535,7 @@ electron-to-chromium@^1.3.896:
|
||||||
resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.3.900.tgz#5be2c5818a2a012c511b4b43e87b6ab7a296d4f5"
|
resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.3.900.tgz#5be2c5818a2a012c511b4b43e87b6ab7a296d4f5"
|
||||||
integrity sha512-SuXbQD8D4EjsaBaJJxySHbC+zq8JrFfxtb4GIr4E9n1BcROyMcRrJCYQNpJ9N+Wjf5mFp7Wp0OHykd14JNEzzQ==
|
integrity sha512-SuXbQD8D4EjsaBaJJxySHbC+zq8JrFfxtb4GIr4E9n1BcROyMcRrJCYQNpJ9N+Wjf5mFp7Wp0OHykd14JNEzzQ==
|
||||||
|
|
||||||
emitter-listener@^1.0.1:
|
emitter-listener@^1.1.2:
|
||||||
version "1.1.2"
|
version "1.1.2"
|
||||||
resolved "https://registry.yarnpkg.com/emitter-listener/-/emitter-listener-1.1.2.tgz#56b140e8f6992375b3d7cb2cab1cc7432d9632e8"
|
resolved "https://registry.yarnpkg.com/emitter-listener/-/emitter-listener-1.1.2.tgz#56b140e8f6992375b3d7cb2cab1cc7432d9632e8"
|
||||||
integrity sha512-Bt1sBAGFHY9DKY+4/2cV6izcKJUf5T7/gkdmkxzX/qv9CcGH8xSwVRW5mtX03SWJtRTWSOpzCuWN9rBFYZepZQ==
|
integrity sha512-Bt1sBAGFHY9DKY+4/2cV6izcKJUf5T7/gkdmkxzX/qv9CcGH8xSwVRW5mtX03SWJtRTWSOpzCuWN9rBFYZepZQ==
|
||||||
|
@ -1588,15 +1590,6 @@ error-ex@^1.3.1:
|
||||||
dependencies:
|
dependencies:
|
||||||
is-arrayish "^0.2.1"
|
is-arrayish "^0.2.1"
|
||||||
|
|
||||||
es3ify@^0.2.2:
|
|
||||||
version "0.2.2"
|
|
||||||
resolved "https://registry.yarnpkg.com/es3ify/-/es3ify-0.2.2.tgz#5dae3e650e5be3684b88066513d528d092629862"
|
|
||||||
integrity sha1-Xa4+ZQ5b42hLiAZlE9Uo0JJimGI=
|
|
||||||
dependencies:
|
|
||||||
esprima "^2.7.1"
|
|
||||||
jstransform "~11.0.0"
|
|
||||||
through "~2.3.4"
|
|
||||||
|
|
||||||
escalade@^3.1.1:
|
escalade@^3.1.1:
|
||||||
version "3.1.1"
|
version "3.1.1"
|
||||||
resolved "https://registry.yarnpkg.com/escalade/-/escalade-3.1.1.tgz#d8cfdc7000965c5a0174b4a82eaa5c0552742e40"
|
resolved "https://registry.yarnpkg.com/escalade/-/escalade-3.1.1.tgz#d8cfdc7000965c5a0174b4a82eaa5c0552742e40"
|
||||||
|
@ -1624,26 +1617,11 @@ escodegen@^2.0.0:
|
||||||
optionalDependencies:
|
optionalDependencies:
|
||||||
source-map "~0.6.1"
|
source-map "~0.6.1"
|
||||||
|
|
||||||
esprima-fb@^15001.1.0-dev-harmony-fb:
|
|
||||||
version "15001.1.0-dev-harmony-fb"
|
|
||||||
resolved "https://registry.yarnpkg.com/esprima-fb/-/esprima-fb-15001.1.0-dev-harmony-fb.tgz#30a947303c6b8d5e955bee2b99b1d233206a6901"
|
|
||||||
integrity sha1-MKlHMDxrjV6VW+4rmbHSMyBqaQE=
|
|
||||||
|
|
||||||
esprima@^2.7.1:
|
|
||||||
version "2.7.3"
|
|
||||||
resolved "https://registry.yarnpkg.com/esprima/-/esprima-2.7.3.tgz#96e3b70d5779f6ad49cd032673d1c312767ba581"
|
|
||||||
integrity sha1-luO3DVd59q1JzQMmc9HDEnZ7pYE=
|
|
||||||
|
|
||||||
esprima@^4.0.0, esprima@^4.0.1:
|
esprima@^4.0.0, esprima@^4.0.1:
|
||||||
version "4.0.1"
|
version "4.0.1"
|
||||||
resolved "https://registry.yarnpkg.com/esprima/-/esprima-4.0.1.tgz#13b04cdb3e6c5d19df91ab6987a8695619b0aa71"
|
resolved "https://registry.yarnpkg.com/esprima/-/esprima-4.0.1.tgz#13b04cdb3e6c5d19df91ab6987a8695619b0aa71"
|
||||||
integrity sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==
|
integrity sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==
|
||||||
|
|
||||||
esprima@~3.1.0:
|
|
||||||
version "3.1.3"
|
|
||||||
resolved "https://registry.yarnpkg.com/esprima/-/esprima-3.1.3.tgz#fdca51cee6133895e3c88d535ce49dbff62a4633"
|
|
||||||
integrity sha1-/cpRzuYTOJXjyI1TXOSdv/YqRjM=
|
|
||||||
|
|
||||||
estraverse@^5.2.0:
|
estraverse@^5.2.0:
|
||||||
version "5.3.0"
|
version "5.3.0"
|
||||||
resolved "https://registry.yarnpkg.com/estraverse/-/estraverse-5.3.0.tgz#2eea5290702f26ab8fe5370370ff86c965d21123"
|
resolved "https://registry.yarnpkg.com/estraverse/-/estraverse-5.3.0.tgz#2eea5290702f26ab8fe5370370ff86c965d21123"
|
||||||
|
@ -1900,6 +1878,13 @@ fs-constants@^1.0.0:
|
||||||
resolved "https://registry.yarnpkg.com/fs-constants/-/fs-constants-1.0.0.tgz#6be0de9be998ce16af8afc24497b9ee9b7ccd9ad"
|
resolved "https://registry.yarnpkg.com/fs-constants/-/fs-constants-1.0.0.tgz#6be0de9be998ce16af8afc24497b9ee9b7ccd9ad"
|
||||||
integrity sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==
|
integrity sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==
|
||||||
|
|
||||||
|
fs-minipass@^2.0.0:
|
||||||
|
version "2.1.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/fs-minipass/-/fs-minipass-2.1.0.tgz#7f5036fdbf12c63c169190cbe4199c852271f9fb"
|
||||||
|
integrity sha512-V/JgOLFCS+R6Vcq0slCuaeWEdNC3ouDlJMNIsacH2VtALiu9mV4LPrHc5cDl8k5aw6J8jwgWWpiTo5RYhmIzvg==
|
||||||
|
dependencies:
|
||||||
|
minipass "^3.0.0"
|
||||||
|
|
||||||
fs.realpath@^1.0.0:
|
fs.realpath@^1.0.0:
|
||||||
version "1.0.0"
|
version "1.0.0"
|
||||||
resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f"
|
resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f"
|
||||||
|
@ -1920,6 +1905,21 @@ functional-red-black-tree@^1.0.1:
|
||||||
resolved "https://registry.yarnpkg.com/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz#1b0ab3bd553b2a0d6399d29c0e3ea0b252078327"
|
resolved "https://registry.yarnpkg.com/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz#1b0ab3bd553b2a0d6399d29c0e3ea0b252078327"
|
||||||
integrity sha1-GwqzvVU7Kg1jmdKcDj6gslIHgyc=
|
integrity sha1-GwqzvVU7Kg1jmdKcDj6gslIHgyc=
|
||||||
|
|
||||||
|
gauge@^3.0.0:
|
||||||
|
version "3.0.2"
|
||||||
|
resolved "https://registry.yarnpkg.com/gauge/-/gauge-3.0.2.tgz#03bf4441c044383908bcfa0656ad91803259b395"
|
||||||
|
integrity sha512-+5J6MS/5XksCuXq++uFRsnUd7Ovu1XenbeuIuNRJxYWjgQbPuFhT14lAvsWfqfAmnwluf1OwMjz39HjfLPci0Q==
|
||||||
|
dependencies:
|
||||||
|
aproba "^1.0.3 || ^2.0.0"
|
||||||
|
color-support "^1.1.2"
|
||||||
|
console-control-strings "^1.0.0"
|
||||||
|
has-unicode "^2.0.1"
|
||||||
|
object-assign "^4.1.1"
|
||||||
|
signal-exit "^3.0.0"
|
||||||
|
string-width "^4.2.3"
|
||||||
|
strip-ansi "^6.0.1"
|
||||||
|
wide-align "^1.1.2"
|
||||||
|
|
||||||
gensync@^1.0.0-beta.2:
|
gensync@^1.0.0-beta.2:
|
||||||
version "1.0.0-beta.2"
|
version "1.0.0-beta.2"
|
||||||
resolved "https://registry.yarnpkg.com/gensync/-/gensync-1.0.0-beta.2.tgz#32a6ee76c3d7f52d46b2b1ae5d93fea8580a25e0"
|
resolved "https://registry.yarnpkg.com/gensync/-/gensync-1.0.0-beta.2.tgz#32a6ee76c3d7f52d46b2b1ae5d93fea8580a25e0"
|
||||||
|
@ -1961,17 +1961,6 @@ getpass@^0.1.1:
|
||||||
dependencies:
|
dependencies:
|
||||||
assert-plus "^1.0.0"
|
assert-plus "^1.0.0"
|
||||||
|
|
||||||
glob@^5.0.15:
|
|
||||||
version "5.0.15"
|
|
||||||
resolved "https://registry.yarnpkg.com/glob/-/glob-5.0.15.tgz#1bc936b9e02f4a603fcc222ecf7633d30b8b93b1"
|
|
||||||
integrity sha1-G8k2ueAvSmA/zCIuz3Yz0wuLk7E=
|
|
||||||
dependencies:
|
|
||||||
inflight "^1.0.4"
|
|
||||||
inherits "2"
|
|
||||||
minimatch "2 || 3"
|
|
||||||
once "^1.3.0"
|
|
||||||
path-is-absolute "^1.0.0"
|
|
||||||
|
|
||||||
glob@^7.1.1, glob@^7.1.2, glob@^7.1.3, glob@^7.1.4:
|
glob@^7.1.1, glob@^7.1.2, glob@^7.1.3, glob@^7.1.4:
|
||||||
version "7.2.0"
|
version "7.2.0"
|
||||||
resolved "https://registry.yarnpkg.com/glob/-/glob-7.2.0.tgz#d15535af7732e02e948f4c41628bd910293f6023"
|
resolved "https://registry.yarnpkg.com/glob/-/glob-7.2.0.tgz#d15535af7732e02e948f4c41628bd910293f6023"
|
||||||
|
@ -2015,7 +2004,7 @@ googleapis@^16.0.0:
|
||||||
google-auth-library "~0.10.0"
|
google-auth-library "~0.10.0"
|
||||||
string-template "~1.0.0"
|
string-template "~1.0.0"
|
||||||
|
|
||||||
graceful-fs@^4.1.2, graceful-fs@^4.2.4:
|
graceful-fs@^4.2.4:
|
||||||
version "4.2.8"
|
version "4.2.8"
|
||||||
resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.8.tgz#e412b8d33f5e006593cbd3cee6df9f2cebbe802a"
|
resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.8.tgz#e412b8d33f5e006593cbd3cee6df9f2cebbe802a"
|
||||||
integrity sha512-qkIilPUYcNhJpd33n0GBXTB1MMPp14TxEsEs0pTrsSVucApsYzW5V+Q8Qxhik6KU3evy+qkAAowTByymK0avdg==
|
integrity sha512-qkIilPUYcNhJpd33n0GBXTB1MMPp14TxEsEs0pTrsSVucApsYzW5V+Q8Qxhik6KU3evy+qkAAowTByymK0avdg==
|
||||||
|
@ -2071,6 +2060,11 @@ has-flag@^4.0.0:
|
||||||
resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-4.0.0.tgz#944771fd9c81c81265c4d6941860da06bb59479b"
|
resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-4.0.0.tgz#944771fd9c81c81265c4d6941860da06bb59479b"
|
||||||
integrity sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==
|
integrity sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==
|
||||||
|
|
||||||
|
has-unicode@^2.0.1:
|
||||||
|
version "2.0.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/has-unicode/-/has-unicode-2.0.1.tgz#e0e6fe6a28cf51138855e086d1691e771de2a8b9"
|
||||||
|
integrity sha1-4Ob+aijPUROIVeCG0Wkedx3iqLk=
|
||||||
|
|
||||||
has-value@^0.3.1:
|
has-value@^0.3.1:
|
||||||
version "0.3.1"
|
version "0.3.1"
|
||||||
resolved "https://registry.yarnpkg.com/has-value/-/has-value-0.3.1.tgz#7b1f58bada62ca827ec0a2078025654845995e1f"
|
resolved "https://registry.yarnpkg.com/has-value/-/has-value-0.3.1.tgz#7b1f58bada62ca827ec0a2078025654845995e1f"
|
||||||
|
@ -2181,7 +2175,7 @@ human-signals@^1.1.1:
|
||||||
resolved "https://registry.yarnpkg.com/human-signals/-/human-signals-1.1.1.tgz#c5b1cd14f50aeae09ab6c59fe63ba3395fe4dfa3"
|
resolved "https://registry.yarnpkg.com/human-signals/-/human-signals-1.1.1.tgz#c5b1cd14f50aeae09ab6c59fe63ba3395fe4dfa3"
|
||||||
integrity sha512-SEQu7vl8KjNL2eoGBLF3+wAjpsNfA9XMlXAYj/3EdaNfAlxKthD1xjEQfGOUhllCGGJVNY34bRr6lPINhNjyZw==
|
integrity sha512-SEQu7vl8KjNL2eoGBLF3+wAjpsNfA9XMlXAYj/3EdaNfAlxKthD1xjEQfGOUhllCGGJVNY34bRr6lPINhNjyZw==
|
||||||
|
|
||||||
iconv-lite@0.4.24, iconv-lite@^0.4.5:
|
iconv-lite@0.4.24:
|
||||||
version "0.4.24"
|
version "0.4.24"
|
||||||
resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.24.tgz#2022b4b25fbddc21d2f524974a474aafe733908b"
|
resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.24.tgz#2022b4b25fbddc21d2f524974a474aafe733908b"
|
||||||
integrity sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==
|
integrity sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==
|
||||||
|
@ -3004,17 +2998,6 @@ jsprim@^1.2.2:
|
||||||
json-schema "0.2.3"
|
json-schema "0.2.3"
|
||||||
verror "1.3.6"
|
verror "1.3.6"
|
||||||
|
|
||||||
jstransform@~11.0.0:
|
|
||||||
version "11.0.3"
|
|
||||||
resolved "https://registry.yarnpkg.com/jstransform/-/jstransform-11.0.3.tgz#09a78993e0ae4d4ef4487f6155a91f6190cb4223"
|
|
||||||
integrity sha1-CaeJk+CuTU70SH9hVakfYZDLQiM=
|
|
||||||
dependencies:
|
|
||||||
base62 "^1.1.0"
|
|
||||||
commoner "^0.10.1"
|
|
||||||
esprima-fb "^15001.1.0-dev-harmony-fb"
|
|
||||||
object-assign "^2.0.0"
|
|
||||||
source-map "^0.4.2"
|
|
||||||
|
|
||||||
jwa@^1.1.4:
|
jwa@^1.1.4:
|
||||||
version "1.1.5"
|
version "1.1.5"
|
||||||
resolved "https://registry.yarnpkg.com/jwa/-/jwa-1.1.5.tgz#a0552ce0220742cd52e153774a32905c30e756e5"
|
resolved "https://registry.yarnpkg.com/jwa/-/jwa-1.1.5.tgz#a0552ce0220742cd52e153774a32905c30e756e5"
|
||||||
|
@ -3290,7 +3273,7 @@ ltgt@2.2.1, ltgt@^2.1.2, ltgt@~2.2.0:
|
||||||
resolved "https://registry.yarnpkg.com/ltgt/-/ltgt-2.2.1.tgz#f35ca91c493f7b73da0e07495304f17b31f87ee5"
|
resolved "https://registry.yarnpkg.com/ltgt/-/ltgt-2.2.1.tgz#f35ca91c493f7b73da0e07495304f17b31f87ee5"
|
||||||
integrity sha1-81ypHEk/e3PaDgdJUwTxezH4fuU=
|
integrity sha1-81ypHEk/e3PaDgdJUwTxezH4fuU=
|
||||||
|
|
||||||
make-dir@^3.0.0:
|
make-dir@^3.0.0, make-dir@^3.1.0:
|
||||||
version "3.1.0"
|
version "3.1.0"
|
||||||
resolved "https://registry.yarnpkg.com/make-dir/-/make-dir-3.1.0.tgz#415e967046b3a7f1d185277d84aa58203726a13f"
|
resolved "https://registry.yarnpkg.com/make-dir/-/make-dir-3.1.0.tgz#415e967046b3a7f1d185277d84aa58203726a13f"
|
||||||
integrity sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==
|
integrity sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==
|
||||||
|
@ -3403,7 +3386,7 @@ mimic-fn@^2.1.0:
|
||||||
resolved "https://registry.yarnpkg.com/mimic-fn/-/mimic-fn-2.1.0.tgz#7ed2c2ccccaf84d3ffcb7a69b57711fc2083401b"
|
resolved "https://registry.yarnpkg.com/mimic-fn/-/mimic-fn-2.1.0.tgz#7ed2c2ccccaf84d3ffcb7a69b57711fc2083401b"
|
||||||
integrity sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==
|
integrity sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==
|
||||||
|
|
||||||
"minimatch@2 || 3", minimatch@^3.0.4:
|
minimatch@^3.0.4:
|
||||||
version "3.0.4"
|
version "3.0.4"
|
||||||
resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.0.4.tgz#5166e286457f03306064be5497e8dbb0c3d32083"
|
resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.0.4.tgz#5166e286457f03306064be5497e8dbb0c3d32083"
|
||||||
integrity sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==
|
integrity sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==
|
||||||
|
@ -3415,6 +3398,21 @@ minimist@^1.1.1, minimist@^1.2.0, minimist@^1.2.5:
|
||||||
resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.6.tgz#8637a5b759ea0d6e98702cfb3a9283323c93af44"
|
resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.6.tgz#8637a5b759ea0d6e98702cfb3a9283323c93af44"
|
||||||
integrity sha512-Jsjnk4bw3YJqYzbdyBiNsPWHPfO++UGG749Cxs6peCu5Xg4nrena6OVxOYxrQTqww0Jmwt+Ref8rggumkTLz9Q==
|
integrity sha512-Jsjnk4bw3YJqYzbdyBiNsPWHPfO++UGG749Cxs6peCu5Xg4nrena6OVxOYxrQTqww0Jmwt+Ref8rggumkTLz9Q==
|
||||||
|
|
||||||
|
minipass@^3.0.0:
|
||||||
|
version "3.1.6"
|
||||||
|
resolved "https://registry.yarnpkg.com/minipass/-/minipass-3.1.6.tgz#3b8150aa688a711a1521af5e8779c1d3bb4f45ee"
|
||||||
|
integrity sha512-rty5kpw9/z8SX9dmxblFA6edItUmwJgMeYDZRrwlIVN27i8gysGbznJwUggw2V/FVqFSDdWy040ZPS811DYAqQ==
|
||||||
|
dependencies:
|
||||||
|
yallist "^4.0.0"
|
||||||
|
|
||||||
|
minizlib@^2.1.1:
|
||||||
|
version "2.1.2"
|
||||||
|
resolved "https://registry.yarnpkg.com/minizlib/-/minizlib-2.1.2.tgz#e90d3466ba209b932451508a11ce3d3632145931"
|
||||||
|
integrity sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg==
|
||||||
|
dependencies:
|
||||||
|
minipass "^3.0.0"
|
||||||
|
yallist "^4.0.0"
|
||||||
|
|
||||||
mixin-deep@^1.2.0:
|
mixin-deep@^1.2.0:
|
||||||
version "1.3.2"
|
version "1.3.2"
|
||||||
resolved "https://registry.yarnpkg.com/mixin-deep/-/mixin-deep-1.3.2.tgz#1120b43dc359a785dce65b55b82e257ccf479566"
|
resolved "https://registry.yarnpkg.com/mixin-deep/-/mixin-deep-1.3.2.tgz#1120b43dc359a785dce65b55b82e257ccf479566"
|
||||||
|
@ -3428,12 +3426,10 @@ mkdirp-classic@^0.5.2:
|
||||||
resolved "https://registry.yarnpkg.com/mkdirp-classic/-/mkdirp-classic-0.5.3.tgz#fa10c9115cc6d8865be221ba47ee9bed78601113"
|
resolved "https://registry.yarnpkg.com/mkdirp-classic/-/mkdirp-classic-0.5.3.tgz#fa10c9115cc6d8865be221ba47ee9bed78601113"
|
||||||
integrity sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A==
|
integrity sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A==
|
||||||
|
|
||||||
mkdirp@^0.5.0:
|
mkdirp@^1.0.3:
|
||||||
version "0.5.5"
|
version "1.0.4"
|
||||||
resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.5.5.tgz#d91cefd62d1436ca0f41620e251288d420099def"
|
resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-1.0.4.tgz#3eb5ed62622756d79a5f0e2a221dfebad75c2f7e"
|
||||||
integrity sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ==
|
integrity sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==
|
||||||
dependencies:
|
|
||||||
minimist "^1.2.5"
|
|
||||||
|
|
||||||
ms@2.0.0:
|
ms@2.0.0:
|
||||||
version "2.0.0"
|
version "2.0.0"
|
||||||
|
@ -3492,12 +3488,17 @@ nice-try@^1.0.4:
|
||||||
resolved "https://registry.yarnpkg.com/nice-try/-/nice-try-1.0.5.tgz#a3378a7696ce7d223e88fc9b764bd7ef1089e366"
|
resolved "https://registry.yarnpkg.com/nice-try/-/nice-try-1.0.5.tgz#a3378a7696ce7d223e88fc9b764bd7ef1089e366"
|
||||||
integrity sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ==
|
integrity sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ==
|
||||||
|
|
||||||
|
node-addon-api@^3.1.0:
|
||||||
|
version "3.2.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/node-addon-api/-/node-addon-api-3.2.1.tgz#81325e0a2117789c0128dab65e7e38f07ceba161"
|
||||||
|
integrity sha512-mmcei9JghVNDYydghQmeDX8KoAm0FAiYyIcUt/N4nhyAipB17pllZQDOJD2fotxABnt4Mdz+dKTO7eftLg4d0A==
|
||||||
|
|
||||||
node-fetch@2.6.0:
|
node-fetch@2.6.0:
|
||||||
version "2.6.0"
|
version "2.6.0"
|
||||||
resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.6.0.tgz#e633456386d4aa55863f676a7ab0daa8fdecb0fd"
|
resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.6.0.tgz#e633456386d4aa55863f676a7ab0daa8fdecb0fd"
|
||||||
integrity sha512-8dG4H5ujfvFiqDmVu9fQ5bOHUC15JMjMY/Zumv26oOvvVJjM67KF8koCWIabKQ1GJIa9r2mMZscBq/TbdOcmNA==
|
integrity sha512-8dG4H5ujfvFiqDmVu9fQ5bOHUC15JMjMY/Zumv26oOvvVJjM67KF8koCWIabKQ1GJIa9r2mMZscBq/TbdOcmNA==
|
||||||
|
|
||||||
node-fetch@2.6.7, node-fetch@^2.6.1:
|
node-fetch@2.6.7, node-fetch@^2.6.1, node-fetch@^2.6.7:
|
||||||
version "2.6.7"
|
version "2.6.7"
|
||||||
resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.6.7.tgz#24de9fba827e3b4ae44dc8b20256a379160052ad"
|
resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.6.7.tgz#24de9fba827e3b4ae44dc8b20256a379160052ad"
|
||||||
integrity sha512-ZjMPFEfVx5j+y2yF35Kzx5sF7kDzxuDj6ziH4FFbOp87zKDZNx8yExJIb05OGF4Nlt9IHFIMBkRl41VdvcNdbQ==
|
integrity sha512-ZjMPFEfVx5j+y2yF35Kzx5sF7kDzxuDj6ziH4FFbOp87zKDZNx8yExJIb05OGF4Nlt9IHFIMBkRl41VdvcNdbQ==
|
||||||
|
@ -3541,6 +3542,13 @@ node-releases@^2.0.1:
|
||||||
resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-2.0.1.tgz#3d1d395f204f1f2f29a54358b9fb678765ad2fc5"
|
resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-2.0.1.tgz#3d1d395f204f1f2f29a54358b9fb678765ad2fc5"
|
||||||
integrity sha512-CqyzN6z7Q6aMeF/ktcMVTzhAHCEpf8SOarwpzpf8pNBY2k5/oM34UHldUwp8VKI7uxct2HxSRdJjBaZeESzcxA==
|
integrity sha512-CqyzN6z7Q6aMeF/ktcMVTzhAHCEpf8SOarwpzpf8pNBY2k5/oM34UHldUwp8VKI7uxct2HxSRdJjBaZeESzcxA==
|
||||||
|
|
||||||
|
nopt@^5.0.0:
|
||||||
|
version "5.0.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/nopt/-/nopt-5.0.0.tgz#530942bb58a512fccafe53fe210f13a25355dc88"
|
||||||
|
integrity sha512-Tbj67rffqceeLpcRXrT7vKAN8CwfPeIBgM7E6iBkmKLV7bEMwpGgYLGv0jACUsECaa/vuxP0IjEont6umdMgtQ==
|
||||||
|
dependencies:
|
||||||
|
abbrev "1"
|
||||||
|
|
||||||
normalize-package-data@^2.5.0:
|
normalize-package-data@^2.5.0:
|
||||||
version "2.5.0"
|
version "2.5.0"
|
||||||
resolved "https://registry.yarnpkg.com/normalize-package-data/-/normalize-package-data-2.5.0.tgz#e66db1838b200c1dfc233225d12cb36520e234a8"
|
resolved "https://registry.yarnpkg.com/normalize-package-data/-/normalize-package-data-2.5.0.tgz#e66db1838b200c1dfc233225d12cb36520e234a8"
|
||||||
|
@ -3577,6 +3585,16 @@ npm-run-path@^4.0.0:
|
||||||
dependencies:
|
dependencies:
|
||||||
path-key "^3.0.0"
|
path-key "^3.0.0"
|
||||||
|
|
||||||
|
npmlog@^5.0.1:
|
||||||
|
version "5.0.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/npmlog/-/npmlog-5.0.1.tgz#f06678e80e29419ad67ab964e0fa69959c1eb8b0"
|
||||||
|
integrity sha512-AqZtDUWOMKs1G/8lwylVjrdYgqA4d9nu8hc+0gzRxlDb1I10+FHBGMXs6aiQHFdCUUlqH99MUMuLfzWDNDtfxw==
|
||||||
|
dependencies:
|
||||||
|
are-we-there-yet "^2.0.0"
|
||||||
|
console-control-strings "^1.1.0"
|
||||||
|
gauge "^3.0.0"
|
||||||
|
set-blocking "^2.0.0"
|
||||||
|
|
||||||
nwsapi@^2.2.0:
|
nwsapi@^2.2.0:
|
||||||
version "2.2.0"
|
version "2.2.0"
|
||||||
resolved "https://registry.yarnpkg.com/nwsapi/-/nwsapi-2.2.0.tgz#204879a9e3d068ff2a55139c2c772780681a38b7"
|
resolved "https://registry.yarnpkg.com/nwsapi/-/nwsapi-2.2.0.tgz#204879a9e3d068ff2a55139c2c772780681a38b7"
|
||||||
|
@ -3597,10 +3615,10 @@ oauth@0.9.x, oauth@^0.9.15:
|
||||||
resolved "https://registry.yarnpkg.com/oauth/-/oauth-0.9.15.tgz#bd1fefaf686c96b75475aed5196412ff60cfb9c1"
|
resolved "https://registry.yarnpkg.com/oauth/-/oauth-0.9.15.tgz#bd1fefaf686c96b75475aed5196412ff60cfb9c1"
|
||||||
integrity sha1-vR/vr2hslrdUda7VGWQS/2DPucE=
|
integrity sha1-vR/vr2hslrdUda7VGWQS/2DPucE=
|
||||||
|
|
||||||
object-assign@^2.0.0:
|
object-assign@^4.1.1:
|
||||||
version "2.1.1"
|
version "4.1.1"
|
||||||
resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-2.1.1.tgz#43c36e5d569ff8e4816c4efa8be02d26967c18aa"
|
resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863"
|
||||||
integrity sha1-Q8NuXVaf+OSBbE76i+AtJpZ8GKo=
|
integrity sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=
|
||||||
|
|
||||||
object-copy@^0.1.0:
|
object-copy@^0.1.0:
|
||||||
version "0.1.0"
|
version "0.1.0"
|
||||||
|
@ -3934,17 +3952,6 @@ pouchdb-adapter-utils@7.2.2:
|
||||||
pouchdb-merge "7.2.2"
|
pouchdb-merge "7.2.2"
|
||||||
pouchdb-utils "7.2.2"
|
pouchdb-utils "7.2.2"
|
||||||
|
|
||||||
pouchdb-all-dbs@^1.0.2:
|
|
||||||
version "1.1.1"
|
|
||||||
resolved "https://registry.yarnpkg.com/pouchdb-all-dbs/-/pouchdb-all-dbs-1.1.1.tgz#85f04a39cafda52497ec49abf1c93bb5e72813f6"
|
|
||||||
integrity sha512-UUnsdmcnRSQ8MAOYSJjfTwKkQNb/6fvOfd/f7dNNivWZ2YDYVuMfgw1WQdL634yEtcXTxAENZ/EyLRdzPCB41A==
|
|
||||||
dependencies:
|
|
||||||
argsarray "0.0.1"
|
|
||||||
es3ify "^0.2.2"
|
|
||||||
inherits "~2.0.1"
|
|
||||||
pouchdb-promise "6.4.3"
|
|
||||||
tiny-queue "^0.2.0"
|
|
||||||
|
|
||||||
pouchdb-binary-utils@7.2.2:
|
pouchdb-binary-utils@7.2.2:
|
||||||
version "7.2.2"
|
version "7.2.2"
|
||||||
resolved "https://registry.yarnpkg.com/pouchdb-binary-utils/-/pouchdb-binary-utils-7.2.2.tgz#0690b348052c543b1e67f032f47092ca82bcb10e"
|
resolved "https://registry.yarnpkg.com/pouchdb-binary-utils/-/pouchdb-binary-utils-7.2.2.tgz#0690b348052c543b1e67f032f47092ca82bcb10e"
|
||||||
|
@ -4021,7 +4028,7 @@ pouchdb-merge@7.2.2:
|
||||||
resolved "https://registry.yarnpkg.com/pouchdb-merge/-/pouchdb-merge-7.2.2.tgz#940d85a2b532d6a93a6cab4b250f5648511bcc16"
|
resolved "https://registry.yarnpkg.com/pouchdb-merge/-/pouchdb-merge-7.2.2.tgz#940d85a2b532d6a93a6cab4b250f5648511bcc16"
|
||||||
integrity sha512-6yzKJfjIchBaS7Tusuk8280WJdESzFfQ0sb4jeMUNnrqs4Cx3b0DIEOYTRRD9EJDM+je7D3AZZ4AT0tFw8gb4A==
|
integrity sha512-6yzKJfjIchBaS7Tusuk8280WJdESzFfQ0sb4jeMUNnrqs4Cx3b0DIEOYTRRD9EJDM+je7D3AZZ4AT0tFw8gb4A==
|
||||||
|
|
||||||
pouchdb-promise@6.4.3, pouchdb-promise@^6.0.4:
|
pouchdb-promise@^6.0.4:
|
||||||
version "6.4.3"
|
version "6.4.3"
|
||||||
resolved "https://registry.yarnpkg.com/pouchdb-promise/-/pouchdb-promise-6.4.3.tgz#74516f4acf74957b54debd0fb2c0e5b5a68ca7b3"
|
resolved "https://registry.yarnpkg.com/pouchdb-promise/-/pouchdb-promise-6.4.3.tgz#74516f4acf74957b54debd0fb2c0e5b5a68ca7b3"
|
||||||
integrity sha512-ruJaSFXwzsxRHQfwNHjQfsj58LBOY1RzGzde4PM5CWINZwFjCQAhZwfMrch2o/0oZT6d+Xtt0HTWhq35p3b0qw==
|
integrity sha512-ruJaSFXwzsxRHQfwNHjQfsj58LBOY1RzGzde4PM5CWINZwFjCQAhZwfMrch2o/0oZT6d+Xtt0HTWhq35p3b0qw==
|
||||||
|
@ -4104,11 +4111,6 @@ pretty-format@^26.6.2:
|
||||||
ansi-styles "^4.0.0"
|
ansi-styles "^4.0.0"
|
||||||
react-is "^17.0.1"
|
react-is "^17.0.1"
|
||||||
|
|
||||||
private@^0.1.6, private@~0.1.5:
|
|
||||||
version "0.1.8"
|
|
||||||
resolved "https://registry.yarnpkg.com/private/-/private-0.1.8.tgz#2381edb3689f7a53d653190060fcf822d2f368ff"
|
|
||||||
integrity sha512-VvivMrbvd2nKkiG38qjULzlc+4Vx4wm/whI9pQD35YrARNnhxeiRktSOhSukRLFNlzg6Br/cJPet5J/u19r/mg==
|
|
||||||
|
|
||||||
process-nextick-args@~2.0.0:
|
process-nextick-args@~2.0.0:
|
||||||
version "2.0.1"
|
version "2.0.1"
|
||||||
resolved "https://registry.yarnpkg.com/process-nextick-args/-/process-nextick-args-2.0.1.tgz#7820d9b16120cc55ca9ae7792680ae7dba6d7fe2"
|
resolved "https://registry.yarnpkg.com/process-nextick-args/-/process-nextick-args-2.0.1.tgz#7820d9b16120cc55ca9ae7792680ae7dba6d7fe2"
|
||||||
|
@ -4155,11 +4157,6 @@ punycode@^2.1.0, punycode@^2.1.1:
|
||||||
resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.1.1.tgz#b58b010ac40c22c5657616c8d2c2c02c7bf479ec"
|
resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.1.1.tgz#b58b010ac40c22c5657616c8d2c2c02c7bf479ec"
|
||||||
integrity sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==
|
integrity sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==
|
||||||
|
|
||||||
q@^1.1.2:
|
|
||||||
version "1.5.1"
|
|
||||||
resolved "https://registry.yarnpkg.com/q/-/q-1.5.1.tgz#7e32f75b41381291d04611f1bf14109ac00651d7"
|
|
||||||
integrity sha1-fjL3W0E4EpHQRhHxvxQQmsAGUdc=
|
|
||||||
|
|
||||||
qs@~6.4.0:
|
qs@~6.4.0:
|
||||||
version "6.4.0"
|
version "6.4.0"
|
||||||
resolved "https://registry.yarnpkg.com/qs/-/qs-6.4.0.tgz#13e26d28ad6b0ffaa91312cd3bf708ed351e7233"
|
resolved "https://registry.yarnpkg.com/qs/-/qs-6.4.0.tgz#13e26d28ad6b0ffaa91312cd3bf708ed351e7233"
|
||||||
|
@ -4209,7 +4206,7 @@ readable-stream@1.1.14, readable-stream@^1.0.27-1:
|
||||||
isarray "0.0.1"
|
isarray "0.0.1"
|
||||||
string_decoder "~0.10.x"
|
string_decoder "~0.10.x"
|
||||||
|
|
||||||
"readable-stream@2 || 3", readable-stream@^3.1.1, readable-stream@^3.4.0:
|
"readable-stream@2 || 3", readable-stream@^3.1.1, readable-stream@^3.4.0, readable-stream@^3.6.0:
|
||||||
version "3.6.0"
|
version "3.6.0"
|
||||||
resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-3.6.0.tgz#337bbda3adc0706bd3e024426a286d4b4b2c9198"
|
resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-3.6.0.tgz#337bbda3adc0706bd3e024426a286d4b4b2c9198"
|
||||||
integrity sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==
|
integrity sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==
|
||||||
|
@ -4241,16 +4238,6 @@ readline-sync@^1.4.9:
|
||||||
resolved "https://registry.yarnpkg.com/readline-sync/-/readline-sync-1.4.10.tgz#41df7fbb4b6312d673011594145705bf56d8873b"
|
resolved "https://registry.yarnpkg.com/readline-sync/-/readline-sync-1.4.10.tgz#41df7fbb4b6312d673011594145705bf56d8873b"
|
||||||
integrity sha512-gNva8/6UAe8QYepIQH/jQ2qn91Qj0B9sYjMBBs3QOB8F2CXcKgLxQaJRP76sWVRQt+QU+8fAkCbCvjjMFu7Ycw==
|
integrity sha512-gNva8/6UAe8QYepIQH/jQ2qn91Qj0B9sYjMBBs3QOB8F2CXcKgLxQaJRP76sWVRQt+QU+8fAkCbCvjjMFu7Ycw==
|
||||||
|
|
||||||
recast@^0.11.17:
|
|
||||||
version "0.11.23"
|
|
||||||
resolved "https://registry.yarnpkg.com/recast/-/recast-0.11.23.tgz#451fd3004ab1e4df9b4e4b66376b2a21912462d3"
|
|
||||||
integrity sha1-RR/TAEqx5N+bTktmN2sqIZEkYtM=
|
|
||||||
dependencies:
|
|
||||||
ast-types "0.9.6"
|
|
||||||
esprima "~3.1.0"
|
|
||||||
private "~0.1.5"
|
|
||||||
source-map "~0.5.0"
|
|
||||||
|
|
||||||
redis-commands@1.7.0:
|
redis-commands@1.7.0:
|
||||||
version "1.7.0"
|
version "1.7.0"
|
||||||
resolved "https://registry.yarnpkg.com/redis-commands/-/redis-commands-1.7.0.tgz#15a6fea2d58281e27b1cd1acfb4b293e278c3a89"
|
resolved "https://registry.yarnpkg.com/redis-commands/-/redis-commands-1.7.0.tgz#15a6fea2d58281e27b1cd1acfb4b293e278c3a89"
|
||||||
|
@ -4395,7 +4382,7 @@ ret@~0.1.10:
|
||||||
resolved "https://registry.yarnpkg.com/ret/-/ret-0.1.15.tgz#b8a4825d5bdb1fc3f6f53c2bc33f81388681c7bc"
|
resolved "https://registry.yarnpkg.com/ret/-/ret-0.1.15.tgz#b8a4825d5bdb1fc3f6f53c2bc33f81388681c7bc"
|
||||||
integrity sha512-TTlYpa+OL+vMMNG24xSlQGEJ3B/RzEfUlLct7b5G/ytav+wPrplCpVMFuwzXbkecJrb6IYo1iFb0S9v37754mg==
|
integrity sha512-TTlYpa+OL+vMMNG24xSlQGEJ3B/RzEfUlLct7b5G/ytav+wPrplCpVMFuwzXbkecJrb6IYo1iFb0S9v37754mg==
|
||||||
|
|
||||||
rimraf@^3.0.0:
|
rimraf@^3.0.0, rimraf@^3.0.2:
|
||||||
version "3.0.2"
|
version "3.0.2"
|
||||||
resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-3.0.2.tgz#f1a5402ba6220ad52cc1282bac1ae3aa49fd061a"
|
resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-3.0.2.tgz#f1a5402ba6220ad52cc1282bac1ae3aa49fd061a"
|
||||||
integrity sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==
|
integrity sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==
|
||||||
|
@ -4466,7 +4453,7 @@ saxes@^5.0.1:
|
||||||
dependencies:
|
dependencies:
|
||||||
xmlchars "^2.2.0"
|
xmlchars "^2.2.0"
|
||||||
|
|
||||||
"semver@2 || 3 || 4 || 5", semver@^5.4.1, semver@^5.5.0, semver@^5.6.0:
|
"semver@2 || 3 || 4 || 5", semver@^5.5.0, semver@^5.6.0:
|
||||||
version "5.7.1"
|
version "5.7.1"
|
||||||
resolved "https://registry.yarnpkg.com/semver/-/semver-5.7.1.tgz#a954f931aeba508d307bbf069eff0c01c96116f7"
|
resolved "https://registry.yarnpkg.com/semver/-/semver-5.7.1.tgz#a954f931aeba508d307bbf069eff0c01c96116f7"
|
||||||
integrity sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==
|
integrity sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==
|
||||||
|
@ -4483,6 +4470,13 @@ semver@^7.3.2:
|
||||||
dependencies:
|
dependencies:
|
||||||
lru-cache "^6.0.0"
|
lru-cache "^6.0.0"
|
||||||
|
|
||||||
|
semver@^7.3.5:
|
||||||
|
version "7.3.7"
|
||||||
|
resolved "https://registry.yarnpkg.com/semver/-/semver-7.3.7.tgz#12c5b649afdbf9049707796e22a4028814ce523f"
|
||||||
|
integrity sha512-QlYTucUYOews+WeEujDoEGziz4K6c47V/Bd+LjSSYcA94p+DmINdf7ncaUinThfvZyu13lN9OY1XDxt8C0Tw0g==
|
||||||
|
dependencies:
|
||||||
|
lru-cache "^6.0.0"
|
||||||
|
|
||||||
set-blocking@^2.0.0:
|
set-blocking@^2.0.0:
|
||||||
version "2.0.0"
|
version "2.0.0"
|
||||||
resolved "https://registry.yarnpkg.com/set-blocking/-/set-blocking-2.0.0.tgz#045f9782d011ae9a6803ddd382b24392b3d890f7"
|
resolved "https://registry.yarnpkg.com/set-blocking/-/set-blocking-2.0.0.tgz#045f9782d011ae9a6803ddd382b24392b3d890f7"
|
||||||
|
@ -4608,14 +4602,7 @@ source-map-url@^0.4.0:
|
||||||
resolved "https://registry.yarnpkg.com/source-map-url/-/source-map-url-0.4.1.tgz#0af66605a745a5a2f91cf1bbf8a7afbc283dec56"
|
resolved "https://registry.yarnpkg.com/source-map-url/-/source-map-url-0.4.1.tgz#0af66605a745a5a2f91cf1bbf8a7afbc283dec56"
|
||||||
integrity sha512-cPiFOTLUKvJFIg4SKVScy4ilPPW6rFgMgfuZJPNoDuMs3nC1HbMUycBoJw77xFIp6z1UJQJOfx6C9GMH80DiTw==
|
integrity sha512-cPiFOTLUKvJFIg4SKVScy4ilPPW6rFgMgfuZJPNoDuMs3nC1HbMUycBoJw77xFIp6z1UJQJOfx6C9GMH80DiTw==
|
||||||
|
|
||||||
source-map@^0.4.2:
|
source-map@^0.5.0, source-map@^0.5.6:
|
||||||
version "0.4.4"
|
|
||||||
resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.4.4.tgz#eba4f5da9c0dc999de68032d8b4f76173652036b"
|
|
||||||
integrity sha1-66T12pwNyZneaAMti092FzZSA2s=
|
|
||||||
dependencies:
|
|
||||||
amdefine ">=0.0.4"
|
|
||||||
|
|
||||||
source-map@^0.5.0, source-map@^0.5.6, source-map@~0.5.0:
|
|
||||||
version "0.5.7"
|
version "0.5.7"
|
||||||
resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.5.7.tgz#8a039d2d1021d22d1ea14c80d8ea468ba2ef3fcc"
|
resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.5.7.tgz#8a039d2d1021d22d1ea14c80d8ea468ba2ef3fcc"
|
||||||
integrity sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=
|
integrity sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=
|
||||||
|
@ -4706,11 +4693,6 @@ sshpk@^1.7.0:
|
||||||
jsbn "~0.1.0"
|
jsbn "~0.1.0"
|
||||||
tweetnacl "~0.14.0"
|
tweetnacl "~0.14.0"
|
||||||
|
|
||||||
stack-chain@^1.3.7:
|
|
||||||
version "1.3.7"
|
|
||||||
resolved "https://registry.yarnpkg.com/stack-chain/-/stack-chain-1.3.7.tgz#d192c9ff4ea6a22c94c4dd459171e3f00cea1285"
|
|
||||||
integrity sha1-0ZLJ/06moiyUxN1FkXHj8AzqEoU=
|
|
||||||
|
|
||||||
stack-utils@^2.0.2:
|
stack-utils@^2.0.2:
|
||||||
version "2.0.5"
|
version "2.0.5"
|
||||||
resolved "https://registry.yarnpkg.com/stack-utils/-/stack-utils-2.0.5.tgz#d25265fca995154659dbbfba3b49254778d2fdd5"
|
resolved "https://registry.yarnpkg.com/stack-utils/-/stack-utils-2.0.5.tgz#d25265fca995154659dbbfba3b49254778d2fdd5"
|
||||||
|
@ -4749,7 +4731,7 @@ string-template@~1.0.0:
|
||||||
resolved "https://registry.yarnpkg.com/string-template/-/string-template-1.0.0.tgz#9e9f2233dc00f218718ec379a28a5673ecca8b96"
|
resolved "https://registry.yarnpkg.com/string-template/-/string-template-1.0.0.tgz#9e9f2233dc00f218718ec379a28a5673ecca8b96"
|
||||||
integrity sha1-np8iM9wA8hhxjsN5oopWc+zKi5Y=
|
integrity sha1-np8iM9wA8hhxjsN5oopWc+zKi5Y=
|
||||||
|
|
||||||
string-width@^4.1.0, string-width@^4.2.0:
|
"string-width@^1.0.2 || 2 || 3 || 4", string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3:
|
||||||
version "4.2.3"
|
version "4.2.3"
|
||||||
resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010"
|
resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010"
|
||||||
integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==
|
integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==
|
||||||
|
@ -4862,6 +4844,18 @@ tar-stream@^2.1.4:
|
||||||
inherits "^2.0.3"
|
inherits "^2.0.3"
|
||||||
readable-stream "^3.1.1"
|
readable-stream "^3.1.1"
|
||||||
|
|
||||||
|
tar@^6.1.11:
|
||||||
|
version "6.1.11"
|
||||||
|
resolved "https://registry.yarnpkg.com/tar/-/tar-6.1.11.tgz#6760a38f003afa1b2ffd0ffe9e9abbd0eab3d621"
|
||||||
|
integrity sha512-an/KZQzQUkZCkuoAA64hM92X0Urb6VpRhAFllDzz44U2mcD5scmT3zBc4VgVpkugF580+DQn8eAFSyoQt0tznA==
|
||||||
|
dependencies:
|
||||||
|
chownr "^2.0.0"
|
||||||
|
fs-minipass "^2.0.0"
|
||||||
|
minipass "^3.0.0"
|
||||||
|
minizlib "^2.1.1"
|
||||||
|
mkdirp "^1.0.3"
|
||||||
|
yallist "^4.0.0"
|
||||||
|
|
||||||
terminal-link@^2.0.0:
|
terminal-link@^2.0.0:
|
||||||
version "2.1.1"
|
version "2.1.1"
|
||||||
resolved "https://registry.yarnpkg.com/terminal-link/-/terminal-link-2.1.1.tgz#14a64a27ab3c0df933ea546fba55f2d078edc994"
|
resolved "https://registry.yarnpkg.com/terminal-link/-/terminal-link-2.1.1.tgz#14a64a27ab3c0df933ea546fba55f2d078edc994"
|
||||||
|
@ -4900,16 +4894,6 @@ through2@^2.0.0, through2@^2.0.2, through2@^2.0.3:
|
||||||
readable-stream "~2.3.6"
|
readable-stream "~2.3.6"
|
||||||
xtend "~4.0.1"
|
xtend "~4.0.1"
|
||||||
|
|
||||||
through@~2.3.4:
|
|
||||||
version "2.3.8"
|
|
||||||
resolved "https://registry.yarnpkg.com/through/-/through-2.3.8.tgz#0dd4c9ffaabc357960b1b724115d7e0e86a2e1f5"
|
|
||||||
integrity sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU=
|
|
||||||
|
|
||||||
tiny-queue@^0.2.0:
|
|
||||||
version "0.2.1"
|
|
||||||
resolved "https://registry.yarnpkg.com/tiny-queue/-/tiny-queue-0.2.1.tgz#25a67f2c6e253b2ca941977b5ef7442ef97a6046"
|
|
||||||
integrity sha1-JaZ/LG4lOyypQZd7XvdELvl6YEY=
|
|
||||||
|
|
||||||
tmp@^0.0.33:
|
tmp@^0.0.33:
|
||||||
version "0.0.33"
|
version "0.0.33"
|
||||||
resolved "https://registry.yarnpkg.com/tmp/-/tmp-0.0.33.tgz#6d34335889768d21b2bcda0aa277ced3b1bfadf9"
|
resolved "https://registry.yarnpkg.com/tmp/-/tmp-0.0.33.tgz#6d34335889768d21b2bcda0aa277ced3b1bfadf9"
|
||||||
|
@ -5250,6 +5234,13 @@ which@^2.0.1, which@^2.0.2:
|
||||||
dependencies:
|
dependencies:
|
||||||
isexe "^2.0.0"
|
isexe "^2.0.0"
|
||||||
|
|
||||||
|
wide-align@^1.1.2:
|
||||||
|
version "1.1.5"
|
||||||
|
resolved "https://registry.yarnpkg.com/wide-align/-/wide-align-1.1.5.tgz#df1d4c206854369ecf3c9a4898f1b23fbd9d15d3"
|
||||||
|
integrity sha512-eDMORYaPNZ4sQIuuYPDHdQvf4gyCF9rEEV/yPxGfwPkRodwEgiMUUXTx/dex+Me0wxx53S+NgUHaP7y3MGlDmg==
|
||||||
|
dependencies:
|
||||||
|
string-width "^1.0.2 || 2 || 3 || 4"
|
||||||
|
|
||||||
word-wrap@~1.2.3:
|
word-wrap@~1.2.3:
|
||||||
version "1.2.3"
|
version "1.2.3"
|
||||||
resolved "https://registry.yarnpkg.com/word-wrap/-/word-wrap-1.2.3.tgz#610636f6b1f703891bd34771ccb17fb93b47079c"
|
resolved "https://registry.yarnpkg.com/word-wrap/-/word-wrap-1.2.3.tgz#610636f6b1f703891bd34771ccb17fb93b47079c"
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
{
|
{
|
||||||
"name": "@budibase/bbui",
|
"name": "@budibase/bbui",
|
||||||
"description": "A UI solution used in the different Budibase projects.",
|
"description": "A UI solution used in the different Budibase projects.",
|
||||||
"version": "1.0.142-alpha.0",
|
"version": "1.0.185-alpha.0",
|
||||||
"license": "MPL-2.0",
|
"license": "MPL-2.0",
|
||||||
"svelte": "src/index.js",
|
"svelte": "src/index.js",
|
||||||
"module": "dist/bbui.es.js",
|
"module": "dist/bbui.es.js",
|
||||||
|
@ -38,7 +38,7 @@
|
||||||
],
|
],
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@adobe/spectrum-css-workflow-icons": "^1.2.1",
|
"@adobe/spectrum-css-workflow-icons": "^1.2.1",
|
||||||
"@budibase/string-templates": "^1.0.142-alpha.0",
|
"@budibase/string-templates": "^1.0.185-alpha.0",
|
||||||
"@spectrum-css/actionbutton": "^1.0.1",
|
"@spectrum-css/actionbutton": "^1.0.1",
|
||||||
"@spectrum-css/actiongroup": "^1.0.1",
|
"@spectrum-css/actiongroup": "^1.0.1",
|
||||||
"@spectrum-css/avatar": "^3.0.2",
|
"@spectrum-css/avatar": "^3.0.2",
|
||||||
|
|
|
@ -0,0 +1,68 @@
|
||||||
|
<script>
|
||||||
|
import "@spectrum-css/fieldgroup/dist/index-vars.css"
|
||||||
|
import "@spectrum-css/radio/dist/index-vars.css"
|
||||||
|
import { createEventDispatcher } from "svelte"
|
||||||
|
|
||||||
|
export let direction = "vertical"
|
||||||
|
export let value = []
|
||||||
|
export let options = []
|
||||||
|
export let error = null
|
||||||
|
export let disabled = false
|
||||||
|
export let getOptionLabel = option => option
|
||||||
|
export let getOptionValue = option => option
|
||||||
|
|
||||||
|
const dispatch = createEventDispatcher()
|
||||||
|
const onChange = e => {
|
||||||
|
let tempValue = value
|
||||||
|
let isChecked = e.target.checked
|
||||||
|
if (!tempValue.includes(e.target.value) && isChecked) {
|
||||||
|
tempValue.push(e.target.value)
|
||||||
|
}
|
||||||
|
value = tempValue
|
||||||
|
dispatch(
|
||||||
|
"change",
|
||||||
|
tempValue.filter(val => val !== e.target.value || isChecked)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div class={`spectrum-FieldGroup spectrum-FieldGroup--${direction}`}>
|
||||||
|
{#if options && Array.isArray(options)}
|
||||||
|
{#each options as option}
|
||||||
|
<div
|
||||||
|
title={getOptionLabel(option)}
|
||||||
|
class="spectrum-Checkbox spectrum-FieldGroup-item"
|
||||||
|
class:is-invalid={!!error}
|
||||||
|
>
|
||||||
|
<label
|
||||||
|
class="spectrum-Checkbox spectrum-Checkbox--sizeM spectrum-FieldGroup-item"
|
||||||
|
>
|
||||||
|
<input
|
||||||
|
on:change={onChange}
|
||||||
|
value={getOptionValue(option)}
|
||||||
|
type="checkbox"
|
||||||
|
class="spectrum-Checkbox-input"
|
||||||
|
{disabled}
|
||||||
|
checked={value.includes(getOptionValue(option))}
|
||||||
|
/>
|
||||||
|
<span class="spectrum-Checkbox-box">
|
||||||
|
<svg
|
||||||
|
class="spectrum-Icon spectrum-UIIcon-Checkmark100 spectrum-Checkbox-checkmark"
|
||||||
|
focusable="false"
|
||||||
|
aria-hidden="true"
|
||||||
|
>
|
||||||
|
<use xlink:href="#spectrum-css-icon-Checkmark100" />
|
||||||
|
</svg>
|
||||||
|
</span>
|
||||||
|
<span class="spectrum-Checkbox-label">{getOptionLabel(option)}</span>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
{/each}
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.spectrum-Checkbox-input {
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -58,6 +58,11 @@
|
||||||
if (timeOnly) {
|
if (timeOnly) {
|
||||||
newValue = `2000-01-01T${newValue.split("T")[1]}`
|
newValue = `2000-01-01T${newValue.split("T")[1]}`
|
||||||
}
|
}
|
||||||
|
// date only, offset for timezone so always right date
|
||||||
|
else if (!enableTime) {
|
||||||
|
const offset = dates[0].getTimezoneOffset() * 60000
|
||||||
|
newValue = new Date(dates[0].getTime() - offset).toISOString()
|
||||||
|
}
|
||||||
dispatch("change", newValue)
|
dispatch("change", newValue)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -156,8 +161,8 @@
|
||||||
<input
|
<input
|
||||||
data-input
|
data-input
|
||||||
type="text"
|
type="text"
|
||||||
{disabled}
|
|
||||||
class="spectrum-Textfield-input spectrum-InputGroup-input"
|
class="spectrum-Textfield-input spectrum-InputGroup-input"
|
||||||
|
class:is-disabled={disabled}
|
||||||
{placeholder}
|
{placeholder}
|
||||||
{id}
|
{id}
|
||||||
{value}
|
{value}
|
||||||
|
@ -167,7 +172,7 @@
|
||||||
type="button"
|
type="button"
|
||||||
class="spectrum-Picker spectrum-Picker--sizeM spectrum-InputGroup-button"
|
class="spectrum-Picker spectrum-Picker--sizeM spectrum-InputGroup-button"
|
||||||
tabindex="-1"
|
tabindex="-1"
|
||||||
{disabled}
|
class:is-disabled={disabled}
|
||||||
class:is-invalid={!!error}
|
class:is-invalid={!!error}
|
||||||
on:click={flatpickr?.open}
|
on:click={flatpickr?.open}
|
||||||
>
|
>
|
||||||
|
@ -212,4 +217,7 @@
|
||||||
:global(.flatpickr-calendar) {
|
:global(.flatpickr-calendar) {
|
||||||
font-family: "Source Sans Pro", sans-serif;
|
font-family: "Source Sans Pro", sans-serif;
|
||||||
}
|
}
|
||||||
|
.is-disabled {
|
||||||
|
pointer-events: none !important;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
|
@ -43,7 +43,7 @@
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
searchTerm = null
|
searchTerm = null
|
||||||
open = true
|
open = !open
|
||||||
}
|
}
|
||||||
|
|
||||||
const getSortedOptions = (options, getLabel, sort) => {
|
const getSortedOptions = (options, getLabel, sort) => {
|
||||||
|
@ -71,7 +71,8 @@
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<button
|
<div use:clickOutside={() => (open = false)}>
|
||||||
|
<button
|
||||||
{id}
|
{id}
|
||||||
class="spectrum-Picker spectrum-Picker--sizeM"
|
class="spectrum-Picker spectrum-Picker--sizeM"
|
||||||
class:spectrum-Picker--quiet={quiet}
|
class:spectrum-Picker--quiet={quiet}
|
||||||
|
@ -80,7 +81,7 @@
|
||||||
class:is-open={open}
|
class:is-open={open}
|
||||||
aria-haspopup="listbox"
|
aria-haspopup="listbox"
|
||||||
on:mousedown={onClick}
|
on:mousedown={onClick}
|
||||||
>
|
>
|
||||||
{#if fieldIcon}
|
{#if fieldIcon}
|
||||||
<span class="icon-Placeholder-Padding">
|
<span class="icon-Placeholder-Padding">
|
||||||
<img src={fieldIcon} alt="icon" width="20" height="15" />
|
<img src={fieldIcon} alt="icon" width="20" height="15" />
|
||||||
|
@ -111,10 +112,9 @@
|
||||||
>
|
>
|
||||||
<use xlink:href="#spectrum-css-icon-Chevron100" />
|
<use xlink:href="#spectrum-css-icon-Chevron100" />
|
||||||
</svg>
|
</svg>
|
||||||
</button>
|
</button>
|
||||||
{#if open}
|
{#if open}
|
||||||
<div
|
<div
|
||||||
use:clickOutside={() => (open = false)}
|
|
||||||
transition:fly|local={{ y: -20, duration: 200 }}
|
transition:fly|local={{ y: -20, duration: 200 }}
|
||||||
class="spectrum-Popover spectrum-Popover--bottom spectrum-Picker-popover is-open"
|
class="spectrum-Popover spectrum-Popover--bottom spectrum-Picker-popover is-open"
|
||||||
class:auto-width={autoWidth}
|
class:auto-width={autoWidth}
|
||||||
|
@ -182,7 +182,8 @@
|
||||||
{/if}
|
{/if}
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
|
</div>
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
.spectrum-Popover {
|
.spectrum-Popover {
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
<script>
|
<script>
|
||||||
import "@spectrum-css/textfield/dist/index-vars.css"
|
import "@spectrum-css/textfield/dist/index-vars.css"
|
||||||
import { createEventDispatcher } from "svelte"
|
import { createEventDispatcher, onMount } from "svelte"
|
||||||
|
|
||||||
export let value = null
|
export let value = null
|
||||||
export let placeholder = null
|
export let placeholder = null
|
||||||
|
@ -13,8 +13,11 @@
|
||||||
export let quiet = false
|
export let quiet = false
|
||||||
export let dataCy
|
export let dataCy
|
||||||
export let align
|
export let align
|
||||||
|
export let autofocus = false
|
||||||
|
|
||||||
const dispatch = createEventDispatcher()
|
const dispatch = createEventDispatcher()
|
||||||
|
|
||||||
|
let field
|
||||||
let focus = false
|
let focus = false
|
||||||
|
|
||||||
const updateValue = newValue => {
|
const updateValue = newValue => {
|
||||||
|
@ -58,6 +61,11 @@
|
||||||
updateValue(event.target.value)
|
updateValue(event.target.value)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
onMount(() => {
|
||||||
|
focus = autofocus
|
||||||
|
if (focus) field.focus()
|
||||||
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div
|
<div
|
||||||
|
@ -77,6 +85,7 @@
|
||||||
</svg>
|
</svg>
|
||||||
{/if}
|
{/if}
|
||||||
<input
|
<input
|
||||||
|
bind:this={field}
|
||||||
{disabled}
|
{disabled}
|
||||||
{readonly}
|
{readonly}
|
||||||
{id}
|
{id}
|
||||||
|
|
|
@ -3,6 +3,7 @@ export { default as CoreSelect } from "./Select.svelte"
|
||||||
export { default as CoreMultiselect } from "./Multiselect.svelte"
|
export { default as CoreMultiselect } from "./Multiselect.svelte"
|
||||||
export { default as CoreCheckbox } from "./Checkbox.svelte"
|
export { default as CoreCheckbox } from "./Checkbox.svelte"
|
||||||
export { default as CoreRadioGroup } from "./RadioGroup.svelte"
|
export { default as CoreRadioGroup } from "./RadioGroup.svelte"
|
||||||
|
export { default as CoreCheckboxGroup } from "./CheckboxGroup.svelte"
|
||||||
export { default as CoreTextArea } from "./TextArea.svelte"
|
export { default as CoreTextArea } from "./TextArea.svelte"
|
||||||
export { default as CoreCombobox } from "./Combobox.svelte"
|
export { default as CoreCombobox } from "./Combobox.svelte"
|
||||||
export { default as CoreSwitch } from "./Switch.svelte"
|
export { default as CoreSwitch } from "./Switch.svelte"
|
||||||
|
|
|
@ -14,6 +14,7 @@
|
||||||
export let updateOnChange = true
|
export let updateOnChange = true
|
||||||
export let quiet = false
|
export let quiet = false
|
||||||
export let dataCy
|
export let dataCy
|
||||||
|
export let autofocus
|
||||||
|
|
||||||
const dispatch = createEventDispatcher()
|
const dispatch = createEventDispatcher()
|
||||||
const onChange = e => {
|
const onChange = e => {
|
||||||
|
@ -33,6 +34,7 @@
|
||||||
{placeholder}
|
{placeholder}
|
||||||
{type}
|
{type}
|
||||||
{quiet}
|
{quiet}
|
||||||
|
{autofocus}
|
||||||
on:change={onChange}
|
on:change={onChange}
|
||||||
on:click
|
on:click
|
||||||
on:input
|
on:input
|
||||||
|
|
|
@ -40,6 +40,10 @@
|
||||||
padding-left: var(--spacing-xl);
|
padding-left: var(--spacing-xl);
|
||||||
padding-right: var(--spacing-xl);
|
padding-right: var(--spacing-xl);
|
||||||
}
|
}
|
||||||
|
.paddingX-XXL {
|
||||||
|
padding-left: var(--spectrum-alias-grid-gutter-large);
|
||||||
|
padding-right: var(--spectrum-alias-grid-gutter-large);
|
||||||
|
}
|
||||||
.paddingY-S {
|
.paddingY-S {
|
||||||
padding-top: var(--spacing-s);
|
padding-top: var(--spacing-s);
|
||||||
padding-bottom: var(--spacing-s);
|
padding-bottom: var(--spacing-s);
|
||||||
|
@ -56,6 +60,10 @@
|
||||||
padding-top: var(--spacing-xl);
|
padding-top: var(--spacing-xl);
|
||||||
padding-bottom: var(--spacing-xl);
|
padding-bottom: var(--spacing-xl);
|
||||||
}
|
}
|
||||||
|
.paddingY-XXL {
|
||||||
|
padding-top: var(--spectrum-alias-grid-gutter-large);
|
||||||
|
padding-bottom: var(--spectrum-alias-grid-gutter-large);
|
||||||
|
}
|
||||||
.gap-XXS {
|
.gap-XXS {
|
||||||
grid-gap: var(--spacing-xs);
|
grid-gap: var(--spacing-xs);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,9 +1,10 @@
|
||||||
<script>
|
<script>
|
||||||
export let wide = false
|
export let wide = false
|
||||||
export let maxWidth = "80ch"
|
export let maxWidth = "80ch"
|
||||||
|
export let noPadding = false
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div style="--max-width: {maxWidth}" class:wide>
|
<div style="--max-width: {maxWidth}" class:wide class:noPadding>
|
||||||
<slot />
|
<slot />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
@ -23,4 +24,9 @@
|
||||||
max-width: none;
|
max-width: none;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.noPadding {
|
||||||
|
padding: 0px;
|
||||||
|
margin: 0px;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
|
@ -7,6 +7,7 @@
|
||||||
export let icon = ""
|
export let icon = ""
|
||||||
export let selected = false
|
export let selected = false
|
||||||
export let disabled = false
|
export let disabled = false
|
||||||
|
export let dataCy
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<li
|
<li
|
||||||
|
@ -14,6 +15,7 @@
|
||||||
class:is-selected={selected}
|
class:is-selected={selected}
|
||||||
class:is-disabled={disabled}
|
class:is-disabled={disabled}
|
||||||
on:click
|
on:click
|
||||||
|
data-cy={dataCy}
|
||||||
>
|
>
|
||||||
{#if heading}
|
{#if heading}
|
||||||
<h2 class="spectrum-SideNav-heading" id="nav-heading-{heading}">
|
<h2 class="spectrum-SideNav-heading" id="nav-heading-{heading}">
|
||||||
|
|
|
@ -6,7 +6,7 @@
|
||||||
|
|
||||||
const dispatch = createEventDispatcher()
|
const dispatch = createEventDispatcher()
|
||||||
let selected = getContext("tab")
|
let selected = getContext("tab")
|
||||||
let tab
|
let tab_internal
|
||||||
let tabInfo
|
let tabInfo
|
||||||
|
|
||||||
const setTabInfo = () => {
|
const setTabInfo = () => {
|
||||||
|
@ -16,7 +16,7 @@
|
||||||
// We just need to get this off the main thread to fix this, by using
|
// We just need to get this off the main thread to fix this, by using
|
||||||
// a 0ms timeout.
|
// a 0ms timeout.
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
tabInfo = tab?.getBoundingClientRect()
|
tabInfo = tab_internal?.getBoundingClientRect()
|
||||||
if (tabInfo && $selected.title === title) {
|
if (tabInfo && $selected.title === title) {
|
||||||
$selected.info = tabInfo
|
$selected.info = tabInfo
|
||||||
}
|
}
|
||||||
|
@ -27,14 +27,30 @@
|
||||||
setTabInfo()
|
setTabInfo()
|
||||||
})
|
})
|
||||||
|
|
||||||
|
//Ensure that the underline is in the correct location
|
||||||
|
$: {
|
||||||
|
if ($selected.title === title && tab_internal) {
|
||||||
|
if ($selected.info?.left !== tab_internal.getBoundingClientRect().left) {
|
||||||
|
$selected = {
|
||||||
|
...$selected,
|
||||||
|
info: tab_internal.getBoundingClientRect(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const onClick = () => {
|
const onClick = () => {
|
||||||
$selected = { ...$selected, title, info: tab.getBoundingClientRect() }
|
$selected = {
|
||||||
|
...$selected,
|
||||||
|
title,
|
||||||
|
info: tab_internal.getBoundingClientRect(),
|
||||||
|
}
|
||||||
dispatch("click")
|
dispatch("click")
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div
|
<div
|
||||||
bind:this={tab}
|
bind:this={tab_internal}
|
||||||
on:click={onClick}
|
on:click={onClick}
|
||||||
class:is-selected={$selected.title === title}
|
class:is-selected={$selected.title === title}
|
||||||
class="spectrum-Tabs-item"
|
class="spectrum-Tabs-item"
|
||||||
|
|
|
@ -1,11 +1,19 @@
|
||||||
{
|
{
|
||||||
"baseUrl": "http://localhost:4100",
|
"baseUrl": "http://localhost:4100",
|
||||||
"video": false,
|
"video": true,
|
||||||
"projectId": "bmbemn",
|
"projectId": "bmbemn",
|
||||||
|
"reporter": "cypress-multi-reporters",
|
||||||
|
"reporterOptions": {
|
||||||
|
"configFile": "reporterConfig.json"
|
||||||
|
},
|
||||||
"env": {
|
"env": {
|
||||||
"PORT": "4100",
|
"PORT": "4100",
|
||||||
"WORKER_PORT": "4200",
|
"WORKER_PORT": "4200",
|
||||||
"JWT_SECRET": "test",
|
"JWT_SECRET": "test",
|
||||||
"HOST_IP": ""
|
"HOST_IP": ""
|
||||||
|
},
|
||||||
|
"retries": {
|
||||||
|
"runMode": 2,
|
||||||
|
"openMode": 0
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
File diff suppressed because one or more lines are too long
|
@ -1,4 +1,5 @@
|
||||||
import filterTests from "../support/filterTests"
|
import filterTests from "../support/filterTests"
|
||||||
|
const interact = require('../support/interact')
|
||||||
|
|
||||||
filterTests(['all'], () => {
|
filterTests(['all'], () => {
|
||||||
context("Add Multi-Option Datatype", () => {
|
context("Add Multi-Option Datatype", () => {
|
||||||
|
@ -17,19 +18,19 @@ filterTests(['all'], () => {
|
||||||
cy.navigateToFrontend()
|
cy.navigateToFrontend()
|
||||||
cy.wait(500)
|
cy.wait(500)
|
||||||
// Add data provider
|
// Add data provider
|
||||||
cy.get(`[data-cy="category-Data"]`).click()
|
cy.get(interact.CATEGORY_DATA).click()
|
||||||
cy.get(`[data-cy="component-Data Provider"]`).click()
|
cy.get(interact.COMPONENT_DATA_PROVIDER).click()
|
||||||
cy.get('[data-cy="dataSource-prop-control"]').click()
|
cy.get(interact.DATASOURCE_PROP_CONTROL).click()
|
||||||
cy.get(".dropdown").contains("Multi Data").click()
|
cy.get(interact.DROPDOWN).contains("Multi Data").click()
|
||||||
cy.wait(500)
|
cy.wait(500)
|
||||||
// Add Form with schema to match table
|
// Add Form with schema to match table
|
||||||
cy.addComponent("Form", "Form")
|
cy.addComponent("Form", "Form")
|
||||||
cy.get('[data-cy="dataSource-prop-control"').click()
|
cy.get(interact.DATASOURCE_PROP_CONTROL).click()
|
||||||
cy.get(".dropdown").contains("Multi Data").click()
|
cy.get(interact.DROPDOWN).contains("Multi Data").click()
|
||||||
cy.wait(500)
|
cy.wait(500)
|
||||||
// Add multi-select picker to form
|
// Add multi-select picker to form
|
||||||
cy.addComponent("Form", "Multi-select Picker").then(componentId => {
|
cy.addComponent("Form", "Multi-select Picker").then(componentId => {
|
||||||
cy.get('[data-cy="field-prop-control"]').type("Test Data").type("{enter}")
|
cy.get(interact.DATASOURCE_FIELD_CONTROL).type("Test Data").type("{enter}")
|
||||||
cy.wait(1000)
|
cy.wait(1000)
|
||||||
cy.getComponent(componentId).contains("Choose some options").click()
|
cy.getComponent(componentId).contains("Choose some options").click()
|
||||||
// Check picker has 5 items
|
// Check picker has 5 items
|
||||||
|
@ -40,7 +41,7 @@ filterTests(['all'], () => {
|
||||||
}
|
}
|
||||||
// Check items have been selected
|
// Check items have been selected
|
||||||
cy.getComponent(componentId)
|
cy.getComponent(componentId)
|
||||||
.find(".spectrum-Picker-label")
|
.find(interact.SPECTRUM_Picker_LABEL)
|
||||||
.contains("(5)")
|
.contains("(5)")
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
import filterTests from "../support/filterTests"
|
import filterTests from "../support/filterTests"
|
||||||
|
const interact = require('../support/interact')
|
||||||
|
|
||||||
filterTests(['all'], () => {
|
filterTests(['all'], () => {
|
||||||
context("Add Radio Buttons", () => {
|
context("Add Radio Buttons", () => {
|
||||||
|
@ -12,10 +13,10 @@ filterTests(['all'], () => {
|
||||||
cy.addComponent("Form", "Form")
|
cy.addComponent("Form", "Form")
|
||||||
cy.addComponent("Form", "Options Picker").then((componentId) => {
|
cy.addComponent("Form", "Options Picker").then((componentId) => {
|
||||||
// Provide field setting
|
// Provide field setting
|
||||||
cy.get(`[data-cy="field-prop-control"]`).type("1")
|
cy.get(interact.DATASOURCE_FIELD_CONTROL).type("1")
|
||||||
// Open dropdown and select Radio buttons
|
// Open dropdown and select Radio buttons
|
||||||
cy.get(`[data-cy="optionsType-prop-control"]`).click().then(() => {
|
cy.get(interact.OPTION_TYPE_PROP_CONTROL).click().then(() => {
|
||||||
cy.get('.spectrum-Popover').contains('Radio buttons')
|
cy.get(interact.SPECTRUM_POPOVER).contains('Radio buttons')
|
||||||
.wait(500)
|
.wait(500)
|
||||||
.click()
|
.click()
|
||||||
})
|
})
|
||||||
|
@ -28,8 +29,8 @@ filterTests(['all'], () => {
|
||||||
})
|
})
|
||||||
|
|
||||||
const addRadioButtonData = (totalRadioButtons) => {
|
const addRadioButtonData = (totalRadioButtons) => {
|
||||||
cy.get(`[data-cy="optionsSource-prop-control"]`).click().then(() => {
|
cy.get(interact.OPTION_SOURCE_PROP_CONROL).click().then(() => {
|
||||||
cy.get('.spectrum-Popover').contains('Custom')
|
cy.get(interact.SPECTRUM_POPOVER).contains('Custom')
|
||||||
.wait(500)
|
.wait(500)
|
||||||
.click()
|
.click()
|
||||||
})
|
})
|
||||||
|
|
|
@ -0,0 +1,346 @@
|
||||||
|
import filterTests from "../support/filterTests"
|
||||||
|
import clientPackage from "@budibase/client/package.json"
|
||||||
|
|
||||||
|
filterTests(['all'], () => {
|
||||||
|
context("Application Overview screen", () => {
|
||||||
|
before(() => {
|
||||||
|
cy.login()
|
||||||
|
cy.createTestApp()
|
||||||
|
})
|
||||||
|
|
||||||
|
it("Should be accessible from the applications list", () => {
|
||||||
|
cy.visit(`${Cypress.config().baseUrl}/builder`)
|
||||||
|
|
||||||
|
cy.get(".appTable .title").eq(0)
|
||||||
|
.invoke('attr', 'data-cy')
|
||||||
|
.then(($dataCy) => {
|
||||||
|
const dataCy = $dataCy;
|
||||||
|
cy.get(".appTable .name").eq(0).click()
|
||||||
|
|
||||||
|
cy.location().should((loc) => {
|
||||||
|
expect(loc.pathname).to.eq('/builder/portal/overview/' + dataCy)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
cy.visit(`${Cypress.config().baseUrl}/builder`)
|
||||||
|
|
||||||
|
cy.get(".appTable .title").eq(0)
|
||||||
|
.invoke('attr', 'data-cy')
|
||||||
|
.then(($dataCy) => {
|
||||||
|
const dataCy = $dataCy;
|
||||||
|
cy.get(".appTable .app-row-actions button").contains("View").click({force: true})
|
||||||
|
|
||||||
|
cy.location().should((loc) => {
|
||||||
|
expect(loc.pathname).to.eq('/builder/portal/overview/' + dataCy)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
})
|
||||||
|
|
||||||
|
// Find a more suitable place for this.
|
||||||
|
it("Should allow unlocking in the app list", () => {
|
||||||
|
cy.visit(`${Cypress.config().baseUrl}/builder`)
|
||||||
|
|
||||||
|
cy.get(".appTable .lock-status").eq(0).contains("Locked by you").click()
|
||||||
|
|
||||||
|
cy.unlockApp({ owned : true })
|
||||||
|
|
||||||
|
cy.get(".appTable").should("exist")
|
||||||
|
cy.get(".lock-status").should('not.be.visible')
|
||||||
|
})
|
||||||
|
|
||||||
|
it("Should allow unlocking in the app overview screen", () => {
|
||||||
|
cy.visit(`${Cypress.config().baseUrl}/builder`)
|
||||||
|
|
||||||
|
cy.get(".appTable .app-row-actions button").contains("Edit").eq(0).click({force: true})
|
||||||
|
cy.wait(1000)
|
||||||
|
cy.visit(`${Cypress.config().baseUrl}/builder`)
|
||||||
|
|
||||||
|
cy.get(".appTable .name").eq(0).click()
|
||||||
|
|
||||||
|
cy.get(".lock-status").eq(0).contains("Locked by you").click()
|
||||||
|
|
||||||
|
cy.unlockApp({ owned : true })
|
||||||
|
|
||||||
|
cy.get(".lock-status").should("not.be.visible")
|
||||||
|
})
|
||||||
|
|
||||||
|
it("Should reflect the deploy state of an app that hasn't been published.", () => {
|
||||||
|
cy.visit(`${Cypress.config().baseUrl}/builder`)
|
||||||
|
|
||||||
|
cy.get(".appTable .name").eq(0).click()
|
||||||
|
|
||||||
|
cy.get(".header-right button.spectrum-Button[data-cy='view-app']").should("be.disabled")
|
||||||
|
|
||||||
|
cy.get(".spectrum-Tabs-item.is-selected").contains("Overview")
|
||||||
|
cy.get(".overview-tab").should("be.visible")
|
||||||
|
|
||||||
|
cy.get(".overview-tab [data-cy='app-status']").within(() => {
|
||||||
|
cy.get(".status-display").contains("Unpublished")
|
||||||
|
cy.get(".status-display .icon svg[aria-label='GlobeStrike']").should("exist")
|
||||||
|
cy.get(".status-text").contains("-")
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
it("Should reflect the app deployment state", () => {
|
||||||
|
cy.visit(`${Cypress.config().baseUrl}/builder`)
|
||||||
|
cy.get(".appTable .app-row-actions button").contains("Edit").eq(0).click({force: true})
|
||||||
|
|
||||||
|
cy.get(".toprightnav button.spectrum-Button").contains("Publish").click({ force : true })
|
||||||
|
cy.get(".spectrum-Modal [data-cy='deploy-app-modal']").should("be.visible")
|
||||||
|
.within(() => {
|
||||||
|
cy.get(".spectrum-Button").contains("Publish").click({ force : true })
|
||||||
|
cy.wait(1000)
|
||||||
|
});
|
||||||
|
|
||||||
|
cy.visit(`${Cypress.config().baseUrl}/builder`)
|
||||||
|
cy.get(".appTable .name").eq(0).click()
|
||||||
|
|
||||||
|
cy.get(".header-right button.spectrum-Button[data-cy='view-app']").should("not.be.disabled")
|
||||||
|
|
||||||
|
cy.get(".overview-tab [data-cy='app-status']").within(() => {
|
||||||
|
cy.get(".status-display").contains("Published")
|
||||||
|
cy.get(".status-display .icon svg[aria-label='GlobeCheck']").should("exist")
|
||||||
|
cy.get(".status-text").contains("Last published a few seconds ago")
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
it("Should reflect an application that has been unpublished", () => {
|
||||||
|
cy.visit(`${Cypress.config().baseUrl}/builder`)
|
||||||
|
cy.get(".appTable .app-row-actions button").contains("Edit").eq(0).click({force: true})
|
||||||
|
|
||||||
|
cy.get(".deployment-top-nav svg[aria-label='Globe']")
|
||||||
|
.click({ force: true })
|
||||||
|
|
||||||
|
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")
|
||||||
|
.within(() => {
|
||||||
|
cy.get(".confirm-wrap button").click({ force: true }
|
||||||
|
)})
|
||||||
|
cy.wait(1000)
|
||||||
|
|
||||||
|
cy.visit(`${Cypress.config().baseUrl}/builder`)
|
||||||
|
cy.get(".appTable .name").eq(0).click()
|
||||||
|
|
||||||
|
cy.get(".overview-tab [data-cy='app-status']").within(() => {
|
||||||
|
cy.get(".status-display").contains("Unpublished")
|
||||||
|
cy.get(".status-display .icon svg[aria-label='GlobeStrike']").should("exist")
|
||||||
|
cy.get(".status-text").contains("Last published a few seconds ago")
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
it("Should allow the editing of the application icon", () => {
|
||||||
|
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')
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
it("Should reflect the last time the application was edited", () => {
|
||||||
|
cy.visit(`${Cypress.config().baseUrl}/builder`)
|
||||||
|
cy.get(".appTable .name").eq(0).click()
|
||||||
|
|
||||||
|
cy.get(".header-right button").contains("Edit").click({ force: true });
|
||||||
|
|
||||||
|
cy.navigateToFrontend()
|
||||||
|
|
||||||
|
cy.addComponent("Elements", "Headline").then(componentId => {
|
||||||
|
cy.getComponent(componentId).should("exist")
|
||||||
|
})
|
||||||
|
|
||||||
|
cy.visit(`${Cypress.config().baseUrl}/builder`)
|
||||||
|
cy.get(".appTable .name").eq(0).click()
|
||||||
|
|
||||||
|
cy.get(".overview-tab [data-cy='edited-by']").within(() => {
|
||||||
|
cy.get(".editor-name").contains("You")
|
||||||
|
cy.get(".last-edit-text").contains("Last edited a few seconds ago")
|
||||||
|
})
|
||||||
|
});
|
||||||
|
|
||||||
|
it("Should reflect application version is up-to-date", () => {
|
||||||
|
cy.visit(`${Cypress.config().baseUrl}/builder`)
|
||||||
|
cy.get(".appTable .name").eq(0).click()
|
||||||
|
|
||||||
|
cy.get(".overview-tab [data-cy='app-version']").within(() => {
|
||||||
|
cy.get(".version-status").contains("You're running the latest!")
|
||||||
|
})
|
||||||
|
});
|
||||||
|
|
||||||
|
it("Should navigate to the settings tab when clicking the App Version card header", () => {
|
||||||
|
cy.visit(`${Cypress.config().baseUrl}/builder`)
|
||||||
|
cy.get(".appTable .name").eq(0).click()
|
||||||
|
|
||||||
|
cy.get(".spectrum-Tabs-item.is-selected").contains("Overview")
|
||||||
|
cy.get(".overview-tab").should("be.visible")
|
||||||
|
|
||||||
|
cy.get(".overview-tab [data-cy='app-version'] .dash-card-header").click({ force : true })
|
||||||
|
|
||||||
|
cy.get(".spectrum-Tabs-item.is-selected").contains("Settings")
|
||||||
|
cy.get(".settings-tab").should("be.visible")
|
||||||
|
cy.get(".overview-tab").should("not.exist")
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
it("Should allow the upgrading of an application, if available.", () => {
|
||||||
|
cy.visit(`${Cypress.config().baseUrl}/builder`)
|
||||||
|
cy.get(".appTable .name").eq(0).click()
|
||||||
|
cy.wait(500)
|
||||||
|
|
||||||
|
cy.location().then(loc => {
|
||||||
|
const params = loc.pathname.split("/")
|
||||||
|
const appId = params[params.length - 1]
|
||||||
|
cy.log(appId)
|
||||||
|
//Downgrade the app for the test
|
||||||
|
cy.alterAppVersion(appId, "0.0.1-alpha.0")
|
||||||
|
.then(()=>{
|
||||||
|
cy.reload()
|
||||||
|
cy.wait(1000)
|
||||||
|
cy.log("Current deployment version: " + clientPackage.version)
|
||||||
|
|
||||||
|
cy.get(".version-status a").contains("Update").click()
|
||||||
|
cy.get(".spectrum-Tabs-item.is-selected").contains("Settings")
|
||||||
|
|
||||||
|
cy.get(".version-section .page-action button").contains("Update").click({ force: true })
|
||||||
|
|
||||||
|
cy.intercept('POST', '**/applications/**/client/update').as('updateVersion')
|
||||||
|
cy.get(".spectrum-Modal.is-open button").contains("Update").click({ force: true })
|
||||||
|
|
||||||
|
cy.wait("@updateVersion")
|
||||||
|
.its('response.statusCode').should('eq', 200)
|
||||||
|
.then(() => {
|
||||||
|
cy.visit(`${Cypress.config().baseUrl}/builder`)
|
||||||
|
cy.get(".appTable .name").eq(0).click()
|
||||||
|
|
||||||
|
cy.get(".spectrum-Tabs-item").contains("Overview").click({ force: true })
|
||||||
|
cy.get(".overview-tab [data-cy='app-version']").within(() => {
|
||||||
|
cy.get(".spectrum-Heading").contains(clientPackage.version)
|
||||||
|
cy.get(".version-status").contains("You're running the latest!")
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
});
|
||||||
|
|
||||||
|
})
|
||||||
|
|
||||||
|
it("Should allow editing of the app details.", () => {
|
||||||
|
cy.visit(`${Cypress.config().baseUrl}/builder`)
|
||||||
|
cy.get(".appTable .name").eq(0).click()
|
||||||
|
|
||||||
|
cy.get(".spectrum-Tabs-item").contains("Settings").click()
|
||||||
|
cy.get(".spectrum-Tabs-item.is-selected").contains("Settings")
|
||||||
|
cy.get(".settings-tab").should("be.visible")
|
||||||
|
|
||||||
|
cy.get(".details-section .page-action button").contains("Edit").click({ force: true })
|
||||||
|
cy.updateAppName("sample name")
|
||||||
|
|
||||||
|
//publish and check its disabled
|
||||||
|
cy.visit(`${Cypress.config().baseUrl}/builder`)
|
||||||
|
cy.get(".appTable .app-row-actions button").contains("Edit").eq(0).click({force: true})
|
||||||
|
|
||||||
|
cy.get(".toprightnav button.spectrum-Button").contains("Publish").click({ force : true })
|
||||||
|
cy.get(".spectrum-Modal [data-cy='deploy-app-modal']").should("be.visible")
|
||||||
|
.within(() => {
|
||||||
|
cy.get(".spectrum-Button").contains("Publish").click({ force : true })
|
||||||
|
cy.wait(1000)
|
||||||
|
});
|
||||||
|
|
||||||
|
cy.visit(`${Cypress.config().baseUrl}/builder`)
|
||||||
|
cy.get(".appTable .name").eq(0).click()
|
||||||
|
cy.get(".spectrum-Tabs-item").contains("Settings").click()
|
||||||
|
cy.get(".spectrum-Tabs-item.is-selected").contains("Settings")
|
||||||
|
|
||||||
|
cy.get(".details-section .page-action .spectrum-Button").scrollIntoView()
|
||||||
|
cy.wait(1000)
|
||||||
|
cy.get(".details-section .page-action .spectrum-Button").should("be.disabled")
|
||||||
|
|
||||||
|
})
|
||||||
|
|
||||||
|
it("Should allow copying of the published application Id", () => {
|
||||||
|
cy.visit(`${Cypress.config().baseUrl}/builder`)
|
||||||
|
cy.get(".appTable .app-row-actions").eq(0)
|
||||||
|
.within(() => {
|
||||||
|
cy.get(".spectrum-Button").contains("Edit").click({ force: true })
|
||||||
|
})
|
||||||
|
|
||||||
|
cy.publishApp("sample-name")
|
||||||
|
|
||||||
|
cy.visit(`${Cypress.config().baseUrl}/builder`)
|
||||||
|
cy.get(".appTable .name").eq(0).click()
|
||||||
|
|
||||||
|
cy.get(".app-overview-actions-icon > .icon").click({ force : true })
|
||||||
|
|
||||||
|
cy.get("[data-cy='app-overview-menu-popover']").eq(0).within(() => {
|
||||||
|
cy.get(".spectrum-Menu-item").contains("Copy App ID").click({ force: true })
|
||||||
|
})
|
||||||
|
|
||||||
|
cy.get(".spectrum-Toast-content").contains("App ID copied to clipboard.").should("be.visible")
|
||||||
|
})
|
||||||
|
|
||||||
|
it("Should allow unpublishing of the application", () => {
|
||||||
|
cy.visit(`${Cypress.config().baseUrl}/builder`)
|
||||||
|
cy.get(".appTable .name").eq(0).click()
|
||||||
|
|
||||||
|
cy.get(".app-overview-actions-icon > .icon").click({ force : true })
|
||||||
|
|
||||||
|
cy.get("[data-cy='app-overview-menu-popover']").eq(0).within(() => {
|
||||||
|
cy.get(".spectrum-Menu-item").contains("Unpublish").click({ force: true })
|
||||||
|
cy.wait(500)
|
||||||
|
})
|
||||||
|
|
||||||
|
cy.get("[data-cy='unpublish-modal']").should("be.visible")
|
||||||
|
.within(() => {
|
||||||
|
cy.get(".confirm-wrap button").click({ force: true }
|
||||||
|
)})
|
||||||
|
|
||||||
|
cy.get(".overview-tab [data-cy='app-status']").within(() => {
|
||||||
|
cy.get(".status-display").contains("Unpublished")
|
||||||
|
cy.get(".status-display .icon svg[aria-label='GlobeStrike']").should("exist")
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
it("Should allow deleting of the application", () => {
|
||||||
|
cy.visit(`${Cypress.config().baseUrl}/builder`)
|
||||||
|
cy.get(".appTable .name").eq(0).click()
|
||||||
|
|
||||||
|
cy.get(".app-overview-actions-icon > .icon").click({ force : true })
|
||||||
|
|
||||||
|
cy.get("[data-cy='app-overview-menu-popover']").eq(0).within(() => {
|
||||||
|
cy.get(".spectrum-Menu-item").contains("Delete").click({ force: true })
|
||||||
|
cy.wait(500)
|
||||||
|
})
|
||||||
|
|
||||||
|
//The test application was renamed earlier in the spec
|
||||||
|
cy.get(".spectrum-Dialog-grid").within(() => {
|
||||||
|
cy.get("input").type("sample name")
|
||||||
|
cy.get(".spectrum-Button--warning").click()
|
||||||
|
})
|
||||||
|
|
||||||
|
cy.location().should((loc) => {
|
||||||
|
expect(loc.pathname).to.eq('/builder/portal/apps')
|
||||||
|
})
|
||||||
|
|
||||||
|
cy.get(".appTable").should("not.exist")
|
||||||
|
|
||||||
|
cy.get(".welcome .container h1").contains("Let's create your first app!")
|
||||||
|
})
|
||||||
|
|
||||||
|
after(() => {
|
||||||
|
cy.deleteAllApps()
|
||||||
|
})
|
||||||
|
|
||||||
|
})
|
||||||
|
})
|
|
@ -19,7 +19,7 @@ filterTests(['all'], () => {
|
||||||
|
|
||||||
cy.get(".appTable .app-row-actions").eq(0)
|
cy.get(".appTable .app-row-actions").eq(0)
|
||||||
.within(() => {
|
.within(() => {
|
||||||
cy.get(".spectrum-Button").contains("Preview")
|
cy.get(".spectrum-Button").contains("View")
|
||||||
cy.get(".spectrum-Button").contains("Edit").click({ force: true })
|
cy.get(".spectrum-Button").contains("Edit").click({ force: true })
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@ -29,22 +29,8 @@ filterTests(['all'], () => {
|
||||||
|
|
||||||
it("Should publish an application and correctly reflect that", () => {
|
it("Should publish an application and correctly reflect that", () => {
|
||||||
//Assuming the previous test was run and the unpublished app is open in edit mode.
|
//Assuming the previous test was run and the unpublished app is open in edit mode.
|
||||||
cy.get(".toprightnav button.spectrum-Button").contains("Publish").click({ force : true })
|
|
||||||
|
|
||||||
cy.get(".spectrum-Modal [data-cy='deploy-app-modal']").should("be.visible")
|
cy.publishApp("cypress-tests")
|
||||||
.within(() => {
|
|
||||||
cy.get(".spectrum-Button").contains("Publish").click({ force : true })
|
|
||||||
cy.wait(1000)
|
|
||||||
});
|
|
||||||
|
|
||||||
//Verify that the app url is presented correctly to the user
|
|
||||||
cy.get(".spectrum-Modal [data-cy='deploy-app-success-modal']")
|
|
||||||
.should("be.visible")
|
|
||||||
.within(() => {
|
|
||||||
let appUrl = Cypress.config().baseUrl + '/app/cypress-tests'
|
|
||||||
cy.get("[data-cy='deployed-app-url'] input").should('have.value', appUrl)
|
|
||||||
cy.get(".spectrum-Button").contains("Done").click({ force: true })
|
|
||||||
})
|
|
||||||
|
|
||||||
cy.visit(`${Cypress.config().baseUrl}/builder`)
|
cy.visit(`${Cypress.config().baseUrl}/builder`)
|
||||||
cy.wait(1000)
|
cy.wait(1000)
|
||||||
|
@ -57,7 +43,7 @@ filterTests(['all'], () => {
|
||||||
|
|
||||||
cy.get(".appTable .app-row-actions").eq(0)
|
cy.get(".appTable .app-row-actions").eq(0)
|
||||||
.within(() => {
|
.within(() => {
|
||||||
cy.get(".spectrum-Button").contains("View app")
|
cy.get(".spectrum-Button").contains("View")
|
||||||
cy.get(".spectrum-Button").contains("Edit").click({ force: true })
|
cy.get(".spectrum-Button").contains("Edit").click({ force: true })
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@ -66,7 +52,7 @@ filterTests(['all'], () => {
|
||||||
cy.get("[data-cy='publish-popover-menu']").should("be.visible")
|
cy.get("[data-cy='publish-popover-menu']").should("be.visible")
|
||||||
.within(() => {
|
.within(() => {
|
||||||
cy.get("[data-cy='publish-popover-action']").should("exist")
|
cy.get("[data-cy='publish-popover-action']").should("exist")
|
||||||
cy.get("button").contains("View app").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(".publish-popover-message").should("have.text", "Last published a few seconds ago")
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
@ -84,7 +70,7 @@ filterTests(['all'], () => {
|
||||||
|
|
||||||
cy.get(".appTable .app-row-actions").eq(0)
|
cy.get(".appTable .app-row-actions").eq(0)
|
||||||
.within(() => {
|
.within(() => {
|
||||||
cy.get(".spectrum-Button").contains("View app")
|
cy.get(".spectrum-Button").contains("View")
|
||||||
cy.get(".spectrum-Button").contains("Edit").click({ force: true })
|
cy.get(".spectrum-Button").contains("Edit").click({ force: true })
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
import filterTests from '../support/filterTests'
|
import filterTests from '../support/filterTests'
|
||||||
|
const interact = require('../support/interact')
|
||||||
|
|
||||||
filterTests(['smoke', 'all'], () => {
|
filterTests(['smoke', 'all'], () => {
|
||||||
context("Create an Application", () => {
|
context("Create an Application", () => {
|
||||||
|
@ -10,14 +11,14 @@ filterTests(['smoke', 'all'], () => {
|
||||||
|
|
||||||
if (!(Cypress.env("TEST_ENV"))) {
|
if (!(Cypress.env("TEST_ENV"))) {
|
||||||
it("should show the new user UI/UX", () => {
|
it("should show the new user UI/UX", () => {
|
||||||
cy.visit(`${Cypress.config().baseUrl}/builder`)
|
cy.visit(`${Cypress.config().baseUrl}/builder/portal/apps/create`) //added /portal/apps/create
|
||||||
cy.get(`[data-cy="create-app-btn"]`).contains('Start from scratch').should("exist")
|
cy.get(interact.CREATE_APP_BUTTON).contains('Start from scratch').should("exist")
|
||||||
cy.get(`[data-cy="import-app-btn"]`).should("exist")
|
cy.get(interact.CREATE_APP_BUTTON).should("exist")
|
||||||
|
|
||||||
cy.get(".template-category-filters").should("exist")
|
cy.get(interact.TEMPLATE_CATEGORY_FILTER).should("exist")
|
||||||
cy.get(".template-categories").should("exist")
|
cy.get(interact.TEMPLATE_CATEGORY).should("exist")
|
||||||
|
|
||||||
cy.get(".appTable").should("not.exist")
|
cy.get(interact.APP_TABLE).should("not.exist")
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -29,21 +30,21 @@ filterTests(['smoke', 'all'], () => {
|
||||||
.its("body")
|
.its("body")
|
||||||
.then(val => {
|
.then(val => {
|
||||||
if (val.length > 0) {
|
if (val.length > 0) {
|
||||||
cy.get(".spectrum-Button").contains("Templates").click({force: true})
|
cy.get(interact.SPECTRUM_BUTTON_TEMPLATE).contains("Templates").click({force: true})
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
cy.get(".template-category-filters").should("exist")
|
cy.get(interact.TEMPLATE_CATEGORY_FILTER).should("exist")
|
||||||
cy.get(".template-categories").should("exist")
|
cy.get(interact.TEMPLATE_CATEGORY).should("exist")
|
||||||
|
|
||||||
cy.get(".template-category").its('length').should('be.gt', 1)
|
cy.get(interact.TEMPLATE_CATEGORY_ACTIONGROUP).its('length').should('be.gt', 1)
|
||||||
cy.get(".template-category-filters .spectrum-ActionButton").its('length').should('be.gt', 2)
|
cy.get(interact.TEMPLATE_CATEGORY_FILTER_ACTIONBUTTON).its('length').should('be.gt', 2)
|
||||||
|
|
||||||
cy.get(".template-category-filters .spectrum-ActionButton").eq(1).click()
|
cy.get(interact.TEMPLATE_CATEGORY_FILTER_ACTIONBUTTON).eq(1).click()
|
||||||
cy.get(".template-category").should('have.length', 1)
|
cy.get(interact.TEMPLATE_CATEGORY_ACTIONGROUP).should('have.length', 1)
|
||||||
|
|
||||||
cy.get(".template-category-filters .spectrum-ActionButton").eq(0).click()
|
cy.get(interact.TEMPLATE_CATEGORY_FILTER_ACTIONBUTTON).eq(0).click()
|
||||||
cy.get(".template-category").its('length').should('be.gt', 1)
|
cy.get(interact.TEMPLATE_CATEGORY_ACTIONGROUP).its('length').should('be.gt', 1)
|
||||||
})
|
})
|
||||||
|
|
||||||
it("should enforce a valid url before submission", () => {
|
it("should enforce a valid url before submission", () => {
|
||||||
|
@ -51,37 +52,40 @@ filterTests(['smoke', 'all'], () => {
|
||||||
cy.wait(500)
|
cy.wait(500)
|
||||||
|
|
||||||
// Start create app process. If apps already exist, click second button
|
// Start create app process. If apps already exist, click second button
|
||||||
cy.get(`[data-cy="create-app-btn"]`).click({ force: true })
|
cy.get(interact.CREATE_APP_BUTTON).click({ force: true })
|
||||||
cy.request(`${Cypress.config().baseUrl}/api/applications?status=all`)
|
cy.request(`${Cypress.config().baseUrl}/api/applications?status=all`)
|
||||||
.its("body")
|
.its("body")
|
||||||
.then(val => {
|
.then(val => {
|
||||||
if (val.length > 0) {
|
if (val.length > 0) {
|
||||||
cy.get(`[data-cy="create-app-btn"]`).click({ force: true })
|
cy.get(interact.CREATE_APP_BUTTON).click({ force: true })
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
const appName = "Cypress Tests"
|
const appName = "Cypress Tests"
|
||||||
cy.get(".spectrum-Modal").within(() => {
|
cy.get(interact.SPECTRUM_MODAL).within(() => {
|
||||||
|
|
||||||
|
cy.get(interact.APP_NAME_INPUT).eq(0).should('have.focus')
|
||||||
|
|
||||||
//Auto fill
|
//Auto fill
|
||||||
cy.get("input").eq(0).type(appName).should("have.value", appName).blur()
|
cy.get(interact.APP_NAME_INPUT).eq(0).clear()
|
||||||
cy.get("input").eq(1).should("have.value", "/cypress-tests")
|
cy.get(interact.APP_NAME_INPUT).eq(0).type(appName).should("have.value", appName).blur()
|
||||||
cy.get(".spectrum-ButtonGroup").contains("Create app").should('not.be.disabled')
|
cy.get(interact.APP_NAME_INPUT).eq(1).should("have.value", "/cypress-tests")
|
||||||
|
cy.get(interact.SPECTRUM_BUTTON_GROUP).contains("Create app").should('not.be.disabled')
|
||||||
|
|
||||||
//Empty the app url - disabled create
|
//Empty the app url - disabled create
|
||||||
cy.get("input").eq(1).clear().blur()
|
cy.get(interact.APP_NAME_INPUT).eq(1).clear().blur()
|
||||||
cy.get(".spectrum-ButtonGroup").contains("Create app").should('be.disabled')
|
cy.get(interact.SPECTRUM_BUTTON_GROUP).contains("Create app").should('be.disabled')
|
||||||
|
|
||||||
//Invalid url
|
//Invalid url
|
||||||
cy.get("input").eq(1).type("/new app-url").blur()
|
cy.get(interact.APP_NAME_INPUT).eq(1).type("/new app-url").blur()
|
||||||
cy.get(".spectrum-ButtonGroup").contains("Create app").should('be.disabled')
|
cy.get(interact.SPECTRUM_BUTTON_GROUP).contains("Create app").should('be.disabled')
|
||||||
|
|
||||||
//Specifically alter the url
|
//Specifically alter the url
|
||||||
cy.get("input").eq(1).clear()
|
cy.get(interact.APP_NAME_INPUT).eq(1).clear()
|
||||||
cy.get("input").eq(1).type("another-app-name").blur()
|
cy.get(interact.APP_NAME_INPUT).eq(1).type("another-app-name").blur()
|
||||||
cy.get("input").eq(1).should("have.value", "/another-app-name")
|
cy.get(interact.APP_NAME_INPUT).eq(1).should("have.value", "/another-app-name")
|
||||||
cy.get("input").eq(0).should("have.value", appName)
|
cy.get(interact.APP_NAME_INPUT).eq(0).should("have.value", appName)
|
||||||
cy.get(".spectrum-ButtonGroup").contains("Create app").should('not.be.disabled')
|
cy.get(interact.SPECTRUM_BUTTON_GROUP).contains("Create app").should('not.be.disabled')
|
||||||
|
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
@ -97,6 +101,77 @@ filterTests(['smoke', 'all'], () => {
|
||||||
cy.deleteApp(appName)
|
cy.deleteApp(appName)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
it("should create the first application from scratch with a default name", () => {
|
||||||
|
cy.createApp()
|
||||||
|
|
||||||
|
cy.visit(`${Cypress.config().baseUrl}/builder`)
|
||||||
|
cy.wait(1000)
|
||||||
|
|
||||||
|
cy.applicationInAppTable("My app")
|
||||||
|
cy.deleteApp("My app")
|
||||||
|
})
|
||||||
|
|
||||||
|
it("should create the first application from scratch, using the users first name as the default app name", () => {
|
||||||
|
cy.visit(`${Cypress.config().baseUrl}/builder`)
|
||||||
|
|
||||||
|
cy.updateUserInformation("Ted", "Userman")
|
||||||
|
|
||||||
|
cy.createApp()
|
||||||
|
|
||||||
|
cy.visit(`${Cypress.config().baseUrl}/builder`)
|
||||||
|
cy.wait(1000)
|
||||||
|
|
||||||
|
cy.applicationInAppTable("Teds app")
|
||||||
|
cy.deleteApp("Teds app")
|
||||||
|
cy.wait(2000)
|
||||||
|
|
||||||
|
//Accomodate names that end in 'S'
|
||||||
|
cy.updateUserInformation("Chris", "Userman")
|
||||||
|
|
||||||
|
cy.createApp()
|
||||||
|
|
||||||
|
cy.visit(`${Cypress.config().baseUrl}/builder`)
|
||||||
|
cy.wait(1000)
|
||||||
|
|
||||||
|
cy.applicationInAppTable("Chris app")
|
||||||
|
cy.deleteApp("Chris app")
|
||||||
|
cy.wait(2000)
|
||||||
|
|
||||||
|
cy.updateUserInformation("", "")
|
||||||
|
})
|
||||||
|
|
||||||
|
it("should create an application from an export", () => {
|
||||||
|
const exportedApp = 'cypress/fixtures/exported-app.txt'
|
||||||
|
|
||||||
|
cy.importApp(exportedApp, "")
|
||||||
|
|
||||||
|
cy.visit(`${Cypress.config().baseUrl}/builder`)
|
||||||
|
|
||||||
|
cy.applicationInAppTable("My app")
|
||||||
|
|
||||||
|
cy.get(".appTable .name").eq(0).click()
|
||||||
|
|
||||||
|
cy.deleteApp("My app")
|
||||||
|
})
|
||||||
|
|
||||||
|
it("should create an application from an export, using the users first name as the default app name", () => {
|
||||||
|
const exportedApp = 'cypress/fixtures/exported-app.txt'
|
||||||
|
|
||||||
|
cy.updateUserInformation("Ted", "Userman")
|
||||||
|
|
||||||
|
cy.importApp(exportedApp, "")
|
||||||
|
|
||||||
|
cy.visit(`${Cypress.config().baseUrl}/builder`)
|
||||||
|
|
||||||
|
cy.applicationInAppTable("Teds app")
|
||||||
|
|
||||||
|
cy.get(".appTable .name").eq(0).click()
|
||||||
|
|
||||||
|
cy.deleteApp("Teds app")
|
||||||
|
|
||||||
|
cy.updateUserInformation("", "")
|
||||||
|
})
|
||||||
|
|
||||||
it("should generate the first application from a template", () => {
|
it("should generate the first application from a template", () => {
|
||||||
cy.visit(`${Cypress.config().baseUrl}/builder`)
|
cy.visit(`${Cypress.config().baseUrl}/builder`)
|
||||||
cy.wait(500)
|
cy.wait(500)
|
||||||
|
@ -106,15 +181,15 @@ filterTests(['smoke', 'all'], () => {
|
||||||
.its("body")
|
.its("body")
|
||||||
.then(val => {
|
.then(val => {
|
||||||
if (val.length > 0) {
|
if (val.length > 0) {
|
||||||
cy.get(`[data-cy="create-app-btn"]`).click({ force: true })
|
cy.get(interact.CREATE_APP_BUTTON).click({ force: true })
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
cy.get(".template-category-filters").should("exist")
|
cy.get(interact.TEMPLATE_CATEGORY_FILTER).should("exist")
|
||||||
cy.get(".template-categories").should("exist")
|
cy.get(interact.TEMPLATE_CATEGORY).should("exist")
|
||||||
|
|
||||||
// Select template
|
// Select template
|
||||||
cy.get('.template-category').eq(0).within(() => {
|
cy.get(interact.TEMPLATE_CATEGORY_ACTIONGROUP).eq(0).within(() => {
|
||||||
const card = cy.get('.template-card').eq(0).should("exist");
|
const card = cy.get('.template-card').eq(0).should("exist");
|
||||||
const cardOverlay = card.get('.template-thumbnail-action-overlay').should("exist")
|
const cardOverlay = card.get('.template-thumbnail-action-overlay').should("exist")
|
||||||
cardOverlay.invoke("show")
|
cardOverlay.invoke("show")
|
||||||
|
@ -128,14 +203,14 @@ filterTests(['smoke', 'all'], () => {
|
||||||
templateName.invoke('text')
|
templateName.invoke('text')
|
||||||
.then(templateNameText => {
|
.then(templateNameText => {
|
||||||
const templateNameParsed = "/"+templateNameText.toLowerCase().replace(/\s+/g, "-")
|
const templateNameParsed = "/"+templateNameText.toLowerCase().replace(/\s+/g, "-")
|
||||||
cy.get(".spectrum-Modal input").eq(0).should("have.value", templateNameText)
|
cy.get(interact.SPECTRUM_MODAL_INPUT).eq(0).should("have.value", templateNameText)
|
||||||
cy.get(".spectrum-Modal input").eq(1).should("have.value", templateNameParsed)
|
cy.get(interact.SPECTRUM_MODAL_INPUT).eq(1).should("have.value", templateNameParsed)
|
||||||
|
|
||||||
cy.get(".spectrum-Modal .spectrum-ButtonGroup").contains("Create app").click()
|
cy.get(".spectrum-Modal .spectrum-ButtonGroup").contains("Create app").click()
|
||||||
cy.wait(5000)
|
cy.wait(5000)
|
||||||
|
|
||||||
cy.visit(`${Cypress.config().baseUrl}/builder`)
|
cy.visit(`${Cypress.config().baseUrl}/builder`)
|
||||||
cy.wait(1000)
|
cy.wait(2000)
|
||||||
|
|
||||||
cy.applicationInAppTable(templateNameText)
|
cy.applicationInAppTable(templateNameText)
|
||||||
cy.deleteApp(templateNameText)
|
cy.deleteApp(templateNameText)
|
||||||
|
|
|
@ -24,7 +24,7 @@ filterTests(['smoke', 'all'], () => {
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
it("should add a URL param binding", () => {
|
xit("should add a URL param binding", () => {
|
||||||
const paramName = "foo"
|
const paramName = "foo"
|
||||||
cy.createScreen(`/test/:${paramName}`)
|
cy.createScreen(`/test/:${paramName}`)
|
||||||
cy.addComponent("Elements", "Paragraph").then(componentId => {
|
cy.addComponent("Elements", "Paragraph").then(componentId => {
|
||||||
|
|
|
@ -9,7 +9,7 @@ filterTests(["smoke", "all"], () => {
|
||||||
})
|
})
|
||||||
|
|
||||||
it("Should successfully create a screen", () => {
|
it("Should successfully create a screen", () => {
|
||||||
cy.createScreen("/test")
|
cy.createScreen("test")
|
||||||
cy.get(".nav-items-container").within(() => {
|
cy.get(".nav-items-container").within(() => {
|
||||||
cy.contains("/test").should("exist")
|
cy.contains("/test").should("exist")
|
||||||
})
|
})
|
||||||
|
|
|
@ -4,6 +4,8 @@ filterTests(["smoke", "all"], () => {
|
||||||
context("Create a User and Assign Roles", () => {
|
context("Create a User and Assign Roles", () => {
|
||||||
before(() => {
|
before(() => {
|
||||||
cy.login()
|
cy.login()
|
||||||
|
cy.deleteApp("Cypress Tests")
|
||||||
|
cy.createApp("Cypress Tests")
|
||||||
})
|
})
|
||||||
|
|
||||||
it("should create a user", () => {
|
it("should create a user", () => {
|
||||||
|
@ -52,7 +54,7 @@ filterTests(["smoke", "all"], () => {
|
||||||
cy.get(".spectrum-Table").contains("bbuser").click()
|
cy.get(".spectrum-Table").contains("bbuser").click()
|
||||||
cy.wait(1000)
|
cy.wait(1000)
|
||||||
for (let i = 0; i < 3; i++) {
|
for (let i = 0; i < 3; i++) {
|
||||||
cy.get(".spectrum-Table")
|
cy.get(".spectrum-Table", { timeout: 3000})
|
||||||
.eq(1)
|
.eq(1)
|
||||||
.find(".spectrum-Table-row")
|
.find(".spectrum-Table-row")
|
||||||
.eq(0)
|
.eq(0)
|
||||||
|
@ -66,19 +68,20 @@ filterTests(["smoke", "all"], () => {
|
||||||
.then(() => {
|
.then(() => {
|
||||||
cy.wait(1000)
|
cy.wait(1000)
|
||||||
if (i == 0) {
|
if (i == 0) {
|
||||||
cy.get(".spectrum-Popover").contains("Admin").click()
|
cy.get(".spectrum-Menu").contains("Admin").click({ force: true })
|
||||||
}
|
}
|
||||||
if (i == 1) {
|
else if (i == 1) {
|
||||||
cy.get(".spectrum-Popover").contains("Power").click()
|
cy.get(".spectrum-Menu").contains("Power").click({ force: true })
|
||||||
}
|
}
|
||||||
if (i == 2) {
|
else if (i == 2) {
|
||||||
cy.get(".spectrum-Popover").contains("Basic").click()
|
cy.get(".spectrum-Menu").contains("Basic").click({ force: true })
|
||||||
}
|
}
|
||||||
cy.wait(1000)
|
cy.wait(1000)
|
||||||
cy.get(".spectrum-Button")
|
cy.get(".spectrum-Button")
|
||||||
.contains("Update role")
|
.contains("Update role")
|
||||||
.click({ force: true })
|
.click({ force: true })
|
||||||
})
|
})
|
||||||
|
cy.reload()
|
||||||
}
|
}
|
||||||
// Confirm roles exist within Configure roles table
|
// Confirm roles exist within Configure roles table
|
||||||
cy.wait(2000)
|
cy.wait(2000)
|
||||||
|
|
|
@ -11,7 +11,7 @@ filterTests(["all"], () => {
|
||||||
const queryName = "Cypress Test Query"
|
const queryName = "Cypress Test Query"
|
||||||
const queryRename = "CT Query Rename"
|
const queryRename = "CT Query Rename"
|
||||||
|
|
||||||
it("Should add PostgreSQL data source without configuration", () => {
|
xit("Should add PostgreSQL data source without configuration", () => {
|
||||||
// Select PostgreSQL data source
|
// Select PostgreSQL data source
|
||||||
cy.selectExternalDatasource(datasource)
|
cy.selectExternalDatasource(datasource)
|
||||||
// Attempt to fetch tables without applying configuration
|
// Attempt to fetch tables without applying configuration
|
||||||
|
@ -107,7 +107,7 @@ filterTests(["all"], () => {
|
||||||
})
|
})
|
||||||
|
|
||||||
it("should delete a relationship", () => {
|
it("should delete a relationship", () => {
|
||||||
cy.get(".hierarchy-items-container").contains("PostgreSQL-2").click()
|
cy.get(".hierarchy-items-container").contains("PostgreSQL").click()
|
||||||
cy.reload()
|
cy.reload()
|
||||||
// Delete one relationship
|
// Delete one relationship
|
||||||
cy.get(".spectrum-Table")
|
cy.get(".spectrum-Table")
|
||||||
|
@ -155,7 +155,7 @@ filterTests(["all"], () => {
|
||||||
|
|
||||||
it("should switch to schema with no tables", () => {
|
it("should switch to schema with no tables", () => {
|
||||||
// Switch Schema - To one without any tables
|
// Switch Schema - To one without any tables
|
||||||
cy.get(".hierarchy-items-container").contains("PostgreSQL-2").click()
|
cy.get(".hierarchy-items-container").contains("PostgreSQL").click()
|
||||||
switchSchema("randomText")
|
switchSchema("randomText")
|
||||||
|
|
||||||
// No tables displayed
|
// No tables displayed
|
||||||
|
@ -207,7 +207,7 @@ filterTests(["all"], () => {
|
||||||
.contains(queryName)
|
.contains(queryName)
|
||||||
.siblings(".actions")
|
.siblings(".actions")
|
||||||
.within(() => {
|
.within(() => {
|
||||||
cy.get(".icon").click({ force: true })
|
cy.get(".spectrum-Icon").click({ force: true })
|
||||||
})
|
})
|
||||||
// Select and confirm duplication
|
// Select and confirm duplication
|
||||||
cy.get(".spectrum-Menu").contains("Duplicate").click()
|
cy.get(".spectrum-Menu").contains("Duplicate").click()
|
||||||
|
|
|
@ -112,19 +112,9 @@ filterTests(['all'], () => {
|
||||||
cy.get("[data-cy='app-row-actions-menu-popover']").eq(0).within(() => {
|
cy.get("[data-cy='app-row-actions-menu-popover']").eq(0).within(() => {
|
||||||
cy.get(".spectrum-Menu-item").contains("Edit").click({ force: true })
|
cy.get(".spectrum-Menu-item").contains("Edit").click({ force: true })
|
||||||
})
|
})
|
||||||
cy.get(".spectrum-Modal")
|
|
||||||
.within(() => {
|
cy.updateAppName(changedName, noName)
|
||||||
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)
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
|
@ -26,6 +26,8 @@ filterTests(['smoke', 'all'], () => {
|
||||||
})
|
})
|
||||||
|
|
||||||
it("should revert a published app", () => {
|
it("should revert a published app", () => {
|
||||||
|
cy.navigateToFrontend()
|
||||||
|
|
||||||
// Add initial component - Paragraph
|
// Add initial component - Paragraph
|
||||||
cy.addComponent("Elements", "Paragraph")
|
cy.addComponent("Elements", "Paragraph")
|
||||||
// Publish app
|
// Publish app
|
||||||
|
@ -37,6 +39,7 @@ filterTests(['smoke', 'all'], () => {
|
||||||
cy.get(".spectrum-ButtonGroup").within(() => {
|
cy.get(".spectrum-ButtonGroup").within(() => {
|
||||||
cy.get(".spectrum-Button").contains("Done").click({ force: true })
|
cy.get(".spectrum-Button").contains("Done").click({ force: true })
|
||||||
})
|
})
|
||||||
|
|
||||||
// Add second component - Button
|
// Add second component - Button
|
||||||
cy.addComponent("Elements", "Button")
|
cy.addComponent("Elements", "Button")
|
||||||
// Click Revert
|
// Click Revert
|
||||||
|
|
|
@ -7,13 +7,7 @@ filterTests(["all"], () => {
|
||||||
cy.login()
|
cy.login()
|
||||||
|
|
||||||
// Template navigation
|
// Template navigation
|
||||||
cy.request(`${Cypress.config().baseUrl}/api/applications?status=all`)
|
cy.visit(`${Cypress.config().baseUrl}/builder/portal/apps/templates`)
|
||||||
.its("body")
|
|
||||||
.then(val => {
|
|
||||||
if (val.length > 0) {
|
|
||||||
cy.get(".spectrum-Button").contains("Templates").click({force: true})
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
// Filter HR Templates
|
// Filter HR Templates
|
||||||
cy.get(".template-category-filters").within(() => {
|
cy.get(".template-category-filters").within(() => {
|
||||||
|
|
|
@ -0,0 +1,222 @@
|
||||||
|
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()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
|
@ -7,13 +7,7 @@ filterTests(["all"], () => {
|
||||||
cy.login()
|
cy.login()
|
||||||
|
|
||||||
// Template navigation
|
// Template navigation
|
||||||
cy.request(`${Cypress.config().baseUrl}/api/applications?status=all`)
|
cy.visit(`${Cypress.config().baseUrl}/builder/portal/apps/templates`)
|
||||||
.its("body")
|
|
||||||
.then(val => {
|
|
||||||
if (val.length > 0) {
|
|
||||||
cy.get(".spectrum-Button").contains("Templates").click({force: true})
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
// Filter IT Templates
|
// Filter IT Templates
|
||||||
cy.get(".template-category-filters").within(() => {
|
cy.get(".template-category-filters").within(() => {
|
||||||
|
|
|
@ -0,0 +1,72 @@
|
||||||
|
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 })
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
|
@ -7,13 +7,7 @@ filterTests(["all"], () => {
|
||||||
cy.login()
|
cy.login()
|
||||||
|
|
||||||
// Template navigation
|
// Template navigation
|
||||||
cy.request(`${Cypress.config().baseUrl}/api/applications?status=all`)
|
cy.visit(`${Cypress.config().baseUrl}/builder/portal/apps/templates`)
|
||||||
.its("body")
|
|
||||||
.then(val => {
|
|
||||||
if (val.length > 0) {
|
|
||||||
cy.get(".spectrum-Button").contains("Templates").click({force: true})
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
// Filter Admin Panels Templates
|
// Filter Admin Panels Templates
|
||||||
cy.get(".template-category-filters").within(() => {
|
cy.get(".template-category-filters").within(() => {
|
||||||
|
|
|
@ -7,13 +7,7 @@ filterTests(["all"], () => {
|
||||||
cy.login()
|
cy.login()
|
||||||
|
|
||||||
// Template navigation
|
// Template navigation
|
||||||
cy.request(`${Cypress.config().baseUrl}/api/applications?status=all`)
|
cy.visit(`${Cypress.config().baseUrl}/builder/portal/apps/templates`)
|
||||||
.its("body")
|
|
||||||
.then(val => {
|
|
||||||
if (val.length > 0) {
|
|
||||||
cy.get(".spectrum-Button").contains("Templates").click({force: true})
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
// Filter Approval Apps Templates
|
// Filter Approval Apps Templates
|
||||||
cy.get(".template-category-filters").within(() => {
|
cy.get(".template-category-filters").within(() => {
|
||||||
|
|
|
@ -7,13 +7,7 @@ filterTests(["all"], () => {
|
||||||
cy.login()
|
cy.login()
|
||||||
|
|
||||||
// Template navigation
|
// Template navigation
|
||||||
cy.request(`${Cypress.config().baseUrl}/api/applications?status=all`)
|
cy.visit(`${Cypress.config().baseUrl}/builder/portal/apps/templates`)
|
||||||
.its("body")
|
|
||||||
.then(val => {
|
|
||||||
if (val.length > 0) {
|
|
||||||
cy.get(".spectrum-Button").contains("Templates").click({force: true})
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
// Filter Business Apps Templates
|
// Filter Business Apps Templates
|
||||||
cy.get(".template-category-filters").within(() => {
|
cy.get(".template-category-filters").within(() => {
|
||||||
|
|
|
@ -7,13 +7,7 @@ filterTests(["all"], () => {
|
||||||
cy.login()
|
cy.login()
|
||||||
|
|
||||||
// Template navigation
|
// Template navigation
|
||||||
cy.request(`${Cypress.config().baseUrl}/api/applications?status=all`)
|
cy.visit(`${Cypress.config().baseUrl}/builder/portal/apps/templates`)
|
||||||
.its("body")
|
|
||||||
.then(val => {
|
|
||||||
if (val.length > 0) {
|
|
||||||
cy.get(".spectrum-Button").contains("Templates").click({force: true})
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
// Filter Directories Templates
|
// Filter Directories Templates
|
||||||
cy.get(".template-category-filters").within(() => {
|
cy.get(".template-category-filters").within(() => {
|
||||||
|
|
|
@ -7,13 +7,7 @@ filterTests(["all"], () => {
|
||||||
cy.login()
|
cy.login()
|
||||||
|
|
||||||
// Template navigation
|
// Template navigation
|
||||||
cy.request(`${Cypress.config().baseUrl}/api/applications?status=all`)
|
cy.visit(`${Cypress.config().baseUrl}/builder/portal/apps/templates`)
|
||||||
.its("body")
|
|
||||||
.then(val => {
|
|
||||||
if (val.length > 0) {
|
|
||||||
cy.get(".spectrum-Button").contains("Templates").click({force: true})
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
// Filter Forms Templates
|
// Filter Forms Templates
|
||||||
cy.get(".template-category-filters").within(() => {
|
cy.get(".template-category-filters").within(() => {
|
||||||
|
|
|
@ -7,13 +7,7 @@ filterTests(["all"], () => {
|
||||||
cy.login()
|
cy.login()
|
||||||
|
|
||||||
// Template navigation
|
// Template navigation
|
||||||
cy.request(`${Cypress.config().baseUrl}/api/applications?status=all`)
|
cy.visit(`${Cypress.config().baseUrl}/builder/portal/apps/templates`)
|
||||||
.its("body")
|
|
||||||
.then(val => {
|
|
||||||
if (val.length > 0) {
|
|
||||||
cy.get(".spectrum-Button").contains("Templates").click({force: true})
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
// Filter Healthcare Templates
|
// Filter Healthcare Templates
|
||||||
cy.get(".template-category-filters").within(() => {
|
cy.get(".template-category-filters").within(() => {
|
||||||
|
|
|
@ -7,13 +7,7 @@ filterTests(["all"], () => {
|
||||||
cy.login()
|
cy.login()
|
||||||
|
|
||||||
// Template navigation
|
// Template navigation
|
||||||
cy.request(`${Cypress.config().baseUrl}/api/applications?status=all`)
|
cy.visit(`${Cypress.config().baseUrl}/builder/portal/apps/templates`)
|
||||||
.its("body")
|
|
||||||
.then(val => {
|
|
||||||
if (val.length > 0) {
|
|
||||||
cy.get(".spectrum-Button").contains("Templates").click({force: true})
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
// Filter Legal Templates
|
// Filter Legal Templates
|
||||||
cy.get(".template-category-filters").within(() => {
|
cy.get(".template-category-filters").within(() => {
|
||||||
|
|
|
@ -7,13 +7,7 @@ filterTests(["all"], () => {
|
||||||
cy.login()
|
cy.login()
|
||||||
|
|
||||||
// Template navigation
|
// Template navigation
|
||||||
cy.request(`${Cypress.config().baseUrl}/api/applications?status=all`)
|
cy.visit(`${Cypress.config().baseUrl}/builder/portal/apps/templates`)
|
||||||
.its("body")
|
|
||||||
.then(val => {
|
|
||||||
if (val.length > 0) {
|
|
||||||
cy.get(".spectrum-Button").contains("Templates").click({force: true})
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
// Filter Logistics Templates
|
// Filter Logistics Templates
|
||||||
cy.get(".template-category-filters").within(() => {
|
cy.get(".template-category-filters").within(() => {
|
||||||
|
|
|
@ -7,13 +7,7 @@ filterTests(["all"], () => {
|
||||||
cy.login()
|
cy.login()
|
||||||
|
|
||||||
// Template navigation
|
// Template navigation
|
||||||
cy.request(`${Cypress.config().baseUrl}/api/applications?status=all`)
|
cy.visit(`${Cypress.config().baseUrl}/builder/portal/apps/templates`)
|
||||||
.its("body")
|
|
||||||
.then(val => {
|
|
||||||
if (val.length > 0) {
|
|
||||||
cy.get(".spectrum-Button").contains("Templates").click({force: true})
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
// Filter Manufacturing Templates
|
// Filter Manufacturing Templates
|
||||||
cy.get(".template-category-filters").within(() => {
|
cy.get(".template-category-filters").within(() => {
|
||||||
|
|
|
@ -0,0 +1,44 @@
|
||||||
|
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')
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
|
@ -7,13 +7,7 @@ filterTests(["all"], () => {
|
||||||
cy.login()
|
cy.login()
|
||||||
|
|
||||||
// Template navigation
|
// Template navigation
|
||||||
cy.request(`${Cypress.config().baseUrl}/api/applications?status=all`)
|
cy.visit(`${Cypress.config().baseUrl}/builder/portal/apps/templates`)
|
||||||
.its("body")
|
|
||||||
.then(val => {
|
|
||||||
if (val.length > 0) {
|
|
||||||
cy.get(".spectrum-Button").contains("Templates").click({force: true})
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
// Filter Marketing Templates
|
// Filter Marketing Templates
|
||||||
cy.get(".template-category-filters").within(() => {
|
cy.get(".template-category-filters").within(() => {
|
||||||
|
|
|
@ -7,13 +7,7 @@ filterTests(["all"], () => {
|
||||||
cy.login()
|
cy.login()
|
||||||
|
|
||||||
// Template navigation
|
// Template navigation
|
||||||
cy.request(`${Cypress.config().baseUrl}/api/applications?status=all`)
|
cy.visit(`${Cypress.config().baseUrl}/builder/portal/apps/templates`)
|
||||||
.its("body")
|
|
||||||
.then(val => {
|
|
||||||
if (val.length > 0) {
|
|
||||||
cy.get(".spectrum-Button").contains("Templates").click({force: true})
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
// Filter Operations Templates
|
// Filter Operations Templates
|
||||||
cy.get(".template-category-filters").within(() => {
|
cy.get(".template-category-filters").within(() => {
|
||||||
|
|
|
@ -7,21 +7,15 @@ filterTests(["all"], () => {
|
||||||
cy.login()
|
cy.login()
|
||||||
|
|
||||||
// Template navigation
|
// Template navigation
|
||||||
cy.request(`${Cypress.config().baseUrl}/api/applications?status=all`)
|
cy.visit(`${Cypress.config().baseUrl}/builder/portal/apps/templates`)
|
||||||
.its("body")
|
|
||||||
.then(val => {
|
|
||||||
if (val.length > 0) {
|
|
||||||
cy.get(".spectrum-Button").contains("Templates").click({force: true})
|
|
||||||
}
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
it("should verify the details option for Portal templates", () => {
|
|
||||||
// Filter Portal Templates
|
// Filter Portal Templates
|
||||||
cy.get(".template-category-filters").within(() => {
|
cy.get(".template-category-filters").within(() => {
|
||||||
cy.get('[data-cy="Portal"]').click()
|
cy.get('[data-cy="Portal"]').click()
|
||||||
})
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
it("should verify the details option for Portal templates", () => {
|
||||||
cy.get(".template-grid").find(".template-card").its('length')
|
cy.get(".template-grid").find(".template-card").its('length')
|
||||||
.then(len => {
|
.then(len => {
|
||||||
for (let i = 0; i < len; i++) {
|
for (let i = 0; i < len; i++) {
|
||||||
|
|
|
@ -7,13 +7,7 @@ filterTests(["all"], () => {
|
||||||
cy.login()
|
cy.login()
|
||||||
|
|
||||||
// Template navigation
|
// Template navigation
|
||||||
cy.request(`${Cypress.config().baseUrl}/api/applications?status=all`)
|
cy.visit(`${Cypress.config().baseUrl}/builder/portal/apps/templates`)
|
||||||
.its("body")
|
|
||||||
.then(val => {
|
|
||||||
if (val.length > 0) {
|
|
||||||
cy.get(".spectrum-Button").contains("Templates").click({force: true})
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
// Filter Professional Services Templates
|
// Filter Professional Services Templates
|
||||||
cy.get(".template-category-filters").within(() => {
|
cy.get(".template-category-filters").within(() => {
|
||||||
|
|
|
@ -7,7 +7,6 @@ const tmpdir = path.join(require("os").tmpdir(), ".budibase")
|
||||||
const SERVER_PORT = cypressConfig.env.PORT
|
const SERVER_PORT = cypressConfig.env.PORT
|
||||||
const WORKER_PORT = cypressConfig.env.WORKER_PORT
|
const WORKER_PORT = cypressConfig.env.WORKER_PORT
|
||||||
|
|
||||||
process.env.BUDIBASE_API_KEY = "6BE826CB-6B30-4AEC-8777-2E90464633DE"
|
|
||||||
process.env.NODE_ENV = "cypress"
|
process.env.NODE_ENV = "cypress"
|
||||||
process.env.ENABLE_ANALYTICS = "false"
|
process.env.ENABLE_ANALYTICS = "false"
|
||||||
process.env.JWT_SECRET = cypressConfig.env.JWT_SECRET
|
process.env.JWT_SECRET = cypressConfig.env.JWT_SECRET
|
||||||
|
|
|
@ -18,24 +18,98 @@ Cypress.Commands.add("login", () => {
|
||||||
cy.get("input").first().type("test@test.com")
|
cy.get("input").first().type("test@test.com")
|
||||||
cy.get('input[type="password"]').first().type("test")
|
cy.get('input[type="password"]').first().type("test")
|
||||||
cy.get('input[type="password"]').eq(1).type("test")
|
cy.get('input[type="password"]').eq(1).type("test")
|
||||||
cy.contains("Create super admin user").click()
|
cy.contains("Create super admin user").click({ force: true })
|
||||||
}
|
}
|
||||||
if (url.includes("builder/auth/login") || url.includes("builder/admin")) {
|
if (url.includes("builder/auth/login") || url.includes("builder/admin")) {
|
||||||
// login
|
// login
|
||||||
cy.contains("Sign in to Budibase").then(() => {
|
cy.contains("Sign in to Budibase").then(() => {
|
||||||
cy.get("input").first().type("test@test.com")
|
cy.get("input").first().type("test@test.com")
|
||||||
cy.get('input[type="password"]').type("test")
|
cy.get('input[type="password"]').type("test")
|
||||||
cy.get("button").first().click()
|
cy.get("button").first().click({ force: true })
|
||||||
cy.wait(1000)
|
cy.wait(1000)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
Cypress.Commands.add("logOut", () => {
|
||||||
|
cy.visit(`${Cypress.config().baseUrl}/builder`)
|
||||||
|
cy.get(".user-dropdown .avatar > .icon").click({ force: true })
|
||||||
|
cy.get(".spectrum-Popover[data-cy='user-menu']").within(() => {
|
||||||
|
cy.get("li[data-cy='user-logout']").click({ force: true })
|
||||||
|
})
|
||||||
|
cy.wait(2000)
|
||||||
|
})
|
||||||
|
|
||||||
Cypress.Commands.add("closeModal", () => {
|
Cypress.Commands.add("closeModal", () => {
|
||||||
cy.get(".spectrum-Modal").within(() => {
|
cy.get(".spectrum-Modal").within(() => {
|
||||||
cy.get(".close-icon").click()
|
cy.get(".close-icon").click()
|
||||||
|
cy.wait(1000) // Wait for modal to close
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
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.wait(500)
|
||||||
|
}
|
||||||
|
cy.get(`[data-cy="import-app-btn"]`).click({ force: true })
|
||||||
|
})
|
||||||
|
|
||||||
|
cy.get(".spectrum-Modal").within(() => {
|
||||||
|
cy.get("input").eq(1).should("have.focus")
|
||||||
|
|
||||||
|
cy.get(".spectrum-Dropzone").selectFile(exportFilePath, {
|
||||||
|
action: "drag-drop",
|
||||||
|
})
|
||||||
|
|
||||||
|
cy.get(".gallery .filename").contains("exported-app.txt")
|
||||||
|
|
||||||
|
if (name && name != "") {
|
||||||
|
cy.get("input").eq(0).type(name).should("have.value", name).blur()
|
||||||
|
}
|
||||||
|
cy.get(".confirm-wrap button")
|
||||||
|
.should("not.be.disabled")
|
||||||
|
.click({ force: true })
|
||||||
|
cy.wait(5000)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
Cypress.Commands.add("updateUserInformation", (firstName, lastName) => {
|
||||||
|
cy.get(".user-dropdown .avatar > .icon").click({ force: true })
|
||||||
|
|
||||||
|
cy.get(".spectrum-Popover[data-cy='user-menu']").within(() => {
|
||||||
|
cy.get("li[data-cy='user-info']").click({ force: true })
|
||||||
|
})
|
||||||
|
|
||||||
|
cy.get(".spectrum-Modal.is-open").within(() => {
|
||||||
|
cy.get("[data-cy='user-first-name']").clear()
|
||||||
|
|
||||||
|
if (!firstName || firstName == "") {
|
||||||
|
cy.get("[data-cy='user-first-name']").invoke("val").should("be.empty")
|
||||||
|
} else {
|
||||||
|
cy.get("[data-cy='user-first-name']")
|
||||||
|
.type(firstName)
|
||||||
|
.should("have.value", firstName)
|
||||||
|
.blur()
|
||||||
|
}
|
||||||
|
|
||||||
|
cy.get("[data-cy='user-last-name']").clear()
|
||||||
|
|
||||||
|
if (!lastName || lastName == "") {
|
||||||
|
cy.get("[data-cy='user-last-name']").invoke("val").should("be.empty")
|
||||||
|
} else {
|
||||||
|
cy.get("[data-cy='user-last-name']")
|
||||||
|
.type(lastName)
|
||||||
|
.should("have.value", lastName)
|
||||||
|
.blur()
|
||||||
|
}
|
||||||
|
cy.get("button").contains("Update information").click({ force: true })
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@ -44,7 +118,7 @@ Cypress.Commands.add("createApp", (name, addDefaultTable) => {
|
||||||
typeof addDefaultTable != "boolean" ? true : addDefaultTable
|
typeof addDefaultTable != "boolean" ? true : addDefaultTable
|
||||||
|
|
||||||
cy.visit(`${Cypress.config().baseUrl}/builder`)
|
cy.visit(`${Cypress.config().baseUrl}/builder`)
|
||||||
cy.wait(500)
|
cy.wait(1000)
|
||||||
cy.get(`[data-cy="create-app-btn"]`).click({ force: true })
|
cy.get(`[data-cy="create-app-btn"]`).click({ force: true })
|
||||||
|
|
||||||
// If apps already exist
|
// If apps already exist
|
||||||
|
@ -57,8 +131,14 @@ Cypress.Commands.add("createApp", (name, addDefaultTable) => {
|
||||||
})
|
})
|
||||||
|
|
||||||
cy.get(".spectrum-Modal").within(() => {
|
cy.get(".spectrum-Modal").within(() => {
|
||||||
|
cy.get("input").eq(0).should("have.focus")
|
||||||
|
if (name && name != "") {
|
||||||
|
cy.get("input").eq(0).clear()
|
||||||
cy.get("input").eq(0).type(name).should("have.value", name).blur()
|
cy.get("input").eq(0).type(name).should("have.value", name).blur()
|
||||||
cy.get(".spectrum-ButtonGroup").contains("Create app").click()
|
}
|
||||||
|
cy.get(".spectrum-ButtonGroup")
|
||||||
|
.contains("Create app")
|
||||||
|
.click({ force: true })
|
||||||
cy.wait(10000)
|
cy.wait(10000)
|
||||||
})
|
})
|
||||||
if (shouldCreateDefaultTable) {
|
if (shouldCreateDefaultTable) {
|
||||||
|
@ -75,9 +155,6 @@ Cypress.Commands.add("deleteApp", name => {
|
||||||
const findAppName = val.some(val => val.name == name)
|
const findAppName = val.some(val => val.name == name)
|
||||||
if (findAppName) {
|
if (findAppName) {
|
||||||
if (val.length > 0) {
|
if (val.length > 0) {
|
||||||
if (Cypress.env("TEST_ENV")) {
|
|
||||||
cy.searchForApplication(name)
|
|
||||||
}
|
|
||||||
const appId = val.reduce((acc, app) => {
|
const appId = val.reduce((acc, app) => {
|
||||||
if (name === app.name) {
|
if (name === app.name) {
|
||||||
acc = app.appId
|
acc = app.appId
|
||||||
|
@ -92,21 +169,23 @@ Cypress.Commands.add("deleteApp", name => {
|
||||||
const appIdParsed = appId.split("_").pop()
|
const appIdParsed = appId.split("_").pop()
|
||||||
const actionEleId = `[data-cy=row_actions_${appIdParsed}]`
|
const actionEleId = `[data-cy=row_actions_${appIdParsed}]`
|
||||||
cy.get(actionEleId).within(() => {
|
cy.get(actionEleId).within(() => {
|
||||||
cy.get(".spectrum-Icon").eq(0).click()
|
cy.get(".spectrum-Icon").eq(0).click({ force: true })
|
||||||
})
|
})
|
||||||
|
|
||||||
cy.get(".spectrum-Menu").then($menu => {
|
cy.get(".spectrum-Menu").then($menu => {
|
||||||
if ($menu.text().includes("Unpublish")) {
|
if ($menu.text().includes("Unpublish")) {
|
||||||
cy.get(".spectrum-Menu").contains("Unpublish").click()
|
cy.get(".spectrum-Menu").contains("Unpublish").click()
|
||||||
cy.get(".spectrum-Dialog-grid").contains("Unpublish app").click()
|
cy.get(".spectrum-Dialog-grid").contains("Unpublish app").click()
|
||||||
} else {
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
cy.get(actionEleId).within(() => {
|
||||||
|
cy.get(".spectrum-Icon").eq(0).click({ force: true })
|
||||||
|
})
|
||||||
cy.get(".spectrum-Menu").contains("Delete").click()
|
cy.get(".spectrum-Menu").contains("Delete").click()
|
||||||
cy.get(".spectrum-Dialog-grid").within(() => {
|
cy.get(".spectrum-Dialog-grid").within(() => {
|
||||||
cy.get("input").type(name)
|
cy.get("input").type(name)
|
||||||
})
|
})
|
||||||
cy.get(".spectrum-Button--warning").click()
|
cy.get(".spectrum-Button--warning").click()
|
||||||
}
|
|
||||||
})
|
|
||||||
} else {
|
} else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -126,7 +205,7 @@ Cypress.Commands.add("deleteAllApps", () => {
|
||||||
const appIdParsed = val[i].appId.split("_").pop()
|
const appIdParsed = val[i].appId.split("_").pop()
|
||||||
const actionEleId = `[data-cy=row_actions_${appIdParsed}]`
|
const actionEleId = `[data-cy=row_actions_${appIdParsed}]`
|
||||||
cy.get(actionEleId).within(() => {
|
cy.get(actionEleId).within(() => {
|
||||||
cy.get(".spectrum-Icon").eq(0).click()
|
cy.get(".spectrum-Icon").eq(0).click({ force: true })
|
||||||
})
|
})
|
||||||
|
|
||||||
cy.get(".spectrum-Menu").contains("Delete").click()
|
cy.get(".spectrum-Menu").contains("Delete").click()
|
||||||
|
@ -139,11 +218,114 @@ 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 }
|
||||||
|
|
||||||
|
cy.get(".spectrum-Modal .spectrum-Dialog[data-cy='app-lock-modal']")
|
||||||
|
.should("be.visible")
|
||||||
|
.within(() => {
|
||||||
|
if (config.owned) {
|
||||||
|
cy.get(".spectrum-Dialog-heading").contains("Locked by you")
|
||||||
|
cy.get(".lock-expiry-body").contains(
|
||||||
|
"This lock will expire in 10 minutes from now"
|
||||||
|
)
|
||||||
|
|
||||||
|
cy.intercept("**/lock").as("unlockApp")
|
||||||
|
cy.get(".spectrum-Button")
|
||||||
|
.contains("Release Lock")
|
||||||
|
.click({ force: true })
|
||||||
|
cy.wait("@unlockApp")
|
||||||
|
cy.get("@unlockApp").its("response.statusCode").should("eq", 200)
|
||||||
|
cy.get("@unlockApp").its("response.body").should("deep.equal", {
|
||||||
|
message: "Lock released successfully.",
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
//Show the name ?
|
||||||
|
cy.get(".lock-expiry-body").should("not.be.visible")
|
||||||
|
cy.get(".spectrum-Button").contains("Done")
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
Cypress.Commands.add("publishApp", resolvedAppPath => {
|
||||||
|
//Assumes you have navigated to an application first
|
||||||
|
cy.get(".toprightnav button.spectrum-Button")
|
||||||
|
.contains("Publish")
|
||||||
|
.click({ force: true })
|
||||||
|
|
||||||
|
cy.get(".spectrum-Modal [data-cy='deploy-app-modal']")
|
||||||
|
.should("be.visible")
|
||||||
|
.within(() => {
|
||||||
|
cy.get(".spectrum-Button").contains("Publish").click({ force: true })
|
||||||
|
cy.wait(1000)
|
||||||
|
})
|
||||||
|
|
||||||
|
//Verify that the app url is presented correctly to the user
|
||||||
|
cy.get(".spectrum-Modal [data-cy='deploy-app-success-modal']")
|
||||||
|
.should("be.visible")
|
||||||
|
.within(() => {
|
||||||
|
let appUrl = Cypress.config().baseUrl + "/app/" + resolvedAppPath
|
||||||
|
cy.get("[data-cy='deployed-app-url'] input").should("have.value", appUrl)
|
||||||
|
cy.get(".spectrum-Button").contains("Done").click({ force: true })
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
Cypress.Commands.add("createTestApp", () => {
|
Cypress.Commands.add("createTestApp", () => {
|
||||||
const appName = "Cypress Tests"
|
const appName = "Cypress Tests"
|
||||||
cy.deleteApp(appName)
|
cy.deleteApp(appName)
|
||||||
cy.createApp(appName, "This app is used for Cypress testing.")
|
cy.createApp(appName, "This app is used for Cypress testing.")
|
||||||
cy.createScreen("home")
|
//cy.createScreen("home")
|
||||||
})
|
})
|
||||||
|
|
||||||
Cypress.Commands.add("createTestTableWithData", () => {
|
Cypress.Commands.add("createTestTableWithData", () => {
|
||||||
|
@ -152,6 +334,21 @@ Cypress.Commands.add("createTestTableWithData", () => {
|
||||||
cy.addColumn("dog", "age", "Number")
|
cy.addColumn("dog", "age", "Number")
|
||||||
})
|
})
|
||||||
|
|
||||||
|
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 })
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
Cypress.Commands.add("createTable", (tableName, initialTable) => {
|
Cypress.Commands.add("createTable", (tableName, initialTable) => {
|
||||||
if (!initialTable) {
|
if (!initialTable) {
|
||||||
cy.navigateToDataSection()
|
cy.navigateToDataSection()
|
||||||
|
@ -244,12 +441,12 @@ Cypress.Commands.add("createUser", email => {
|
||||||
|
|
||||||
Cypress.Commands.add("addComponent", (category, component) => {
|
Cypress.Commands.add("addComponent", (category, component) => {
|
||||||
if (category) {
|
if (category) {
|
||||||
cy.get(`[data-cy="category-${category}"]`).click()
|
cy.get(`[data-cy="category-${category}"]`).click({ force: true })
|
||||||
}
|
}
|
||||||
if (component) {
|
if (component) {
|
||||||
cy.get(`[data-cy="component-${component}"]`).click()
|
cy.get(`[data-cy="component-${component}"]`).click({ force: true })
|
||||||
}
|
}
|
||||||
cy.wait(1000)
|
cy.wait(2000)
|
||||||
cy.location().then(loc => {
|
cy.location().then(loc => {
|
||||||
const params = loc.pathname.split("/")
|
const params = loc.pathname.split("/")
|
||||||
const componentId = params[params.length - 1]
|
const componentId = params[params.length - 1]
|
||||||
|
@ -454,7 +651,12 @@ Cypress.Commands.add("createAppFromScratch", appName => {
|
||||||
.contains("Start from scratch")
|
.contains("Start from scratch")
|
||||||
.click({ force: true })
|
.click({ force: true })
|
||||||
cy.get(".spectrum-Modal").within(() => {
|
cy.get(".spectrum-Modal").within(() => {
|
||||||
cy.get("input").eq(0).type(appName).should("have.value", appName).blur()
|
cy.get("input")
|
||||||
|
.eq(0)
|
||||||
|
.clear()
|
||||||
|
.type(appName)
|
||||||
|
.should("have.value", appName)
|
||||||
|
.blur()
|
||||||
cy.get(".spectrum-ButtonGroup").contains("Create app").click()
|
cy.get(".spectrum-ButtonGroup").contains("Create app").click()
|
||||||
cy.wait(10000)
|
cy.wait(10000)
|
||||||
})
|
})
|
||||||
|
@ -568,12 +770,14 @@ Cypress.Commands.add("addDatasourceConfig", (datasource, skipFetch) => {
|
||||||
.click({ force: true })
|
.click({ force: true })
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
|
cy.intercept("**/tables").as("datasourceTables")
|
||||||
cy.get(".spectrum-Dialog-grid").within(() => {
|
cy.get(".spectrum-Dialog-grid").within(() => {
|
||||||
cy.get(".spectrum-Button")
|
cy.get(".spectrum-Button")
|
||||||
.contains("Save and fetch tables")
|
.contains("Save and fetch tables")
|
||||||
.click({ force: true })
|
.click({ force: true })
|
||||||
cy.wait(1000)
|
|
||||||
})
|
})
|
||||||
|
// Wait for tables to be fetched
|
||||||
|
cy.wait("@datasourceTables", { timeout: 60000 })
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,26 @@
|
||||||
|
// createApp test
|
||||||
|
export const CREATE_APP_BUTTON = '[data-cy="create-app-btn"]'
|
||||||
|
export const TEMPLATE_CATEGORY_FILTER = ".template-category-filters"
|
||||||
|
export const TEMPLATE_CATEGORY = ".template-categories"
|
||||||
|
export const APP_TABLE = ".appTable"
|
||||||
|
export const SPECTRUM_BUTTON_TEMPLATE = ".spectrum-Button"
|
||||||
|
export const TEMPLATE_CATEGORY_ACTIONGROUP = ".template-category"
|
||||||
|
export const TEMPLATE_CATEGORY_FILTER_ACTIONBUTTON =
|
||||||
|
".template-category-filters .spectrum-ActionButton"
|
||||||
|
export const SPECTRUM_MODAL = ".spectrum-Modal"
|
||||||
|
export const APP_NAME_INPUT = "input" // we need to update this with atribute cy-data;
|
||||||
|
export const SPECTRUM_BUTTON_GROUP = ".spectrum-ButtonGroup"
|
||||||
|
export const SPECTRUM_MODAL_INPUT = ".spectrum-Modal input"
|
||||||
|
|
||||||
|
//AddMultiOptionDatatype test
|
||||||
|
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 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'
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "@budibase/builder",
|
"name": "@budibase/builder",
|
||||||
"version": "1.0.142-alpha.0",
|
"version": "1.0.185-alpha.0",
|
||||||
"license": "GPL-3.0",
|
"license": "GPL-3.0",
|
||||||
"private": true,
|
"private": true,
|
||||||
"scripts": {
|
"scripts": {
|
||||||
|
@ -13,11 +13,13 @@
|
||||||
"cy:setup:ci": "node ./cypress/setup.js",
|
"cy:setup:ci": "node ./cypress/setup.js",
|
||||||
"cy:open": "cypress open",
|
"cy:open": "cypress open",
|
||||||
"cy:run": "cypress run",
|
"cy:run": "cypress run",
|
||||||
"cy:run:ci": "xvfb-run cypress run --headed --browser chrome",
|
"cy:run:ci": "cypress run --headed --browser chrome --spec cypress/integration/createApp.spec.js",
|
||||||
"cy:run:ci:record": "xvfb-run cypress run --headed --browser chrome --record",
|
"cy:run:ci:record": "xvfb-run cypress run --headed --browser chrome --record",
|
||||||
"cy:test": "start-server-and-test cy:setup http://localhost:4100/builder cy:run",
|
"cy:test": "start-server-and-test cy:setup http://localhost:4100/builder cy:run",
|
||||||
"cy:ci": "start-server-and-test cy:setup:ci http://localhost:4100/builder cy:run:ci",
|
"cy:ci": "start-server-and-test cy:setup:ci http://localhost:4100/builder cy:run:ci",
|
||||||
"cy:ci:record": "start-server-and-test cy:setup:ci http://localhost:4100/builder cy:run:ci:record",
|
"cy:ci:record": "start-server-and-test cy:setup:ci http://localhost:4100/builder cy:run:ci:record && npm run cy:ci:report",
|
||||||
|
"cy:ci:report": "mochawesome-merge cypress/reports/*.json > cypress/reports/testReport.json && marge cypress/reports/testReport.json --reportDir cypress/reports --inline",
|
||||||
|
"cy:ci:notify": "node scripts/cypressResultsWebhook",
|
||||||
"cy:debug": "start-server-and-test cy:setup http://localhost:4100/builder cy:open",
|
"cy:debug": "start-server-and-test cy:setup http://localhost:4100/builder cy:open",
|
||||||
"cy:debug:ci": "start-server-and-test cy:setup:ci http://localhost:4100/builder cy:open"
|
"cy:debug:ci": "start-server-and-test cy:setup:ci http://localhost:4100/builder cy:open"
|
||||||
},
|
},
|
||||||
|
@ -67,10 +69,10 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@budibase/bbui": "^1.0.142-alpha.0",
|
"@budibase/bbui": "^1.0.185-alpha.0",
|
||||||
"@budibase/client": "^1.0.142-alpha.0",
|
"@budibase/client": "^1.0.185-alpha.0",
|
||||||
"@budibase/frontend-core": "^1.0.142-alpha.0",
|
"@budibase/frontend-core": "^1.0.185-alpha.0",
|
||||||
"@budibase/string-templates": "^1.0.142-alpha.0",
|
"@budibase/string-templates": "^1.0.185-alpha.0",
|
||||||
"@sentry/browser": "5.19.1",
|
"@sentry/browser": "5.19.1",
|
||||||
"@spectrum-css/page": "^3.0.1",
|
"@spectrum-css/page": "^3.0.1",
|
||||||
"@spectrum-css/vars": "^3.0.1",
|
"@spectrum-css/vars": "^3.0.1",
|
||||||
|
@ -98,9 +100,13 @@
|
||||||
"@testing-library/svelte": "^3.0.0",
|
"@testing-library/svelte": "^3.0.0",
|
||||||
"babel-jest": "^26.6.3",
|
"babel-jest": "^26.6.3",
|
||||||
"cypress": "^9.3.1",
|
"cypress": "^9.3.1",
|
||||||
|
"cypress-multi-reporters": "^1.6.0",
|
||||||
"cypress-terminal-report": "^1.4.1",
|
"cypress-terminal-report": "^1.4.1",
|
||||||
"identity-obj-proxy": "^3.0.0",
|
"identity-obj-proxy": "^3.0.0",
|
||||||
"jest": "^26.6.3",
|
"jest": "^26.6.3",
|
||||||
|
"mochawesome": "^7.1.3",
|
||||||
|
"mochawesome-merge": "^4.2.1",
|
||||||
|
"mochawesome-report-generator": "^6.2.0",
|
||||||
"ncp": "^2.0.0",
|
"ncp": "^2.0.0",
|
||||||
"rimraf": "^3.0.2",
|
"rimraf": "^3.0.2",
|
||||||
"rollup": "^2.44.0",
|
"rollup": "^2.44.0",
|
||||||
|
|
|
@ -0,0 +1,10 @@
|
||||||
|
{
|
||||||
|
"reporterEnabled": "mochawesome",
|
||||||
|
"mochawesomeReporterOptions": {
|
||||||
|
"reportDir": "cypress/reports",
|
||||||
|
"quiet": true,
|
||||||
|
"overwrite": false,
|
||||||
|
"html": false,
|
||||||
|
"json": true
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,130 @@
|
||||||
|
#!/usr/bin/env node
|
||||||
|
|
||||||
|
const fetch = require("node-fetch")
|
||||||
|
const path = require("path")
|
||||||
|
const fs = require("fs")
|
||||||
|
|
||||||
|
const WEBHOOK_URL = process.env.CYPRESS_WEBHOOK_URL
|
||||||
|
const OUTCOME = process.env.CYPRESS_OUTCOME
|
||||||
|
const DASHBOARD_URL = process.env.CYPRESS_DASHBOARD_URL
|
||||||
|
const GIT_SHA = process.env.GITHUB_SHA
|
||||||
|
const GITHUB_ACTIONS_RUN_URL = process.env.GITHUB_ACTIONS_RUN_URL
|
||||||
|
|
||||||
|
async function generateReport() {
|
||||||
|
// read the report file
|
||||||
|
const REPORT_PATH = path.resolve(
|
||||||
|
__dirname,
|
||||||
|
"..",
|
||||||
|
"cypress",
|
||||||
|
"reports",
|
||||||
|
"testReport.json"
|
||||||
|
)
|
||||||
|
const report = fs.readFileSync(REPORT_PATH, "utf-8")
|
||||||
|
return JSON.parse(report)
|
||||||
|
}
|
||||||
|
|
||||||
|
async function discordCypressResultsNotification(report) {
|
||||||
|
const {
|
||||||
|
suites,
|
||||||
|
tests,
|
||||||
|
passes,
|
||||||
|
pending,
|
||||||
|
failures,
|
||||||
|
duration,
|
||||||
|
passPercent,
|
||||||
|
skipped,
|
||||||
|
} = report.stats
|
||||||
|
|
||||||
|
const options = {
|
||||||
|
method: "POST",
|
||||||
|
headers: {
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
Accept: "application/json",
|
||||||
|
},
|
||||||
|
body: JSON.stringify({
|
||||||
|
content: `**Nightly Tests Status**: ${OUTCOME}`,
|
||||||
|
embeds: [
|
||||||
|
{
|
||||||
|
title: "Budi QA Bot",
|
||||||
|
description: `Nightly Tests`,
|
||||||
|
url: GITHUB_ACTIONS_RUN_URL,
|
||||||
|
color: OUTCOME === "success" ? 3066993 : 15548997,
|
||||||
|
timestamp: new Date(),
|
||||||
|
footer: {
|
||||||
|
icon_url: "http://bbui.budibase.com/budibase-logo.png",
|
||||||
|
text: "Budibase QA Bot",
|
||||||
|
},
|
||||||
|
thumbnail: {
|
||||||
|
url: "http://bbui.budibase.com/budibase-logo.png",
|
||||||
|
},
|
||||||
|
author: {
|
||||||
|
name: "Budibase QA Bot",
|
||||||
|
url: "https://discordapp.com",
|
||||||
|
icon_url: "http://bbui.budibase.com/budibase-logo.png",
|
||||||
|
},
|
||||||
|
fields: [
|
||||||
|
{
|
||||||
|
name: "Commit",
|
||||||
|
value: `https://github.com/Budibase/budibase/commit/${GIT_SHA}`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Cypress Dashboard URL",
|
||||||
|
value: DASHBOARD_URL || "None Supplied",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Github Actions Run URL",
|
||||||
|
value: GITHUB_ACTIONS_RUN_URL || "None Supplied",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Test Suites",
|
||||||
|
value: suites,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Tests",
|
||||||
|
value: tests,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Passed",
|
||||||
|
value: passes,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Pending",
|
||||||
|
value: pending,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Skipped",
|
||||||
|
value: skipped,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Failures",
|
||||||
|
value: failures,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Duration",
|
||||||
|
value: `${duration / 1000} Seconds`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Pass Percentage",
|
||||||
|
value: Math.floor(passPercent),
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
}),
|
||||||
|
}
|
||||||
|
const response = await fetch(WEBHOOK_URL, options)
|
||||||
|
|
||||||
|
if (response.status >= 400) {
|
||||||
|
const text = await response.text()
|
||||||
|
console.error(
|
||||||
|
`Error sending discord webhook. \nStatus: ${response.status}. \nResponse Body: ${text}. \nRequest Body: ${options.body}`
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function run() {
|
||||||
|
const report = await generateReport()
|
||||||
|
await discordCypressResultsNotification(report)
|
||||||
|
}
|
||||||
|
|
||||||
|
run()
|
|
@ -105,9 +105,7 @@ const automationActions = store => ({
|
||||||
},
|
},
|
||||||
select: automation => {
|
select: automation => {
|
||||||
store.update(state => {
|
store.update(state => {
|
||||||
let testResults = state.selectedAutomation?.testResults
|
|
||||||
state.selectedAutomation = new Automation(cloneDeep(automation))
|
state.selectedAutomation = new Automation(cloneDeep(automation))
|
||||||
state.selectedAutomation.testResults = testResults
|
|
||||||
state.selectedBlock = null
|
state.selectedBlock = null
|
||||||
return state
|
return state
|
||||||
})
|
})
|
||||||
|
|
|
@ -14,7 +14,6 @@
|
||||||
} from "@budibase/bbui"
|
} from "@budibase/bbui"
|
||||||
|
|
||||||
export let automation
|
export let automation
|
||||||
|
|
||||||
let testDataModal
|
let testDataModal
|
||||||
let blocks
|
let blocks
|
||||||
let confirmDeleteDialog
|
let confirmDeleteDialog
|
||||||
|
@ -41,12 +40,12 @@
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="canvas">
|
<div class="canvas">
|
||||||
<div class="content">
|
<div style="float: left; padding-left: var(--spacing-xl);">
|
||||||
<div class="title">
|
|
||||||
<div class="subtitle">
|
|
||||||
<Heading size="S">{automation.name}</Heading>
|
<Heading size="S">{automation.name}</Heading>
|
||||||
|
</div>
|
||||||
|
<div style="float: right; padding-right: var(--spacing-xl);" class="title">
|
||||||
|
<div class="subtitle">
|
||||||
<div style="display:flex; align-items: center;">
|
<div style="display:flex; align-items: center;">
|
||||||
<div class="iconPadding">
|
|
||||||
<div class="icon">
|
<div class="icon">
|
||||||
<Icon
|
<Icon
|
||||||
on:click={confirmDeleteDialog.show}
|
on:click={confirmDeleteDialog.show}
|
||||||
|
@ -55,7 +54,6 @@
|
||||||
name="DeleteOutline"
|
name="DeleteOutline"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
<ActionButton
|
<ActionButton
|
||||||
on:click={() => {
|
on:click={() => {
|
||||||
testDataModal.show()
|
testDataModal.show()
|
||||||
|
@ -63,44 +61,49 @@
|
||||||
icon="MultipleCheck"
|
icon="MultipleCheck"
|
||||||
size="M">Run test</ActionButton
|
size="M">Run test</ActionButton
|
||||||
>
|
>
|
||||||
|
<div style="padding-left: var(--spacing-m);">
|
||||||
|
<ActionButton
|
||||||
|
disabled={!$automationStore.selectedAutomation?.testResults}
|
||||||
|
on:click={() => {
|
||||||
|
$automationStore.selectedAutomation.automation.showTestPanel = true
|
||||||
|
}}
|
||||||
|
size="M">Test Details</ActionButton
|
||||||
|
>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="content">
|
||||||
{#each blocks as block, idx (block.id)}
|
{#each blocks as block, idx (block.id)}
|
||||||
<div
|
<div
|
||||||
class="block"
|
class="block"
|
||||||
animate:flip={{ duration: 500 }}
|
animate:flip={{ duration: 500 }}
|
||||||
in:fly|local={{ x: 500, duration: 1500 }}
|
in:fly|local={{ x: 500, duration: 500 }}
|
||||||
|
out:fly|local={{ x: 500, duration: 500 }}
|
||||||
>
|
>
|
||||||
{#if block.stepId !== "LOOP"}
|
{#if block.stepId !== "LOOP"}
|
||||||
<FlowItem {testDataModal} {block} />
|
<FlowItem {testDataModal} {block} />
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
{/each}
|
{/each}
|
||||||
</div>
|
</div>
|
||||||
<ConfirmDialog
|
<ConfirmDialog
|
||||||
bind:this={confirmDeleteDialog}
|
bind:this={confirmDeleteDialog}
|
||||||
okText="Delete Automation"
|
okText="Delete Automation"
|
||||||
onOk={deleteAutomation}
|
onOk={deleteAutomation}
|
||||||
title="Confirm Deletion"
|
title="Confirm Deletion"
|
||||||
>
|
>
|
||||||
Are you sure you wish to delete the automation
|
Are you sure you wish to delete the automation
|
||||||
<i>{automation.name}?</i>
|
<i>{automation.name}?</i>
|
||||||
This action cannot be undone.
|
This action cannot be undone.
|
||||||
</ConfirmDialog>
|
</ConfirmDialog>
|
||||||
|
|
||||||
<Modal bind:this={testDataModal} width="30%">
|
<Modal bind:this={testDataModal} width="30%">
|
||||||
<TestDataModal />
|
<TestDataModal />
|
||||||
</Modal>
|
</Modal>
|
||||||
</div>
|
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
.canvas {
|
|
||||||
margin: 0 -40px calc(-1 * var(--spacing-l)) -40px;
|
|
||||||
overflow-y: auto;
|
|
||||||
text-align: center;
|
|
||||||
height: 100%;
|
|
||||||
}
|
|
||||||
/* Fix for firefox not respecting bottom padding in scrolling containers */
|
/* Fix for firefox not respecting bottom padding in scrolling containers */
|
||||||
.canvas > *:last-child {
|
.canvas > *:last-child {
|
||||||
padding-bottom: 40px;
|
padding-bottom: 40px;
|
||||||
|
@ -128,10 +131,6 @@
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
}
|
}
|
||||||
.iconPadding {
|
|
||||||
padding-top: var(--spacing-s);
|
|
||||||
}
|
|
||||||
|
|
||||||
.icon {
|
.icon {
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
padding-right: var(--spacing-m);
|
padding-right: var(--spacing-m);
|
||||||
|
|
|
@ -1,40 +1,33 @@
|
||||||
<script>
|
<script>
|
||||||
|
import FlowItemHeader from "./FlowItemHeader.svelte"
|
||||||
|
|
||||||
import { automationStore } from "builderStore"
|
import { automationStore } from "builderStore"
|
||||||
import {
|
import {
|
||||||
Icon,
|
Icon,
|
||||||
Divider,
|
Divider,
|
||||||
Layout,
|
Layout,
|
||||||
Body,
|
|
||||||
Detail,
|
Detail,
|
||||||
Modal,
|
Modal,
|
||||||
Button,
|
Button,
|
||||||
StatusLight,
|
|
||||||
Select,
|
Select,
|
||||||
ActionButton,
|
ActionButton,
|
||||||
notifications,
|
notifications,
|
||||||
} from "@budibase/bbui"
|
} from "@budibase/bbui"
|
||||||
import AutomationBlockSetup from "../../SetupPanel/AutomationBlockSetup.svelte"
|
import AutomationBlockSetup from "../../SetupPanel/AutomationBlockSetup.svelte"
|
||||||
import CreateWebhookModal from "components/automation/Shared/CreateWebhookModal.svelte"
|
import CreateWebhookModal from "components/automation/Shared/CreateWebhookModal.svelte"
|
||||||
import ResultsModal from "./ResultsModal.svelte"
|
|
||||||
import ActionModal from "./ActionModal.svelte"
|
import ActionModal from "./ActionModal.svelte"
|
||||||
import { externalActions } from "./ExternalActions"
|
|
||||||
|
|
||||||
export let block
|
export let block
|
||||||
export let testDataModal
|
export let testDataModal
|
||||||
let selected
|
let selected
|
||||||
let webhookModal
|
let webhookModal
|
||||||
let actionModal
|
let actionModal
|
||||||
let resultsModal
|
|
||||||
let blockComplete
|
let blockComplete
|
||||||
let showLooping = false
|
let showLooping = false
|
||||||
|
|
||||||
$: rowControl = $automationStore.selectedAutomation.automation.rowControl
|
|
||||||
$: showBindingPicker =
|
$: showBindingPicker =
|
||||||
block.stepId === "CREATE_ROW" || block.stepId === "UPDATE_ROW"
|
block.stepId === "CREATE_ROW" || block.stepId === "UPDATE_ROW"
|
||||||
|
|
||||||
$: testResult = $automationStore.selectedAutomation.testResults?.steps.filter(
|
|
||||||
step => (block.id ? step.id === block.id : step.stepId === block.stepId)
|
|
||||||
)
|
|
||||||
$: isTrigger = block.type === "TRIGGER"
|
$: isTrigger = block.type === "TRIGGER"
|
||||||
|
|
||||||
$: selected = $automationStore.selectedBlock?.id === block.id
|
$: selected = $automationStore.selectedBlock?.id === block.id
|
||||||
|
@ -182,63 +175,7 @@
|
||||||
{/if}
|
{/if}
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
<div class="blockSection">
|
<FlowItemHeader bind:blockComplete {block} {testDataModal} />
|
||||||
<div
|
|
||||||
on:click={() => {
|
|
||||||
blockComplete = !blockComplete
|
|
||||||
}}
|
|
||||||
class="splitHeader"
|
|
||||||
>
|
|
||||||
<div class="center-items">
|
|
||||||
{#if externalActions[block.stepId]}
|
|
||||||
<img
|
|
||||||
alt={externalActions[block.stepId].name}
|
|
||||||
width="28px"
|
|
||||||
height="28px"
|
|
||||||
src={externalActions[block.stepId].icon}
|
|
||||||
/>
|
|
||||||
{:else}
|
|
||||||
<svg
|
|
||||||
width="28px"
|
|
||||||
height="28px"
|
|
||||||
class="spectrum-Icon"
|
|
||||||
style="color:grey;"
|
|
||||||
focusable="false"
|
|
||||||
>
|
|
||||||
<use xlink:href="#spectrum-icon-18-{block.icon}" />
|
|
||||||
</svg>
|
|
||||||
{/if}
|
|
||||||
<div class="iconAlign">
|
|
||||||
{#if isTrigger}
|
|
||||||
<Body size="XS">When this happens:</Body>
|
|
||||||
{:else}
|
|
||||||
<Body size="XS">Do this:</Body>
|
|
||||||
{/if}
|
|
||||||
|
|
||||||
<Detail size="S">{block?.name?.toUpperCase() || ""}</Detail>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="blockTitle">
|
|
||||||
{#if testResult && testResult[0]}
|
|
||||||
<div style="float: right;" on:click={() => resultsModal.show()}>
|
|
||||||
<StatusLight
|
|
||||||
positive={isTrigger || testResult[0].outputs?.success}
|
|
||||||
negative={!testResult[0].outputs?.success}
|
|
||||||
><Body size="XS">View response</Body></StatusLight
|
|
||||||
>
|
|
||||||
</div>
|
|
||||||
{/if}
|
|
||||||
<div
|
|
||||||
style="margin-left: 10px;"
|
|
||||||
on:click={() => {
|
|
||||||
onSelect(block)
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<Icon name={blockComplete ? "ChevronDown" : "ChevronUp"} />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{#if !blockComplete}
|
{#if !blockComplete}
|
||||||
<Divider noMargin />
|
<Divider noMargin />
|
||||||
<div class="blockSection">
|
<div class="blockSection">
|
||||||
|
@ -256,7 +193,7 @@
|
||||||
on:change={toggleFieldControl}
|
on:change={toggleFieldControl}
|
||||||
defaultValue="Use values"
|
defaultValue="Use values"
|
||||||
autoWidth
|
autoWidth
|
||||||
value={rowControl ? "Use bindings" : "Use values"}
|
value={block.rowControl ? "Use bindings" : "Use values"}
|
||||||
options={["Use values", "Use bindings"]}
|
options={["Use values", "Use bindings"]}
|
||||||
placeholder={null}
|
placeholder={null}
|
||||||
/>
|
/>
|
||||||
|
@ -283,10 +220,6 @@
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
<Modal bind:this={resultsModal} width="30%">
|
|
||||||
<ResultsModal {isTrigger} {testResult} />
|
|
||||||
</Modal>
|
|
||||||
|
|
||||||
<Modal bind:this={actionModal} width="30%">
|
<Modal bind:this={actionModal} width="30%">
|
||||||
<ActionModal {blockIdx} bind:blockComplete />
|
<ActionModal {blockIdx} bind:blockComplete />
|
||||||
</Modal>
|
</Modal>
|
||||||
|
|
|
@ -0,0 +1,111 @@
|
||||||
|
<script>
|
||||||
|
import { automationStore } from "builderStore"
|
||||||
|
import { Icon, Body, Detail, StatusLight } from "@budibase/bbui"
|
||||||
|
import { externalActions } from "./ExternalActions"
|
||||||
|
|
||||||
|
export let block
|
||||||
|
export let blockComplete
|
||||||
|
export let showTestStatus = false
|
||||||
|
export let showParameters = {}
|
||||||
|
|
||||||
|
$: testResult =
|
||||||
|
$automationStore.selectedAutomation?.testResults?.steps.filter(step =>
|
||||||
|
block.id ? step.id === block.id : step.stepId === block.stepId
|
||||||
|
)
|
||||||
|
$: isTrigger = block.type === "TRIGGER"
|
||||||
|
|
||||||
|
async function onSelect(block) {
|
||||||
|
await automationStore.update(state => {
|
||||||
|
state.selectedBlock = block
|
||||||
|
return state
|
||||||
|
})
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div class="blockSection">
|
||||||
|
<div
|
||||||
|
on:click={() => {
|
||||||
|
blockComplete = !blockComplete
|
||||||
|
showParameters[block.id] = blockComplete
|
||||||
|
}}
|
||||||
|
class="splitHeader"
|
||||||
|
>
|
||||||
|
<div class="center-items">
|
||||||
|
{#if externalActions[block.stepId]}
|
||||||
|
<img
|
||||||
|
alt={externalActions[block.stepId].name}
|
||||||
|
width="28px"
|
||||||
|
height="28px"
|
||||||
|
src={externalActions[block.stepId].icon}
|
||||||
|
/>
|
||||||
|
{:else}
|
||||||
|
<svg
|
||||||
|
width="28px"
|
||||||
|
height="28px"
|
||||||
|
class="spectrum-Icon"
|
||||||
|
style="color:grey;"
|
||||||
|
focusable="false"
|
||||||
|
>
|
||||||
|
<use xlink:href="#spectrum-icon-18-{block.icon}" />
|
||||||
|
</svg>
|
||||||
|
{/if}
|
||||||
|
<div class="iconAlign">
|
||||||
|
{#if isTrigger}
|
||||||
|
<Body size="XS">When this happens:</Body>
|
||||||
|
{:else}
|
||||||
|
<Body size="XS">Do this:</Body>
|
||||||
|
{/if}
|
||||||
|
|
||||||
|
<Detail size="S">{block?.name?.toUpperCase() || ""}</Detail>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="blockTitle">
|
||||||
|
{#if showTestStatus && testResult && testResult[0]}
|
||||||
|
<div style="float: right;">
|
||||||
|
<StatusLight
|
||||||
|
positive={isTrigger || testResult[0].outputs?.success}
|
||||||
|
negative={!testResult[0].outputs?.success}
|
||||||
|
><Body size="XS"
|
||||||
|
>{testResult[0].outputs?.success || isTrigger
|
||||||
|
? "Success"
|
||||||
|
: "Error"}</Body
|
||||||
|
></StatusLight
|
||||||
|
>
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
<div
|
||||||
|
style="margin-left: 10px; margin-bottom: var(--spacing-xs);"
|
||||||
|
on:click={() => {
|
||||||
|
onSelect(block)
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Icon name={blockComplete ? "ChevronUp" : "ChevronDown"} />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.center-items {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
.splitHeader {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
.iconAlign {
|
||||||
|
padding: 0 0 0 var(--spacing-m);
|
||||||
|
display: inline-block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.blockSection {
|
||||||
|
padding: var(--spacing-xl);
|
||||||
|
}
|
||||||
|
|
||||||
|
.blockTitle {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -1,133 +0,0 @@
|
||||||
<script>
|
|
||||||
import { ModalContent, Icon, Detail, TextArea, Label } from "@budibase/bbui"
|
|
||||||
|
|
||||||
export let testResult
|
|
||||||
export let isTrigger
|
|
||||||
let inputToggled
|
|
||||||
let outputToggled
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<ModalContent
|
|
||||||
showCloseIcon={false}
|
|
||||||
showConfirmButton={false}
|
|
||||||
cancelText="Close"
|
|
||||||
>
|
|
||||||
<div slot="header" class="result-modal-header">
|
|
||||||
<span>Test Results</span>
|
|
||||||
<div>
|
|
||||||
{#if isTrigger || testResult[0].outputs.success}
|
|
||||||
<div class="iconSuccess">
|
|
||||||
<Icon size="S" name="CheckmarkCircle" />
|
|
||||||
</div>
|
|
||||||
{:else}
|
|
||||||
<div class="iconFailure">
|
|
||||||
<Icon size="S" name="CloseCircle" />
|
|
||||||
</div>
|
|
||||||
{/if}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<span>
|
|
||||||
{#if testResult[0].outputs.iterations}
|
|
||||||
<div style="display: flex;">
|
|
||||||
<Icon name="Reuse" />
|
|
||||||
<div style="margin-left: 10px;">
|
|
||||||
<Label>
|
|
||||||
This loop ran {testResult[0].outputs.iterations} times.</Label
|
|
||||||
>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{/if}
|
|
||||||
</span>
|
|
||||||
<div
|
|
||||||
on:click={() => {
|
|
||||||
inputToggled = !inputToggled
|
|
||||||
}}
|
|
||||||
class="toggle splitHeader"
|
|
||||||
>
|
|
||||||
<div>
|
|
||||||
<div style="display: flex; align-items: center;">
|
|
||||||
<span style="padding-left: var(--spacing-s);">
|
|
||||||
<Detail size="S">Input</Detail>
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
{#if inputToggled}
|
|
||||||
<Icon size="M" name="ChevronDown" />
|
|
||||||
{:else}
|
|
||||||
<Icon size="M" name="ChevronRight" />
|
|
||||||
{/if}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{#if inputToggled}
|
|
||||||
<div class="text-area-container">
|
|
||||||
<TextArea
|
|
||||||
disabled
|
|
||||||
value={JSON.stringify(testResult[0].inputs, null, 2)}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
{/if}
|
|
||||||
|
|
||||||
<div
|
|
||||||
on:click={() => {
|
|
||||||
outputToggled = !outputToggled
|
|
||||||
}}
|
|
||||||
class="toggle splitHeader"
|
|
||||||
>
|
|
||||||
<div>
|
|
||||||
<div style="display: flex; align-items: center;">
|
|
||||||
<span style="padding-left: var(--spacing-s);">
|
|
||||||
<Detail size="S">Output</Detail>
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
{#if outputToggled}
|
|
||||||
<Icon size="M" name="ChevronDown" />
|
|
||||||
{:else}
|
|
||||||
<Icon size="M" name="ChevronRight" />
|
|
||||||
{/if}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{#if outputToggled}
|
|
||||||
<div class="text-area-container">
|
|
||||||
<TextArea
|
|
||||||
disabled
|
|
||||||
value={JSON.stringify(testResult[0].outputs, null, 2)}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
{/if}
|
|
||||||
</ModalContent>
|
|
||||||
|
|
||||||
<style>
|
|
||||||
.result-modal-header {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: row;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: space-between;
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.iconSuccess {
|
|
||||||
color: var(--spectrum-global-color-green-600);
|
|
||||||
}
|
|
||||||
|
|
||||||
.iconFailure {
|
|
||||||
color: var(--spectrum-global-color-red-600);
|
|
||||||
}
|
|
||||||
|
|
||||||
.splitHeader {
|
|
||||||
cursor: pointer;
|
|
||||||
display: flex;
|
|
||||||
justify-content: space-between;
|
|
||||||
}
|
|
||||||
|
|
||||||
.toggle {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.text-area-container :global(textarea) {
|
|
||||||
height: 150px;
|
|
||||||
}
|
|
||||||
</style>
|
|
|
@ -51,6 +51,7 @@
|
||||||
$automationStore.selectedAutomation?.automation,
|
$automationStore.selectedAutomation?.automation,
|
||||||
testData
|
testData
|
||||||
)
|
)
|
||||||
|
$automationStore.selectedAutomation.automation.showTestPanel = true
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
notifications.error("Error testing notification")
|
notifications.error("Error testing notification")
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,146 @@
|
||||||
|
<script>
|
||||||
|
import { Icon, Divider, Tabs, Tab, TextArea, Label } from "@budibase/bbui"
|
||||||
|
import FlowItemHeader from "./FlowChart/FlowItemHeader.svelte"
|
||||||
|
import { automationStore } from "builderStore"
|
||||||
|
|
||||||
|
export let automation
|
||||||
|
|
||||||
|
let showParameters
|
||||||
|
let blocks
|
||||||
|
|
||||||
|
$: {
|
||||||
|
blocks = []
|
||||||
|
if (automation) {
|
||||||
|
if (automation.definition.trigger) {
|
||||||
|
blocks.push(automation.definition.trigger)
|
||||||
|
}
|
||||||
|
blocks = blocks
|
||||||
|
.concat(automation.definition.steps || [])
|
||||||
|
.filter(x => x.stepId !== "LOOP")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$: testResults =
|
||||||
|
$automationStore.selectedAutomation?.testResults?.steps.filter(
|
||||||
|
x => x.stepId !== "LOOP" || []
|
||||||
|
)
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div class="title">
|
||||||
|
<div class="title-text">
|
||||||
|
<Icon name="MultipleCheck" />
|
||||||
|
<div style="padding-left: var(--spacing-l)">Test Details</div>
|
||||||
|
</div>
|
||||||
|
<div style="padding-right: var(--spacing-xl)">
|
||||||
|
<Icon
|
||||||
|
on:click={async () => {
|
||||||
|
$automationStore.selectedAutomation.automation.showTestPanel = false
|
||||||
|
}}
|
||||||
|
hoverable
|
||||||
|
name="Close"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<Divider />
|
||||||
|
|
||||||
|
<div class="container">
|
||||||
|
{#each blocks as block, idx}
|
||||||
|
<div class="block">
|
||||||
|
{#if block.stepId !== "LOOP"}
|
||||||
|
<FlowItemHeader showTestStatus={true} bind:showParameters {block} />
|
||||||
|
{#if showParameters && showParameters[block.id]}
|
||||||
|
<Divider noMargin />
|
||||||
|
{#if testResults?.[idx]?.outputs.iterations}
|
||||||
|
<div style="display: flex; padding: 10px 10px 0px 12px;">
|
||||||
|
<Icon name="Reuse" />
|
||||||
|
<div style="margin-left: 10px;">
|
||||||
|
<Label>
|
||||||
|
This loop ran {testResults?.[idx]?.outputs.iterations} times.</Label
|
||||||
|
>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
|
||||||
|
<div class="tabs">
|
||||||
|
<Tabs quiet noPadding selected="Input">
|
||||||
|
<Tab title="Input">
|
||||||
|
<div style="padding: 10px 10px 10px 10px;">
|
||||||
|
<TextArea
|
||||||
|
minHeight="80px"
|
||||||
|
disabled
|
||||||
|
value={JSON.stringify(testResults?.[idx]?.inputs, null, 2)}
|
||||||
|
/>
|
||||||
|
</div></Tab
|
||||||
|
>
|
||||||
|
<Tab title="Output">
|
||||||
|
<div style="padding: 10px 10px 10px 10px;">
|
||||||
|
<TextArea
|
||||||
|
minHeight="100px"
|
||||||
|
disabled
|
||||||
|
value={JSON.stringify(testResults?.[idx]?.outputs, null, 2)}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</Tab>
|
||||||
|
</Tabs>
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
|
{#if blocks.length - 1 !== idx}
|
||||||
|
<div class="separator" />
|
||||||
|
{/if}
|
||||||
|
{/each}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.container {
|
||||||
|
padding: 0px 30px 0px 30px;
|
||||||
|
}
|
||||||
|
.title {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
align-items: center;
|
||||||
|
gap: var(--spacing-xs);
|
||||||
|
padding-left: var(--spacing-xl);
|
||||||
|
justify-content: space-between;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tabs {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
justify-content: flex-start;
|
||||||
|
align-items: stretch;
|
||||||
|
position: relative;
|
||||||
|
flex: 1 1 auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.title-text {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.title :global(h1) {
|
||||||
|
flex: 1 1 auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.block {
|
||||||
|
display: inline-block;
|
||||||
|
width: 400px;
|
||||||
|
font-size: 16px;
|
||||||
|
background-color: var(--background);
|
||||||
|
border: 1px solid var(--spectrum-global-color-gray-300);
|
||||||
|
border-radius: 4px 4px 4px 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.separator {
|
||||||
|
width: 1px;
|
||||||
|
height: 40px;
|
||||||
|
border-left: 1px dashed var(--grey-4);
|
||||||
|
color: var(--grey-4);
|
||||||
|
/* center horizontally */
|
||||||
|
text-align: center;
|
||||||
|
margin-left: 50%;
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -53,6 +53,7 @@
|
||||||
: { schema: {} }
|
: { schema: {} }
|
||||||
$: schema = getSchemaForTable(tableId, { searchableSchema: true }).schema
|
$: schema = getSchemaForTable(tableId, { searchableSchema: true }).schema
|
||||||
$: schemaFields = Object.values(schema || {})
|
$: schemaFields = Object.values(schema || {})
|
||||||
|
$: queryLimit = tableId?.includes("datasource") ? "∞" : "1000"
|
||||||
|
|
||||||
const onChange = Utils.sequential(async (e, key) => {
|
const onChange = Utils.sequential(async (e, key) => {
|
||||||
try {
|
try {
|
||||||
|
@ -330,6 +331,7 @@
|
||||||
on:change={e => onChange(e, key)}
|
on:change={e => onChange(e, key)}
|
||||||
{bindings}
|
{bindings}
|
||||||
updateOnChange={false}
|
updateOnChange={false}
|
||||||
|
placeholder={value.customType === "queryLimit" ? queryLimit : ""}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
|
|
|
@ -12,4 +12,4 @@
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<CopyInput {value} copyValue={fullWebhookURL(value)} />
|
<CopyInput value={fullWebhookURL(value)} />
|
||||||
|
|
|
@ -60,7 +60,7 @@ export function getBindings({
|
||||||
)
|
)
|
||||||
|
|
||||||
const label = path == null ? column : `${path}.0.${column}`
|
const label = path == null ? column : `${path}.0.${column}`
|
||||||
const binding = path == null ? `[${column}]` : `${path}.0.[${column}]`
|
const binding = path == null ? `[${column}]` : `[${path}].0.[${column}]`
|
||||||
// only supply a description for relationship paths
|
// only supply a description for relationship paths
|
||||||
const description =
|
const description =
|
||||||
path == null
|
path == null
|
||||||
|
|
|
@ -0,0 +1,144 @@
|
||||||
|
<script>
|
||||||
|
import {
|
||||||
|
Button,
|
||||||
|
ButtonGroup,
|
||||||
|
ModalContent,
|
||||||
|
Modal,
|
||||||
|
notifications,
|
||||||
|
ProgressCircle,
|
||||||
|
} from "@budibase/bbui"
|
||||||
|
import { auth, apps } from "stores/portal"
|
||||||
|
import { processStringSync } from "@budibase/string-templates"
|
||||||
|
import { API } from "api"
|
||||||
|
|
||||||
|
export let app
|
||||||
|
export let buttonSize = "M"
|
||||||
|
|
||||||
|
let APP_DEV_LOCK_SECONDS = 600 //common area for this?
|
||||||
|
let appLockModal
|
||||||
|
let processing = false
|
||||||
|
|
||||||
|
$: lockedBy = app?.lockedBy
|
||||||
|
$: lockedByYou = $auth.user.email === lockedBy?.email
|
||||||
|
|
||||||
|
$: lockIdentifer = `${
|
||||||
|
lockedBy && lockedBy.firstName ? lockedBy?.firstName : lockedBy?.email
|
||||||
|
}`
|
||||||
|
|
||||||
|
$: lockedByHeading =
|
||||||
|
lockedBy && lockedByYou ? "Locked by you" : `Locked by ${lockIdentifer}`
|
||||||
|
|
||||||
|
const getExpiryDuration = app => {
|
||||||
|
if (!app?.lockedBy?.lockedAt) {
|
||||||
|
return -1
|
||||||
|
}
|
||||||
|
let expiry =
|
||||||
|
new Date(app.lockedBy.lockedAt).getTime() + APP_DEV_LOCK_SECONDS * 1000
|
||||||
|
return expiry - new Date().getTime()
|
||||||
|
}
|
||||||
|
|
||||||
|
const releaseLock = async () => {
|
||||||
|
processing = true
|
||||||
|
if (app) {
|
||||||
|
try {
|
||||||
|
await API.releaseAppLock(app.devId)
|
||||||
|
await apps.load()
|
||||||
|
notifications.success("Lock released successfully")
|
||||||
|
} catch (err) {
|
||||||
|
notifications.error("Error releasing lock")
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
notifications.error("No application is selected")
|
||||||
|
}
|
||||||
|
processing = false
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div class="lock-status">
|
||||||
|
{#if lockedBy}
|
||||||
|
<Button
|
||||||
|
quiet
|
||||||
|
secondary
|
||||||
|
icon="LockClosed"
|
||||||
|
size={buttonSize}
|
||||||
|
on:click={() => {
|
||||||
|
appLockModal.show()
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<span class="lock-status-text">
|
||||||
|
{lockedByHeading}
|
||||||
|
</span>
|
||||||
|
</Button>
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<Modal bind:this={appLockModal}>
|
||||||
|
<ModalContent
|
||||||
|
title={lockedByHeading}
|
||||||
|
dataCy={"app-lock-modal"}
|
||||||
|
showConfirmButton={false}
|
||||||
|
showCancelButton={false}
|
||||||
|
>
|
||||||
|
<p>
|
||||||
|
Apps are locked to prevent work from being lost from overlapping changes
|
||||||
|
between your team.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
{#if lockedByYou && getExpiryDuration(app) > 0}
|
||||||
|
<span class="lock-expiry-body">
|
||||||
|
{processStringSync(
|
||||||
|
"This lock will expire in {{ duration time 'millisecond' }} from now",
|
||||||
|
{
|
||||||
|
time: getExpiryDuration(app),
|
||||||
|
}
|
||||||
|
)}
|
||||||
|
</span>
|
||||||
|
{/if}
|
||||||
|
<div class="lock-modal-actions">
|
||||||
|
<ButtonGroup>
|
||||||
|
<Button
|
||||||
|
secondary
|
||||||
|
quiet={lockedBy && lockedByYou}
|
||||||
|
disabled={processing}
|
||||||
|
on:click={() => {
|
||||||
|
appLockModal.hide()
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<span class="cancel"
|
||||||
|
>{lockedBy && !lockedByYou ? "Done" : "Cancel"}</span
|
||||||
|
>
|
||||||
|
</Button>
|
||||||
|
{#if lockedByYou}
|
||||||
|
<Button
|
||||||
|
secondary
|
||||||
|
disabled={processing}
|
||||||
|
on:click={() => {
|
||||||
|
releaseLock()
|
||||||
|
appLockModal.hide()
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{#if processing}
|
||||||
|
<ProgressCircle overBackground={true} size="S" />
|
||||||
|
{:else}
|
||||||
|
<span class="unlock">Release Lock</span>
|
||||||
|
{/if}
|
||||||
|
</Button>
|
||||||
|
{/if}
|
||||||
|
</ButtonGroup>
|
||||||
|
</div>
|
||||||
|
</ModalContent>
|
||||||
|
</Modal>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.lock-modal-actions {
|
||||||
|
display: flex;
|
||||||
|
justify-content: flex-end;
|
||||||
|
margin-top: var(--spacing-l);
|
||||||
|
gap: var(--spacing-xl);
|
||||||
|
}
|
||||||
|
.lock-status {
|
||||||
|
display: flex;
|
||||||
|
gap: var(--spacing-s);
|
||||||
|
max-width: 175px;
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -0,0 +1,55 @@
|
||||||
|
<script>
|
||||||
|
import { Icon, Detail } from "@budibase/bbui"
|
||||||
|
|
||||||
|
export let title = ""
|
||||||
|
export let actionIcon
|
||||||
|
export let action
|
||||||
|
export let dataCy
|
||||||
|
|
||||||
|
$: actionDefined = typeof action === "function"
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div class="dash-card" data-cy={dataCy}>
|
||||||
|
<div class="dash-card-header" class:active={actionDefined} on:click={action}>
|
||||||
|
<span class="dash-card-title">
|
||||||
|
<Detail size="M">{title}</Detail>
|
||||||
|
</span>
|
||||||
|
<span class="dash-card-action">
|
||||||
|
{#if actionDefined}
|
||||||
|
<Icon name={actionIcon || "ChevronRight"} />
|
||||||
|
{/if}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div class="dash-card-body">
|
||||||
|
<slot />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.dash-card {
|
||||||
|
background: var(--spectrum-alias-background-color-primary);
|
||||||
|
border-radius: var(--border-radius-s);
|
||||||
|
overflow: hidden;
|
||||||
|
min-height: 150px;
|
||||||
|
}
|
||||||
|
.dash-card-header {
|
||||||
|
padding: var(--spacing-xl) var(--spectrum-global-dimension-static-size-400);
|
||||||
|
border-bottom: 1px solid var(--spectrum-global-color-gray-300);
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
}
|
||||||
|
.dash-card-body {
|
||||||
|
padding: var(--spacing-xl) calc(var(--spacing-xl) * 2);
|
||||||
|
}
|
||||||
|
.dash-card-title :global(.spectrum-Detail) {
|
||||||
|
color: var(
|
||||||
|
--spectrum-sidenav-heading-text-color,
|
||||||
|
var(--spectrum-global-color-gray-700)
|
||||||
|
);
|
||||||
|
display: inline-block;
|
||||||
|
}
|
||||||
|
.dash-card-header.active:hover {
|
||||||
|
background-color: var(--spectrum-global-color-gray-200);
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -0,0 +1,47 @@
|
||||||
|
<script>
|
||||||
|
import { Icon } from "@budibase/bbui"
|
||||||
|
import ChooseIconModal from "components/start/ChooseIconModal.svelte"
|
||||||
|
|
||||||
|
export let name
|
||||||
|
export let size
|
||||||
|
export let app
|
||||||
|
|
||||||
|
let iconModal
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div class="editable-icon">
|
||||||
|
<div
|
||||||
|
class="edit-hover"
|
||||||
|
on:click={() => {
|
||||||
|
iconModal.show()
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Icon name={"Edit"} size={"L"} />
|
||||||
|
</div>
|
||||||
|
<div class="app-icon">
|
||||||
|
<Icon {name} {size} />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<ChooseIconModal {app} bind:this={iconModal} />
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.editable-icon:hover .app-icon {
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
.editable-icon {
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
.editable-icon:hover .edit-hover {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
.edit-hover {
|
||||||
|
color: var(--spectrum-global-color-gray-600);
|
||||||
|
cursor: pointer;
|
||||||
|
z-index: 100;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
position: absolute;
|
||||||
|
opacity: 0;
|
||||||
|
/* transition: opacity var(--spectrum-global-animation-duration-100) ease; */
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -3,7 +3,6 @@
|
||||||
|
|
||||||
export let label = null
|
export let label = null
|
||||||
export let value
|
export let value
|
||||||
export let copyValue
|
|
||||||
export let dataCy = null
|
export let dataCy = null
|
||||||
|
|
||||||
const copyToClipboard = val => {
|
const copyToClipboard = val => {
|
||||||
|
@ -19,7 +18,7 @@
|
||||||
|
|
||||||
<div data-cy={dataCy}>
|
<div data-cy={dataCy}>
|
||||||
<Input readonly {value} {label} />
|
<Input readonly {value} {label} />
|
||||||
<div class="icon" on:click={() => copyToClipboard(value || copyValue)}>
|
<div class="icon" on:click={() => copyToClipboard(value)}>
|
||||||
<Icon size="S" name="Copy" />
|
<Icon size="S" name="Copy" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue