Browse Source

Merge branch 'develop' into feature/posthog-v2

master
Rory Powell 4 years ago
parent
commit
64d73e2f96
  1. 53
      .github/workflows/release-develop.yml
  2. 2
      lerna.json
  3. 1
      package.json
  4. 3
      packages/backend-core/cache.js
  5. 4
      packages/backend-core/package.json
  6. 2
      packages/backend-core/src/auth.js
  7. 49
      packages/backend-core/src/cache/generic.js
  8. 12
      packages/backend-core/src/context/index.js
  9. 9
      packages/backend-core/src/db/index.js
  10. 8
      packages/backend-core/src/db/pouch.js
  11. 1
      packages/backend-core/src/environment.js
  12. 4
      packages/backend-core/src/errors/http.js
  13. 2
      packages/backend-core/src/hashing.js
  14. 2
      packages/backend-core/src/middleware/passport/local.js
  15. 10
      packages/backend-core/src/redis/authRedis.js
  16. 1
      packages/backend-core/src/redis/utils.js
  17. 45
      packages/backend-core/src/security/sessions.js
  18. 324
      packages/backend-core/yarn.lock
  19. 4
      packages/bbui/package.json
  20. 8
      packages/bbui/src/Form/Core/Dropzone.svelte
  21. 2
      packages/bbui/src/Form/Dropzone.svelte
  22. 8
      packages/bbui/src/Layout/Layout.svelte
  23. 8
      packages/bbui/src/Layout/Page.svelte
  24. 2
      packages/bbui/src/SideNavigation/Item.svelte
  25. 24
      packages/bbui/src/Tabs/Tab.svelte
  26. 346
      packages/builder/cypress/integration/appOverview.spec.js
  27. 24
      packages/builder/cypress/integration/appPublishWorkflow.spec.js
  28. 20
      packages/builder/cypress/integration/renameAnApplication.spec.js
  29. 8
      packages/builder/cypress/integration/templates/HR/hrTemplateDetails.spec.js
  30. 19
      packages/builder/cypress/integration/templates/HR/jobApplicationTracker.spec.js
  31. 8
      packages/builder/cypress/integration/templates/IT/ITTemplateDetails.spec.js
  32. 19
      packages/builder/cypress/integration/templates/IT/ITTicketingSystem.spec.js
  33. 8
      packages/builder/cypress/integration/templates/adminPanels/adminPanelsTemplateDetails.spec.js
  34. 8
      packages/builder/cypress/integration/templates/approvalApps/approvalAppsTemplateDetails.spec.js
  35. 10
      packages/builder/cypress/integration/templates/businessApps/businessAppsTemplateDetails.spec.js
  36. 8
      packages/builder/cypress/integration/templates/directories/directoriesTemplateDetails.spec.js
  37. 8
      packages/builder/cypress/integration/templates/forms/formsTemplateDetails.spec.js
  38. 8
      packages/builder/cypress/integration/templates/healthcare/healthcareTemplateDetails.spec.js
  39. 8
      packages/builder/cypress/integration/templates/legal/legalTemplateDetails.spec.js
  40. 8
      packages/builder/cypress/integration/templates/logistics/logisticsTemplateDetails.spec.js
  41. 8
      packages/builder/cypress/integration/templates/manufacturing/manufacturingTemplateDetails.spec.js
  42. 44
      packages/builder/cypress/integration/templates/marketing/leadGenerationForm.spec.js
  43. 8
      packages/builder/cypress/integration/templates/marketing/marketingTemplateDetails.spec.js
  44. 8
      packages/builder/cypress/integration/templates/operations/operationsTemplateDetails.spec.js
  45. 18
      packages/builder/cypress/integration/templates/portals/portalsTemplateDetails.spec.js
  46. 8
      packages/builder/cypress/integration/templates/professionalServices/professionalServicesTemplateDetails.spec.js
  47. 141
      packages/builder/cypress/support/commands.js
  48. 10
      packages/builder/package.json
  49. 2
      packages/builder/src/builderStore/store/automation/index.js
  50. 107
      packages/builder/src/components/automation/AutomationBuilder/FlowChart/FlowChart.svelte
  51. 75
      packages/builder/src/components/automation/AutomationBuilder/FlowChart/FlowItem.svelte
  52. 111
      packages/builder/src/components/automation/AutomationBuilder/FlowChart/FlowItemHeader.svelte
  53. 133
      packages/builder/src/components/automation/AutomationBuilder/FlowChart/ResultsModal.svelte
  54. 1
      packages/builder/src/components/automation/AutomationBuilder/FlowChart/TestDataModal.svelte
  55. 146
      packages/builder/src/components/automation/AutomationBuilder/TestPanel.svelte
  56. 4
      packages/builder/src/components/backend/DataTable/modals/CreateEditColumn.svelte
  57. 2
      packages/builder/src/components/backend/TableNavigator/modals/CreateTableModal.svelte
  58. 144
      packages/builder/src/components/common/AppLockModal.svelte
  59. 55
      packages/builder/src/components/common/DashCard.svelte
  60. 47
      packages/builder/src/components/common/EditableIcon.svelte
  61. 18
      packages/builder/src/components/deploy/VersionModal.svelte
  62. 48
      packages/builder/src/components/start/AppRow.svelte
  63. 35
      packages/builder/src/pages/builder/app/[application]/automate/_layout.svelte
  64. 6
      packages/builder/src/pages/builder/auth/login.svelte
  65. 6
      packages/builder/src/pages/builder/portal/_layout.svelte
  66. 8
      packages/builder/src/pages/builder/portal/apps/create.svelte
  67. 98
      packages/builder/src/pages/builder/portal/apps/index.svelte
  68. 9
      packages/builder/src/pages/builder/portal/apps/templates.svelte
  69. 413
      packages/builder/src/pages/builder/portal/overview/[application]/index.svelte
  70. 11
      packages/builder/src/pages/builder/portal/overview/_components/AutomationTab.svelte
  71. 250
      packages/builder/src/pages/builder/portal/overview/_components/OverviewTab.svelte
  72. 131
      packages/builder/src/pages/builder/portal/overview/_components/SettingsTab.svelte
  73. 2
      packages/cli/package.json
  74. 8
      packages/client/manifest.json
  75. 8
      packages/client/package.json
  76. 9
      packages/client/src/components/app/forms/AttachmentField.svelte
  77. 4
      packages/client/src/components/app/table/Table.svelte
  78. 66
      packages/client/src/utils/schema.js
  79. 4
      packages/frontend-core/package.json
  80. 25
      packages/frontend-core/src/fetch/DataFetch.js
  81. 14
      packages/server/Dockerfile
  82. 4
      packages/server/__mocks__/pg.ts
  83. 7
      packages/server/docker_run.sh
  84. 11
      packages/server/package.json
  85. 9
      packages/server/pm2.config.js
  86. 11
      packages/server/src/api/controllers/application.ts
  87. 27
      packages/server/src/api/controllers/public/users.ts
  88. 1
      packages/server/src/automations/steps/sendSmtpEmail.js
  89. 7
      packages/server/src/environment.js
  90. 49
      packages/server/src/integrations/postgres.ts
  91. 15
      packages/server/src/middleware/builder.js
  92. 2
      packages/server/src/threads/automation.js
  93. 15
      packages/server/src/utilities/bcrypt.js
  94. 2
      packages/server/src/utilities/redis.js
  95. 21
      packages/server/src/utilities/users.js
  96. 181
      packages/server/yarn.lock
  97. 2
      packages/string-templates/package.json
  98. 8
      packages/worker/Dockerfile
  99. 7
      packages/worker/docker_run.sh
  100. 9
      packages/worker/package.json

53
.github/workflows/release-develop.yml

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

2
lerna.json

@ -1,5 +1,5 @@
{
"version": "1.0.167-alpha.8",
"version": "1.0.189",
"npmClient": "yarn",
"packages": [
"packages/*"

1
package.json

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

3
packages/backend-core/cache.js

@ -1,4 +1,7 @@
const generic = require("./src/cache/generic")
module.exports = {
user: require("./src/cache/user"),
app: require("./src/cache/appMetadata"),
...generic,
}

4
packages/backend-core/package.json

@ -1,6 +1,6 @@
{
"name": "@budibase/backend-core",
"version": "1.0.167-alpha.8",
"version": "1.0.189",
"description": "Budibase backend core libraries used in server and worker",
"main": "src/index.js",
"types": "dist/src/index.d.ts",
@ -14,7 +14,7 @@
"dependencies": {
"@techpass/passport-openidconnect": "^0.3.0",
"aws-sdk": "^2.901.0",
"bcryptjs": "^2.4.3",
"bcrypt": "^5.0.1",
"dotenv": "^16.0.1",
"emitter-listener": "^1.1.2",
"ioredis": "^4.27.1",

2
packages/backend-core/src/auth.js

@ -29,7 +29,7 @@ passport.deserializeUser(async (user, done) => {
const user = await db.get(user._id)
return done(null, user)
} catch (err) {
console.error("User not found", err)
console.error(`User not found`, err)
return done(null, false, { message: "User not found" })
}
})

49
packages/backend-core/src/cache/generic.js

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

12
packages/backend-core/src/context/index.js

@ -73,7 +73,7 @@ exports.isMultiTenant = () => {
}
// 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
// context - don't want to close DB on a parent context
async function internal(opts = { existing: false }) {
@ -98,7 +98,11 @@ exports.doInTenant = (tenantId, task) => {
}
}
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)
return internal({ existing: true })
} else {
@ -135,7 +139,7 @@ const setAppTenantId = appId => {
exports.updateTenantId(appTenantId)
}
exports.doInAppContext = (appId, task) => {
exports.doInAppContext = (appId, task, { forceNew } = {}) => {
if (!appId) {
throw new Error("appId is required")
}
@ -162,7 +166,7 @@ exports.doInAppContext = (appId, task) => {
}
}
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)
return internal({ existing: true })
} else {

9
packages/backend-core/src/db/index.js

@ -3,6 +3,7 @@ const env = require("../environment")
let PouchDB
let initialised = false
const dbList = new Set()
const put =
dbPut =>
@ -30,6 +31,9 @@ exports.init = opts => {
// in situations that using the function doWithDB does not work
exports.dangerousGetDB = (dbName, opts) => {
checkInitialised()
if (env.isTest()) {
dbList.add(dbName)
}
const db = new PouchDB(dbName, opts)
const dbPut = db.put
db.put = put(dbPut)
@ -65,6 +69,9 @@ exports.doWithDB = async (dbName, cb, opts) => {
}
exports.allDbs = () => {
if (!env.isTest()) {
throw new Error("Cannot be used outside test environment.")
}
checkInitialised()
return PouchDB.allDbs()
return [...dbList]
}

8
packages/backend-core/src/db/pouch.js

@ -92,11 +92,5 @@ exports.getPouch = (opts = {}) => {
PouchDB.plugin(find)
}
const Pouch = PouchDB.defaults(POUCH_DB_DEFAULTS)
if (opts.allDbs) {
const allDbs = require("pouchdb-all-dbs")
allDbs(Pouch)
}
return Pouch
return PouchDB.defaults(POUCH_DB_DEFAULTS)
}

1
packages/backend-core/src/environment.js

@ -52,6 +52,7 @@ module.exports = {
process.env.GLOBAL_CLOUD_BUCKET_NAME || "prod-budi-tenant-uploads",
USE_COUCH: process.env.USE_COUCH || true,
DISABLE_DEVELOPER_LICENSE: process.env.DISABLE_DEVELOPER_LICENSE,
DEFAULT_LICENSE: process.env.DEFAULT_LICENSE,
_set(key, value) {
process.env[key] = value
module.exports[key] = value

4
packages/backend-core/src/errors/http.js

@ -1,8 +1,8 @@
const { GenericError } = require("./generic")
class HTTPError extends GenericError {
constructor(message, httpStatus, code, type) {
super(message, code ? code : "http", type)
constructor(message, httpStatus, code = "http", type = "generic") {
super(message, code, type)
this.status = httpStatus
}
}

2
packages/backend-core/src/hashing.js

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

2
packages/backend-core/src/middleware/passport/local.js

@ -30,7 +30,7 @@ exports.authenticate = async function (ctx, email, password, done) {
const dbUser = await users.getGlobalUserByEmail(email)
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

10
packages/backend-core/src/redis/authRedis.js

@ -1,18 +1,20 @@
const Client = require("./index")
const utils = require("./utils")
let userClient, sessionClient, appClient
let userClient, sessionClient, appClient, cacheClient
async function init() {
userClient = await new Client(utils.Databases.USER_CACHE).init()
sessionClient = await new Client(utils.Databases.SESSIONS).init()
appClient = await new Client(utils.Databases.APP_METADATA).init()
cacheClient = await new Client(utils.Databases.GENERIC_CACHE).init()
}
process.on("exit", async () => {
if (userClient) await userClient.finish()
if (sessionClient) await sessionClient.finish()
if (appClient) await appClient.finish()
if (cacheClient) await cacheClient.finish()
})
module.exports = {
@ -34,4 +36,10 @@ module.exports = {
}
return appClient
},
getCacheClient: async () => {
if (!cacheClient) {
await init()
}
return cacheClient
},
}

1
packages/backend-core/src/redis/utils.js

@ -18,6 +18,7 @@ exports.Databases = {
APP_METADATA: "appMetadata",
QUERY_VARS: "queryVars",
LICENSES: "license",
GENERIC_CACHE: "data_cache",
}
exports.SEPARATOR = SEPARATOR

45
packages/backend-core/src/security/sessions.js

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

324
packages/backend-core/yarn.lock

@ -505,6 +505,22 @@
koa "^2.13.4"
node-mocks-http "^1.5.8"
"@mapbox/node-pre-gyp@^1.0.0":
version "1.0.9"
resolved "https://registry.yarnpkg.com/@mapbox/node-pre-gyp/-/node-pre-gyp-1.0.9.tgz#09a8781a3a036151cdebbe8719d6f8b25d4058bc"
integrity sha512-aDF3S3rK9Q2gey/WAttUlISduDItz5BU3306M9Eyv6/oS40aMprnopshtlKTykxRNIBEZuRMaZAnbrQ4QtKGyw==
dependencies:
detect-libc "^2.0.0"
https-proxy-agent "^5.0.0"
make-dir "^3.1.0"
node-fetch "^2.6.7"
nopt "^5.0.0"
npmlog "^5.0.1"
rimraf "^3.0.2"
semver "^7.3.5"
tar "^6.1.11"
"@sinonjs/commons@^1.7.0":
version "1.8.3"
resolved "https://registry.yarnpkg.com/@sinonjs/commons/-/commons-1.8.3.tgz#3802ddd21a50a949b6721ddd72da36e67e7f1b2d"
@ -652,6 +668,11 @@ abab@^2.0.3, abab@^2.0.5:
resolved "https://registry.yarnpkg.com/abab/-/abab-2.0.5.tgz#c0b678fb32d60fc1219c784d6a826fe385aeb79a"
integrity sha512-9IK9EadsbHo6jLWIpxpR6pL0sazTXV6+SQv25ZB+F7Bj9mJNaOc4nCRabwd5M/JwmUa8idz6Eci6eKfJryPs6Q==
abbrev@1:
version "1.1.1"
resolved "https://registry.yarnpkg.com/abbrev/-/abbrev-1.1.1.tgz#f8f2c887ad10bf67f634f005b6987fed3179aac8"
integrity sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==
abort-controller@3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/abort-controller/-/abort-controller-3.0.0.tgz#eaf54d53b62bae4138e809ca225c8439a6efb392"
@ -698,11 +719,6 @@ acorn-walk@^7.1.1:
resolved "https://registry.yarnpkg.com/acorn-walk/-/acorn-walk-7.2.0.tgz#0de889a601203909b0fbe07b8938dc21d2e967bc"
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:
version "7.4.1"
resolved "https://registry.yarnpkg.com/acorn/-/acorn-7.4.1.tgz#feaed255973d2e77555b83dbc08851a6c63520fa"
@ -738,11 +754,6 @@ ajv@^6.12.3:
json-schema-traverse "^0.4.1"
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:
version "4.3.2"
resolved "https://registry.yarnpkg.com/ansi-escapes/-/ansi-escapes-4.3.2.tgz#6b2291d1db7d98b6521d5f1efa42d0f3a9feb65e"
@ -790,6 +801,19 @@ anymatch@^3.0.3:
normalize-path "^3.0.0"
picomatch "^2.0.4"
"aproba@^1.0.3 || ^2.0.0":
version "2.0.0"
resolved "https://registry.yarnpkg.com/aproba/-/aproba-2.0.0.tgz#52520b8ae5b569215b354efc0caa3fe1e45a8adc"
integrity sha512-lYe4Gx7QT+MKGbDsA+Z+he/Wtef0BiwDOlK/XkBrdfsh9J/jPPXbX0tE9x9cl27Tmu5gg3QUbUrQYa/y+KOHPQ==
are-we-there-yet@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/are-we-there-yet/-/are-we-there-yet-2.0.0.tgz#372e0e7bd279d8e94c653aaa1f67200884bf3e1c"
integrity sha512-Ci/qENmwHnsYo9xKIcUJN5LeDKdJ6R1Z1j9V/J5wyq8nh/mYPEpIKJbBZXtZjG04HiK7zV/p6Vs9952MrMeUIw==
dependencies:
delegates "^1.0.0"
readable-stream "^3.6.0"
argparse@^1.0.7:
version "1.0.10"
resolved "https://registry.yarnpkg.com/argparse/-/argparse-1.0.10.tgz#bcd6791ea5ae09725e17e5ad988134cd40b3d911"
@ -842,11 +866,6 @@ assign-symbols@^1.0.0:
resolved "https://registry.yarnpkg.com/assign-symbols/-/assign-symbols-1.0.0.tgz#59667f41fadd4f20ccbc2bb96b8d4f7f78ec0367"
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@~2.1.4:
version "2.1.5"
resolved "https://registry.yarnpkg.com/async/-/async-2.1.5.tgz#e587c68580994ac67fc56ff86d3ac56bdbe810bc"
@ -980,11 +999,6 @@ balanced-match@^1.0.0:
resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.2.tgz#e83e3a7e3f300b34cb9d87f615fa0cbf357690ee"
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:
version "1.5.1"
resolved "https://registry.yarnpkg.com/base64-js/-/base64-js-1.5.1.tgz#1b1b440160a5bf7ad40b650f095963481903930a"
@ -1020,10 +1034,13 @@ bcrypt-pbkdf@^1.0.0:
dependencies:
tweetnacl "^0.14.3"
bcryptjs@^2.4.3:
version "2.4.3"
resolved "https://registry.yarnpkg.com/bcryptjs/-/bcryptjs-2.4.3.tgz#9ab5627b93e60621ff7cdac5da9733027df1d0cb"
integrity sha1-mrVie5PmBiH/fNrF2pczAn3x0Ms=
bcrypt@^5.0.1:
version "5.0.1"
resolved "https://registry.yarnpkg.com/bcrypt/-/bcrypt-5.0.1.tgz#f1a2c20f208e2ccdceea4433df0c8b2c54ecdf71"
integrity sha512-9BTgmrhZM2t1bNuDtrtIMVSmmxZBrJ71n8Wg+YgdjHuIWYF7SjjmCPZFB+/5i/o/PIeRpwVJR3P+NrpIItUjqw==
dependencies:
"@mapbox/node-pre-gyp" "^1.0.0"
node-addon-api "^3.1.0"
bl@^4.0.3:
version "4.1.0"
@ -1214,6 +1231,11 @@ chownr@^1.1.1:
resolved "https://registry.yarnpkg.com/chownr/-/chownr-1.1.4.tgz#6fc9d7b42d32a583596337666e7d08084da2cc6b"
integrity sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==
chownr@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/chownr/-/chownr-2.0.0.tgz#15bfbe53d2eab4cf70f18a8cd68ebe5b3cb1dece"
integrity sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==
ci-info@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/ci-info/-/ci-info-2.0.0.tgz#67a9e964be31a51e15e5010d58e6f12834002f46"
@ -1295,6 +1317,11 @@ color-name@~1.1.4:
resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.4.tgz#c2a09a87acbde69543de6f63fa3995c826c536a2"
integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==
color-support@^1.1.2:
version "1.1.3"
resolved "https://registry.yarnpkg.com/color-support/-/color-support-1.1.3.tgz#93834379a1cc9a0c61f82f52f0d04322251bd5a2"
integrity sha512-qiBjkpbMLO/HL68y+lh4q0/O1MZFj2RX6X/KmMa3+gJD3z+WwI1ZzDHysvqHGS3mP6mznPckpXmw1nI9cJjyRg==
combined-stream@^1.0.5, combined-stream@~1.0.5:
version "1.0.5"
resolved "https://registry.yarnpkg.com/combined-stream/-/combined-stream-1.0.5.tgz#938370a57b4a51dea2c77c15d5c5fdf895164009"
@ -1309,26 +1336,6 @@ combined-stream@^1.0.6, combined-stream@^1.0.8, combined-stream@~1.0.6:
dependencies:
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:
version "1.3.0"
resolved "https://registry.yarnpkg.com/component-emitter/-/component-emitter-1.3.0.tgz#16e4070fba8ae29b679f2215853ee181ab2eabc0"
@ -1356,6 +1363,12 @@ content-type@^1.0.4:
resolved "https://registry.yarnpkg.com/content-type/-/content-type-1.0.4.tgz#e138cc75e040c727b1966fe5e5f8c9aee256fe3b"
integrity sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA==
console-control-strings@^1.0.0, console-control-strings@^1.1.0:
version "1.1.0"
resolved "https://registry.yarnpkg.com/console-control-strings/-/console-control-strings-1.1.0.tgz#3d7cf4464db6446ea644bf4b39507f9851008e8e"
integrity sha1-PXz0Rk22RG6mRL9LOVB/mFEAjo4=
convert-source-map@^1.4.0, convert-source-map@^1.6.0, convert-source-map@^1.7.0:
version "1.8.0"
resolved "https://registry.yarnpkg.com/convert-source-map/-/convert-source-map-1.8.0.tgz#f3373c32d21b4d780dd8004514684fb791ca4369"
@ -1527,11 +1540,6 @@ define-property@^2.0.2:
is-descriptor "^1.0.2"
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:
version "1.0.0"
resolved "https://registry.yarnpkg.com/delayed-stream/-/delayed-stream-1.0.0.tgz#df3ae199acadfb7d440aaae0b29e2272b24ec619"
@ -1561,20 +1569,16 @@ destroy@^1.0.4:
version "1.2.0"
resolved "https://registry.yarnpkg.com/destroy/-/destroy-1.2.0.tgz#4803735509ad8be552934c67df614f94e66fa015"
integrity sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==
detect-libc@^2.0.0:
version "2.0.1"
resolved "https://registry.yarnpkg.com/detect-libc/-/detect-libc-2.0.1.tgz#e1897aa88fa6ad197862937fbc0441ef352ee0cd"
integrity sha512-463v3ZeIrcWtdgIg6vI6XUncguvr2TnGl4SzDXinkt9mSLpBJKXT3mW6xT3VQdDN11+WVs29pgvivTc4Lp8v+w==
detect-newline@^3.0.0:
version "3.1.0"
resolved "https://registry.yarnpkg.com/detect-newline/-/detect-newline-3.1.0.tgz#576f5dfc63ae1a192ff192d8ad3af6308991b651"
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:
version "26.6.2"
resolved "https://registry.yarnpkg.com/diff-sequences/-/diff-sequences-26.6.2.tgz#48ba99157de1923412eed41db6b6d4aa9ca7c0b1"
@ -1694,15 +1698,6 @@ error-ex@^1.3.1:
dependencies:
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:
version "3.1.1"
resolved "https://registry.yarnpkg.com/escalade/-/escalade-3.1.1.tgz#d8cfdc7000965c5a0174b4a82eaa5c0552742e40"
@ -1735,26 +1730,11 @@ escodegen@^2.0.0:
optionalDependencies:
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:
version "4.0.1"
resolved "https://registry.yarnpkg.com/esprima/-/esprima-4.0.1.tgz#13b04cdb3e6c5d19df91ab6987a8695619b0aa71"
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:
version "5.3.0"
resolved "https://registry.yarnpkg.com/estraverse/-/estraverse-5.3.0.tgz#2eea5290702f26ab8fe5370370ff86c965d21123"
@ -2016,6 +1996,13 @@ fs-constants@^1.0.0:
resolved "https://registry.yarnpkg.com/fs-constants/-/fs-constants-1.0.0.tgz#6be0de9be998ce16af8afc24497b9ee9b7ccd9ad"
integrity sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==
fs-minipass@^2.0.0:
version "2.1.0"
resolved "https://registry.yarnpkg.com/fs-minipass/-/fs-minipass-2.1.0.tgz#7f5036fdbf12c63c169190cbe4199c852271f9fb"
integrity sha512-V/JgOLFCS+R6Vcq0slCuaeWEdNC3ouDlJMNIsacH2VtALiu9mV4LPrHc5cDl8k5aw6J8jwgWWpiTo5RYhmIzvg==
dependencies:
minipass "^3.0.0"
fs.realpath@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f"
@ -2036,6 +2023,21 @@ functional-red-black-tree@^1.0.1:
resolved "https://registry.yarnpkg.com/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz#1b0ab3bd553b2a0d6399d29c0e3ea0b252078327"
integrity sha1-GwqzvVU7Kg1jmdKcDj6gslIHgyc=
gauge@^3.0.0:
version "3.0.2"
resolved "https://registry.yarnpkg.com/gauge/-/gauge-3.0.2.tgz#03bf4441c044383908bcfa0656ad91803259b395"
integrity sha512-+5J6MS/5XksCuXq++uFRsnUd7Ovu1XenbeuIuNRJxYWjgQbPuFhT14lAvsWfqfAmnwluf1OwMjz39HjfLPci0Q==
dependencies:
aproba "^1.0.3 || ^2.0.0"
color-support "^1.1.2"
console-control-strings "^1.0.0"
has-unicode "^2.0.1"
object-assign "^4.1.1"
signal-exit "^3.0.0"
string-width "^4.2.3"
strip-ansi "^6.0.1"
wide-align "^1.1.2"
gensync@^1.0.0-beta.2:
version "1.0.0-beta.2"
resolved "https://registry.yarnpkg.com/gensync/-/gensync-1.0.0-beta.2.tgz#32a6ee76c3d7f52d46b2b1ae5d93fea8580a25e0"
@ -2077,17 +2079,6 @@ getpass@^0.1.1:
dependencies:
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:
version "7.2.0"
resolved "https://registry.yarnpkg.com/glob/-/glob-7.2.0.tgz#d15535af7732e02e948f4c41628bd910293f6023"
@ -2131,7 +2122,7 @@ googleapis@^16.0.0:
google-auth-library "~0.10.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"
resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.8.tgz#e412b8d33f5e006593cbd3cee6df9f2cebbe802a"
integrity sha512-qkIilPUYcNhJpd33n0GBXTB1MMPp14TxEsEs0pTrsSVucApsYzW5V+Q8Qxhik6KU3evy+qkAAowTByymK0avdg==
@ -2198,6 +2189,10 @@ has-tostringtag@^1.0.0:
integrity sha512-kFjcSNhnlGV1kyoGk7OXKSawH5JOb/LzUc5w9B02hOTO0dfFRjbHQKvg1d6cf3HbeUmtU9VbbV3qzZ2Teh97WQ==
dependencies:
has-symbols "^1.0.2"
has-unicode@^2.0.1:
version "2.0.1"
resolved "https://registry.yarnpkg.com/has-unicode/-/has-unicode-2.0.1.tgz#e0e6fe6a28cf51138855e086d1691e771de2a8b9"
integrity sha1-4Ob+aijPUROIVeCG0Wkedx3iqLk=
has-value@^0.3.1:
version "0.3.1"
@ -2328,7 +2323,7 @@ human-signals@^1.1.1:
resolved "https://registry.yarnpkg.com/human-signals/-/human-signals-1.1.1.tgz#c5b1cd14f50aeae09ab6c59fe63ba3395fe4dfa3"
integrity sha512-SEQu7vl8KjNL2eoGBLF3+wAjpsNfA9XMlXAYj/3EdaNfAlxKthD1xjEQfGOUhllCGGJVNY34bRr6lPINhNjyZw==
iconv-lite@0.4.24, iconv-lite@^0.4.5:
iconv-lite@0.4.24:
version "0.4.24"
resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.24.tgz#2022b4b25fbddc21d2f524974a474aafe733908b"
integrity sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==
@ -3183,17 +3178,6 @@ jsprim@^1.2.2:
json-schema "0.2.3"
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:
version "1.1.5"
resolved "https://registry.yarnpkg.com/jwa/-/jwa-1.1.5.tgz#a0552ce0220742cd52e153774a32905c30e756e5"
@ -3518,7 +3502,7 @@ ltgt@2.2.1, ltgt@^2.1.2, ltgt@~2.2.0:
resolved "https://registry.yarnpkg.com/ltgt/-/ltgt-2.2.1.tgz#f35ca91c493f7b73da0e07495304f17b31f87ee5"
integrity sha1-81ypHEk/e3PaDgdJUwTxezH4fuU=
make-dir@^3.0.0:
make-dir@^3.0.0, make-dir@^3.1.0:
version "3.1.0"
resolved "https://registry.yarnpkg.com/make-dir/-/make-dir-3.1.0.tgz#415e967046b3a7f1d185277d84aa58203726a13f"
integrity sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==
@ -3663,7 +3647,7 @@ mimic-fn@^2.1.0:
resolved "https://registry.yarnpkg.com/mimic-fn/-/mimic-fn-2.1.0.tgz#7ed2c2ccccaf84d3ffcb7a69b57711fc2083401b"
integrity sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==
"minimatch@2 || 3", minimatch@^3.0.4:
minimatch@^3.0.4:
version "3.0.4"
resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.0.4.tgz#5166e286457f03306064be5497e8dbb0c3d32083"
integrity sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==
@ -3675,6 +3659,21 @@ minimist@^1.1.1, minimist@^1.2.0, minimist@^1.2.5:
resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.6.tgz#8637a5b759ea0d6e98702cfb3a9283323c93af44"
integrity sha512-Jsjnk4bw3YJqYzbdyBiNsPWHPfO++UGG749Cxs6peCu5Xg4nrena6OVxOYxrQTqww0Jmwt+Ref8rggumkTLz9Q==
minipass@^3.0.0:
version "3.1.6"
resolved "https://registry.yarnpkg.com/minipass/-/minipass-3.1.6.tgz#3b8150aa688a711a1521af5e8779c1d3bb4f45ee"
integrity sha512-rty5kpw9/z8SX9dmxblFA6edItUmwJgMeYDZRrwlIVN27i8gysGbznJwUggw2V/FVqFSDdWy040ZPS811DYAqQ==
dependencies:
yallist "^4.0.0"
minizlib@^2.1.1:
version "2.1.2"
resolved "https://registry.yarnpkg.com/minizlib/-/minizlib-2.1.2.tgz#e90d3466ba209b932451508a11ce3d3632145931"
integrity sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg==
dependencies:
minipass "^3.0.0"
yallist "^4.0.0"
mixin-deep@^1.2.0:
version "1.3.2"
resolved "https://registry.yarnpkg.com/mixin-deep/-/mixin-deep-1.3.2.tgz#1120b43dc359a785dce65b55b82e257ccf479566"
@ -3688,12 +3687,10 @@ mkdirp-classic@^0.5.2:
resolved "https://registry.yarnpkg.com/mkdirp-classic/-/mkdirp-classic-0.5.3.tgz#fa10c9115cc6d8865be221ba47ee9bed78601113"
integrity sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A==
mkdirp@^0.5.0:
version "0.5.5"
resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.5.5.tgz#d91cefd62d1436ca0f41620e251288d420099def"
integrity sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ==
dependencies:
minimist "^1.2.5"
mkdirp@^1.0.3:
version "1.0.4"
resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-1.0.4.tgz#3eb5ed62622756d79a5f0e2a221dfebad75c2f7e"
integrity sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==
ms@2.0.0:
version "2.0.0"
@ -3757,12 +3754,17 @@ nice-try@^1.0.4:
resolved "https://registry.yarnpkg.com/nice-try/-/nice-try-1.0.5.tgz#a3378a7696ce7d223e88fc9b764bd7ef1089e366"
integrity sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ==
node-addon-api@^3.1.0:
version "3.2.1"
resolved "https://registry.yarnpkg.com/node-addon-api/-/node-addon-api-3.2.1.tgz#81325e0a2117789c0128dab65e7e38f07ceba161"
integrity sha512-mmcei9JghVNDYydghQmeDX8KoAm0FAiYyIcUt/N4nhyAipB17pllZQDOJD2fotxABnt4Mdz+dKTO7eftLg4d0A==
node-fetch@2.6.0:
version "2.6.0"
resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.6.0.tgz#e633456386d4aa55863f676a7ab0daa8fdecb0fd"
integrity sha512-8dG4H5ujfvFiqDmVu9fQ5bOHUC15JMjMY/Zumv26oOvvVJjM67KF8koCWIabKQ1GJIa9r2mMZscBq/TbdOcmNA==
node-fetch@2.6.7, node-fetch@^2.6.1:
node-fetch@2.6.7, node-fetch@^2.6.1, node-fetch@^2.6.7:
version "2.6.7"
resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.6.7.tgz#24de9fba827e3b4ae44dc8b20256a379160052ad"
integrity sha512-ZjMPFEfVx5j+y2yF35Kzx5sF7kDzxuDj6ziH4FFbOp87zKDZNx8yExJIb05OGF4Nlt9IHFIMBkRl41VdvcNdbQ==
@ -3822,6 +3824,13 @@ node-releases@^2.0.1:
resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-2.0.1.tgz#3d1d395f204f1f2f29a54358b9fb678765ad2fc5"
integrity sha512-CqyzN6z7Q6aMeF/ktcMVTzhAHCEpf8SOarwpzpf8pNBY2k5/oM34UHldUwp8VKI7uxct2HxSRdJjBaZeESzcxA==
nopt@^5.0.0:
version "5.0.0"
resolved "https://registry.yarnpkg.com/nopt/-/nopt-5.0.0.tgz#530942bb58a512fccafe53fe210f13a25355dc88"
integrity sha512-Tbj67rffqceeLpcRXrT7vKAN8CwfPeIBgM7E6iBkmKLV7bEMwpGgYLGv0jACUsECaa/vuxP0IjEont6umdMgtQ==
dependencies:
abbrev "1"
normalize-package-data@^2.5.0:
version "2.5.0"
resolved "https://registry.yarnpkg.com/normalize-package-data/-/normalize-package-data-2.5.0.tgz#e66db1838b200c1dfc233225d12cb36520e234a8"
@ -3858,6 +3867,16 @@ npm-run-path@^4.0.0:
dependencies:
path-key "^3.0.0"
npmlog@^5.0.1:
version "5.0.1"
resolved "https://registry.yarnpkg.com/npmlog/-/npmlog-5.0.1.tgz#f06678e80e29419ad67ab964e0fa69959c1eb8b0"
integrity sha512-AqZtDUWOMKs1G/8lwylVjrdYgqA4d9nu8hc+0gzRxlDb1I10+FHBGMXs6aiQHFdCUUlqH99MUMuLfzWDNDtfxw==
dependencies:
are-we-there-yet "^2.0.0"
console-control-strings "^1.1.0"
gauge "^3.0.0"
set-blocking "^2.0.0"
nwsapi@^2.2.0:
version "2.2.0"
resolved "https://registry.yarnpkg.com/nwsapi/-/nwsapi-2.2.0.tgz#204879a9e3d068ff2a55139c2c772780681a38b7"
@ -3878,10 +3897,10 @@ oauth@0.9.x, oauth@^0.9.15:
resolved "https://registry.yarnpkg.com/oauth/-/oauth-0.9.15.tgz#bd1fefaf686c96b75475aed5196412ff60cfb9c1"
integrity sha1-vR/vr2hslrdUda7VGWQS/2DPucE=
object-assign@^2.0.0:
version "2.1.1"
resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-2.1.1.tgz#43c36e5d569ff8e4816c4efa8be02d26967c18aa"
integrity sha1-Q8NuXVaf+OSBbE76i+AtJpZ8GKo=
object-assign@^4.1.1:
version "4.1.1"
resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863"
integrity sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=
object-copy@^0.1.0:
version "0.1.0"
@ -4232,17 +4251,6 @@ pouchdb-adapter-utils@7.2.2:
pouchdb-merge "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:
version "7.2.2"
resolved "https://registry.yarnpkg.com/pouchdb-binary-utils/-/pouchdb-binary-utils-7.2.2.tgz#0690b348052c543b1e67f032f47092ca82bcb10e"
@ -4319,7 +4327,7 @@ pouchdb-merge@7.2.2:
resolved "https://registry.yarnpkg.com/pouchdb-merge/-/pouchdb-merge-7.2.2.tgz#940d85a2b532d6a93a6cab4b250f5648511bcc16"
integrity sha512-6yzKJfjIchBaS7Tusuk8280WJdESzFfQ0sb4jeMUNnrqs4Cx3b0DIEOYTRRD9EJDM+je7D3AZZ4AT0tFw8gb4A==
pouchdb-promise@6.4.3, pouchdb-promise@^6.0.4:
pouchdb-promise@^6.0.4:
version "6.4.3"
resolved "https://registry.yarnpkg.com/pouchdb-promise/-/pouchdb-promise-6.4.3.tgz#74516f4acf74957b54debd0fb2c0e5b5a68ca7b3"
integrity sha512-ruJaSFXwzsxRHQfwNHjQfsj58LBOY1RzGzde4PM5CWINZwFjCQAhZwfMrch2o/0oZT6d+Xtt0HTWhq35p3b0qw==
@ -4462,11 +4470,6 @@ punycode@^2.1.0, punycode@^2.1.1:
resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.1.1.tgz#b58b010ac40c22c5657616c8d2c2c02c7bf479ec"
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:
version "6.4.0"
resolved "https://registry.yarnpkg.com/qs/-/qs-6.4.0.tgz#13e26d28ad6b0ffaa91312cd3bf708ed351e7233"
@ -4521,7 +4524,7 @@ readable-stream@1.1.14, readable-stream@^1.0.27-1:
isarray "0.0.1"
string_decoder "~0.10.x"
"readable-stream@2 || 3", readable-stream@^3.1.1, readable-stream@^3.4.0:
"readable-stream@2 || 3", readable-stream@^3.1.1, readable-stream@^3.4.0, readable-stream@^3.6.0:
version "3.6.0"
resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-3.6.0.tgz#337bbda3adc0706bd3e024426a286d4b4b2c9198"
integrity sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==
@ -4553,16 +4556,6 @@ readline-sync@^1.4.9:
resolved "https://registry.yarnpkg.com/readline-sync/-/readline-sync-1.4.10.tgz#41df7fbb4b6312d673011594145705bf56d8873b"
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:
version "1.7.0"
resolved "https://registry.yarnpkg.com/redis-commands/-/redis-commands-1.7.0.tgz#15a6fea2d58281e27b1cd1acfb4b293e278c3a89"
@ -4707,7 +4700,7 @@ ret@~0.1.10:
resolved "https://registry.yarnpkg.com/ret/-/ret-0.1.15.tgz#b8a4825d5bdb1fc3f6f53c2bc33f81388681c7bc"
integrity sha512-TTlYpa+OL+vMMNG24xSlQGEJ3B/RzEfUlLct7b5G/ytav+wPrplCpVMFuwzXbkecJrb6IYo1iFb0S9v37754mg==
rimraf@^3.0.0:
rimraf@^3.0.0, rimraf@^3.0.2:
version "3.0.2"
resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-3.0.2.tgz#f1a5402ba6220ad52cc1282bac1ae3aa49fd061a"
integrity sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==
@ -4795,6 +4788,13 @@ semver@^7.3.2:
dependencies:
lru-cache "^6.0.0"
semver@^7.3.5:
version "7.3.7"
resolved "https://registry.yarnpkg.com/semver/-/semver-7.3.7.tgz#12c5b649afdbf9049707796e22a4028814ce523f"
integrity sha512-QlYTucUYOews+WeEujDoEGziz4K6c47V/Bd+LjSSYcA94p+DmINdf7ncaUinThfvZyu13lN9OY1XDxt8C0Tw0g==
dependencies:
lru-cache "^6.0.0"
set-blocking@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/set-blocking/-/set-blocking-2.0.0.tgz#045f9782d011ae9a6803ddd382b24392b3d890f7"
@ -4925,14 +4925,7 @@ source-map-url@^0.4.0:
resolved "https://registry.yarnpkg.com/source-map-url/-/source-map-url-0.4.1.tgz#0af66605a745a5a2f91cf1bbf8a7afbc283dec56"
integrity sha512-cPiFOTLUKvJFIg4SKVScy4ilPPW6rFgMgfuZJPNoDuMs3nC1HbMUycBoJw77xFIp6z1UJQJOfx6C9GMH80DiTw==
source-map@^0.4.2:
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:
source-map@^0.5.0, source-map@^0.5.6:
version "0.5.7"
resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.5.7.tgz#8a039d2d1021d22d1ea14c80d8ea468ba2ef3fcc"
integrity sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=
@ -5066,7 +5059,7 @@ string-template@~1.0.0:
resolved "https://registry.yarnpkg.com/string-template/-/string-template-1.0.0.tgz#9e9f2233dc00f218718ec379a28a5673ecca8b96"
integrity sha1-np8iM9wA8hhxjsN5oopWc+zKi5Y=
string-width@^4.1.0, string-width@^4.2.0:
"string-width@^1.0.2 || 2 || 3 || 4", string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3:
version "4.2.3"
resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010"
integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==
@ -5179,6 +5172,18 @@ tar-stream@^2.1.4:
inherits "^2.0.3"
readable-stream "^3.1.1"
tar@^6.1.11:
version "6.1.11"
resolved "https://registry.yarnpkg.com/tar/-/tar-6.1.11.tgz#6760a38f003afa1b2ffd0ffe9e9abbd0eab3d621"
integrity sha512-an/KZQzQUkZCkuoAA64hM92X0Urb6VpRhAFllDzz44U2mcD5scmT3zBc4VgVpkugF580+DQn8eAFSyoQt0tznA==
dependencies:
chownr "^2.0.0"
fs-minipass "^2.0.0"
minipass "^3.0.0"
minizlib "^2.1.1"
mkdirp "^1.0.3"
yallist "^4.0.0"
terminal-link@^2.0.0:
version "2.1.1"
resolved "https://registry.yarnpkg.com/terminal-link/-/terminal-link-2.1.1.tgz#14a64a27ab3c0df933ea546fba55f2d078edc994"
@ -5600,6 +5605,13 @@ which@^2.0.1, which@^2.0.2:
dependencies:
isexe "^2.0.0"
wide-align@^1.1.2:
version "1.1.5"
resolved "https://registry.yarnpkg.com/wide-align/-/wide-align-1.1.5.tgz#df1d4c206854369ecf3c9a4898f1b23fbd9d15d3"
integrity sha512-eDMORYaPNZ4sQIuuYPDHdQvf4gyCF9rEEV/yPxGfwPkRodwEgiMUUXTx/dex+Me0wxx53S+NgUHaP7y3MGlDmg==
dependencies:
string-width "^1.0.2 || 2 || 3 || 4"
word-wrap@~1.2.3:
version "1.2.3"
resolved "https://registry.yarnpkg.com/word-wrap/-/word-wrap-1.2.3.tgz#610636f6b1f703891bd34771ccb17fb93b47079c"

4
packages/bbui/package.json

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

8
packages/bbui/src/Form/Core/Dropzone.svelte

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

2
packages/bbui/src/Form/Dropzone.svelte

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

8
packages/bbui/src/Layout/Layout.svelte

@ -40,6 +40,10 @@
padding-left: 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 {
padding-top: var(--spacing-s);
padding-bottom: var(--spacing-s);
@ -56,6 +60,10 @@
padding-top: 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 {
grid-gap: var(--spacing-xs);
}

8
packages/bbui/src/Layout/Page.svelte

@ -1,9 +1,10 @@
<script>
export let wide = false
export let maxWidth = "80ch"
export let noPadding = false
</script>
<div style="--max-width: {maxWidth}" class:wide>
<div style="--max-width: {maxWidth}" class:wide class:noPadding>
<slot />
</div>
@ -23,4 +24,9 @@
max-width: none;
margin: 0;
}
.noPadding {
padding: 0px;
margin: 0px;
}
</style>

2
packages/bbui/src/SideNavigation/Item.svelte

@ -7,6 +7,7 @@
export let icon = ""
export let selected = false
export let disabled = false
export let dataCy
</script>
<li
@ -14,6 +15,7 @@
class:is-selected={selected}
class:is-disabled={disabled}
on:click
data-cy={dataCy}
>
{#if heading}
<h2 class="spectrum-SideNav-heading" id="nav-heading-{heading}">

24
packages/bbui/src/Tabs/Tab.svelte

@ -6,7 +6,7 @@
const dispatch = createEventDispatcher()
let selected = getContext("tab")
let tab
let tab_internal
let tabInfo
const setTabInfo = () => {
@ -16,7 +16,7 @@
// We just need to get this off the main thread to fix this, by using
// a 0ms timeout.
setTimeout(() => {
tabInfo = tab?.getBoundingClientRect()
tabInfo = tab_internal?.getBoundingClientRect()
if (tabInfo && $selected.title === title) {
$selected.info = tabInfo
}
@ -27,14 +27,30 @@
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 = () => {
$selected = { ...$selected, title, info: tab.getBoundingClientRect() }
$selected = {
...$selected,
title,
info: tab_internal.getBoundingClientRect(),
}
dispatch("click")
}
</script>
<div
bind:this={tab}
bind:this={tab_internal}
on:click={onClick}
class:is-selected={$selected.title === title}
class="spectrum-Tabs-item"

346
packages/builder/cypress/integration/appOverview.spec.js

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

24
packages/builder/cypress/integration/appPublishWorkflow.spec.js

@ -19,7 +19,7 @@ filterTests(['all'], () => {
cy.get(".appTable .app-row-actions").eq(0)
.within(() => {
cy.get(".spectrum-Button").contains("Preview")
cy.get(".spectrum-Button").contains("View")
cy.get(".spectrum-Button").contains("Edit").click({ force: true })
})
@ -29,22 +29,8 @@ filterTests(['all'], () => {
it("Should publish an application and correctly reflect that", () => {
//Assuming the previous test was run and the unpublished app is open in edit mode.
cy.get(".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/cypress-tests'
cy.get("[data-cy='deployed-app-url'] input").should('have.value', appUrl)
cy.get(".spectrum-Button").contains("Done").click({ force: true })
})
cy.publishApp("cypress-tests")
cy.visit(`${Cypress.config().baseUrl}/builder`)
cy.wait(1000)
@ -57,7 +43,7 @@ filterTests(['all'], () => {
cy.get(".appTable .app-row-actions").eq(0)
.within(() => {
cy.get(".spectrum-Button").contains("View app")
cy.get(".spectrum-Button").contains("View")
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")
.within(() => {
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")
})
})
@ -84,7 +70,7 @@ filterTests(['all'], () => {
cy.get(".appTable .app-row-actions").eq(0)
.within(() => {
cy.get(".spectrum-Button").contains("View app")
cy.get(".spectrum-Button").contains("View")
cy.get(".spectrum-Button").contains("Edit").click({ force: true })
})

20
packages/builder/cypress/integration/renameAnApplication.spec.js

@ -112,19 +112,9 @@ filterTests(['all'], () => {
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-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)
})
}
})
cy.updateAppName(changedName, noName)
}
})
})

8
packages/builder/cypress/integration/templates/HR/hrTemplateDetails.spec.js

@ -7,13 +7,7 @@ filterTests(["all"], () => {
cy.login()
// Template navigation
cy.request(`${Cypress.config().baseUrl}/api/applications?status=all`)
.its("body")
.then(val => {
if (val.length > 0) {
cy.get(".spectrum-Button").contains("Templates").click({force: true})
}
})
cy.visit(`${Cypress.config().baseUrl}/builder/portal/apps/templates`)
// Filter HR Templates
cy.get(".template-category-filters").within(() => {

19
packages/builder/cypress/integration/templates/HR/jobApplicationTracker.spec.js

@ -8,13 +8,13 @@ filterTests(["all"], () => {
before(() => {
cy.login()
cy.deleteApp(templateName)
cy.visit(`${Cypress.config().baseUrl}/builder`, {
// Template navigation
cy.visit(`${Cypress.config().baseUrl}/builder/portal/apps/templates`, {
onBeforeLoad(win) {
cy.stub(win, 'open')
}
})
cy.wait(2000)
cy.templateNavigation()
})
it("should create and publish app with Job Application Tracker template", () => {
@ -35,19 +35,10 @@ filterTests(["all"], () => {
cy.get(".spectrum-Button").contains("Create app").click({ force: true })
})
// Publish App
// Publish App & Verify it opened
cy.wait(2000) // Wait for app to generate
cy.get(".toprightnav").contains("Publish").click({ force: true })
cy.get(".spectrum-Dialog-grid").within(() => {
cy.get(".spectrum-Button").contains("Publish").click({ force: true })
})
// Verify Published app
cy.wait(2000) // Wait for App to publish and modal to appear
cy.get(".spectrum-Dialog-grid").within(() => {
cy.get(".spectrum-Button").contains("View App").click({ force: true })
cy.window().its('open').should('be.calledOnce')
})
cy.publishApp(true)
cy.window().its('open').should('be.calledOnce')
})
it("should add active/inactive vacancies", () => {

8
packages/builder/cypress/integration/templates/IT/ITTemplateDetails.spec.js

@ -7,13 +7,7 @@ filterTests(["all"], () => {
cy.login()
// Template navigation
cy.request(`${Cypress.config().baseUrl}/api/applications?status=all`)
.its("body")
.then(val => {
if (val.length > 0) {
cy.get(".spectrum-Button").contains("Templates").click({force: true})
}
})
cy.visit(`${Cypress.config().baseUrl}/builder/portal/apps/templates`)
// Filter IT Templates
cy.get(".template-category-filters").within(() => {

19
packages/builder/cypress/integration/templates/IT/ITTicketingSystem.spec.js

@ -8,13 +8,13 @@ filterTests(["all"], () => {
before(() => {
cy.login()
cy.deleteApp(templateName)
cy.visit(`${Cypress.config().baseUrl}/builder`, {
// Template navigation
cy.visit(`${Cypress.config().baseUrl}/builder/portal/apps/templates`, {
onBeforeLoad(win) {
cy.stub(win, 'open')
}
})
cy.wait(2000)
cy.templateNavigation()
})
it("should create and publish app with IT Ticketing System template", () => {
@ -35,19 +35,10 @@ filterTests(["all"], () => {
cy.get(".spectrum-Button").contains("Create app").click({ force: true })
})
// Publish App
// Publish App & Verify it opened
cy.wait(2000) // Wait for app to generate
cy.get(".toprightnav").contains("Publish").click({ force: true })
cy.get(".spectrum-Dialog-grid").within(() => {
cy.get(".spectrum-Button").contains("Publish").click({ force: true })
})
// Verify Published app
cy.wait(2000) // Wait for App to publish and modal to appear
cy.get(".spectrum-Dialog-grid").within(() => {
cy.get(".spectrum-Button").contains("View App").click({ force: true })
cy.window().its('open').should('be.calledOnce')
})
cy.publishApp(true)
cy.window().its('open').should('be.calledOnce')
})
xit("should filter tickets by status", () => {

8
packages/builder/cypress/integration/templates/adminPanels/adminPanelsTemplateDetails.spec.js

@ -7,13 +7,7 @@ filterTests(["all"], () => {
cy.login()
// Template navigation
cy.request(`${Cypress.config().baseUrl}/api/applications?status=all`)
.its("body")
.then(val => {
if (val.length > 0) {
cy.get(".spectrum-Button").contains("Templates").click({force: true})
}
})
cy.visit(`${Cypress.config().baseUrl}/builder/portal/apps/templates`)
// Filter Admin Panels Templates
cy.get(".template-category-filters").within(() => {

8
packages/builder/cypress/integration/templates/approvalApps/approvalAppsTemplateDetails.spec.js

@ -7,13 +7,7 @@ filterTests(["all"], () => {
cy.login()
// Template navigation
cy.request(`${Cypress.config().baseUrl}/api/applications?status=all`)
.its("body")
.then(val => {
if (val.length > 0) {
cy.get(".spectrum-Button").contains("Templates").click({force: true})
}
})
cy.visit(`${Cypress.config().baseUrl}/builder/portal/apps/templates`)
// Filter Approval Apps Templates
cy.get(".template-category-filters").within(() => {

10
packages/builder/cypress/integration/templates/businessApps/businessAppsTemplateDetails.spec.js

@ -7,14 +7,8 @@ filterTests(["all"], () => {
cy.login()
// Template navigation
cy.request(`${Cypress.config().baseUrl}/api/applications?status=all`)
.its("body")
.then(val => {
if (val.length > 0) {
cy.get(".spectrum-Button").contains("Templates").click({force: true})
}
})
cy.visit(`${Cypress.config().baseUrl}/builder/portal/apps/templates`)
// Filter Business Apps Templates
cy.get(".template-category-filters").within(() => {
cy.get('[data-cy="Business Apps"]').click()

8
packages/builder/cypress/integration/templates/directories/directoriesTemplateDetails.spec.js

@ -7,13 +7,7 @@ filterTests(["all"], () => {
cy.login()
// Template navigation
cy.request(`${Cypress.config().baseUrl}/api/applications?status=all`)
.its("body")
.then(val => {
if (val.length > 0) {
cy.get(".spectrum-Button").contains("Templates").click({force: true})
}
})
cy.visit(`${Cypress.config().baseUrl}/builder/portal/apps/templates`)
// Filter Directories Templates
cy.get(".template-category-filters").within(() => {

8
packages/builder/cypress/integration/templates/forms/formsTemplateDetails.spec.js

@ -7,13 +7,7 @@ filterTests(["all"], () => {
cy.login()
// Template navigation
cy.request(`${Cypress.config().baseUrl}/api/applications?status=all`)
.its("body")
.then(val => {
if (val.length > 0) {
cy.get(".spectrum-Button").contains("Templates").click({force: true})
}
})
cy.visit(`${Cypress.config().baseUrl}/builder/portal/apps/templates`)
// Filter Forms Templates
cy.get(".template-category-filters").within(() => {

8
packages/builder/cypress/integration/templates/healthcare/healthcareTemplateDetails.spec.js

@ -7,13 +7,7 @@ filterTests(["all"], () => {
cy.login()
// Template navigation
cy.request(`${Cypress.config().baseUrl}/api/applications?status=all`)
.its("body")
.then(val => {
if (val.length > 0) {
cy.get(".spectrum-Button").contains("Templates").click({force: true})
}
})
cy.visit(`${Cypress.config().baseUrl}/builder/portal/apps/templates`)
// Filter Healthcare Templates
cy.get(".template-category-filters").within(() => {

8
packages/builder/cypress/integration/templates/legal/legalTemplateDetails.spec.js

@ -7,13 +7,7 @@ filterTests(["all"], () => {
cy.login()
// Template navigation
cy.request(`${Cypress.config().baseUrl}/api/applications?status=all`)
.its("body")
.then(val => {
if (val.length > 0) {
cy.get(".spectrum-Button").contains("Templates").click({force: true})
}
})
cy.visit(`${Cypress.config().baseUrl}/builder/portal/apps/templates`)
// Filter Legal Templates
cy.get(".template-category-filters").within(() => {

8
packages/builder/cypress/integration/templates/logistics/logisticsTemplateDetails.spec.js

@ -7,13 +7,7 @@ filterTests(["all"], () => {
cy.login()
// Template navigation
cy.request(`${Cypress.config().baseUrl}/api/applications?status=all`)
.its("body")
.then(val => {
if (val.length > 0) {
cy.get(".spectrum-Button").contains("Templates").click({force: true})
}
})
cy.visit(`${Cypress.config().baseUrl}/builder/portal/apps/templates`)
// Filter Logistics Templates
cy.get(".template-category-filters").within(() => {

8
packages/builder/cypress/integration/templates/manufacturing/manufacturingTemplateDetails.spec.js

@ -7,13 +7,7 @@ filterTests(["all"], () => {
cy.login()
// Template navigation
cy.request(`${Cypress.config().baseUrl}/api/applications?status=all`)
.its("body")
.then(val => {
if (val.length > 0) {
cy.get(".spectrum-Button").contains("Templates").click({force: true})
}
})
cy.visit(`${Cypress.config().baseUrl}/builder/portal/apps/templates`)
// Filter Manufacturing Templates
cy.get(".template-category-filters").within(() => {

44
packages/builder/cypress/integration/templates/marketing/leadGenerationForm.spec.js

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

8
packages/builder/cypress/integration/templates/marketing/marketingTemplateDetails.spec.js

@ -7,13 +7,7 @@ filterTests(["all"], () => {
cy.login()
// Template navigation
cy.request(`${Cypress.config().baseUrl}/api/applications?status=all`)
.its("body")
.then(val => {
if (val.length > 0) {
cy.get(".spectrum-Button").contains("Templates").click({force: true})
}
})
cy.visit(`${Cypress.config().baseUrl}/builder/portal/apps/templates`)
// Filter Marketing Templates
cy.get(".template-category-filters").within(() => {

8
packages/builder/cypress/integration/templates/operations/operationsTemplateDetails.spec.js

@ -7,13 +7,7 @@ filterTests(["all"], () => {
cy.login()
// Template navigation
cy.request(`${Cypress.config().baseUrl}/api/applications?status=all`)
.its("body")
.then(val => {
if (val.length > 0) {
cy.get(".spectrum-Button").contains("Templates").click({force: true})
}
})
cy.visit(`${Cypress.config().baseUrl}/builder/portal/apps/templates`)
// Filter Operations Templates
cy.get(".template-category-filters").within(() => {

18
packages/builder/cypress/integration/templates/portals/portalsTemplateDetails.spec.js

@ -7,21 +7,15 @@ filterTests(["all"], () => {
cy.login()
// Template navigation
cy.request(`${Cypress.config().baseUrl}/api/applications?status=all`)
.its("body")
.then(val => {
if (val.length > 0) {
cy.get(".spectrum-Button").contains("Templates").click({force: true})
}
})
cy.visit(`${Cypress.config().baseUrl}/builder/portal/apps/templates`)
// Filter Portal Templates
cy.get(".template-category-filters").within(() => {
cy.get('[data-cy="Portal"]').click()
})
})
it("should verify the details option for Portal templates", () => {
// Filter Portal Templates
cy.get(".template-category-filters").within(() => {
cy.get('[data-cy="Portal"]').click()
})
cy.get(".template-grid").find(".template-card").its('length')
.then(len => {
for (let i = 0; i < len; i++) {

8
packages/builder/cypress/integration/templates/professionalServices/professionalServicesTemplateDetails.spec.js

@ -7,13 +7,7 @@ filterTests(["all"], () => {
cy.login()
// Template navigation
cy.request(`${Cypress.config().baseUrl}/api/applications?status=all`)
.its("body")
.then(val => {
if (val.length > 0) {
cy.get(".spectrum-Button").contains("Templates").click({force: true})
}
})
cy.visit(`${Cypress.config().baseUrl}/builder/portal/apps/templates`)
// Filter Professional Services Templates
cy.get(".template-category-filters").within(() => {

141
packages/builder/cypress/support/commands.js

@ -32,10 +32,19 @@ Cypress.Commands.add("login", () => {
})
})
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", () => {
cy.get(".spectrum-Modal").within(() => {
cy.get(".close-icon").click()
cy.wait(500)
cy.wait(1000) // Wait for modal to close
})
})
@ -209,6 +218,109 @@ 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", () => {
const appName = "Cypress Tests"
cy.deleteApp(appName)
@ -222,6 +334,21 @@ Cypress.Commands.add("createTestTableWithData", () => {
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) => {
if (!initialTable) {
cy.navigateToDataSection()
@ -671,15 +798,3 @@ Cypress.Commands.add("createRestQuery", (method, restUrl, queryPrettyName) => {
.should("contain", method)
.and("contain", queryPrettyName)
})
Cypress.Commands.add("templateNavigation", () => {
// Navigates to templates section
cy.request(`${Cypress.config().baseUrl}/api/applications?status=all`)
.its("body")
.then(val => {
// Templates button needs clicked if apps already exist
if (val.length > 0) {
cy.get(".spectrum-Button").contains("Templates").click({ force: true })
}
})
})

10
packages/builder/package.json

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

2
packages/builder/src/builderStore/store/automation/index.js

@ -105,9 +105,7 @@ const automationActions = store => ({
},
select: automation => {
store.update(state => {
let testResults = state.selectedAutomation?.testResults
state.selectedAutomation = new Automation(cloneDeep(automation))
state.selectedAutomation.testResults = testResults
state.selectedBlock = null
return state
})

107
packages/builder/src/components/automation/AutomationBuilder/FlowChart/FlowChart.svelte

@ -14,7 +14,6 @@
} from "@budibase/bbui"
export let automation
let testDataModal
let blocks
let confirmDeleteDialog
@ -41,66 +40,70 @@
</script>
<div class="canvas">
<div class="content">
<div class="title">
<div class="subtitle">
<Heading size="S">{automation.name}</Heading>
<div style="display:flex; align-items: center;">
<div class="iconPadding">
<div class="icon">
<Icon
on:click={confirmDeleteDialog.show}
hoverable
size="M"
name="DeleteOutline"
/>
</div>
</div>
<div style="float: left; padding-left: var(--spacing-xl);">
<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 class="icon">
<Icon
on:click={confirmDeleteDialog.show}
hoverable
size="M"
name="DeleteOutline"
/>
</div>
<ActionButton
on:click={() => {
testDataModal.show()
}}
icon="MultipleCheck"
size="M">Run test</ActionButton
>
<div style="padding-left: var(--spacing-m);">
<ActionButton
disabled={!$automationStore.selectedAutomation?.testResults}
on:click={() => {
testDataModal.show()
$automationStore.selectedAutomation.automation.showTestPanel = true
}}
icon="MultipleCheck"
size="M">Run test</ActionButton
size="M">Test Details</ActionButton
>
</div>
</div>
</div>
{#each blocks as block, idx (block.id)}
<div
class="block"
animate:flip={{ duration: 500 }}
in:fly|local={{ x: 500, duration: 1500 }}
>
{#if block.stepId !== "LOOP"}
<FlowItem {testDataModal} {block} />
{/if}
</div>
{/each}
</div>
<ConfirmDialog
bind:this={confirmDeleteDialog}
okText="Delete Automation"
onOk={deleteAutomation}
title="Confirm Deletion"
>
Are you sure you wish to delete the automation
<i>{automation.name}?</i>
This action cannot be undone.
</ConfirmDialog>
<Modal bind:this={testDataModal} width="30%">
<TestDataModal />
</Modal>
</div>
<div class="content">
{#each blocks as block, idx (block.id)}
<div
class="block"
animate:flip={{ duration: 500 }}
in:fly|local={{ x: 500, duration: 500 }}
out:fly|local={{ x: 500, duration: 500 }}
>
{#if block.stepId !== "LOOP"}
<FlowItem {testDataModal} {block} />
{/if}
</div>
{/each}
</div>
<ConfirmDialog
bind:this={confirmDeleteDialog}
okText="Delete Automation"
onOk={deleteAutomation}
title="Confirm Deletion"
>
Are you sure you wish to delete the automation
<i>{automation.name}?</i>
This action cannot be undone.
</ConfirmDialog>
<Modal bind:this={testDataModal} width="30%">
<TestDataModal />
</Modal>
<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 */
.canvas > *:last-child {
padding-bottom: 40px;
@ -128,10 +131,6 @@
justify-content: space-between;
align-items: center;
}
.iconPadding {
padding-top: var(--spacing-s);
}
.icon {
cursor: pointer;
padding-right: var(--spacing-m);

75
packages/builder/src/components/automation/AutomationBuilder/FlowChart/FlowItem.svelte

@ -1,40 +1,33 @@
<script>
import FlowItemHeader from "./FlowItemHeader.svelte"
import { automationStore } from "builderStore"
import {
Icon,
Divider,
Layout,
Body,
Detail,
Modal,
Button,
StatusLight,
Select,
ActionButton,
notifications,
} from "@budibase/bbui"
import AutomationBlockSetup from "../../SetupPanel/AutomationBlockSetup.svelte"
import CreateWebhookModal from "components/automation/Shared/CreateWebhookModal.svelte"
import ResultsModal from "./ResultsModal.svelte"
import ActionModal from "./ActionModal.svelte"
import { externalActions } from "./ExternalActions"
export let block
export let testDataModal
let selected
let webhookModal
let actionModal
let resultsModal
let blockComplete
let showLooping = false
$: rowControl = $automationStore.selectedAutomation.automation.rowControl
$: showBindingPicker =
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"
$: selected = $automationStore.selectedBlock?.id === block.id
@ -182,63 +175,7 @@
{/if}
{/if}
<div class="blockSection">
<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>
<FlowItemHeader bind:blockComplete {block} {testDataModal} />
{#if !blockComplete}
<Divider noMargin />
<div class="blockSection">
@ -256,7 +193,7 @@
on:change={toggleFieldControl}
defaultValue="Use values"
autoWidth
value={rowControl ? "Use bindings" : "Use values"}
value={block.rowControl ? "Use bindings" : "Use values"}
options={["Use values", "Use bindings"]}
placeholder={null}
/>
@ -283,10 +220,6 @@
</div>
{/if}
<Modal bind:this={resultsModal} width="30%">
<ResultsModal {isTrigger} {testResult} />
</Modal>
<Modal bind:this={actionModal} width="30%">
<ActionModal {blockIdx} bind:blockComplete />
</Modal>

111
packages/builder/src/components/automation/AutomationBuilder/FlowChart/FlowItemHeader.svelte

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

133
packages/builder/src/components/automation/AutomationBuilder/FlowChart/ResultsModal.svelte

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

1
packages/builder/src/components/automation/AutomationBuilder/FlowChart/TestDataModal.svelte

@ -51,6 +51,7 @@
$automationStore.selectedAutomation?.automation,
testData
)
$automationStore.selectedAutomation.automation.showTestPanel = true
} catch (error) {
notifications.error("Error testing notification")
}

146
packages/builder/src/components/automation/AutomationBuilder/TestPanel.svelte

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

4
packages/builder/src/components/backend/DataTable/modals/CreateEditColumn.svelte

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

2
packages/builder/src/components/backend/TableNavigator/modals/CreateTableModal.svelte

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

144
packages/builder/src/components/common/AppLockModal.svelte

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

55
packages/builder/src/components/common/DashCard.svelte

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

47
packages/builder/src/components/common/EditableIcon.svelte

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

18
packages/builder/src/components/deploy/VersionModal.svelte

@ -11,6 +11,16 @@
import { API } from "api"
import clientPackage from "@budibase/client/package.json"
export function show() {
updateModal.show()
}
export function hide() {
updateModal.hide()
}
export let hideIcon = false
let updateModal
$: appId = $store.appId
@ -57,9 +67,11 @@
}
</script>
<div class="icon-wrapper" class:highlight={updateAvailable}>
<Icon name="Refresh" hoverable on:click={updateModal.show} />
</div>
{#if !hideIcon}
<div class="icon-wrapper" class:highlight={updateAvailable}>
<Icon name="Refresh" hoverable on:click={updateModal.show} />
</div>
{/if}
<Modal bind:this={updateModal}>
<ModalContent
title="App version"

48
packages/builder/src/components/start/AppRow.svelte

@ -1,33 +1,26 @@
<script>
import {
Heading,
Button,
Icon,
ActionMenu,
MenuItem,
StatusLight,
} from "@budibase/bbui"
import { Heading, Button, Icon, ActionMenu, MenuItem } from "@budibase/bbui"
import AppLockModal from "../common/AppLockModal.svelte"
import { processStringSync } from "@budibase/string-templates"
export let app
export let exportApp
export let viewApp
export let editApp
export let updateApp
export let deleteApp
export let previewApp
export let unpublishApp
export let appOverview
export let releaseLock
export let editIcon
export let copyAppId
</script>
<div class="title">
<div class="title" data-cy={`${app.devId}`}>
<div style="display: flex;">
<div class="app-icon" style="color: {app.icon?.color || ''}">
<Icon size="XL" name={app.icon?.name || "Apps"} />
</div>
<div class="name" on:click={() => editApp(app)}>
<div class="name" on:click={() => appOverview(app)}>
<Heading size="XS">
{app.name}
</Heading>
@ -44,19 +37,7 @@
{/if}
</div>
<div class="desktop">
<StatusLight
positive={!app.lockedYou && !app.lockedOther}
notice={app.lockedYou}
negative={app.lockedOther}
>
{#if app.lockedYou}
Locked by you
{:else if app.lockedOther}
Locked by {app.lockedBy.email}
{:else}
Open
{/if}
</StatusLight>
<AppLockModal {app} buttonSize="S" />
</div>
<div class="desktop">
<div class="app-status">
@ -71,23 +52,15 @@
</div>
<div data-cy={`row_actions_${app.appId}`}>
<div class="app-row-actions">
{#if app.deployed}
<Button size="S" secondary quiet on:click={() => viewApp(app)}
>View app
</Button>
{:else}
<Button size="S" secondary quiet on:click={() => previewApp(app)}
>Preview
</Button>
{/if}
<Button
size="S"
cta
secondary
quiet
disabled={app.lockedOther}
on:click={() => editApp(app)}
>
Edit
>Edit
</Button>
<Button size="S" cta on:click={() => appOverview(app)}>View</Button>
</div>
<ActionMenu align="right" dataCy="app-row-actions-menu-popover">
<span slot="control" class="app-row-actions-icon">
@ -123,6 +96,7 @@
}
.app-status {
display: grid;
grid-gap: var(--spacing-s);
grid-template-columns: 24px 100px;
}
.app-status span.disabled {

35
packages/builder/src/pages/builder/app/[application]/automate/_layout.svelte

@ -4,7 +4,12 @@
import AutomationPanel from "components/automation/AutomationPanel/AutomationPanel.svelte"
import CreateAutomationModal from "components/automation/AutomationPanel/CreateAutomationModal.svelte"
import CreateWebhookModal from "components/automation/Shared/CreateWebhookModal.svelte"
$: automation = $automationStore.automations[0]
import TestPanel from "components/automation/AutomationBuilder/TestPanel.svelte"
$: automation =
$automationStore.selectedAutomation?.automation ||
$automationStore.automations[0]
let modal
let webhookModal
</script>
@ -39,6 +44,12 @@
</div>
{/if}
</div>
{#if automation?.showTestPanel}
<div class="setup">
<TestPanel {automation} />
</div>
{/if}
<Modal bind:this={modal}>
<CreateAutomationModal {webhookModal} />
</Modal>
@ -52,7 +63,9 @@
flex: 1 1 auto;
height: 0;
display: grid;
grid-template-columns: 260px minmax(510px, 1fr);
grid-auto-flow: column dense;
grid-template-columns: 260px minmax(510px, 1fr) fit-content(500px);
overflow: hidden;
}
.nav {
@ -64,17 +77,18 @@
border-right: var(--border-light);
background-color: var(--background);
padding-bottom: 60px;
overflow: hidden;
}
.content {
position: relative;
padding: var(--spacing-l) 40px;
padding-top: var(--spacing-l);
display: flex;
flex-direction: column;
justify-content: flex-start;
align-items: stretch;
gap: var(--spacing-l);
overflow: hidden;
overflow: auto;
}
.centered {
top: 0;
@ -92,4 +106,17 @@
.main {
width: 300px;
}
.setup {
padding-top: var(--spectrum-global-dimension-size-200);
border-left: var(--border-light);
display: flex;
flex-direction: column;
justify-content: flex-start;
align-items: stretch;
gap: var(--spacing-l);
background-color: var(--background);
grid-column: 3;
overflow: auto;
}
</style>

6
packages/builder/src/pages/builder/auth/login.svelte

@ -28,7 +28,7 @@
async function login() {
try {
await auth.login({
username,
username: username.trim(),
password,
})
if ($auth?.user?.forceResetPassword) {
@ -80,7 +80,9 @@
/>
</Layout>
<Layout gap="XS" noPadding>
<Button cta on:click={login}>Sign in to {company}</Button>
<Button cta disabled={!username && !password} on:click={login}
>Sign in to {company}</Button
>
<ActionButton quiet on:click={() => $goto("./forgot")}>
Forgot password?
</ActionButton>

6
packages/builder/src/pages/builder/portal/_layout.svelte

@ -203,7 +203,9 @@
<MenuItem icon="UserDeveloper" on:click={() => $goto("../apps")}>
Close developer mode
</MenuItem>
<MenuItem icon="LogOut" on:click={logout}>Log out</MenuItem>
<MenuItem dataCy="user-logout" icon="LogOut" on:click={logout}
>Log out
</MenuItem>
</ActionMenu>
</div>
</div>
@ -332,7 +334,7 @@
.mobile-toggle,
.user-dropdown {
flex: 1 1 0;
flex: 0 1 0;
}
/* Reduce BBUI page padding */

8
packages/builder/src/pages/builder/portal/apps/create.svelte

@ -9,6 +9,7 @@
Body,
Modal,
Divider,
ActionButton,
} from "@budibase/bbui"
import CreateAppModal from "components/start/CreateAppModal.svelte"
import TemplateDisplay from "components/common/TemplateDisplay.svelte"
@ -60,16 +61,15 @@
<Page wide>
<Layout noPadding gap="XL">
<span>
<Button
quiet
<ActionButton
secondary
icon={"ChevronLeft"}
icon={"ArrowLeft"}
on:click={() => {
$goto("../")
}}
>
Back
</Button>
</ActionButton>
</span>
<div class="title">

98
packages/builder/src/pages/builder/portal/apps/index.svelte

@ -2,7 +2,6 @@
import {
Heading,
Layout,
Detail,
Button,
Input,
Select,
@ -11,7 +10,6 @@
notifications,
Body,
Search,
Divider,
Helpers,
} from "@budibase/bbui"
import TemplateDisplay from "components/common/TemplateDisplay.svelte"
@ -67,6 +65,9 @@
app?.name?.toLowerCase().includes(searchTerm.toLowerCase())
)
$: lockedApps = filteredApps.filter(app => app?.lockedYou || app?.lockedOther)
$: unlocked = lockedApps?.length == 0
const enrichApps = (apps, user, sortBy) => {
const enrichedApps = apps.map(app => ({
...app,
@ -180,8 +181,8 @@
}
}
const previewApp = app => {
window.open(`/${app.devId}`)
const appOverview = app => {
$goto(`../overview/${app.devId}`)
}
const editApp = app => {
@ -305,7 +306,7 @@
</script>
<Page wide>
<Layout noPadding gap="XL">
<Layout noPadding gap="M">
{#if loaded}
<div class="title">
<div class="welcome">
@ -315,29 +316,17 @@
{welcomeBody}
</Body>
</Layout>
<div class="buttons">
<Button
dataCy="create-app-btn"
size="M"
icon="Add"
cta
on:click={initiateAppCreation}
>
{createAppButtonText}
</Button>
{#if $apps?.length > 0}
{#if !$apps?.length}
<div class="buttons">
<Button
icon="Experience"
dataCy="create-app-btn"
size="M"
quiet
secondary
on:click={$goto("/builder/portal/apps/templates")}
icon="Add"
cta
on:click={initiateAppCreation}
>
Templates
{createAppButtonText}
</Button>
{/if}
{#if !$apps?.length}
<Button
dataCy="import-app-btn"
icon="Import"
@ -348,15 +337,9 @@
>
Import app
</Button>
{/if}
</div>
</div>
<div>
<Layout gap="S" justifyItems="center">
<img class="img-logo img-size" alt="logo" src={Logo} />
</Layout>
</div>
{/if}
</div>
<Divider size="S" />
</div>
{#if !$apps?.length && $templates?.length}
@ -364,9 +347,42 @@
{/if}
{#if enrichedApps.length}
<Layout noPadding gap="S">
<Layout noPadding gap="L">
<div class="title">
<Detail size="L">Apps</Detail>
<div class="buttons">
<Button
dataCy="create-app-btn"
size="M"
icon="Add"
cta
on:click={initiateAppCreation}
>
{createAppButtonText}
</Button>
{#if $apps?.length > 0}
<Button
icon="Experience"
size="M"
quiet
secondary
on:click={$goto("/builder/portal/apps/templates")}
>
Templates
</Button>
{/if}
{#if !$apps?.length}
<Button
dataCy="import-app-btn"
icon="Import"
size="L"
quiet
secondary
on:click={initiateAppImport}
>
Import app
</Button>
{/if}
</div>
{#if enrichedApps.length > 1}
<div class="app-actions">
{#if cloud}
@ -398,7 +414,7 @@
{/if}
</div>
<div class="appTable">
<div class="appTable" class:unlocked>
{#each filteredApps as app (app.appId)}
<AppRow
{copyAppId}
@ -411,7 +427,7 @@
{exportApp}
{deleteApp}
{updateApp}
{previewApp}
{appOverview}
/>
{/each}
</div>
@ -472,6 +488,9 @@
<ChooseIconModal app={selectedApp} bind:this={iconModal} />
<style>
.appTable {
border-top: var(--border-light);
}
.app-actions {
display: flex;
}
@ -479,7 +498,7 @@
margin-right: 10px;
}
.title .welcome > .buttons {
padding-top: 30px;
padding-top: var(--spacing-l);
}
.title {
display: flex;
@ -515,6 +534,11 @@
grid-template-columns: 1fr 1fr 1fr 1fr auto;
align-items: center;
}
.appTable.unlocked {
grid-template-columns: 1fr 1fr auto 1fr auto;
}
.appTable :global(> div) {
height: 70px;
display: grid;

9
packages/builder/src/pages/builder/portal/apps/templates.svelte

@ -1,6 +1,6 @@
<script>
import { goto } from "@roxi/routify"
import { Layout, Page, notifications, Button } from "@budibase/bbui"
import { Layout, Page, notifications, ActionButton } from "@budibase/bbui"
import TemplateDisplay from "components/common/TemplateDisplay.svelte"
import { onMount } from "svelte"
import { templates } from "stores/portal"
@ -25,16 +25,15 @@
<Page wide>
<Layout noPadding gap="XL">
<span>
<Button
quiet
<ActionButton
secondary
icon={"ChevronLeft"}
icon={"ArrowLeft"}
on:click={() => {
$goto("../")
}}
>
Back
</Button>
</ActionButton>
</span>
{#if loaded && $templates?.length}
<TemplateDisplay templates={$templates} />

413
packages/builder/src/pages/builder/portal/overview/[application]/index.svelte

@ -0,0 +1,413 @@
<script>
import { goto } from "@roxi/routify"
import {
Layout,
Page,
Button,
ActionButton,
ButtonGroup,
Heading,
Tab,
Tabs,
notifications,
ProgressCircle,
Input,
ActionMenu,
MenuItem,
Icon,
Helpers,
} from "@budibase/bbui"
import OverviewTab from "../_components/OverviewTab.svelte"
import SettingsTab from "../_components/SettingsTab.svelte"
import { API } from "api"
import { store } from "builderStore"
import { apps, auth } from "stores/portal"
import analytics, { Events, EventSource } from "analytics"
import { AppStatus } from "constants"
import AppLockModal from "components/common/AppLockModal.svelte"
import EditableIcon from "components/common/EditableIcon.svelte"
import ConfirmDialog from "components/common/ConfirmDialog.svelte"
import { checkIncomingDeploymentStatus } from "components/deploy/utils"
import { onDestroy, onMount } from "svelte"
export let application
let promise = getPackage()
let loaded = false
let deletionModal
let unpublishModal
let appName = ""
// App
$: filteredApps = $apps.filter(app => app.devId === application)
$: selectedApp = filteredApps?.length ? filteredApps[0] : null
// Locking
$: lockedBy = selectedApp?.lockedBy
$: lockedByYou = $auth.user.email === lockedBy?.email
$: lockIdentifer = `${
lockedBy && Object.prototype.hasOwnProperty.call(lockedBy, "firstName")
? lockedBy?.firstName
: lockedBy?.email
}`
// App deployments
$: deployments = []
$: latestDeployments = deployments
.filter(
deployment =>
deployment.status === "SUCCESS" && application === deployment.appId
)
.sort((a, b) => a.updatedAt > b.updatedAt)
$: isPublished =
selectedApp?.status === AppStatus.DEPLOYED && latestDeployments?.length > 0
$: appUrl = `${window.origin}/app${selectedApp?.url}`
$: tabs = ["Overview", "Automation History", "Backups", "Settings"]
$: selectedTab = "Overview"
const backToAppList = () => {
$goto(`../../../portal/`)
}
const handleTabChange = tabKey => {
if (tabKey === selectedTab) {
return
} else if (tabKey && tabs.indexOf(tabKey) > -1) {
selectedTab = tabKey
} else {
notifications.error("Invalid tab key")
}
}
async function getPackage() {
try {
const pkg = await API.fetchAppPackage(application)
await store.actions.initialise(pkg)
loaded = true
return pkg
} catch (error) {
notifications.error(`Error initialising app: ${error?.message}`)
}
}
const reviewPendingDeployments = (deployments, newDeployments) => {
if (deployments.length > 0) {
const pending = checkIncomingDeploymentStatus(deployments, newDeployments)
if (pending.length) {
notifications.warning(
"Deployment has been queued and will be processed shortly"
)
}
}
}
async function fetchDeployments() {
try {
const newDeployments = await API.getAppDeployments()
reviewPendingDeployments(deployments, newDeployments)
return newDeployments
} catch (err) {
notifications.error("Error fetching deployment history")
}
}
const viewApp = () => {
if (isPublished) {
analytics.captureEvent(Events.APP.VIEW_PUBLISHED, {
appId: $store.appId,
eventSource: EventSource.PORTAL,
})
window.open(appUrl, "_blank")
}
}
const editApp = app => {
if (lockedBy && !lockedByYou) {
notifications.warning(
`App locked by ${lockIdentifer}. Please allow lock to expire or have them unlock this app.`
)
return
}
$goto(`../../../app/${app.devId}`)
}
const copyAppId = async app => {
await Helpers.copyToClipboard(app.prodId)
notifications.success("App ID copied to clipboard.")
}
const exportApp = app => {
const id = isPublished ? app.prodId : app.devId
const appName = encodeURIComponent(app.name)
window.location = `/api/backups/export?appId=${id}&appname=${appName}`
}
const unpublishApp = app => {
selectedApp = app
unpublishModal.show()
}
const confirmUnpublishApp = async () => {
if (!selectedApp) {
return
}
try {
analytics.captureEvent(Events.APP.UNPUBLISHED, {
appId: selectedApp.appId,
})
await API.unpublishApp(selectedApp.prodId)
await apps.load()
notifications.success("App unpublished successfully")
} catch (err) {
notifications.error("Error unpublishing app")
}
}
const deleteApp = app => {
selectedApp = app
deletionModal.show()
}
const confirmDeleteApp = async () => {
if (!selectedApp) {
return
}
try {
await API.deleteApp(selectedApp?.devId)
backToAppList()
notifications.success("App deleted successfully")
} catch (err) {
notifications.error("Error deleting app")
}
selectedApp = null
appName = null
}
onDestroy(() => {
store.actions.reset()
})
onMount(async () => {
try {
if (!apps.length) {
await apps.load()
}
await API.syncApp(application)
deployments = await fetchDeployments()
} catch (error) {
notifications.error("Error initialising app overview")
}
})
</script>
<span class="overview-wrap">
<Page wide noPadding>
{#await promise}
<span class="page-header">
<ActionButton secondary icon={"ArrowLeft"} on:click={backToAppList}>
Back
</ActionButton>
</span>
<div class="loading">
<ProgressCircle size="XL" />
</div>
{:then _}
<Layout paddingX="XXL" paddingY="XXL" gap="XL">
<span class="page-header" class:loaded>
<ActionButton secondary icon={"ArrowLeft"} on:click={backToAppList}>
Back
</ActionButton>
</span>
<div class="overview-header">
<div class="app-title">
<div class="app-logo">
<div
class="app-icon"
style="color: {selectedApp?.icon?.color || ''}"
>
<EditableIcon
app={selectedApp}
size="XL"
name={selectedApp?.icon?.name || "Apps"}
/>
</div>
</div>
<div class="app-details">
<Heading size="M">{selectedApp?.name}</Heading>
<div class="app-url">{appUrl}</div>
</div>
</div>
<div class="header-right">
<AppLockModal app={selectedApp} />
<ButtonGroup gap="XS">
<Button
size="M"
quiet
secondary
icon="Globe"
disabled={!isPublished}
on:click={viewApp}
dataCy="view-app"
>
View app
</Button>
<Button
size="M"
cta
icon="Edit"
disabled={lockedBy && !lockedByYou}
on:click={() => {
editApp(selectedApp)
}}
>
<span>Edit</span>
</Button>
</ButtonGroup>
<ActionMenu align="right" dataCy="app-overview-menu-popover">
<span slot="control" class="app-overview-actions-icon">
<Icon hoverable name="More" />
</span>
<MenuItem on:click={() => exportApp(selectedApp)} icon="Download">
Export
</MenuItem>
{#if isPublished}
<MenuItem
on:click={() => unpublishApp(selectedApp)}
icon="GlobeRemove"
>
Unpublish
</MenuItem>
<MenuItem on:click={() => copyAppId(selectedApp)} icon="Copy">
Copy App ID
</MenuItem>
{/if}
{#if !isPublished}
<MenuItem on:click={() => deleteApp(selectedApp)} icon="Delete">
Delete
</MenuItem>
{/if}
</ActionMenu>
</div>
</div>
</Layout>
<div class="tab-wrap">
<Tabs
selected={selectedTab}
noPadding
on:select={e => {
selectedTab = e.detail
}}
>
<Tab title="Overview">
<OverviewTab
app={selectedApp}
deployments={latestDeployments}
navigateTab={handleTabChange}
/>
</Tab>
{#if false}
<Tab title="Automation History">
<div class="container">Automation History contents</div>
</Tab>
<Tab title="Backups">
<div class="container">Backups contents</div>
</Tab>
{/if}
<Tab title="Settings">
<SettingsTab app={selectedApp} />
</Tab>
</Tabs>
</div>
<ConfirmDialog
bind:this={deletionModal}
title="Confirm deletion"
okText="Delete app"
onOk={confirmDeleteApp}
onCancel={() => (appName = null)}
disabled={appName !== selectedApp?.name}
>
Are you sure you want to delete the app <b>{selectedApp?.name}</b>?
<p>Please enter the app name below to confirm.</p>
<Input
bind:value={appName}
data-cy="delete-app-confirmation"
placeholder={selectedApp?.name}
/>
</ConfirmDialog>
<ConfirmDialog
bind:this={unpublishModal}
title="Confirm unpublish"
okText="Unpublish app"
onOk={confirmUnpublishApp}
dataCy={"unpublish-modal"}
>
Are you sure you want to unpublish the app <b>{selectedApp?.name}</b>?
</ConfirmDialog>
{:catch error}
<p>Something went wrong: {error.message}</p>
{/await}
</Page>
</span>
<style>
.app-url {
color: var(--spectrum-global-color-gray-600);
}
.loading {
display: flex;
justify-content: center;
align-items: center;
flex: 1;
}
.overview-header {
display: flex;
justify-content: space-between;
}
.page-header.loaded {
padding: 0px;
}
.overview-wrap :global(> div > .container),
.tab-wrap :global(.spectrum-Tabs) {
background-color: var(--background);
background-clip: padding-box;
}
@media (max-width: 1000px) {
.overview-header {
flex-direction: column;
gap: var(--spacing-l);
}
}
@media (max-width: 640px) {
.overview-wrap :global(.content > *) {
padding: calc(var(--spacing-xl) * 1.5) !important;
}
}
.app-title {
display: flex;
gap: var(--spacing-m);
}
.header-right {
display: flex;
align-items: center;
gap: var(--spacing-xl);
}
.app-details :global(.spectrum-Heading) {
line-height: 1em;
margin-bottom: var(--spacing-s);
}
.tab-wrap :global(.spectrum-Tabs) {
padding-left: var(--spectrum-alias-grid-gutter-large);
padding-right: var(--spectrum-alias-grid-gutter-large);
}
.page-header {
padding-left: var(--spectrum-alias-grid-gutter-large);
padding-right: var(--spectrum-alias-grid-gutter-large);
padding-top: var(--spectrum-alias-grid-gutter-large);
}
</style>

11
packages/builder/src/pages/builder/portal/overview/_components/AutomationTab.svelte

@ -0,0 +1,11 @@
<script>
//export let app
</script>
<div class="automation-tab" />
<style>
.automation-tab {
color: pink;
}
</style>

250
packages/builder/src/pages/builder/portal/overview/_components/OverviewTab.svelte

@ -0,0 +1,250 @@
<script>
import DashCard from "components/common/DashCard.svelte"
import { AppStatus } from "constants"
import {
Icon,
Heading,
Link,
Avatar,
notifications,
Layout,
} from "@budibase/bbui"
import { store } from "builderStore"
import clientPackage from "@budibase/client/package.json"
import { processStringSync } from "@budibase/string-templates"
import { users, auth } from "stores/portal"
export let app
export let deployments
export let navigateTab
const userInit = async () => {
try {
await users.init()
} catch (error) {
notifications.error("Error getting user list")
}
}
let userPromise = userInit()
$: updateAvailable = clientPackage.version !== $store.version
$: isPublished = app && app?.status === AppStatus.DEPLOYED
$: appEditorId = !app?.updatedBy ? $auth.user._id : app?.updatedBy
$: appEditorText = appEditor?.firstName || appEditor?.email
$: filteredUsers = !appEditorId
? []
: $users.filter(user => user._id === appEditorId)
$: appEditor = filteredUsers.length ? filteredUsers[0] : null
const getInitials = user => {
let initials = ""
initials += user.firstName ? user.firstName[0] : ""
initials += user.lastName ? user.lastName[0] : ""
return initials == "" ? user.email[0] : initials
}
</script>
<div class="overview-tab">
<Layout paddingX="XXL" paddingY="XXL" gap="XL">
<div class="top">
<DashCard title={"App Status"} dataCy={"app-status"}>
<div class="status-content">
<div class="status-display">
{#if isPublished}
<Icon name="GlobeCheck" size="XL" disabled={false} />
<span>Published</span>
{:else}
<Icon name="GlobeStrike" size="XL" disabled={true} />
<span class="disabled"> Unpublished </span>
{/if}
</div>
<div class="status-text">
{#if deployments?.length}
{processStringSync(
"Last published {{ duration time 'millisecond' }} ago",
{
time:
new Date().getTime() -
new Date(deployments[0].updatedAt).getTime(),
}
)}
{/if}
{#if !deployments?.length}
-
{/if}
</div>
</div>
</DashCard>
<DashCard title={"Last Edited"} dataCy={"edited-by"}>
<div class="last-edited-content">
{#await userPromise}
<Avatar size="M" initials={"-"} />
{:then _}
<div class="updated-by">
{#if appEditor}
<Avatar size="M" initials={getInitials(appEditor)} />
<div class="editor-name">
{appEditor._id === $auth.user._id ? "You" : appEditorText}
</div>
{/if}
</div>
{:catch error}
<p>Could not fetch user: {error.message}</p>
{/await}
<div class="last-edit-text">
{#if app}
{processStringSync(
"Last edited {{ duration time 'millisecond' }} ago",
{
time:
new Date().getTime() - new Date(app?.updatedAt).getTime(),
}
)}
{/if}
</div>
</div>
</DashCard>
<DashCard
title={"App Version"}
showIcon={true}
action={() => {
navigateTab("Settings")
}}
dataCy={"app-version"}
>
<div class="version-content" data-cy={$store.version}>
<Heading size="XS">{$store.version}</Heading>
{#if updateAvailable}
<div class="version-status">
New version <strong>{clientPackage.version}</strong> is available
-
<Link
on:click={() => {
if (typeof navigateTab === "function") {
navigateTab("Settings")
}
}}
>
Update
</Link>
</div>
{:else}
<div class="version-status">You're running the latest!</div>
{/if}
</div>
</DashCard>
</div>
{#if false}
<div class="bottom">
<DashCard
title={"Automation History"}
action={() => {
navigateTab("Automation History")
}}
dataCy={"automation-history"}
>
<div class="automation-content">
<div class="automation-metrics">
<div class="succeeded">
<Heading size="XL">0</Heading>
<div class="metric-info">
<Icon name="CheckmarkCircle" />
Success
</div>
</div>
<div class="failed">
<Heading size="XL">0</Heading>
<div class="metric-info">
<Icon name="Alert" />
Error
</div>
</div>
</div>
</div>
</DashCard>
<DashCard
title={"Backups"}
action={() => {
navigateTab("Backups")
}}
dataCy={"backups"}
>
<div class="backups-content">test</div>
</DashCard>
</div>
{/if}
</Layout>
</div>
<style>
.overview-tab {
display: grid;
}
.overview-tab .top {
display: grid;
grid-gap: var(--spectrum-alias-grid-gutter-medium);
grid-template-columns: repeat(auto-fill, minmax(30%, 1fr));
}
.overview-tab .bottom,
.automation-metrics {
display: grid;
grid-gap: var(--spectrum-alias-grid-gutter-large);
grid-template-columns: 1fr 1fr;
}
@media (max-width: 1000px) {
.overview-tab .top {
grid-template-columns: 1fr 1fr;
}
.overview-tab .bottom {
grid-template-columns: 1fr;
}
}
@media (max-width: 800px) {
.overview-tab .top,
.overview-tab .bottom {
grid-template-columns: 1fr;
}
}
.status-display {
display: flex;
align-items: center;
gap: var(--spacing-m);
}
.status-text,
.last-edit-text {
color: var(--spectrum-global-color-gray-600);
}
.updated-by {
display: flex;
align-items: center;
gap: var(--spacing-m);
}
.succeeded :global(.icon) {
color: var(--spectrum-global-color-green-600);
}
.failed :global(.icon) {
color: var(
--spectrum-semantic-negative-color-default,
var(--spectrum-global-color-red-500)
);
}
.metric-info {
display: flex;
gap: var(--spacing-l);
margin-top: var(--spacing-s);
}
.version-status,
.last-edit-text,
.status-text {
padding-top: var(--spacing-xl);
}
</style>

131
packages/builder/src/pages/builder/portal/overview/_components/SettingsTab.svelte

@ -0,0 +1,131 @@
<script>
import {
Layout,
Divider,
Heading,
Body,
Page,
Button,
Modal,
} from "@budibase/bbui"
import { store } from "builderStore"
import clientPackage from "@budibase/client/package.json"
import VersionModal from "components/deploy/VersionModal.svelte"
import UpdateAppModal from "components/start/UpdateAppModal.svelte"
import { AppStatus } from "constants"
export let app
let versionModal
let updatingModal
let selfHostPath =
"https://docs.budibase.com/docs/hosting-methods#self-host-budibase"
$: updateAvailable = clientPackage.version !== $store.version
$: appUrl = `${window.origin}/app${app?.url}`
$: appDeployed = app.status === AppStatus.DEPLOYED
</script>
<div class="settings-tab">
<Page wide={false}>
<Layout gap="XL" paddingY="XXL" paddingX="">
<span class="details-section">
<Layout gap="XS" noPadding>
<Heading size="S">Name and URL</Heading>
<Divider />
<Body>
<div class="app-details">
<div class="app-name">
<div class="name-title detail-title">Name</div>
<div class="name">{app?.name}</div>
</div>
<div class="app-url">
<div class="url-title detail-title">Url Path</div>
<div class="url">{appUrl}</div>
</div>
</div>
<div class="page-action">
<Button
cta
secondary
on:click={() => {
updatingModal.show()
}}
disabled={appDeployed}
>
Edit
</Button>
</div>
</Body>
</Layout>
</span>
<span class="version-section">
<Layout gap="XS" paddingY="XXL" paddingX="">
<Heading size="S">App version</Heading>
<Divider />
<Body>
{#if updateAvailable}
<p class="version-status">
The app is currently using version
<strong>{$store.version}</strong>
but version <strong>{clientPackage.version}</strong> is available.
</p>
{:else}
<p class="version-status">
The app is currently using version
<strong>{$store.version}</strong>. You're running the latest!
</p>
{/if}
Updates can contain new features, performance improvements and bug
fixes.
<div class="page-action">
<Button cta on:click={versionModal.show()}>Update app</Button>
</div>
</Body>
</Layout>
</span>
<span class="selfhost-section">
<Layout gap="XS" paddingY="XXL" paddingX="">
<Heading size="S">Self-host Budibase</Heading>
<Divider />
<Body>
Self-host Budibase for free to get unlimited apps and more - and it
only takes a few minutes!
<div class="page-action">
<Button
secondary
on:click={() => {
window.open(selfHostPath, "_blank")
}}>Self-host Budibase</Button
>
</div>
</Body>
</Layout>
</span>
</Layout>
<VersionModal bind:this={versionModal} hideIcon={true} />
<Modal bind:this={updatingModal} padding={false} width="600px">
<UpdateAppModal {app} />
</Modal>
</Page>
</div>
<style>
.page-action {
padding-top: var(--spacing-xl);
}
.app-details {
display: flex;
flex-direction: column;
gap: var(--spacing-m);
}
.detail-title {
color: var(--spectrum-global-color-gray-600);
font-size: var(
--spectrum-alias-font-size-default,
var(--spectrum-global-dimension-font-size-100)
);
}
</style>

2
packages/cli/package.json

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

8
packages/client/manifest.json

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

8
packages/client/package.json

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

9
packages/client/src/components/app/forms/AttachmentField.svelte

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

4
packages/client/src/components/app/table/Table.svelte

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

66
packages/client/src/utils/schema.js

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

4
packages/frontend-core/package.json

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

25
packages/frontend-core/src/fetch/DataFetch.js

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

14
packages/server/Dockerfile

@ -1,7 +1,5 @@
FROM node:14-slim
RUN apt-get update
LABEL com.centurylinklabs.watchtower.lifecycle.pre-check="scripts/watchtower-hooks/pre-check.sh"
LABEL com.centurylinklabs.watchtower.lifecycle.pre-update="scripts/watchtower-hooks/pre-update.sh"
LABEL com.centurylinklabs.watchtower.lifecycle.post-update="scripts/watchtower-hooks/post-update.sh"
@ -15,7 +13,14 @@ ENV BUDIBASE_ENVIRONMENT=PRODUCTION
# copy files and install dependencies
COPY . ./
RUN yarn
# handle node-gyp
RUN apt-get update \
&& apt-get install -y --no-install-recommends g++ make python \
&& yarn \
&& yarn cache clean \
&& apt-get remove -y --purge --auto-remove g++ make python \
&& rm -rf /tmp/* /root/.node-gyp /usr/local/lib/node_modules/npm/node_modules/node-gyp
RUN yarn global add pm2
RUN yarn build
# Install client for oracle datasource
@ -28,4 +33,5 @@ EXPOSE 4001
# due to this causing yarn to stop installing dev dependencies
# which are actually needed to get this environment up and running
ENV NODE_ENV=production
CMD ["yarn", "run:docker"]
ENV CLUSTER_MODE=${CLUSTER_MODE}
CMD ["./docker_run.sh"]

4
packages/server/__mocks__/pg.ts

@ -14,7 +14,9 @@ module PgMock {
function Client() {}
Client.prototype.query = query
Client.prototype.end = jest.fn()
Client.prototype.end = jest.fn(cb => {
if (cb) cb()
})
Client.prototype.connect = jest.fn()
Client.prototype.release = jest.fn()

7
packages/server/docker_run.sh

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

11
packages/server/package.json

@ -1,7 +1,7 @@
{
"name": "@budibase/server",
"email": "hi@budibase.com",
"version": "1.0.167-alpha.8",
"version": "1.0.189",
"description": "Budibase Web Server",
"main": "src/index.ts",
"repository": {
@ -18,6 +18,7 @@
"build:docker": "yarn run predocker && docker build . -t app-service --label version=$BUDIBASE_RELEASE_VERSION",
"build:docs": "node ./scripts/docs/generate.js open",
"run:docker": "node dist/index.js",
"run:docker:cluster": "pm2-runtime start pm2.config.js",
"dev:stack:up": "node scripts/dev/manage.js up",
"dev:stack:down": "node scripts/dev/manage.js down",
"dev:stack:nuke": "node scripts/dev/manage.js nuke",
@ -69,10 +70,10 @@
"license": "GPL-3.0",
"dependencies": {
"@apidevtools/swagger-parser": "^10.0.3",
"@budibase/backend-core": "^1.0.167-alpha.8",
"@budibase/client": "^1.0.167-alpha.8",
"@budibase/pro": "1.0.167-alpha.8",
"@budibase/string-templates": "^1.0.167-alpha.8",
"@budibase/backend-core": "^1.0.189",
"@budibase/client": "^1.0.189",
"@budibase/pro": "1.0.189",
"@budibase/string-templates": "^1.0.189",
"@bull-board/api": "^3.7.0",
"@bull-board/koa": "^3.7.0",
"@elastic/elasticsearch": "7.10.0",

9
packages/server/pm2.config.js

@ -0,0 +1,9 @@
module.exports = {
apps: [
{
script: "dist/index.js",
instances: "max",
exec_mode: "cluster",
},
],
}

11
packages/server/src/api/controllers/application.ts

@ -25,6 +25,7 @@ const {
import { BASE_LAYOUTS } from "../../constants/layouts"
import { cloneDeep } from "lodash/fp"
const { processObject } = require("@budibase/string-templates")
const { CacheKeys, bustCache } = require("@budibase/backend-core/cache")
const {
getAllApps,
isDevAppID,
@ -342,6 +343,7 @@ const appPostCreate = async (ctx: any, appId: string) => {
export const create = async (ctx: any) => {
const newApplication = await quotas.addApp(() => performAppCreate(ctx))
await appPostCreate(ctx, newApplication.appId)
await bustCache(CacheKeys.CHECKLIST)
ctx.body = newApplication
ctx.status = 200
}
@ -470,6 +472,15 @@ export const destroy = async (ctx: any) => {
}
export const sync = async (ctx: any, next: any) => {
if (env.DISABLE_AUTO_PROD_APP_SYNC) {
ctx.status = 200
ctx.body = {
message:
"App sync disabled. You can reenable with the DISABLE_AUTO_PROD_APP_SYNC environment variable.",
}
return next()
}
const appId = ctx.params.appId
if (!isDevAppID(appId)) {
ctx.throw(400, "This action cannot be performed for production apps")

27
packages/server/src/api/controllers/public/users.ts

@ -4,30 +4,9 @@ import {
readGlobalUser,
saveGlobalUser,
} from "../../../utilities/workerRequests"
import { publicApiUserFix } from "../../../utilities/users"
import { search as stringSearch } from "./utils"
const { getProdAppID } = require("@budibase/backend-core/db")
function fixUser(ctx: any) {
if (!ctx.request.body) {
return ctx
}
if (!ctx.request.body._id && ctx.params.userId) {
ctx.request.body._id = ctx.params.userId
}
if (!ctx.request.body.roles) {
ctx.request.body.roles = {}
} else {
const newRoles: { [key: string]: string } = {}
for (let [appId, role] of Object.entries(ctx.request.body.roles)) {
// @ts-ignore
newRoles[getProdAppID(appId)] = role
}
ctx.request.body.roles = newRoles
}
return ctx
}
function getUser(ctx: any, userId?: string) {
if (userId) {
ctx.params = { userId }
@ -45,7 +24,7 @@ export async function search(ctx: any, next: any) {
}
export async function create(ctx: any, next: any) {
const response = await saveGlobalUser(fixUser(ctx))
const response = await saveGlobalUser(publicApiUserFix(ctx))
ctx.body = await getUser(ctx, response._id)
await next()
}
@ -61,7 +40,7 @@ export async function update(ctx: any, next: any) {
...ctx.request.body,
_rev: user._rev,
}
const response = await saveGlobalUser(fixUser(ctx))
const response = await saveGlobalUser(publicApiUserFix(ctx))
ctx.body = await getUser(ctx, response._id)
await next()
}

1
packages/server/src/automations/steps/sendSmtpEmail.js

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

7
packages/server/src/environment.js

@ -1,3 +1,5 @@
const { join } = require("path")
function isTest() {
return isCypress() || isJest()
}
@ -23,7 +25,9 @@ function isCypress() {
let LOADED = false
if (!LOADED && isDev() && !isTest()) {
require("dotenv").config()
require("dotenv").config({
path: join(__dirname, "..", ".env"),
})
LOADED = true
}
@ -58,6 +62,7 @@ module.exports = {
BUDIBASE_ENVIRONMENT: process.env.BUDIBASE_ENVIRONMENT,
DISABLE_ACCOUNT_PORTAL: process.env.DISABLE_ACCOUNT_PORTAL,
TEMPLATE_REPOSITORY: process.env.TEMPLATE_REPOSITORY || "app",
DISABLE_AUTO_PROD_APP_SYNC: process.env.DISABLE_AUTO_PROD_APP_SYNC,
// minor
SALT_ROUNDS: process.env.SALT_ROUNDS,
LOGGER: process.env.LOGGER,

49
packages/server/src/integrations/postgres.ts

@ -136,7 +136,7 @@ module PostgresModule {
: undefined,
}
this.client = new Client(newConfig)
this.setSchema()
this.open = false
}
getBindingIdentifier(): string {
@ -147,7 +147,34 @@ module PostgresModule {
return parts.join(" || ")
}
async openConnection() {
await this.client.connect()
if (!this.config.schema) {
this.config.schema = "public"
}
this.client.query(`SET search_path TO ${this.config.schema}`)
this.COLUMNS_SQL = `select * from information_schema.columns where table_schema = '${this.config.schema}'`
this.open = true
}
closeConnection() {
const pg = this
return new Promise<void>((resolve, reject) => {
this.client.end((err: any) => {
pg.open = false
if (err) {
reject(err)
} else {
resolve()
}
})
})
}
async internalQuery(query: SqlQuery, close: boolean = true) {
if (!this.open) {
await this.openConnection()
}
const client = this.client
this.index = 1
// need to handle a specific issue with json data types in postgres,
@ -164,21 +191,14 @@ module PostgresModule {
try {
return await client.query(query.sql, query.bindings || [])
} catch (err) {
await this.client.end()
await this.closeConnection()
// @ts-ignore
throw new Error(err)
} finally {
if (close) await this.client.end()
}
}
async setSchema() {
await this.client.connect()
if (!this.config.schema) {
this.config.schema = "public"
if (close) {
await this.closeConnection()
}
}
this.client.query(`SET search_path TO ${this.config.schema}`)
this.COLUMNS_SQL = `select * from information_schema.columns where table_schema = '${this.config.schema}'`
}
/**
@ -188,6 +208,7 @@ module PostgresModule {
*/
async buildSchema(datasourceId: string, entities: Record<string, Table>) {
let tableKeys: { [key: string]: string[] } = {}
await this.openConnection()
try {
const primaryKeysResponse = await this.client.query(
this.PRIMARY_KEYS_SQL
@ -251,7 +272,7 @@ module PostgresModule {
// @ts-ignore
throw new Error(err)
} finally {
await this.client.end()
await this.closeConnection()
}
}
@ -283,7 +304,7 @@ module PostgresModule {
for (let query of input) {
responses.push(await this.internalQuery(query, false))
}
await this.client.end()
await this.closeConnection()
return responses
} else {
const response = await this.internalQuery(input)

15
packages/server/src/middleware/builder.js

@ -6,7 +6,7 @@ const {
setDebounce,
} = require("../utilities/redis")
const { doWithDB } = require("@budibase/backend-core/db")
const { DocumentTypes } = require("../db/utils")
const { DocumentTypes, getGlobalIDFromUserMetadataID } = require("../db/utils")
const { PermissionTypes } = require("@budibase/backend-core/permissions")
const { app: appCache } = require("@budibase/backend-core/cache")
@ -51,6 +51,9 @@ async function updateAppUpdatedAt(ctx) {
await doWithDB(appId, async db => {
const metadata = await db.get(DocumentTypes.APP_METADATA)
metadata.updatedAt = new Date().toISOString()
metadata.updatedBy = getGlobalIDFromUserMetadataID(ctx.user.userId)
const response = await db.put(metadata)
metadata._rev = response.rev
await appCache.invalidateAppMetadata(appId, metadata)
@ -67,7 +70,15 @@ module.exports = async (ctx, permType) => {
}
const isBuilderApi = permType === PermissionTypes.BUILDER
const referer = ctx.headers["referer"]
const editingApp = referer ? referer.includes(appId) : false
const overviewPath = "/builder/portal/overview/"
const overviewContext = !referer ? false : referer.includes(overviewPath)
if (overviewContext) {
return
}
const hasAppId = !referer ? false : referer.includes(appId)
const editingApp = referer ? hasAppId : false
// check this is a builder call and editing
if (!isBuilderApi || !editingApp) {
return

2
packages/server/src/threads/automation.js

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

15
packages/server/src/utilities/bcrypt.js

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

2
packages/server/src/utilities/redis.js

@ -48,7 +48,9 @@ exports.updateLock = async (devAppId, user) => {
...user,
userId: globalId,
_id: globalId,
lockedAt: new Date().getTime(),
}
await devAppClient.store(devAppId, inputUser, APP_DEV_LOCK_SECONDS)
}

21
packages/server/src/utilities/users.js

@ -1,6 +1,7 @@
const { InternalTables } = require("../db/utils")
const { getGlobalUser } = require("../utilities/global")
const { getAppDB } = require("@budibase/backend-core/context")
const { getProdAppID } = require("@budibase/backend-core/db")
exports.getFullUser = async (ctx, userId) => {
const global = await getGlobalUser(userId)
@ -22,3 +23,23 @@ exports.getFullUser = async (ctx, userId) => {
_id: userId,
}
}
exports.publicApiUserFix = ctx => {
if (!ctx.request.body) {
return ctx
}
if (!ctx.request.body._id && ctx.params.userId) {
ctx.request.body._id = ctx.params.userId
}
if (!ctx.request.body.roles) {
ctx.request.body.roles = {}
} else {
const newRoles = {}
for (let [appId, role] of Object.entries(ctx.request.body.roles)) {
// @ts-ignore
newRoles[getProdAppID(appId)] = role
}
ctx.request.body.roles = newRoles
}
return ctx
}

181
packages/server/yarn.lock

@ -1021,15 +1021,14 @@
resolved "https://registry.yarnpkg.com/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz#75a2e8b51cb758a7553d6804a5932d7aace75c39"
integrity sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==
"@budibase/backend-core@1.0.167-alpha.8":
version "1.0.167-alpha.8"
resolved "https://registry.yarnpkg.com/@budibase/backend-core/-/backend-core-1.0.167-alpha.8.tgz#e663928587cb3ec90d06b3c5766fce61c11e93a6"
integrity sha512-J4LtucuaJJd5lAs9mV6Rt9D5dCZhKNrrKajnFTXqOrjsyw0jsIfN2ku8ClV0C9J3NA3wAu2zjpfDaKSnNfg2OQ==
"@budibase/backend-core@1.0.187":
version "1.0.187"
resolved "https://registry.yarnpkg.com/@budibase/backend-core/-/backend-core-1.0.187.tgz#bf2d6de627185e5388d567762491334bc1d2d546"
integrity sha512-C9FqOf+/SNNWk7bZU7PfnzDvXM5ntnZFgpX1bI/bfHH3xYGw6jOFB2BpoTaV2sQGq14wfmo9e9vNHqYIT9tPrg==
dependencies:
"@techpass/passport-openidconnect" "^0.3.0"
aws-sdk "^2.901.0"
bcryptjs "^2.4.3"
dotenv "^16.0.1"
bcrypt "^5.0.1"
emitter-listener "^1.1.2"
ioredis "^4.27.1"
jsonwebtoken "^8.5.1"
@ -1099,12 +1098,12 @@
svelte-flatpickr "^3.2.3"
svelte-portal "^1.0.0"
"@budibase/pro@1.0.167-alpha.8":
version "1.0.167-alpha.8"
resolved "https://registry.yarnpkg.com/@budibase/pro/-/pro-1.0.167-alpha.8.tgz#dfcecb85550bc07a89b8e0bd2dbff13543d99117"
integrity sha512-MPEtYLnHfFchBzQs5nZSAavCANh32hd1VSXUB5KJSkEdtpQ9eoJ+hOGThrM9PJtTVX3Jq31nsg0/PmUkswBdAQ==
"@budibase/pro@1.0.187":
version "1.0.187"
resolved "https://registry.yarnpkg.com/@budibase/pro/-/pro-1.0.187.tgz#40ed5e9809f924e5d0c7bbb7d09a602ea47dfe92"
integrity sha512-whEcWAcgkIL4EHgh08jp8itR37LZYlZxFLl0GYY5AOIguEScJ1FRe9WcYFNW0Pw768uaq7IE7hKkrAzWetzu4g==
dependencies:
"@budibase/backend-core" "1.0.167-alpha.8"
"@budibase/backend-core" "1.0.187"
node-fetch "^2.6.1"
"@budibase/standard-components@^0.9.139":
@ -1899,6 +1898,21 @@
path-to-regexp "^1.1.1"
urijs "^1.19.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"
"@node-redis/bloom@1.0.1":
version "1.0.1"
resolved "https://registry.yarnpkg.com/@node-redis/bloom/-/bloom-1.0.1.tgz#144474a0b7dc4a4b91badea2cfa9538ce0a1854e"
@ -3321,6 +3335,11 @@ apidoc@^0.50.2:
webpack-cli "^4.9.1"
winston "^3.3.3"
"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==
arangojs@7.2.0:
version "7.2.0"
resolved "https://registry.yarnpkg.com/arangojs/-/arangojs-7.2.0.tgz#e576926b4b3469c5a130cceba45fada8b5f015d1"
@ -3339,6 +3358,14 @@ archive-type@^4.0.0:
dependencies:
file-type "^4.2.0"
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"
arg@^4.1.0:
version "4.1.3"
resolved "https://registry.yarnpkg.com/arg/-/arg-4.1.3.tgz#269fc7ad5b8e42cb63c896d5666017261c144089"
@ -3724,7 +3751,15 @@ bcrypt-pbkdf@^1.0.0:
dependencies:
tweetnacl "^0.14.3"
bcryptjs@2.4.3, bcryptjs@^2.4.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:
version "2.4.3"
resolved "https://registry.yarnpkg.com/bcryptjs/-/bcryptjs-2.4.3.tgz#9ab5627b93e60621ff7cdac5da9733027df1d0cb"
integrity sha1-mrVie5PmBiH/fNrF2pczAn3x0Ms=
@ -4159,6 +4194,11 @@ chownr@^1.1.1:
resolved "https://registry.yarnpkg.com/chownr/-/chownr-1.1.4.tgz#6fc9d7b42d32a583596337666e7d08084da2cc6b"
integrity sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==
chownr@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/chownr/-/chownr-2.0.0.tgz#15bfbe53d2eab4cf70f18a8cd68ebe5b3cb1dece"
integrity sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==
chrome-trace-event@^1.0.2:
version "1.0.3"
resolved "https://registry.yarnpkg.com/chrome-trace-event/-/chrome-trace-event-1.0.3.tgz#1015eced4741e15d06664a957dbbf50d041e26ac"
@ -4310,6 +4350,11 @@ color-string@^1.6.0:
color-name "^1.0.0"
simple-swizzle "^0.2.2"
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==
color@^3.1.3:
version "3.2.1"
resolved "https://registry.yarnpkg.com/color/-/color-3.2.1.tgz#3544dc198caf4490c3ecc9a790b54fe9ff45e164"
@ -4434,6 +4479,11 @@ configstore@^5.0.1:
write-file-atomic "^3.0.0"
xdg-basedir "^4.0.0"
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=
consolidate@^0.16.0:
version "0.16.0"
resolved "https://registry.yarnpkg.com/consolidate/-/consolidate-0.16.0.tgz#a11864768930f2f19431660a65906668f5fbdc16"
@ -4898,6 +4948,11 @@ destroy@^1.0.4:
resolved "https://registry.yarnpkg.com/destroy/-/destroy-1.1.0.tgz#b77ae22e472d85437141319d32ae40b344dff38a"
integrity sha512-R5QZrOXxSs0JDUIU/VANvRJlQVMts9C0L76HToQdPdlftfZCE7W6dyH0G4GZ5UW9fRqUOhAoCE2aGekuu+3HjQ==
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@^2.1.0:
version "2.1.0"
resolved "https://registry.yarnpkg.com/detect-newline/-/detect-newline-2.1.0.tgz#f41f1c10be4b00e87b5f13da680759f2c5bfd3e2"
@ -6150,6 +6205,13 @@ fs-extra@^9.0.0:
jsonfile "^6.0.1"
universalify "^2.0.0"
fs-minipass@^2.0.0:
version "2.1.0"
resolved "https://registry.yarnpkg.com/fs-minipass/-/fs-minipass-2.1.0.tgz#7f5036fdbf12c63c169190cbe4199c852271f9fb"
integrity sha512-V/JgOLFCS+R6Vcq0slCuaeWEdNC3ouDlJMNIsacH2VtALiu9mV4LPrHc5cDl8k5aw6J8jwgWWpiTo5RYhmIzvg==
dependencies:
minipass "^3.0.0"
fs.realpath@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f"
@ -6178,6 +6240,21 @@ functional-red-black-tree@^1.0.1:
resolved "https://registry.yarnpkg.com/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz#1b0ab3bd553b2a0d6399d29c0e3ea0b252078327"
integrity sha1-GwqzvVU7Kg1jmdKcDj6gslIHgyc=
gauge@^3.0.0:
version "3.0.2"
resolved "https://registry.yarnpkg.com/gauge/-/gauge-3.0.2.tgz#03bf4441c044383908bcfa0656ad91803259b395"
integrity sha512-+5J6MS/5XksCuXq++uFRsnUd7Ovu1XenbeuIuNRJxYWjgQbPuFhT14lAvsWfqfAmnwluf1OwMjz39HjfLPci0Q==
dependencies:
aproba "^1.0.3 || ^2.0.0"
color-support "^1.1.2"
console-control-strings "^1.0.0"
has-unicode "^2.0.1"
object-assign "^4.1.1"
signal-exit "^3.0.0"
string-width "^4.2.3"
strip-ansi "^6.0.1"
wide-align "^1.1.2"
gaxios@^4.0.0:
version "4.3.2"
resolved "https://registry.yarnpkg.com/gaxios/-/gaxios-4.3.2.tgz#845827c2dc25a0213c8ab4155c7a28910f5be83f"
@ -6659,6 +6736,11 @@ has-tostringtag@^1.0.0:
dependencies:
has-symbols "^1.0.2"
has-unicode@^2.0.1:
version "2.0.1"
resolved "https://registry.yarnpkg.com/has-unicode/-/has-unicode-2.0.1.tgz#e0e6fe6a28cf51138855e086d1691e771de2a8b9"
integrity sha1-4Ob+aijPUROIVeCG0Wkedx3iqLk=
has-value@^0.3.1:
version "0.3.1"
resolved "https://registry.yarnpkg.com/has-value/-/has-value-0.3.1.tgz#7b1f58bada62ca827ec0a2078025654845995e1f"
@ -9275,7 +9357,7 @@ make-dir@^2.1.0:
pify "^4.0.1"
semver "^5.6.0"
make-dir@^3.0.0:
make-dir@^3.0.0, make-dir@^3.1.0:
version "3.1.0"
resolved "https://registry.yarnpkg.com/make-dir/-/make-dir-3.1.0.tgz#415e967046b3a7f1d185277d84aa58203726a13f"
integrity sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==
@ -9489,6 +9571,21 @@ minimist@^1.1.1, minimist@^1.2.0, minimist@^1.2.5:
resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.6.tgz#8637a5b759ea0d6e98702cfb3a9283323c93af44"
integrity sha512-Jsjnk4bw3YJqYzbdyBiNsPWHPfO++UGG749Cxs6peCu5Xg4nrena6OVxOYxrQTqww0Jmwt+Ref8rggumkTLz9Q==
minipass@^3.0.0:
version "3.1.6"
resolved "https://registry.yarnpkg.com/minipass/-/minipass-3.1.6.tgz#3b8150aa688a711a1521af5e8779c1d3bb4f45ee"
integrity sha512-rty5kpw9/z8SX9dmxblFA6edItUmwJgMeYDZRrwlIVN27i8gysGbznJwUggw2V/FVqFSDdWy040ZPS811DYAqQ==
dependencies:
yallist "^4.0.0"
minizlib@^2.1.1:
version "2.1.2"
resolved "https://registry.yarnpkg.com/minizlib/-/minizlib-2.1.2.tgz#e90d3466ba209b932451508a11ce3d3632145931"
integrity sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg==
dependencies:
minipass "^3.0.0"
yallist "^4.0.0"
mixin-deep@^1.2.0:
version "1.3.2"
resolved "https://registry.yarnpkg.com/mixin-deep/-/mixin-deep-1.3.2.tgz#1120b43dc359a785dce65b55b82e257ccf479566"
@ -9509,7 +9606,7 @@ mkdirp@^0.5.0, mkdirp@^0.5.1:
dependencies:
minimist "^1.2.5"
mkdirp@^1.0.4:
mkdirp@^1.0.3, mkdirp@^1.0.4:
version "1.0.4"
resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-1.0.4.tgz#3eb5ed62622756d79a5f0e2a221dfebad75c2f7e"
integrity sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==
@ -9678,12 +9775,17 @@ nice-try@^1.0.4:
resolved "https://registry.yarnpkg.com/nice-try/-/nice-try-1.0.5.tgz#a3378a7696ce7d223e88fc9b764bd7ef1089e366"
integrity sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ==
node-addon-api@^3.1.0:
version "3.2.1"
resolved "https://registry.yarnpkg.com/node-addon-api/-/node-addon-api-3.2.1.tgz#81325e0a2117789c0128dab65e7e38f07ceba161"
integrity sha512-mmcei9JghVNDYydghQmeDX8KoAm0FAiYyIcUt/N4nhyAipB17pllZQDOJD2fotxABnt4Mdz+dKTO7eftLg4d0A==
node-fetch@2.6.0:
version "2.6.0"
resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.6.0.tgz#e633456386d4aa55863f676a7ab0daa8fdecb0fd"
integrity sha512-8dG4H5ujfvFiqDmVu9fQ5bOHUC15JMjMY/Zumv26oOvvVJjM67KF8koCWIabKQ1GJIa9r2mMZscBq/TbdOcmNA==
node-fetch@2.6.7, node-fetch@^2.6.0, node-fetch@^2.6.1:
node-fetch@2.6.7, node-fetch@^2.6.0, node-fetch@^2.6.1, node-fetch@^2.6.7:
version "2.6.7"
resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.6.7.tgz#24de9fba827e3b4ae44dc8b20256a379160052ad"
integrity sha512-ZjMPFEfVx5j+y2yF35Kzx5sF7kDzxuDj6ziH4FFbOp87zKDZNx8yExJIb05OGF4Nlt9IHFIMBkRl41VdvcNdbQ==
@ -9803,6 +9905,16 @@ npm-run-path@^4.0.1:
dependencies:
path-key "^3.0.0"
npmlog@^5.0.1:
version "5.0.1"
resolved "https://registry.yarnpkg.com/npmlog/-/npmlog-5.0.1.tgz#f06678e80e29419ad67ab964e0fa69959c1eb8b0"
integrity sha512-AqZtDUWOMKs1G/8lwylVjrdYgqA4d9nu8hc+0gzRxlDb1I10+FHBGMXs6aiQHFdCUUlqH99MUMuLfzWDNDtfxw==
dependencies:
are-we-there-yet "^2.0.0"
console-control-strings "^1.1.0"
gauge "^3.0.0"
set-blocking "^2.0.0"
nunjucks@^3.2.3:
version "3.2.3"
resolved "https://registry.yarnpkg.com/nunjucks/-/nunjucks-3.2.3.tgz#1b33615247290e94e28263b5d855ece765648a31"
@ -9832,7 +9944,7 @@ object-assign@^2.0.0:
resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-2.1.1.tgz#43c36e5d569ff8e4816c4efa8be02d26967c18aa"
integrity sha1-Q8NuXVaf+OSBbE76i+AtJpZ8GKo=
object-assign@^4.0.1, object-assign@^4.1.0:
object-assign@^4.0.1, object-assign@^4.1.0, object-assign@^4.1.1:
version "4.1.1"
resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863"
integrity sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=
@ -12097,6 +12209,15 @@ string-template@~1.0.0:
resolved "https://registry.yarnpkg.com/string-template/-/string-template-1.0.0.tgz#9e9f2233dc00f218718ec379a28a5673ecca8b96"
integrity sha1-np8iM9wA8hhxjsN5oopWc+zKi5Y=
"string-width@^1.0.2 || 2 || 3 || 4", string-width@^4.0.0, string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.2, string-width@^4.2.3:
version "4.2.3"
resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010"
integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==
dependencies:
emoji-regex "^8.0.0"
is-fullwidth-code-point "^3.0.0"
strip-ansi "^6.0.1"
string-width@^3.0.0, string-width@^3.1.0:
version "3.1.0"
resolved "https://registry.yarnpkg.com/string-width/-/string-width-3.1.0.tgz#22767be21b62af1081574306f69ac51b62203961"
@ -12106,15 +12227,6 @@ string-width@^3.0.0, string-width@^3.1.0:
is-fullwidth-code-point "^2.0.0"
strip-ansi "^5.1.0"
string-width@^4.0.0, string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.2:
version "4.2.3"
resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010"
integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==
dependencies:
emoji-regex "^8.0.0"
is-fullwidth-code-point "^3.0.0"
strip-ansi "^6.0.1"
string.prototype.startswith@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/string.prototype.startswith/-/string.prototype.startswith-1.0.0.tgz#92a361fb1ac172033d53eb1db3d659b0cfab6280"
@ -12469,6 +12581,18 @@ tar-stream@^2.1.4:
inherits "^2.0.3"
readable-stream "^3.1.1"
tar@^6.1.11:
version "6.1.11"
resolved "https://registry.yarnpkg.com/tar/-/tar-6.1.11.tgz#6760a38f003afa1b2ffd0ffe9e9abbd0eab3d621"
integrity sha512-an/KZQzQUkZCkuoAA64hM92X0Urb6VpRhAFllDzz44U2mcD5scmT3zBc4VgVpkugF580+DQn8eAFSyoQt0tznA==
dependencies:
chownr "^2.0.0"
fs-minipass "^2.0.0"
minipass "^3.0.0"
minizlib "^2.1.1"
mkdirp "^1.0.3"
yallist "^4.0.0"
tarn@^1.1.5:
version "1.1.5"
resolved "https://registry.yarnpkg.com/tarn/-/tarn-1.1.5.tgz#7be88622e951738b9fa3fb77477309242cdddc2d"
@ -13423,6 +13547,13 @@ which@^2.0.1:
dependencies:
isexe "^2.0.0"
wide-align@^1.1.2:
version "1.1.5"
resolved "https://registry.yarnpkg.com/wide-align/-/wide-align-1.1.5.tgz#df1d4c206854369ecf3c9a4898f1b23fbd9d15d3"
integrity sha512-eDMORYaPNZ4sQIuuYPDHdQvf4gyCF9rEEV/yPxGfwPkRodwEgiMUUXTx/dex+Me0wxx53S+NgUHaP7y3MGlDmg==
dependencies:
string-width "^1.0.2 || 2 || 3 || 4"
widest-line@^3.1.0:
version "3.1.0"
resolved "https://registry.yarnpkg.com/widest-line/-/widest-line-3.1.0.tgz#8292333bbf66cb45ff0de1603b136b7ae1496eca"

2
packages/string-templates/package.json

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

8
packages/worker/Dockerfile

@ -9,7 +9,10 @@ WORKDIR /app
# copy files and install dependencies
COPY . ./
RUN yarn
# handle node-gyp
RUN apk add --no-cache --virtual .gyp python3 make g++ \
&& yarn && apk del .gyp
RUN yarn global add pm2
EXPOSE 4001
@ -17,4 +20,5 @@ EXPOSE 4001
# due to this causing yarn to stop installing dev dependencies
# which are actually needed to get this environment up and running
ENV NODE_ENV=production
CMD ["yarn", "run:docker"]
ENV CLUSTER_MODE=${CLUSTER_MODE}
CMD ["./docker_run.sh"]

7
packages/worker/docker_run.sh

@ -0,0 +1,7 @@
#!/bin/sh
if [[ -z $CLUSTER_MODE ]]; then
yarn run:docker
else
yarn run:docker:cluster
fi

9
packages/worker/package.json

@ -1,7 +1,7 @@
{
"name": "@budibase/worker",
"email": "hi@budibase.com",
"version": "1.0.167-alpha.8",
"version": "1.0.189",
"description": "Budibase background service",
"main": "src/index.ts",
"repository": {
@ -15,6 +15,7 @@
"build": "rimraf dist/ && tsc",
"postbuild": "copyfiles -u 1 src/**/*.hbs dist/",
"run:docker": "node dist/index.js",
"run:docker:cluster": "pm2-runtime start pm2.config.js",
"build:docker": "docker build . -t worker-service --label version=$BUDIBASE_RELEASE_VERSION",
"dev:stack:init": "node ./scripts/dev/manage.js init",
"dev:builder": "npm run dev:stack:init && nodemon",
@ -31,9 +32,9 @@
"author": "Budibase",
"license": "GPL-3.0",
"dependencies": {
"@budibase/backend-core": "^1.0.167-alpha.8",
"@budibase/pro": "1.0.167-alpha.8",
"@budibase/string-templates": "^1.0.167-alpha.8",
"@budibase/backend-core": "^1.0.189",
"@budibase/pro": "1.0.189",
"@budibase/string-templates": "^1.0.189",
"@koa/router": "^8.0.0",
"@sentry/node": "6.17.7",
"@techpass/passport-openidconnect": "^0.3.0",

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

Loading…
Cancel
Save