Browse Source

Merge branch 'develop' of github.com:Budibase/budibase into new-design-ui

pull/6046/head
Andrew Kingston 4 years ago
parent
commit
8e03c8a33c
  1. 14
      .vscode/settings.json
  2. 4
      charts/budibase/Chart.yaml
  3. 4
      charts/budibase/templates/app-service-deployment.yaml
  4. 6
      hosting/.env
  5. 2
      hosting/docker-compose.yaml
  6. 4
      hosting/hosting.properties
  7. 4
      lerna.json
  8. 3
      package.json
  9. 1
      packages/backend-core/cache.js
  10. 8
      packages/backend-core/package.json
  11. 127
      packages/backend-core/src/auth.js
  12. 1
      packages/backend-core/src/context/index.js
  13. 42
      packages/backend-core/src/db/constants.js
  14. 58
      packages/backend-core/src/db/constants.ts
  15. 21
      packages/backend-core/src/db/utils.ts
  16. 2
      packages/backend-core/src/environment.ts
  17. 2
      packages/backend-core/src/index.ts
  18. 8
      packages/backend-core/src/logging.ts
  19. 3
      packages/backend-core/src/middleware/authenticated.js
  20. 3
      packages/backend-core/src/middleware/index.js
  21. 8
      packages/backend-core/src/middleware/passport/google.js
  22. 1
      packages/backend-core/src/middleware/passport/local.js
  23. 47
      packages/backend-core/src/middleware/passport/oidc.js
  24. 4
      packages/backend-core/src/middleware/passport/tests/oidc.spec.js
  25. 23
      packages/backend-core/src/middleware/passport/utils.js
  26. 5
      packages/backend-core/yarn.lock
  27. 6
      packages/bbui/package.json
  28. 6
      packages/bbui/src/ActionButton/ActionButton.svelte
  29. 1
      packages/bbui/src/Form/Combobox.svelte
  30. 5
      packages/bbui/src/Form/Core/Combobox.svelte
  31. 25
      packages/bbui/src/Notification/Notification.svelte
  32. 4
      packages/bbui/src/Notification/NotificationDisplay.svelte
  33. 30
      packages/bbui/src/Stores/notifications.js
  34. 2
      packages/bbui/src/Table/ArrayRenderer.svelte
  35. 10
      packages/bbui/src/Table/CellRenderer.svelte
  36. 3
      packages/bbui/src/Table/Table.svelte
  37. 2
      packages/builder/cypress.json
  38. 5
      packages/builder/cypress/integration/addRadioButtons.spec.js
  39. 5
      packages/builder/cypress/integration/adminAndManagement/accountPortals.spec.js
  40. 43
      packages/builder/cypress/integration/adminAndManagement/userManagement.spec.js
  41. 2
      packages/builder/cypress/integration/adminAndManagement/userSettings.spec.js
  42. 17
      packages/builder/cypress/integration/appOverview.spec.js
  43. 24
      packages/builder/cypress/integration/appPublishWorkflow.spec.js
  44. 12
      packages/builder/cypress/integration/createApp.spec.js
  45. 1
      packages/builder/cypress/integration/createView.spec.js
  46. 1
      packages/builder/cypress/integration/datasources/mySql.spec.js
  47. 19
      packages/builder/cypress/integration/datasources/postgreSql.spec.js
  48. 13
      packages/builder/cypress/integration/renameAnApplication.spec.js
  49. 34
      packages/builder/cypress/support/commands.js
  50. 13
      packages/builder/package.json
  51. 2
      packages/builder/src/analytics/IntercomClient.js
  52. 90
      packages/builder/src/builderStore/dataBinding.js
  53. 29
      packages/builder/src/builderStore/store/automation/index.js
  54. 2
      packages/builder/src/components/automation/AutomationBuilder/FlowChart/FlowChart.svelte
  55. 3
      packages/builder/src/components/automation/AutomationBuilder/FlowChart/FlowItem.svelte
  56. 43
      packages/builder/src/components/automation/AutomationBuilder/FlowChart/FlowItemHeader.svelte
  57. 2
      packages/builder/src/components/automation/AutomationBuilder/FlowChart/TestDataModal.svelte
  58. 137
      packages/builder/src/components/automation/AutomationBuilder/TestDisplay.svelte
  59. 100
      packages/builder/src/components/automation/AutomationBuilder/TestPanel.svelte
  60. 7
      packages/builder/src/components/backend/DataTable/modals/CreateEditColumn.svelte
  61. 12
      packages/builder/src/components/backend/DataTable/modals/CreateEditUser.svelte
  62. 25
      packages/builder/src/components/backend/DatasourceNavigator/TableIntegrationMenu/rest/RestExtraConfigForm.svelte
  63. 22
      packages/builder/src/components/backend/DatasourceNavigator/TableIntegrationMenu/rest/auth/RestAuthenticationModal.svelte
  64. 422
      packages/builder/src/components/backend/DatasourceNavigator/icons/SQLServer.svelte
  65. 68
      packages/builder/src/components/common/bindings/BindableCombobox.svelte
  66. 4
      packages/builder/src/components/common/bindings/DrawerBindableCombobox.svelte
  67. 6
      packages/builder/src/components/common/renderers/DateTimeRenderer.svelte
  68. 2
      packages/builder/src/components/deploy/DeployNavigation.svelte
  69. 2
      packages/builder/src/components/deploy/DeploymentHistory.svelte
  70. 2
      packages/builder/src/components/design/settings/controls/ColumnEditor/ColumnDrawer.svelte
  71. 12
      packages/builder/src/components/integration/KeyValueBuilder.svelte
  72. 3
      packages/builder/src/components/integration/QueryBindingBuilder.svelte
  73. 91
      packages/builder/src/components/portal/overview/automation/HistoryDetailsPanel.svelte
  74. 219
      packages/builder/src/components/portal/overview/automation/HistoryTab.svelte
  75. 38
      packages/builder/src/components/portal/overview/automation/StatusRenderer.svelte
  76. 4
      packages/builder/src/constants/backend/index.js
  77. 6
      packages/builder/src/pages/builder/app/[application]/automate/_layout.svelte
  78. 4
      packages/builder/src/pages/builder/app/[application]/data/datasource/[selectedDatasource]/_layout.svelte
  79. 116
      packages/builder/src/pages/builder/app/[application]/data/datasource/[selectedDatasource]/rest/[query]/index.svelte
  80. 52
      packages/builder/src/pages/builder/portal/apps/index.svelte
  81. 5
      packages/builder/src/pages/builder/portal/manage/email/index.svelte
  82. 12
      packages/builder/src/pages/builder/portal/manage/users/index.svelte
  83. 13
      packages/builder/src/pages/builder/portal/overview/[application]/index.svelte
  84. 7
      packages/builder/src/pages/builder/portal/overview/_components/OverviewTab.svelte
  85. 2
      packages/builder/src/stores/portal/auth.js
  86. 23
      packages/builder/yarn.lock
  87. 4
      packages/cli/package.json
  88. 10
      packages/client/package.json
  89. 4
      packages/client/src/components/app/dynamic-filter/DynamicFilter.svelte
  90. 3
      packages/client/src/components/app/forms/validation.js
  91. 1293
      packages/client/yarn.lock
  92. 6
      packages/frontend-core/package.json
  93. 35
      packages/frontend-core/src/api/automations.js
  94. 9
      packages/server/__mocks__/node-fetch.ts
  95. 14
      packages/server/package.json
  96. 2
      packages/server/scripts/dev/manage.js
  97. 14
      packages/server/specs/openapi.json
  98. 14
      packages/server/specs/openapi.yaml
  99. 32
      packages/server/src/api/controllers/application.ts
  100. 37
      packages/server/src/api/controllers/automation.js

14
.vscode/settings.json

@ -3,5 +3,17 @@
"editor.codeActionsOnSave": {
"source.fixAll": true
},
"editor.defaultFormatter": "svelte.svelte-vscode"
"editor.defaultFormatter": "svelte.svelte-vscode",
"[json]": {
"editor.defaultFormatter": "vscode.json-language-features"
},
"[javascript]": {
"editor.defaultFormatter": "esbenp.prettier-vscode"
},
"debug.javascript.terminalOptions": {
"skipFiles": [
"${workspaceFolder}/packages/backend-core/node_modules/**",
"<node_internals>/**"
]
},
}

4
charts/budibase/Chart.yaml

@ -11,8 +11,8 @@ sources:
- https://github.com/Budibase/budibase
- https://budibase.com
type: application
version: 0.2.10
appVersion: 1.0.48
version: 0.2.11
appVersion: 1.0.214
dependencies:
- name: couchdb
version: 3.6.1

4
charts/budibase/templates/app-service-deployment.yaml

@ -122,6 +122,10 @@ spec:
value: {{ .Values.globals.automationMaxIterations | quote }}
- name: TENANT_FEATURE_FLAGS
value: {{ .Values.globals.tenantFeatureFlags | quote }}
- name: BB_ADMIN_USER_EMAIL
value: { { .Values.globals.bbAdminUserEmail | quote } }
- name: BB_ADMIN_USER_PASSWORD
value: { { .Values.globals.bbAdminUserPassword | quote } }
image: budibase/apps:{{ .Values.globals.appVersion }}
imagePullPolicy: Always

6
hosting/.env

@ -18,4 +18,8 @@ MINIO_PORT=4004
COUCH_DB_PORT=4005
REDIS_PORT=6379
WATCHTOWER_PORT=6161
BUDIBASE_ENVIRONMENT=PRODUCTION
BUDIBASE_ENVIRONMENT=PRODUCTION
# An admin user can be automatically created initially if these are set
BB_ADMIN_USER_EMAIL=
BB_ADMIN_USER_PASSWORD=

2
hosting/docker-compose.yaml

@ -23,6 +23,8 @@ services:
ENABLE_ANALYTICS: "true"
REDIS_URL: redis-service:6379
REDIS_PASSWORD: ${REDIS_PASSWORD}
BB_ADMIN_USER_EMAIL: ${BB_ADMIN_USER_EMAIL}
BB_ADMIN_USER_PASSWORD: ${BB_ADMIN_USER_PASSWORD}
depends_on:
- worker-service
- redis-service

4
hosting/hosting.properties

@ -19,3 +19,7 @@ COUCH_DB_PORT=4005
REDIS_PORT=6379
WATCHTOWER_PORT=6161
BUDIBASE_ENVIRONMENT=PRODUCTION
# An admin user can be automatically created initially if these are set
BB_ADMIN_USER_EMAIL=
BB_ADMIN_USER_PASSWORD=

4
lerna.json

@ -1,5 +1,5 @@
{
"version": "1.0.212-alpha.8",
"version": "1.0.219",
"npmClient": "yarn",
"packages": [
"packages/*"
@ -15,4 +15,4 @@
]
}
}
}
}

3
package.json

@ -40,7 +40,8 @@
"dev": "yarn run kill-all && lerna link && lerna run --parallel dev:builder --concurrency 1",
"dev:noserver": "yarn run kill-builder && lerna link && lerna run dev:stack:up && lerna run --parallel dev:builder --concurrency 1 --ignore @budibase/backend-core --ignore @budibase/server --ignore @budibase/worker",
"dev:server": "yarn run kill-server && lerna run --parallel dev:builder --concurrency 1 --scope @budibase/backend-core --scope @budibase/worker --scope @budibase/server",
"test": "lerna run test",
"test": "lerna run test && yarn test:pro",
"test:pro": "bash scripts/pro/test.sh",
"lint:eslint": "eslint packages",
"lint:prettier": "prettier --check \"packages/**/*.{js,ts,svelte}\"",
"lint": "yarn run lint:eslint && yarn run lint:prettier",

1
packages/backend-core/cache.js

@ -5,4 +5,5 @@ module.exports = {
app: require("./src/cache/appMetadata"),
writethrough: require("./src/cache/writethrough"),
...generic,
cache: generic,
}

8
packages/backend-core/package.json

@ -1,6 +1,6 @@
{
"name": "@budibase/backend-core",
"version": "1.0.212-alpha.8",
"version": "1.0.219",
"description": "Budibase backend core libraries used in server and worker",
"main": "dist/src/index.js",
"types": "dist/src/index.d.ts",
@ -20,7 +20,7 @@
"test:watch": "jest --watchAll"
},
"dependencies": {
"@budibase/types": "^1.0.212-alpha.8",
"@budibase/types": "^1.0.219-alpha.17",
"@techpass/passport-openidconnect": "0.3.2",
"aws-sdk": "2.1030.0",
"bcrypt": "5.0.1",
@ -36,6 +36,7 @@
"passport-google-oauth": "2.0.0",
"passport-jwt": "4.0.0",
"passport-local": "1.0.0",
"passport-oauth2-refresh": "^2.1.0",
"posthog-node": "1.3.0",
"pouchdb": "7.3.0",
"pouchdb-find": "7.2.2",
@ -58,6 +59,7 @@
]
},
"devDependencies": {
"@budibase/types": "^1.0.219",
"@shopify/jest-koa-mocks": "3.1.5",
"@types/jest": "27.5.1",
"@types/koa": "2.0.52",
@ -78,4 +80,4 @@
"typescript": "4.7.3"
},
"gitHead": "d1836a898cab3f8ab80ee6d8f42be1a9eed7dcdc"
}
}

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

@ -2,6 +2,9 @@ const passport = require("koa-passport")
const LocalStrategy = require("passport-local").Strategy
const JwtStrategy = require("passport-jwt").Strategy
const { getGlobalDB } = require("./tenancy")
const refresh = require("passport-oauth2-refresh")
const { Configs } = require("./constants")
const { getScopedConfig } = require("./db/utils")
const {
jwt,
local,
@ -12,10 +15,13 @@ const {
tenancy,
appTenancy,
authError,
ssoCallbackUrl,
csrf,
internalApi,
} = require("./middleware")
const { invalidateUser } = require("./cache/user")
// Strategies
passport.use(new LocalStrategy(local.options, local.authenticate))
passport.use(new JwtStrategy(jwt.options, jwt.authenticate))
@ -34,6 +40,124 @@ passport.deserializeUser(async (user, done) => {
}
})
async function refreshOIDCAccessToken(db, chosenConfig, refreshToken) {
const callbackUrl = await oidc.getCallbackUrl(db, chosenConfig)
let enrichedConfig
let strategy
try {
enrichedConfig = await oidc.fetchStrategyConfig(chosenConfig, callbackUrl)
if (!enrichedConfig) {
throw new Error("OIDC Config contents invalid")
}
strategy = await oidc.strategyFactory(enrichedConfig)
} catch (err) {
console.error(err)
throw new Error("Could not refresh OAuth Token")
}
refresh.use(strategy, {
setRefreshOAuth2() {
return strategy._getOAuth2Client(enrichedConfig)
},
})
return new Promise(resolve => {
refresh.requestNewAccessToken(
Configs.OIDC,
refreshToken,
(err, accessToken, refreshToken, params) => {
resolve({ err, accessToken, refreshToken, params })
}
)
})
}
async function refreshGoogleAccessToken(db, config, refreshToken) {
let callbackUrl = await google.getCallbackUrl(db, config)
let strategy
try {
strategy = await google.strategyFactory(config, callbackUrl)
} catch (err) {
console.error(err)
throw new Error("Error constructing OIDC refresh strategy", err)
}
refresh.use(strategy)
return new Promise(resolve => {
refresh.requestNewAccessToken(
Configs.GOOGLE,
refreshToken,
(err, accessToken, refreshToken, params) => {
resolve({ err, accessToken, refreshToken, params })
}
)
})
}
async function refreshOAuthToken(refreshToken, configType, configId) {
const db = getGlobalDB()
const config = await getScopedConfig(db, {
type: configType,
group: {},
})
let chosenConfig = {}
let refreshResponse
if (configType === Configs.OIDC) {
// configId - retrieved from cookie.
chosenConfig = config.configs.filter(c => c.uuid === configId)[0]
if (!chosenConfig) {
throw new Error("Invalid OIDC configuration")
}
refreshResponse = await refreshOIDCAccessToken(
db,
chosenConfig,
refreshToken
)
} else {
chosenConfig = config
refreshResponse = await refreshGoogleAccessToken(
db,
chosenConfig,
refreshToken
)
}
return refreshResponse
}
async function updateUserOAuth(userId, oAuthConfig) {
const details = {
accessToken: oAuthConfig.accessToken,
refreshToken: oAuthConfig.refreshToken,
}
try {
const db = getGlobalDB()
const dbUser = await db.get(userId)
//Do not overwrite the refresh token if a valid one is not provided.
if (typeof details.refreshToken !== "string") {
delete details.refreshToken
}
dbUser.oauth2 = {
...dbUser.oauth2,
...details,
}
await db.put(dbUser)
await invalidateUser(userId)
} catch (e) {
console.error("Could not update OAuth details for current user", e)
}
}
module.exports = {
buildAuthMiddleware: authenticated,
passport,
@ -46,4 +170,7 @@ module.exports = {
authError,
buildCsrfMiddleware: csrf,
internalApi,
refreshOAuthToken,
updateUserOAuth,
ssoCallbackUrl,
}

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

@ -314,6 +314,7 @@ function getContextDB(key, opts) {
toUseAppId = getDevelopmentAppID(appId)
break
}
db = dangerousGetDB(toUseAppId, opts)
try {
cls.setOnContext(key, db)

42
packages/backend-core/src/db/constants.js

@ -1,42 +0,0 @@
exports.SEPARATOR = "_"
exports.UNICODE_MAX = "\ufff0"
const PRE_APP = "app"
const PRE_DEV = "dev"
exports.DocumentTypes = {
USER: "us",
WORKSPACE: "workspace",
CONFIG: "config",
TEMPLATE: "template",
APP: PRE_APP,
DEV: PRE_DEV,
APP_DEV: `${PRE_APP}${exports.SEPARATOR}${PRE_DEV}`,
APP_METADATA: `${PRE_APP}${exports.SEPARATOR}metadata`,
ROLE: "role",
MIGRATIONS: "migrations",
DEV_INFO: "devinfo",
}
exports.StaticDatabases = {
GLOBAL: {
name: "global-db",
docs: {
apiKeys: "apikeys",
usageQuota: "usage_quota",
licenseInfo: "license_info",
},
},
// contains information about tenancy and so on
PLATFORM_INFO: {
name: "global-info",
docs: {
tenants: "tenants",
install: "install",
},
},
}
exports.APP_PREFIX = exports.DocumentTypes.APP + exports.SEPARATOR
exports.APP_DEV = exports.APP_DEV_PREFIX =
exports.DocumentTypes.APP_DEV + exports.SEPARATOR

58
packages/backend-core/src/db/constants.ts

@ -0,0 +1,58 @@
export const SEPARATOR = "_"
export const UNICODE_MAX = "\ufff0"
/**
* Can be used to create a few different forms of querying a view.
*/
export enum AutomationViewModes {
ALL = "all",
AUTOMATION = "automation",
STATUS = "status",
}
export enum ViewNames {
USER_BY_EMAIL = "by_email",
BY_API_KEY = "by_api_key",
USER_BY_BUILDERS = "by_builders",
LINK = "by_link",
ROUTING = "screen_routes",
AUTOMATION_LOGS = "automation_logs",
}
export enum DocumentTypes {
USER = "us",
WORKSPACE = "workspace",
CONFIG = "config",
TEMPLATE = "template",
APP = "app",
DEV = "dev",
APP_DEV = "app_dev",
APP_METADATA = "app_metadata",
ROLE = "role",
MIGRATIONS = "migrations",
DEV_INFO = "devinfo",
AUTOMATION_LOG = "log_au",
}
export const StaticDatabases = {
GLOBAL: {
name: "global-db",
docs: {
apiKeys: "apikeys",
usageQuota: "usage_quota",
licenseInfo: "license_info",
},
},
// contains information about tenancy and so on
PLATFORM_INFO: {
name: "global-info",
docs: {
tenants: "tenants",
install: "install",
},
},
}
export const APP_PREFIX = exports.DocumentTypes.APP + exports.SEPARATOR
export const APP_DEV = exports.DocumentTypes.APP_DEV + exports.SEPARATOR
export const APP_DEV_PREFIX = APP_DEV

21
packages/backend-core/src/db/utils.ts

@ -1,7 +1,7 @@
import { newid } from "../hashing"
import { DEFAULT_TENANT_ID, Configs } from "../constants"
import env from "../environment"
import { SEPARATOR, DocumentTypes, UNICODE_MAX } from "./constants"
import { SEPARATOR, DocumentTypes, UNICODE_MAX, ViewNames } from "./constants"
import { getTenantId, getGlobalDBName, getGlobalDB } from "../tenancy"
import fetch from "node-fetch"
import { doWithDB, allDbs } from "./index"
@ -12,12 +12,6 @@ import { isDevApp, isDevAppID } from "./conversions"
import { APP_PREFIX } from "./constants"
import * as events from "../events"
export const ViewNames = {
USER_BY_EMAIL: "by_email",
BY_API_KEY: "by_api_key",
USER_BY_BUILDERS: "by_builders",
}
export * from "./constants"
export * from "./conversions"
export { default as Replication } from "./Replication"
@ -61,6 +55,13 @@ export function getDocParams(
}
}
/**
* Retrieve the correct index for a view based on default design DB.
*/
export function getQueryIndex(viewName: ViewNames) {
return `database/${viewName}`
}
/**
* Generates a new workspace ID.
* @returns {string} The new workspace ID which the workspace doc can be stored under.
@ -386,7 +387,9 @@ export const getScopedFullConfig = async function (
if (type === Configs.SETTINGS) {
if (scopedConfig && scopedConfig.doc) {
// overrides affected by environment variables
scopedConfig.doc.config.platformUrl = await getPlatformUrl()
scopedConfig.doc.config.platformUrl = await getPlatformUrl({
tenantAware: true,
})
scopedConfig.doc.config.analyticsEnabled =
await events.analytics.enabled()
} else {
@ -395,7 +398,7 @@ export const getScopedFullConfig = async function (
doc: {
_id: generateConfigID({ type, user, workspace }),
config: {
platformUrl: await getPlatformUrl(),
platformUrl: await getPlatformUrl({ tenantAware: true }),
analyticsEnabled: await events.analytics.enabled(),
},
},

2
packages/backend-core/src/environment.ts

@ -40,7 +40,7 @@ const env = {
DISABLE_ACCOUNT_PORTAL: process.env.DISABLE_ACCOUNT_PORTAL,
SELF_HOSTED: !!parseInt(process.env.SELF_HOSTED || ""),
COOKIE_DOMAIN: process.env.COOKIE_DOMAIN,
PLATFORM_URL: process.env.PLATFORM_URL,
PLATFORM_URL: process.env.PLATFORM_URL || "",
POSTHOG_TOKEN: process.env.POSTHOG_TOKEN,
ENABLE_ANALYTICS: process.env.ENABLE_ANALYTICS,
TENANT_FEATURE_FLAGS: process.env.TENANT_FEATURE_FLAGS,

2
packages/backend-core/src/index.ts

@ -13,6 +13,7 @@ import deprovisioning from "./context/deprovision"
import auth from "./auth"
import constants from "./constants"
import * as dbConstants from "./db/constants"
import logging from "./logging"
// mimic the outer package exports
import * as db from "./pkg/db"
@ -49,6 +50,7 @@ const core = {
deprovisioning,
installation,
errors,
logging,
...errorClasses,
}

8
packages/backend-core/src/logging.js → packages/backend-core/src/logging.ts

@ -1,10 +1,10 @@
const NonErrors = ["AccountError"]
function isSuppressed(e) {
function isSuppressed(e?: any) {
return e && e["suppressAlert"]
}
module.exports.logAlert = (message, e) => {
export function logAlert(message: string, e?: any) {
if (e && NonErrors.includes(e.name) && isSuppressed(e)) {
return
}
@ -14,3 +14,7 @@ module.exports.logAlert = (message, e) => {
}
console.error(`bb-alert: ${message} ${errorJson}`)
}
export default {
logAlert,
}

3
packages/backend-core/src/middleware/authenticated.js

@ -94,7 +94,6 @@ module.exports = (
user = await getUser(userId, session.tenantId)
}
user.csrfToken = session.csrfToken
delete user.password
authenticated = true
} catch (err) {
error = err
@ -128,6 +127,8 @@ module.exports = (
}
if (!user && tenantId) {
user = { tenantId }
} else {
delete user.password
}
// be explicit
if (authenticated !== true) {

3
packages/backend-core/src/middleware/index.js

@ -2,7 +2,7 @@ const jwt = require("./passport/jwt")
const local = require("./passport/local")
const google = require("./passport/google")
const oidc = require("./passport/oidc")
const { authError } = require("./passport/utils")
const { authError, ssoCallbackUrl } = require("./passport/utils")
const authenticated = require("./authenticated")
const auditLog = require("./auditLog")
const tenancy = require("./tenancy")
@ -20,6 +20,7 @@ module.exports = {
tenancy,
authError,
internalApi,
ssoCallbackUrl,
datasource: {
google: datasourceGoogle,
},

8
packages/backend-core/src/middleware/passport/google.js

@ -1,6 +1,7 @@
const GoogleStrategy = require("passport-google-oauth").OAuth2Strategy
const { ssoCallbackUrl } = require("./utils")
const { authenticateThirdParty } = require("./third-party-common")
const { Configs } = require("../../../constants")
const buildVerifyFn = saveUserFn => {
return (accessToken, refreshToken, profile, done) => {
@ -57,5 +58,10 @@ exports.strategyFactory = async function (config, callbackUrl, saveUserFn) {
)
}
}
exports.getCallbackUrl = async function (db, config) {
return ssoCallbackUrl(db, config, Configs.GOOGLE)
}
// expose for testing
exports.buildVerifyFn = buildVerifyFn

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

@ -55,6 +55,7 @@ exports.authenticate = async function (ctx, email, password, done) {
if (await compare(password, dbUser.password)) {
const sessionId = newid()
const tenantId = getTenantId()
await createASession(dbUser._id, { sessionId, tenantId })
dbUser.token = jwt.sign(

47
packages/backend-core/src/middleware/passport/oidc.js

@ -1,6 +1,8 @@
const fetch = require("node-fetch")
const OIDCStrategy = require("@techpass/passport-openidconnect").Strategy
const { authenticateThirdParty } = require("./third-party-common")
const { ssoCallbackUrl } = require("./utils")
const { Configs } = require("../../../constants")
const buildVerifyFn = saveUserFn => {
/**
@ -89,11 +91,24 @@ function validEmail(value) {
* from couchDB rather than environment variables, using this factory is necessary for dynamically configuring passport.
* @returns Dynamically configured Passport OIDC Strategy
*/
exports.strategyFactory = async function (config, callbackUrl, saveUserFn) {
exports.strategyFactory = async function (config, saveUserFn) {
try {
const { clientID, clientSecret, configUrl } = config
const verify = buildVerifyFn(saveUserFn)
const strategy = new OIDCStrategy(config, verify)
strategy.name = "oidc"
return strategy
} catch (err) {
console.error(err)
throw new Error("Error constructing OIDC authentication strategy", err)
}
}
exports.fetchStrategyConfig = async function (enrichedConfig, callbackUrl) {
try {
const { clientID, clientSecret, configUrl } = enrichedConfig
if (!clientID || !clientSecret || !callbackUrl || !configUrl) {
//check for remote config and all required elements
throw new Error(
"Configuration invalid. Must contain clientID, clientSecret, callbackUrl and configUrl"
)
@ -109,24 +124,24 @@ exports.strategyFactory = async function (config, callbackUrl, saveUserFn) {
const body = await response.json()
const verify = buildVerifyFn(saveUserFn)
return new OIDCStrategy(
{
issuer: body.issuer,
authorizationURL: body.authorization_endpoint,
tokenURL: body.token_endpoint,
userInfoURL: body.userinfo_endpoint,
clientID: clientID,
clientSecret: clientSecret,
callbackURL: callbackUrl,
},
verify
)
return {
issuer: body.issuer,
authorizationURL: body.authorization_endpoint,
tokenURL: body.token_endpoint,
userInfoURL: body.userinfo_endpoint,
clientID: clientID,
clientSecret: clientSecret,
callbackURL: callbackUrl,
}
} catch (err) {
console.error(err)
throw new Error("Error constructing OIDC authentication strategy", err)
throw new Error("Error constructing OIDC authentication configuration", err)
}
}
exports.getCallbackUrl = async function (db, config) {
return ssoCallbackUrl(db, config, Configs.OIDC)
}
// expose for testing
exports.buildVerifyFn = buildVerifyFn

4
packages/backend-core/src/middleware/passport/tests/oidc.spec.js

@ -48,8 +48,8 @@ describe("oidc", () => {
it("should create successfully create an oidc strategy", async () => {
const oidc = require("../oidc")
await oidc.strategyFactory(oidcConfig, callbackUrl)
const enrichedConfig = await oidc.fetchStrategyConfig(oidcConfig, callbackUrl)
await oidc.strategyFactory(enrichedConfig, callbackUrl)
expect(mockFetch).toHaveBeenCalledWith(oidcConfig.configUrl)

23
packages/backend-core/src/middleware/passport/utils.js

@ -1,3 +1,7 @@
const { isMultiTenant, getTenantId } = require("../../tenancy")
const { getScopedConfig } = require("../../db/utils")
const { Configs } = require("../../constants")
/**
* Utility to handle authentication errors.
*
@ -5,6 +9,7 @@
* @param {*} message Message that will be returned in the response body
* @param {*} err (Optional) error that will be logged
*/
exports.authError = function (done, message, err = null) {
return done(
err,
@ -12,3 +17,21 @@ exports.authError = function (done, message, err = null) {
{ message: message }
)
}
exports.ssoCallbackUrl = async (db, config, type) => {
// incase there is a callback URL from before
if (config && config.callbackURL) {
return config.callbackURL
}
const publicConfig = await getScopedConfig(db, {
type: Configs.SETTINGS,
})
let callbackUrl = `/api/global/auth`
if (isMultiTenant()) {
callbackUrl += `/${getTenantId()}`
}
callbackUrl += `/${type}/callback`
return `${publicConfig.platformUrl}${callbackUrl}`
}

5
packages/backend-core/yarn.lock

@ -4123,6 +4123,11 @@ passport-oauth1@1.x.x:
passport-strategy "1.x.x"
utils-merge "1.x.x"
passport-oauth2-refresh@^2.1.0:
version "2.1.0"
resolved "https://registry.yarnpkg.com/passport-oauth2-refresh/-/passport-oauth2-refresh-2.1.0.tgz#c31cd133826383f5539d16ad8ab4f35ca73ce4a4"
integrity sha512-4ML7ooCESCqiTgdDBzNUFTBcPR8zQq9iM6eppEUGMMvLdsjqRL93jKwWm4Az3OJcI+Q2eIVyI8sVRcPFvxcF/A==
passport-oauth2@1.x.x:
version "1.6.1"
resolved "https://registry.yarnpkg.com/passport-oauth2/-/passport-oauth2-1.6.1.tgz#c5aee8f849ce8bd436c7f81d904a3cd1666f181b"

6
packages/bbui/package.json

@ -1,7 +1,7 @@
{
"name": "@budibase/bbui",
"description": "A UI solution used in the different Budibase projects.",
"version": "1.0.212-alpha.8",
"version": "1.0.219",
"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.212-alpha.8",
"@budibase/string-templates": "^1.0.219",
"@spectrum-css/actionbutton": "^1.0.1",
"@spectrum-css/actiongroup": "^1.0.1",
"@spectrum-css/avatar": "^3.0.2",
@ -86,4 +86,4 @@
"svelte-portal": "^1.0.0"
},
"gitHead": "d1836a898cab3f8ab80ee6d8f42be1a9eed7dcdc"
}
}

6
packages/bbui/src/ActionButton/ActionButton.svelte

@ -13,6 +13,7 @@
export let size = "M"
export let active = false
export let fullWidth = false
export let noPadding = false
function longPress(element) {
if (!longPressable) return
@ -41,6 +42,7 @@
class:spectrum-ActionButton--quiet={quiet}
class:spectrum-ActionButton--emphasized={emphasized}
class:is-selected={selected}
class:noPadding
class:fullWidth
class="spectrum-ActionButton spectrum-ActionButton--size{size}"
class:active
@ -86,4 +88,8 @@
.is-selected:not(.spectrum-ActionButton--emphasized) {
background: var(--spectrum-global-color-gray-300);
}
.noPadding {
padding: 0;
min-width: 0;
}
</style>

1
packages/bbui/src/Form/Combobox.svelte

@ -40,5 +40,6 @@
on:change={onChange}
on:pick
on:type
on:blur
/>
</Field>

5
packages/bbui/src/Form/Core/Combobox.svelte

@ -52,7 +52,10 @@
{id}
type="text"
on:focus={() => (focus = true)}
on:blur={() => (focus = false)}
on:blur={() => {
focus = false
dispatch("blur")
}}
on:change={onType}
value={value || ""}
placeholder={placeholder || ""}

25
packages/bbui/src/Notification/Notification.svelte

@ -1,15 +1,20 @@
<script>
import { ActionButton } from "../"
import { createEventDispatcher } from "svelte"
export let type = "info"
export let icon = "Info"
export let message = ""
export let dismissable = false
export let actionMessage = null
export let action = null
export let wide = false
const dispatch = createEventDispatcher()
</script>
<div class="spectrum-Toast spectrum-Toast--{type}">
<div class="spectrum-Toast spectrum-Toast--{type}" class:wide>
{#if icon}
<svg
class="spectrum-Icon spectrum-Icon--sizeM spectrum-Toast-typeIcon"
@ -19,8 +24,13 @@
<use xlink:href="#spectrum-icon-18-{icon}" />
</svg>
{/if}
<div class="spectrum-Toast-body">
<div class="spectrum-Toast-body" class:actionBody={!!action}>
<div class="spectrum-Toast-content">{message || ""}</div>
{#if action}
<ActionButton quiet emphasized on:click={action}>
<div style="color: white; font-weight: 600;">{actionMessage}</div>
</ActionButton>
{/if}
</div>
{#if dismissable}
<div class="spectrum-Toast-buttons">
@ -46,4 +56,15 @@
.spectrum-Toast {
pointer-events: all;
}
.wide {
width: 100%;
}
.actionBody {
justify-content: space-between;
display: flex;
width: 100%;
align-items: center;
}
</style>

4
packages/bbui/src/Notification/NotificationDisplay.svelte

@ -8,13 +8,15 @@
<Portal target=".modal-container">
<div class="notifications">
{#each $notifications as { type, icon, message, id, dismissable } (id)}
{#each $notifications as { type, icon, message, id, dismissable, action, wide } (id)}
<div transition:fly={{ y: -30 }}>
<Notification
{type}
{icon}
{message}
{dismissable}
{action}
{wide}
on:dismiss={() => notifications.dismiss(id)}
/>
</div>

30
packages/bbui/src/Stores/notifications.js

@ -20,7 +20,16 @@ export const createNotificationStore = () => {
setTimeout(() => (block = false), timeout)
}
const send = (message, type = "default", icon = "", autoDismiss = true) => {
const send = (
message,
{
type = "default",
icon = "",
autoDismiss = true,
action = null,
wide = false,
}
) => {
if (block) {
return
}
@ -28,7 +37,15 @@ export const createNotificationStore = () => {
_notifications.update(state => {
return [
...state,
{ id: _id, type, message, icon, dismissable: !autoDismiss },
{
id: _id,
type,
message,
icon,
dismissable: !autoDismiss,
action,
wide,
},
]
})
if (autoDismiss) {
@ -50,10 +67,11 @@ export const createNotificationStore = () => {
return {
subscribe,
send,
info: msg => send(msg, "info", "Info"),
error: msg => send(msg, "error", "Alert", false),
warning: msg => send(msg, "warning", "Alert"),
success: msg => send(msg, "success", "CheckmarkCircle"),
info: msg => send(msg, { type: "info", icon: "Info" }),
error: msg =>
send(msg, { type: "error", icon: "Alert", autoDismiss: false }),
warning: msg => send(msg, { type: "warning", icon: "Alert" }),
success: msg => send(msg, { type: "success", icon: "CheckmarkCircle" }),
blockNotifications,
dismiss: dismissNotification,
}

2
packages/bbui/src/Table/ArrayRenderer.svelte

@ -5,7 +5,7 @@
const displayLimit = 5
$: badges = value?.slice(0, displayLimit) ?? []
$: badges = Array.isArray(value) ? value.slice(0, displayLimit) : []
$: leftover = (value?.length ?? 0) - badges.length
</script>

10
packages/bbui/src/Table/CellRenderer.svelte

@ -26,12 +26,20 @@
array: ArrayRenderer,
internal: InternalRenderer,
}
$: type = schema?.type ?? "string"
$: type = getType(schema)
$: customRenderer = customRenderers?.find(x => x.column === schema?.name)
$: renderer = customRenderer?.component ?? typeMap[type] ?? StringRenderer
$: width = schema?.width || "150px"
$: cellValue = getCellValue(value, schema.template)
const getType = schema => {
// Use a string renderer for dates if we use a custom template
if (schema?.type === "datetime" && schema?.template) {
return "string"
}
return schema?.type || "string"
}
const getCellValue = (value, template) => {
if (!template) {
return value

3
packages/bbui/src/Table/Table.svelte

@ -37,6 +37,7 @@
export let autoSortColumns = true
export let compact = false
export let customPlaceholder = false
export let placeholderText = "No rows found"
const dispatch = createEventDispatcher()
@ -405,7 +406,7 @@
>
<use xlink:href="#spectrum-icon-18-Table" />
</svg>
<div>No rows found</div>
<div>{placeholderText}</div>
</div>
{/if}
</div>

2
packages/builder/cypress.json

@ -16,4 +16,4 @@
"runMode": 1,
"openMode": 0
}
}
}

5
packages/builder/cypress/integration/addRadioButtons.spec.js

@ -10,6 +10,7 @@ filterTests(['all'], () => {
it("should add Radio Buttons options picker on form, add data, and confirm", () => {
cy.navigateToFrontend()
cy.wait(500)
cy.addComponent("Form", "Form")
cy.addComponent("Form", "Options Picker").then((componentId) => {
// Provide field setting
@ -36,5 +37,9 @@ filterTests(['all'], () => {
})
cy.addCustomSourceOptions(totalRadioButtons)
}
after(() => {
cy.deleteAllApps()
})
})
})

5
packages/builder/cypress/integration/adminAndManagement/accountPortals.spec.js

@ -9,10 +9,11 @@ filterTests(["smoke", "all"], () => {
before(() => {
cy.login()
cy.deleteApp("Cypress Tests")
cy.createApp("Cypress Tests")
cy.createApp("Cypress Tests", false)
// Create new user
cy.visit(`${Cypress.config().baseUrl}/builder`, { timeout: 1000})
cy.wait(500)
cy.visit(`${Cypress.config().baseUrl}/builder`, { timeout: 5000})
cy.createUser(bbUserEmail)
cy.contains("bbuser").click()
cy.wait(500)

43
packages/builder/cypress/integration/adminAndManagement/userManagement.spec.js

@ -6,11 +6,11 @@ filterTests(["smoke", "all"], () => {
before(() => {
cy.login()
cy.deleteApp("Cypress Tests")
cy.createApp("Cypress Tests")
cy.createApp("Cypress Tests", false)
})
it("should create a user via basic onboarding", () => {
cy.visit(`${Cypress.config().baseUrl}/builder`, { timeout: 1000})
cy.visit(`${Cypress.config().baseUrl}/builder`, { timeout: 5000})
cy.createUser("bbuser@test.com")
cy.get(interact.SPECTRUM_TABLE).should("contain", "bbuser")
})
@ -43,19 +43,20 @@ filterTests(["smoke", "all"], () => {
const uuid = () => Cypress._.random(0, 1e6)
const name = uuid()
if(i < 1){
cy.createApp(name)
cy.createApp(name, false)
} else {
cy.visit(`${Cypress.config().baseUrl}/builder`)
cy.get(interact.CREATE_APP_BUTTON, { timeout: 1000 }).click({ force: true })
cy.visit(`${Cypress.config().baseUrl}/builder`, { timeout: 5000})
cy.wait(1000)
cy.get(interact.CREATE_APP_BUTTON, { timeout: 2000 }).click({ force: true })
cy.createAppFromScratch(name)
}
}
}
})
// Navigate back to the user
cy.visit(`${Cypress.config().baseUrl}/builder`, { timeout: 500})
cy.visit(`${Cypress.config().baseUrl}/builder`, { timeout: 5000})
cy.get(interact.SPECTRUM_SIDENAV).contains("Users").click()
cy.get(interact.SPECTRUM_TABLE, { timeout: 500 }).contains("bbuser").click()
cy.get(interact.SPECTRUM_TABLE, { timeout: 1000 }).contains("bbuser").click()
for (let i = 0; i < 3; i++) {
cy.get(interact.SPECTRUM_TABLE, { timeout: 3000})
.eq(1)
@ -64,24 +65,24 @@ filterTests(["smoke", "all"], () => {
.find(interact.SPECTRUM_TABLE_CELL)
.eq(0)
.click()
cy.get(interact.SPECTRUM_DIALOG_GRID, { timeout: 500 })
cy.get(interact.SPECTRUM_DIALOG_GRID, { timeout: 1000 })
.contains("Choose an option")
.click()
.then(() => {
if (i == 0) {
cy.get(interact.SPECTRUM_MENU, { timeout: 1000 }).contains("Admin").click({ force: true })
cy.get(interact.SPECTRUM_MENU, { timeout: 2000 }).contains("Admin").click({ force: true })
}
else if (i == 1) {
cy.get(interact.SPECTRUM_MENU, { timeout: 1000 }).contains("Power").click({ force: true })
cy.get(interact.SPECTRUM_MENU, { timeout: 2000 }).contains("Power").click({ force: true })
}
else if (i == 2) {
cy.get(interact.SPECTRUM_MENU, { timeout: 1000 }).contains("Basic").click({ force: true })
cy.get(interact.SPECTRUM_MENU, { timeout: 2000 }).contains("Basic").click({ force: true })
}
cy.get(interact.SPECTRUM_BUTTON, { timeout: 1000 })
cy.get(interact.SPECTRUM_BUTTON, { timeout: 2000 })
.contains("Update role")
.click({ force: true })
})
cy.reload()
cy.reload({ timeout: 5000 })
cy.wait(1000)
}
// Confirm roles exist within Configure roles table
@ -173,14 +174,16 @@ filterTests(["smoke", "all"], () => {
it("Should edit user details within user details page", () => {
// Add First name
cy.get(interact.FIELD, { timeout: 500 }).eq(2).within(() => {
cy.get(interact.SPECTRUM_TEXTFIELD_INPUT, { timeout: 500 }).type("bb")
cy.get(interact.FIELD, { timeout: 1000 }).eq(2).within(() => {
cy.wait(500)
cy.get(interact.SPECTRUM_TEXTFIELD_INPUT, { timeout: 1000 }).wait(500).clear().click().type("bb")
})
// Add Last name
cy.get(interact.FIELD).eq(3).within(() => {
cy.get(interact.SPECTRUM_TEXTFIELD_INPUT).type("test")
cy.get(interact.FIELD, { timeout: 1000 }).eq(3).within(() => {
cy.wait(500)
cy.get(interact.SPECTRUM_TEXTFIELD_INPUT, { timeout: 1000 }).click().wait(500).clear().type("test")
})
cy.get(interact.FIELD).eq(0).click()
cy.get(interact.FIELD, { timeout: 1000 }).eq(0).click()
// Reload page
cy.reload()
@ -188,8 +191,8 @@ filterTests(["smoke", "all"], () => {
cy.get(interact.FIELD, { timeout: 1000 }).eq(2).within(() => {
cy.get(interact.SPECTRUM_TEXTFIELD_INPUT).should('have.value', "bb")
})
cy.get(interact.FIELD).eq(3).within(() => {
cy.get(interact.SPECTRUM_TEXTFIELD_INPUT, { timeout: 500 }).should('have.value', "test")
cy.get(interact.FIELD, { timeout: 1000 }).eq(3).within(() => {
cy.get(interact.SPECTRUM_TEXTFIELD_INPUT, { timeout: 1000 }).should('have.value', "test")
})
})

2
packages/builder/cypress/integration/adminAndManagement/userSettings.spec.js

@ -103,6 +103,8 @@ filterTests(["smoke", "all"], () => {
}
cy.get("button").contains("Update password").click({ force: true })
})
// Remove users name
cy.updateUserInformation()
})
})
})

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

@ -5,7 +5,8 @@ filterTests(["all"], () => {
context("Application Overview screen", () => {
before(() => {
cy.login()
cy.createTestApp()
cy.deleteAllApps()
cy.createApp("Cypress Tests")
})
it("Should be accessible from the applications list", () => {
@ -81,13 +82,14 @@ filterTests(["all"], () => {
})
it("Should reflect the app deployment state", () => {
cy.visit(`${Cypress.config().baseUrl}/builder`)
cy.visit(`${Cypress.config().baseUrl}/builder`, { timeout: 5000 })
cy.get(".appTable .app-row-actions button")
.contains("Edit")
.eq(0)
.click({ force: true })
cy.get(".toprightnav button.spectrum-Button")
cy.wait(500)
cy.get(".toprightnav button.spectrum-Button", { timeout: 2000 })
.contains("Publish")
.click({ force: true })
cy.get(".spectrum-Modal [data-cy='deploy-app-modal']")
@ -300,7 +302,7 @@ filterTests(["all"], () => {
})
it("Should allow editing of the app details.", () => {
cy.visit(`${Cypress.config().baseUrl}/builder`)
cy.visit(`${Cypress.config().baseUrl}/builder`, { timeout: 5000 })
cy.get(".appTable .app-row-actions button")
.contains("Manage")
.eq(0)
@ -315,7 +317,8 @@ filterTests(["all"], () => {
cy.updateAppName("sample name")
//publish and check its disabled
cy.visit(`${Cypress.config().baseUrl}/builder`)
cy.visit(`${Cypress.config().baseUrl}/builder`, { timeout: 5000 })
cy.wait(500)
cy.get(".appTable .app-row-actions button")
.contains("Edit")
.eq(0)
@ -331,8 +334,8 @@ filterTests(["all"], () => {
cy.wait(1000)
})
cy.visit(`${Cypress.config().baseUrl}/builder`)
cy.get(".appTable .app-row-actions button", { timeout: 1000 })
cy.visit(`${Cypress.config().baseUrl}/builder`, { timeout: 10000 })
cy.get(".appTable .app-row-actions button", { timeout: 5000 })
.contains("Manage")
.eq(0)
.click({ force: true })

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

@ -6,11 +6,12 @@ filterTests(['all'], () => {
context("Publish Application Workflow", () => {
before(() => {
cy.login()
cy.createTestApp()
cy.deleteAllApps()
cy.createApp("Cypress Tests", false)
})
it("Should reflect the unpublished status correctly", () => {
cy.visit(`${Cypress.config().baseUrl}/builder`)
cy.visit(`${Cypress.config().baseUrl}/builder`, { timeout: 5000 })
cy.get(interact.APP_TABLE_STATUS, { timeout: 3000 }).eq(0)
.within(() => {
@ -29,6 +30,7 @@ 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.closeModal()
cy.get(interact.TOPRIGHTNAV_BUTTON_SPECTRUM).contains("Publish").click({ force : true })
cy.get(interact.DEPLOY_APP_MODAL).should("be.visible")
@ -72,7 +74,7 @@ filterTests(['all'], () => {
it("Should unpublish an application using the link and reflect the status change", () => {
//Assuming the previous test app exists and is published
cy.visit(`${Cypress.config().baseUrl}/builder`)
cy.visit(`${Cypress.config().baseUrl}/builder`, { timeout: 5000 })
cy.get(interact.APP_TABLE_STATUS).eq(0)
.within(() => {
@ -85,19 +87,21 @@ filterTests(['all'], () => {
cy.get(interact.APP_TABLE_APP_NAME).click({ force: true })
})
cy.get(interact.DEPLOYMENT_TOP_NAV).click()
cy.get(interact.PUBLISH_POPOVER_ACTION).click({ force: true })
cy.get(interact.UNPUBLISH_MODAL)
.within(() => {
cy.get(interact.CONFIRM_WRAP_BUTTON).click({ force: true })
})
cy.closeModal()
cy.get(interact.DEPLOYMENT_TOP_GLOBE).should("exist").click({ force: true })
cy.get("[data-cy='publish-popover-menu']")
.within(() => {
cy.get(interact.PUBLISH_POPOVER_ACTION).click({ force: true })
})
cy.get(interact.UNPUBLISH_MODAL).should("be.visible")
.within(() => {
cy.get(interact.CONFIRM_WRAP_BUTTON).click({ force: true }
)})
cy.visit(`${Cypress.config().baseUrl}/builder`)
cy.visit(`${Cypress.config().baseUrl}/builder`, { timeout: 6000 })
cy.wait(500)
cy.get(interact.APP_TABLE_STATUS, { timeout: 1000 }).eq(0).contains("Unpublished")
})

12
packages/builder/cypress/integration/createApp.spec.js

@ -51,14 +51,8 @@ filterTests(['smoke', 'all'], () => {
cy.visit(`${Cypress.config().baseUrl}/builder`, { timeout: 10000 })
// Start create app process. If apps already exist, click second button
cy.get(interact.CREATE_APP_BUTTON, { timeout: 1000 }).click({ force: true })
cy.request(`${Cypress.config().baseUrl}/api/applications?status=all`)
.its("body")
.then(val => {
if (val.length > 0) {
cy.get(interact.CREATE_APP_BUTTON).click({ force: true })
}
})
cy.wait(1000)
cy.get(interact.CREATE_APP_BUTTON, { timeout: 3000 }).click({ force: true })
const appName = "Cypress Tests"
cy.get(interact.SPECTRUM_MODAL).within(() => {
@ -93,7 +87,7 @@ filterTests(['smoke', 'all'], () => {
const appName = "Cypress Tests"
cy.createApp(appName, false)
cy.visit(`${Cypress.config().baseUrl}/builder`)
cy.visit(`${Cypress.config().baseUrl}/builder`, { timeout: 5000 })
cy.applicationInAppTable(appName)
cy.deleteApp(appName)

1
packages/builder/cypress/integration/createView.spec.js

@ -5,7 +5,6 @@ filterTests(['smoke', 'all'], () => {
context("Create a View", () => {
before(() => {
cy.login()
cy.createTestApp()
cy.createTable("data")
cy.addColumn("data", "group", "Text")

1
packages/builder/cypress/integration/datasources/mySql.spec.js

@ -111,6 +111,7 @@ filterTests(["all"], () => {
// Save relationship & reload page
cy.get(".spectrum-Button").contains("Save").click({ force: true })
cy.reload()
cy.wait(1000)
})
// Confirm table length & relationship name
cy.get(".spectrum-Table", { timeout: 1000 })

19
packages/builder/cypress/integration/datasources/postgreSql.spec.js

@ -151,7 +151,7 @@ filterTests(["all"], () => {
cy.get("@query").its("response.body").should("not.be.empty")
// Save query
cy.get(".spectrum-Button").contains("Save Query").click({ force: true })
cy.get(".hierarchy-items-container").should("contain", queryName)
cy.get(".spectrum-Tabs-content", { timeout: 2000 }).should("contain", queryName)
})
it("should switch to schema with no tables", () => {
@ -217,24 +217,24 @@ filterTests(["all"], () => {
it("should edit a query name", () => {
// Access query
cy.get(".hierarchy-items-container")
cy.get(".hierarchy-items-container", { timeout: 2000 })
.contains(queryName + " (1)")
.click()
// Rename query
cy.get(".spectrum-Form-item")
cy.wait(1000)
cy.get(".spectrum-Form-item", { timeout: 2000 })
.eq(0)
.within(() => {
cy.get("input").clear().type(queryRename)
})
// Run and Save query
cy.get(".spectrum-Button").contains("Run Query").click({ force: true })
cy.wait(500)
cy.get(".spectrum-Button", { timeout: 500 }).contains("Save Query").click({ force: true })
//cy.reload()
//cy.wait(500)
cy.get(".nav-item").should("contain", queryRename)
cy.get(".spectrum-Button", { timeout: 2000 }).contains("Run Query").click({ force: true })
cy.wait(1000)
cy.get(".spectrum-Button", { timeout: 2000 }).contains("Save Query").click({ force: true })
cy.reload({ timeout: 5000 })
cy.get(".nav-item", { timeout: 2000 }).should("contain", queryRename)
})
it("should delete a query", () => {
@ -251,6 +251,7 @@ filterTests(["all"], () => {
.contains("Delete Query")
.click({ force: true })
// Confirm deletion
cy.reload({ timeout: 5000 })
cy.get(".nav-item", { timeout: 1000 }).should("not.contain", queryName)
})

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

@ -12,7 +12,7 @@ filterTests(["all"], () => {
const appName = "Cypress Tests"
const appRename = "Cypress Renamed"
// Rename app, Search for app, Confirm name was changed
cy.visit(`${Cypress.config().baseUrl}/builder`)
cy.visit(`${Cypress.config().baseUrl}/builder`, { timeout: 5000 })
renameApp(appName, appRename)
cy.reload()
cy.searchForApplication(appRename)
@ -39,7 +39,7 @@ filterTests(["all"], () => {
.click({ force: true })
})
// Rename app, Search for app, Confirm name was changed
cy.visit(`${Cypress.config().baseUrl}/builder`)
cy.visit(`${Cypress.config().baseUrl}/builder`, { timeout: 5000 })
renameApp(appName, appRename, true)
cy.get(interact.APP_TABLE).find(interact.WRAPPER).should("have.length", 1)
cy.applicationInAppTable(appRename)
@ -47,7 +47,7 @@ filterTests(["all"], () => {
it("Should try to rename an application to have no name", () => {
const appName = "Cypress Tests"
cy.visit(`${Cypress.config().baseUrl}/builder`)
cy.visit(`${Cypress.config().baseUrl}/builder`, { timeout: 5000 })
renameApp(appName, " ", false, true)
// Close modal and confirm name has not been changed
cy.get(interact.SPECTRUM_DIALOG_GRID, { timeout: 1000 }).contains("Cancel").click()
@ -57,7 +57,7 @@ filterTests(["all"], () => {
xit("Should create two applications with the same name", () => {
// It is not possible to have applications with the same name
const appName = "Cypress Tests"
cy.visit(`${Cypress.config().baseUrl}/builder`)
cy.visit(`${Cypress.config().baseUrl}/builder`, { timeout: 5000 })
cy.get(interact.SPECTRUM_BUTTON), { timeout: 500 }
.contains("Create app")
.click({ force: true })
@ -80,18 +80,15 @@ filterTests(["all"], () => {
const appName = "Cypress Tests"
const numberName = 12345
const specialCharName = "£$%^"
cy.visit(`${Cypress.config().baseUrl}/builder`)
cy.visit(`${Cypress.config().baseUrl}/builder`, { timeout: 5000 })
renameApp(appName, numberName)
cy.reload()
cy.applicationInAppTable(numberName)
cy.reload()
renameApp(numberName, specialCharName)
cy.get(interact.ERROR).should(
"have.text",
"App name must be letters, numbers and spaces only"
)
// Set app name back to Cypress Tests
cy.reload()
renameApp(numberName, appName)
})

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

@ -134,15 +134,18 @@ Cypress.Commands.add("createApp", (name, addDefaultTable) => {
const shouldCreateDefaultTable =
typeof addDefaultTable != "boolean" ? true : addDefaultTable
cy.visit(`${Cypress.config().baseUrl}/builder`, { timeout: 5000 })
cy.get(`[data-cy="create-app-btn"]`, { timeout: 2000 }).click({ force: true })
cy.visit(`${Cypress.config().baseUrl}/builder`, { timeout: 10000 })
cy.wait(1000)
cy.get(`[data-cy="create-app-btn"]`, { timeout: 5000 }).click({ force: true })
// If apps already exist
cy.request(`${Cypress.config().baseUrl}/api/applications?status=all`)
.its("body")
.then(val => {
if (val.length > 0) {
cy.get(`[data-cy="create-app-btn"]`).click({ force: true })
cy.get(`[data-cy="create-app-btn"]`, { timeout: 5000 }).click({
force: true,
})
}
})
@ -400,17 +403,19 @@ Cypress.Commands.add("createAppFromScratch", appName => {
Cypress.Commands.add("createTable", (tableName, initialTable) => {
if (!initialTable) {
cy.navigateToDataSection()
cy.get(`[data-cy="new-table"]`).click()
cy.get(`[data-cy="new-table"]`, { timeout: 2000 }).click()
}
cy.wait(2000)
cy.get(".item")
cy.get(".item", { timeout: 2000 })
.contains("Budibase DB")
.click({ force: true })
.then(() => {
cy.get(".spectrum-Button").contains("Continue").click({ force: true })
cy.get(".spectrum-Button", { timeout: 2000 })
.contains("Continue")
.click({ force: true })
})
cy.get(".spectrum-Modal").within(() => {
cy.get("input", { timeout: 1000 }).first().type(tableName).blur()
cy.get(".spectrum-Modal", { timeout: 2000 }).within(() => {
cy.get("input", { timeout: 2000 }).first().type(tableName).blur()
cy.get(".spectrum-ButtonGroup").contains("Create").click()
})
cy.contains(tableName).should("be.visible")
@ -504,12 +509,13 @@ Cypress.Commands.add("addCustomSourceOptions", totalOptions => {
// DESIGN AREA
Cypress.Commands.add("addComponent", (category, component) => {
if (category) {
cy.get(`[data-cy="category-${category}"]`, { timeout: 1000 }).click({
cy.get(`[data-cy="category-${category}"]`, { timeout: 3000 }).click({
force: true,
})
}
cy.wait(500)
if (component) {
cy.get(`[data-cy="component-${component}"]`, { timeout: 1000 }).click({
cy.get(`[data-cy="component-${component}"]`, { timeout: 3000 }).click({
force: true,
})
}
@ -517,7 +523,7 @@ Cypress.Commands.add("addComponent", (category, component) => {
cy.location().then(loc => {
const params = loc.pathname.split("/")
const componentId = params[params.length - 1]
cy.getComponent(componentId).should("exist")
cy.getComponent(componentId, { timeout: 3000 }).should("exist")
return cy.wrap(componentId)
})
})
@ -621,8 +627,8 @@ Cypress.Commands.add("navigateToFrontend", () => {
// Clicks on Design tab and then the Home nav item
cy.wait(500)
cy.contains("Design").click()
cy.get(".spectrum-Search").type("/")
cy.get(".nav-item").contains("home").click()
cy.get(".spectrum-Search", { timeout: 2000 }).type("/")
cy.get(".nav-item", { timeout: 2000 }).contains("home").click()
})
Cypress.Commands.add("navigateToDataSection", () => {
@ -782,7 +788,7 @@ Cypress.Commands.add("createRestQuery", (method, restUrl, queryPrettyName) => {
// MISC
Cypress.Commands.add("closeModal", () => {
cy.get(".spectrum-Modal").within(() => {
cy.get(".spectrum-Modal", { timeout: 2000 }).within(() => {
cy.get(".close-icon").click()
cy.wait(1000) // Wait for modal to close
})

13
packages/builder/package.json

@ -1,6 +1,6 @@
{
"name": "@budibase/builder",
"version": "1.0.212-alpha.8",
"version": "1.0.219",
"license": "GPL-3.0",
"private": true,
"scripts": {
@ -69,14 +69,15 @@
}
},
"dependencies": {
"@budibase/bbui": "^1.0.212-alpha.8",
"@budibase/client": "^1.0.212-alpha.8",
"@budibase/frontend-core": "^1.0.212-alpha.8",
"@budibase/string-templates": "^1.0.212-alpha.8",
"@budibase/bbui": "^1.0.219",
"@budibase/client": "^1.0.219",
"@budibase/frontend-core": "^1.0.219",
"@budibase/string-templates": "^1.0.219",
"@sentry/browser": "5.19.1",
"@spectrum-css/page": "^3.0.1",
"@spectrum-css/vars": "^3.0.1",
"codemirror": "^5.59.0",
"dayjs": "^1.11.2",
"downloadjs": "1.4.7",
"lodash": "4.17.21",
"posthog-js": "1.4.5",
@ -120,4 +121,4 @@
"vite": "^2.1.5"
},
"gitHead": "115189f72a850bfb52b65ec61d932531bf327072"
}
}

2
packages/builder/src/analytics/IntercomClient.js

@ -53,7 +53,7 @@ export default class IntercomClient {
* @returns Intercom global object
*/
show(user = {}) {
if (!this.initialised) return
if (!this.initialised || !user?.admin) return
return window.Intercom("boot", {
app_id: this.token,

90
packages/builder/src/builderStore/dataBinding.js

@ -49,6 +49,95 @@ export const getBindableProperties = (asset, componentId) => {
]
}
/**
* Gets all rest bindable data fields
*/
export const getRestBindings = () => {
const userBindings = getUserBindings()
return [...userBindings, ...getAuthBindings()]
}
/**
* Gets all rest bindable auth fields
*/
export const getAuthBindings = () => {
let bindings = []
const safeUser = makePropSafe("user")
const safeOAuth2 = makePropSafe("oauth2")
const safeAccessToken = makePropSafe("accessToken")
const authBindings = [
{
runtime: `${safeUser}.${safeOAuth2}.${safeAccessToken}`,
readable: `Current User.OAuthToken`,
key: "accessToken",
},
]
bindings = Object.keys(authBindings).map(key => {
const fieldBinding = authBindings[key]
return {
type: "context",
runtimeBinding: fieldBinding.runtime,
readableBinding: fieldBinding.readable,
fieldSchema: { type: "string", name: fieldBinding.key },
providerId: "user",
}
})
return bindings
}
/**
* Utility - convert a key/value map to an array of custom 'context' bindings
* @param {object} valueMap Key/value pairings
* @param {string} prefix A contextual string prefix/path for a user readable binding
* @return {object[]} An array containing readable/runtime binding objects
*/
export const toBindingsArray = (valueMap, prefix) => {
if (!valueMap) {
return []
}
return Object.keys(valueMap).reduce((acc, binding) => {
if (!binding || !valueMap[binding]) {
return acc
}
acc.push({
type: "context",
runtimeBinding: binding,
readableBinding: `${prefix}.${binding}`,
})
return acc
}, [])
}
/**
* Utility - coverting a map of readable bindings to runtime
*/
export const readableToRuntimeMap = (bindings, ctx) => {
if (!bindings || !ctx) {
return {}
}
return Object.keys(ctx).reduce((acc, key) => {
let parsedQuery = readableToRuntimeBinding(bindings, ctx[key])
acc[key] = parsedQuery
return acc
}, {})
}
/**
* Utility - coverting a map of runtime bindings to readable
*/
export const runtimeToReadableMap = (bindings, ctx) => {
if (!bindings || !ctx) {
return {}
}
return Object.keys(ctx).reduce((acc, key) => {
let parsedQuery = runtimeToReadableBinding(bindings, ctx[key])
acc[key] = parsedQuery
return acc
}, {})
}
/**
* Gets the bindable properties exposed by a certain component.
*/
@ -298,7 +387,6 @@ const getUserBindings = () => {
providerId: "user",
})
})
return bindings
}

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

@ -5,6 +5,7 @@ import { cloneDeep } from "lodash/fp"
const initialAutomationState = {
automations: [],
showTestPanel: false,
blockDefinitions: {
TRIGGER: [],
ACTION: [],
@ -19,6 +20,17 @@ export const getAutomationStore = () => {
}
const automationActions = store => ({
definitions: async () => {
const response = await API.getAutomationDefinitions()
store.update(state => {
state.blockDefinitions = {
TRIGGER: response.trigger,
ACTION: response.action,
}
return state
})
return response
},
fetch: async () => {
const responses = await Promise.all([
API.getAutomations(),
@ -109,6 +121,20 @@ const automationActions = store => ({
return state
})
},
getLogs: async ({ automationId, startDate, status, page } = {}) => {
return await API.getAutomationLogs({
automationId,
startDate,
status,
page,
})
},
clearLogErrors: async ({ automationId, appId } = {}) => {
return await API.clearAutomationLogErrors({
automationId,
appId,
})
},
addTestDataToAutomation: data => {
store.update(state => {
state.selectedAutomation.addTestData(data)
@ -117,11 +143,10 @@ const automationActions = store => ({
},
addBlockToAutomation: (block, blockIdx) => {
store.update(state => {
const newBlock = state.selectedAutomation.addBlock(
state.selectedBlock = state.selectedAutomation.addBlock(
cloneDeep(block),
blockIdx
)
state.selectedBlock = newBlock
return state
})
},

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

@ -65,7 +65,7 @@
<ActionButton
disabled={!$automationStore.selectedAutomation?.testResults}
on:click={() => {
$automationStore.selectedAutomation.automation.showTestPanel = true
$automationStore.showTestPanel = true
}}
size="M">Test Details</ActionButton
>

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

@ -1,6 +1,4 @@
<script>
import FlowItemHeader from "./FlowItemHeader.svelte"
import { automationStore } from "builderStore"
import {
Icon,
@ -16,6 +14,7 @@
import AutomationBlockSetup from "../../SetupPanel/AutomationBlockSetup.svelte"
import CreateWebhookModal from "components/automation/Shared/CreateWebhookModal.svelte"
import ActionModal from "./ActionModal.svelte"
import FlowItemHeader from "./FlowItemHeader.svelte"
export let block
export let testDataModal

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

@ -7,12 +7,19 @@
export let blockComplete
export let showTestStatus = false
export let showParameters = {}
export let testResult
export let isTrigger
$: testResult =
$automationStore.selectedAutomation?.testResults?.steps.filter(step =>
block.id ? step.id === block.id : step.stepId === block.stepId
)
$: isTrigger = block.type === "TRIGGER"
$: {
if (!testResult) {
testResult =
$automationStore.selectedAutomation?.testResults?.steps.filter(step =>
block.id ? step.id === block.id : step.stepId === block.stepId
)[0]
}
}
$: isTrigger = isTrigger || block.type === "TRIGGER"
$: status = updateStatus(testResult, isTrigger)
async function onSelect(block) {
await automationStore.update(state => {
@ -20,6 +27,19 @@
return state
})
}
function updateStatus(results, isTrigger) {
if (!results) {
return {}
}
if (results.outputs?.status?.toLowerCase() === "stopped") {
return { yellow: true, message: "Stopped" }
} else if (results.outputs?.success || isTrigger) {
return { positive: true, message: "Success" }
} else {
return { negative: true, message: "Error" }
}
}
</script>
<div class="blockSection">
@ -60,16 +80,13 @@
</div>
</div>
<div class="blockTitle">
{#if showTestStatus && testResult && testResult[0]}
{#if showTestStatus && testResult}
<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
positive={status?.positive}
yellow={status?.yellow}
negative={status?.negative}
><Body size="XS">{status?.message}</Body></StatusLight
>
</div>
{/if}

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

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

137
packages/builder/src/components/automation/AutomationBuilder/TestDisplay.svelte

@ -0,0 +1,137 @@
<script>
import { Icon, Divider, Tabs, Tab, TextArea, Label } from "@budibase/bbui"
import FlowItemHeader from "./FlowChart/FlowItemHeader.svelte"
export let automation
export let testResults
export let width = "400px"
let showParameters
let blocks
function prepTestResults(results) {
return results?.steps.filter(x => x.stepId !== "LOOP" || [])
}
function textArea(results, message) {
if (!results) {
return message
}
return JSON.stringify(results, null, 2)
}
$: filteredResults = prepTestResults(testResults)
$: {
blocks = []
if (automation) {
if (automation.definition.trigger) {
blocks.push(automation.definition.trigger)
}
blocks = blocks
.concat(automation.definition.steps || [])
.filter(x => x.stepId !== "LOOP")
} else if (filteredResults) {
blocks = filteredResults || []
// make sure there is an ID for each block being displayed
let count = 0
for (let block of blocks) {
block.id = count++
}
}
}
</script>
<div class="container">
{#each blocks as block, idx}
<div class="block" style={width ? `width: ${width}` : ""}>
{#if block.stepId !== "LOOP"}
<FlowItemHeader
showTestStatus={true}
bind:showParameters
{block}
isTrigger={idx === 0}
testResult={filteredResults?.[idx]}
/>
{#if showParameters && showParameters[block.id]}
<Divider noMargin />
{#if filteredResults?.[idx]?.outputs.iterations}
<div style="display: flex; padding: 10px 10px 0px 12px;">
<Icon name="Reuse" />
<div style="margin-left: 10px;">
<Label>
This loop ran {filteredResults?.[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={textArea(filteredResults?.[idx]?.inputs, "No input")}
/>
</div></Tab
>
<Tab title="Output">
<div style="padding: 10px 10px 10px 10px;">
<TextArea
minHeight="100px"
disabled
value={textArea(
filteredResults?.[idx]?.outputs,
"No output"
)}
/>
</div>
</Tab>
</Tabs>
</div>
{/if}
{/if}
</div>
{#if blocks.length - 1 !== idx}
<div class="separator" />
{/if}
{/each}
</div>
<style>
.container {
padding: 0 30px 0 30px;
height: 100%;
}
.tabs {
display: flex;
flex-direction: column;
justify-content: flex-start;
align-items: stretch;
position: relative;
flex: 1 1 auto;
}
.block {
display: inline-block;
width: 400px;
height: auto;
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>

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

@ -1,11 +1,11 @@
<script>
import { Icon, Divider, Tabs, Tab, TextArea, Label } from "@budibase/bbui"
import FlowItemHeader from "./FlowChart/FlowItemHeader.svelte"
import { Icon, Divider } from "@budibase/bbui"
import TestDisplay from "./TestDisplay.svelte"
import { automationStore } from "builderStore"
export let automation
export let testResults
let showParameters
let blocks
$: {
@ -17,13 +17,15 @@
blocks = blocks
.concat(automation.definition.steps || [])
.filter(x => x.stepId !== "LOOP")
} else if (testResults) {
blocks = testResults.steps || []
}
}
$: {
if (!testResults) {
testResults = $automationStore.selectedAutomation?.testResults
}
}
$: testResults =
$automationStore.selectedAutomation?.testResults?.steps.filter(
x => x.stepId !== "LOOP" || []
)
</script>
<div class="title">
@ -34,7 +36,7 @@
<div style="padding-right: var(--spacing-xl)">
<Icon
on:click={async () => {
$automationStore.selectedAutomation.automation.showTestPanel = false
$automationStore.showTestPanel = false
}}
hoverable
name="Close"
@ -44,59 +46,9 @@
<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>
<TestDisplay {automation} {testResults} />
<style>
.container {
padding: 0px 30px 0px 30px;
}
.title {
display: flex;
flex-direction: row;
@ -106,15 +58,6 @@
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;
@ -124,23 +67,4 @@
.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>

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

@ -22,10 +22,8 @@
RelationshipTypes,
ALLOWABLE_STRING_OPTIONS,
ALLOWABLE_NUMBER_OPTIONS,
ALLOWABLE_JSON_OPTIONS,
ALLOWABLE_STRING_TYPES,
ALLOWABLE_NUMBER_TYPES,
ALLOWABLE_JSON_TYPES,
SWITCHABLE_TYPES,
} from "constants/backend"
import { getAutoColumnInformation, buildAutoColumn } from "builderStore/utils"
@ -255,11 +253,6 @@
ALLOWABLE_NUMBER_TYPES.indexOf(field.type) !== -1
) {
return ALLOWABLE_NUMBER_OPTIONS
} else if (
originalName &&
ALLOWABLE_JSON_TYPES.indexOf(field.type) !== -1
) {
return ALLOWABLE_JSON_OPTIONS
} else if (!external) {
return [
...Object.values(fieldDefinitions),

12
packages/builder/src/components/backend/DataTable/modals/CreateEditUser.svelte

@ -5,8 +5,9 @@
import { notifications } from "@budibase/bbui"
import RowFieldControl from "../RowFieldControl.svelte"
import { API } from "api"
import { ModalContent, Select } from "@budibase/bbui"
import { ModalContent, Select, Link } from "@budibase/bbui"
import ErrorsBox from "components/common/ErrorsBox.svelte"
import { goto } from "@roxi/routify"
export let row = {}
@ -87,6 +88,15 @@
onConfirm={saveRow}
>
<ErrorsBox {errors} />
<!-- need to explain to the user the readonly fields -->
{#if !creating}
<div>
A user's email, role, first and last names cannot be changed from within
the app builder. Please go to the <Link
on:click={$goto("/builder/portal/manage/users")}>user portal</Link
> to do this.
</div>
{/if}
<RowFieldControl
meta={{ ...tableSchema.email, name: "Email" }}
bind:value={row.email}

25
packages/builder/src/components/backend/DatasourceNavigator/TableIntegrationMenu/rest/RestExtraConfigForm.svelte

@ -10,11 +10,31 @@
import KeyValueBuilder from "components/integration/KeyValueBuilder.svelte"
import RestAuthenticationBuilder from "./auth/RestAuthenticationBuilder.svelte"
import ViewDynamicVariables from "./variables/ViewDynamicVariables.svelte"
import {
getRestBindings,
readableToRuntimeBinding,
runtimeToReadableMap,
} from "builderStore/dataBinding"
import { cloneDeep } from "lodash/fp"
export let datasource
export let queries
let addHeader
let parsedHeaders = runtimeToReadableMap(
getRestBindings(),
cloneDeep(datasource?.config?.defaultHeaders)
)
const onDefaultHeaderUpdate = headers => {
const flatHeaders = cloneDeep(headers).reduce((acc, entry) => {
acc[entry.name] = readableToRuntimeBinding(getRestBindings(), entry.value)
return acc
}, {})
datasource.config.defaultHeaders = flatHeaders
}
</script>
<Divider size="S" />
@ -30,9 +50,10 @@
</Body>
<KeyValueBuilder
bind:this={addHeader}
bind:object={datasource.config.defaultHeaders}
on:change
bind:object={parsedHeaders}
on:change={evt => onDefaultHeaderUpdate(evt.detail)}
noAddButton
bindings={getRestBindings()}
/>
<div>
<ActionButton icon="Add" on:click={() => addHeader.addEntry()}>

22
packages/builder/src/components/backend/DatasourceNavigator/TableIntegrationMenu/rest/auth/RestAuthenticationModal.svelte

@ -2,6 +2,8 @@
import { onMount } from "svelte"
import { ModalContent, Layout, Select, Body, Input } from "@budibase/bbui"
import { AUTH_TYPE_LABELS, AUTH_TYPES } from "./authTypes"
import BindableCombobox from "components/common/bindings/BindableCombobox.svelte"
import { getAuthBindings } from "builderStore/dataBinding"
export let configs
export let currentConfig
@ -203,11 +205,23 @@
/>
{/if}
{#if form.type === AUTH_TYPES.BEARER}
<Input
<BindableCombobox
label="Token"
bind:value={form.bearer.token}
on:change={onFieldChange}
on:blur={() => (blurred.bearer.token = true)}
value={form.bearer.token}
bindings={getAuthBindings()}
on:change={e => {
form.bearer.token = e.detail
console.log(e.detail)
onFieldChange()
}}
on:blur={() => {
blurred.bearer.token = true
onFieldChange()
}}
allowJS={false}
placeholder="Token"
appendBindingsAsOptions={true}
drawerEnabled={false}
error={blurred.bearer.token ? errors.bearer.token : null}
/>
{/if}

422
packages/builder/src/components/backend/DatasourceNavigator/icons/SQLServer.svelte

@ -4,396 +4,40 @@
</script>
<svg
xmlns="http://www.w3.org/2000/svg"
version="1.1"
{width}
{height}
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 1478.201 1195.111"
viewBox="-16 -16 150 150"
style="shape-rendering:geometricPrecision; text-rendering:geometricPrecision; image-rendering:optimizeQuality; fill-rule:evenodd; clip-rule:evenodd"
xmlns:xlink="http://www.w3.org/1999/xlink"
>
<g transform="matrix(.569 0 0 .569 199.451 -82.735)">
<linearGradient
id="a"
gradientUnits="userSpaceOnUse"
x1="-2901.952"
y1="923.573"
x2="-2061.249"
y2="1420.331"
gradientTransform="matrix(.1234 0 0 -.1234 1158.33 1550.273)"
>
<stop offset="0" stop-color="#909ca9" />
<stop offset="1" stop-color="#ededee" />
</linearGradient>
<path
fill="url(#a)"
d="M1410.773 814.195l-286.9 93.683-249.599 110.161-69.829 18.435c-17.784
16.916-36.431 34.049-56.599 51.397-22.119 19.082-42.72 36.433-58.553
49.008-17.564 13.88-43.587 39.902-56.814 56.38-19.735 24.721-35.348
50.96-42.071 71.13-11.928 36.433-6.07 73.297 16.916 107.346 29.492 43.369
88.261 87.606 156.785 117.749 34.916 15.4 93.683 35.132 137.92 46.19
73.512 18.651 215.771 38.819 294.054 41.857 15.828.65 37.082.65 37.947 0
1.737-1.088 13.881-24.289 27.979-53.129 48.142-98.238 82.838-190.402
101.703-269.119 11.276-47.706 20.166-111.246 26.019-186.492 1.521-21.036
2.169-91.514.868-115.37-1.953-39.033-5.423-70.692-10.84-101.703-.868-4.555-1.088-8.676-.652-8.892.865-.65
3.467-1.517 38.815-11.712l-7.153-16.912v-.005h.004zm-65.49 38.386c2.602 0
9.539 66.573 11.273 108.646.436 8.89.216 14.745-.216 14.745-1.733
0-36.649-20.599-61.583-36.212-21.687-13.663-62.888-40.988-69.393-46.192-2.173-1.517-1.957-1.733
15.828-7.807 30.14-10.194 101.706-33.18 104.091-33.18zm-146.161
48.143c1.953 0 6.937 2.816 18.865 10.191 44.671 27.974 105.393 61.805
131.415 73.083 8.022 3.469 8.887 2.166-9.542 14.746-39.468 26.889-88.697
53.344-148.983 80.018-10.624 4.771-19.514 8.456-19.73 8.456-.432 0
.865-5.418 2.598-11.925 14.53-54.001 22.772-108.647
23.208-152.452.216-21.687.216-21.687 2.169-22.334-.436.217-.22.217 0
.217zm-30.142 11.492c1.297 1.299.432 49.877-1.304 63.104-3.903
31.662-9.975 61.153-19.947 94.335-2.386 8.018-4.558 14.745-4.987
15.177-.872
1.083-30.581-27.975-40.339-39.251-16.916-19.518-30.141-39.035-39.9-58.117-4.988-9.759-12.793-28.84-12.144-29.492
3.469-2.385 117.753-46.622 118.621-45.756zm-141.826 55.731c.216 0 .432 0
.652.216.432.434 1.953 3.905 3.254 7.807 6.937 18.867 22.548 46.624 35.997
64.407 14.746 19.518 34.048 40.334 50.091 53.996 5.207 4.337 9.975 8.456
10.624 9.108 1.304 1.302 1.737 1.083-33.612 14.53-40.981 15.613-85.656
31.226-136.835 47.706a6825.474 6825.474 0 0 0-36.643
11.928c-1.955.652-1.303-.434 4.335-9.323 25.371-39.686 63.97-117.536
85.657-172.618 3.687-9.542 7.373-19.082 8.025-21.251.868-3.038 1.95-4.121
4.768-5.64 1.518-.43 3.038-.866 3.687-.866zm-43.367 17.999c.649.436-10.411
23.637-21.254 44.889-21.036 40.985-44.022 81.323-74.815 130.331-5.204
8.456-10.19 16.265-10.842 17.132-1.083 1.519-1.519
1.083-4.988-5.638-7.373-14.53-13.447-33.181-16.699-50.313-3.254-16.916-2.602-46.406
1.086-64.621 2.816-13.444 2.602-13.227 9.107-16.481 27.757-14.095
117.537-56.166 118.405-55.299zm374.073 15.182v9.107c0 48.359-5.204
114.716-12.797 163.077-1.301 8.456-2.389 15.393-2.602 15.613 0
0-6.288-1.733-13.661-3.905-32.527-10.193-67.875-25.156-99.754-42.718-21.038-11.494-51.612-30.363-50.743-31.231.213-.215
9.323-4.986 19.947-10.625 42.509-22.118 83.274-45.972 118.622-69.609
13.229-8.892 33.176-23.202 37.518-27.107l3.47-2.602zm-537.802 64.185c.867
0 .65 1.735-.651 9.542-.868 5.64-1.951 16.049-2.382 23.202-1.739 31.662
3.469 55.084 19.082 87.177 4.337 8.892 7.809 16.265 7.589 16.48-1.519
1.303-145.074 43.375-190.183 55.734-13.444 3.685-25.152 6.939-26.024
7.153-1.515.436-1.733.22-1.083-3.47 4.987-31.875 29.276-73.512
63.104-108.644 22.554-23.419 40.554-37.08 71.347-54.648 22.119-12.575
56.165-31.439 58.767-32.309.002-.217.218-.217.434-.217zm338.295
60.503c.216-.216 5.42 2.605 11.708 6.29 46.408 26.891 111.03 51.83 166.108
64.623l4.991 1.086-6.941 3.899c-28.84 16.049-123.606 55.515-220.538
91.732-14.098 5.202-27.975 10.409-30.581 11.492-2.602 1.083-4.988
1.735-4.988 1.519 0-.22 3.906-7.809 8.89-17.132 27.107-50.744
54.433-112.547 68.311-155.485 1.739-4.12 2.82-7.805 3.04-8.024zm-34.48
11.278c.22.221-1.517 4.771-3.687 9.975-18.865 45.756-43.59 95.636-75.249
151.583-8.022 14.314-14.746 25.808-14.966 25.808-.213
0-6.721-3.906-14.527-8.676-45.976-28.192-86.743-62.888-113.414-96.501l-3.905-4.771
19.732-5.422c70.696-19.298 130.762-40.116 190.4-65.704 8.459-3.471
15.4-6.292 15.616-6.292zm214.253 74.815s.217.217 0 0c.216 4.988-10.844
49.661-19.953 81.969-7.589 27.107-14.098 48.361-26.022 85.874-5.204
16.485-9.755 30.143-9.975 30.143-.216
0-1.517-.216-2.818-.647-64.405-11.714-122.089-27.977-176.303-49.661-15.182-6.074-36.866-15.833-38.167-16.916-.432-.438
12.58-6.506 29.06-13.663 98.669-43.154 201.024-92.164 236.153-113.196
4.119-2.603 7.373-3.903 8.025-3.903zm-494.646 16.916c.434.432-27.107
40.118-65.709 94.114-13.444 18.867-29.057 40.985-34.911 49.225-5.856
8.241-14.746 21.253-19.734 29.06l-9.112
14.096-9.759-8.24c-11.494-9.544-31.442-29.927-40.333-41.204-18.651-23.201-31.226-47.706-36.214-70.04-2.386-10.411-2.386-15.618-.22-16.265
3.252-.867 61.153-14.53 115.37-27.11 30.143-6.937 65.054-15.177
77.632-18.213 12.579-3.041 22.774-5.423 22.99-5.423zm27.756 10.626l6.937
7.806c31.231 34.914 63.108 60.724 101.708 83.272 6.941 3.906 12.144 7.373
11.708 7.594-1.514 1.083-134.016 48.136-195.385 69.389-34.478
12.143-62.888 21.901-63.102 21.901-.216
0-2.169-1.299-4.341-2.818l-3.901-2.82 6.288-9.106c20.383-29.493
45.976-61.803 101.707-129.028l38.381-46.19zm173.053 123.822c.213-.215
9.755 3.252 21.464 7.594 28.195 10.624 50.527 17.345 80.456 24.936 36.866
9.326 90.211 18.434 121.657 21.035 4.771.432 7.373.868 6.505
1.519-1.521.868-33.395 11.494-56.816 18.867-37.302 11.708-151.149
45.32-243.962 71.995-17.132 4.987-31.879 9.108-32.746
9.323-2.166.436-9.325-1.519-9.325-2.386 0-.431 5.204-7.153 11.494-14.527
31.225-37.3 62.238-78.935 88.044-118.403 7.154-10.846 13.229-19.736
13.229-19.953zm-38.17 1.087c.216.216-15.179 24.936-42.066 67.439-11.496
17.999-24.291 38.383-28.846 45.54-4.337 6.939-10.842 17.784-14.527
23.854l-6.29
11.061-3.252-.868c-7.809-2.169-62.672-21.471-77.202-27.325-18-7.157-36.649-15.829-50.529-23.202-17.346-9.326-39.03-23.206-37.297-23.637.433-.216
30.143-8.243 65.922-17.999 94.984-25.809 147.678-40.77 182.161-51.612
6.29-1.952 11.71-3.471 11.926-3.251zm269.985 63.318h.216c.868 2.171-34.26
99.755-47.06 130.547-2.815 6.939-3.896 8.677-5.417
8.456-3.687-.213-54.646-7.37-85.66-11.925-53.994-8.24-144.641-24.073-167.409-29.275l-5.204-1.083
32.307-7.378c69.396-15.613 102.791-24.069 136.619-34.478 42.722-13.011
85.011-29.276 127.729-49.225 6.722-3.037 12.361-5.422 13.879-5.639z"
/>
<linearGradient
id="b"
gradientUnits="userSpaceOnUse"
x1="-2882.7"
y1="10288.81"
x2="-2206.249"
y2="10288.81"
gradientTransform="matrix(.1234 0 0 -.1234 1158.33 1550.273)"
>
<stop offset="0" stop-color="#939fab" />
<stop offset="1" stop-color="#dcdee1" />
</linearGradient>
<path
fill="url(#b)"
d="M1114.983 145.414c-4.771-.647-81.757 27.11-131.415 47.275-67.01
27.327-119.052 53.351-151.148 75.899-11.925 8.461-26.891 23.422-29.273
29.276-.867 2.169-1.303 4.771-1.303 7.373l29.06 27.541 69.175 22.119
164.594 29.493 188.228 32.312 1.953-16.264c-.649
0-1.085-.216-1.73-.216l-24.728-3.905-4.984-8.89c-25.59-45.107-53.781-101.056-70.261-138.789-12.793-29.276-24.938-63.102-31.662-87.391-3.687-14.746-4.119-15.613-6.501-15.829v-.005h-.005zm-3.474
11.063h.223c.213.214 1.081 6.29 1.95 13.442 3.683 30.364 10.411 59.635
21.035 91.297 8.022 23.855 8.022 22.555-1.301
19.734-22.119-6.07-121.221-23.202-193-33.177-11.494-1.519-21.253-3.036-21.253-3.252-.867-.867
51.827-28.41 75.031-39.25 29.709-13.665 111.246-47.711
117.315-48.794zm-209.047 97.15l8.461 2.816c45.97 15.616 161.551 37.736
225.31 42.94 7.154.651 13.229 1.303 13.442 1.303.216.216-5.852
3.469-13.661 7.154-30.79 15.397-64.621 34.264-88.042 48.794-6.937
4.335-13.229 7.807-14.094 7.807-.868
0-5.42-.868-10.191-1.519l-8.674-1.303-21.683-21.253c-38.167-37.08-68.094-65.704-79.588-76.549l-11.28-10.19zm-8.671
6.721l30.576 38.168c16.696 21.035 33.611 41.635 37.301 46.187 3.683 4.557
6.721 8.245 6.505
8.461-.868.65-44.236-7.809-67.226-13.011-23.637-5.423-33.395-8.025-47.924-12.577l-11.928-3.905v-3.038c.216-14.53
18.651-36.214 49.877-58.331l2.819-1.954zm259.791 52.046c.869 0 1.95 1.951
4.552 7.806 7.373 16.263 30.364 60.07 35.997 68.526 1.74 2.822 4.771
3.038-25.802-1.95-73.512-11.93-97.152-15.829-97.152-16.263 0-.216
2.169-1.735 4.988-3.254 22.771-12.575 45.756-28.624 66.142-45.756
4.988-4.121 9.542-8.024 10.407-8.676.216-.433.652-.649.868-.433z"
/>
<radialGradient
id="c"
cx="-14217.448"
cy="7277.705"
r="898.12"
gradientTransform="matrix(-.1185 -.0178 -.036 .237 -198.955 -1314.415)"
gradientUnits="userSpaceOnUse"
>
<stop offset="0" stop-color="#ee352c" />
<stop offset="1" stop-color="#a91d22" />
</radialGradient>
<path
fill="url(#c)"
d="M804.66 294.828s-4.768 7.593-.215 18.87c2.822 6.937 11.061 15.393
20.384 24.069 0 0 96.5 94.114 108.211 107.561 53.344 61.585 76.549 122.305
78.718 206.012 1.301 53.78-8.894 101.054-34.264 155.919-45.106
98.453-140.307 207.098-287.117 327.67l21.472-7.157c13.878-10.411
32.745-21.467 76.982-45.756 102.137-55.952 217.071-107.346 358.028-160.258
202.971-76.335 536.715-165.681
726.676-194.736l19.737-3.038-3.038-4.771c-17.345-26.891-29.276-43.587-43.59-61.369-41.633-51.612-92.157-93.463-153.964-128.161-85.007-47.489-194.956-84.571-334.173-112.112-26.239-5.207-83.923-15.181-130.763-22.337-99.321-15.393-163.51-26.021-234.203-38.165-25.37-4.339-63.323-10.843-88.478-16.263-13.011-2.822-37.947-8.676-57.464-15.398-15.613-6.075-38.168-12.147-42.939-30.58zm55.952
54.216c.214-.214 3.683 1.083 8.24 2.602 8.24 2.816 18.865 6.07 31.446
9.542a1599.47 1599.47 0 0 0 28.624 7.589c13.011 3.251 23.852 6.288 24.068
6.288 1.521 1.519 23.424 71.558 30.797 98.449 2.815 10.195 4.988 18.867
4.771
18.867-.223.22-2.605-3.469-5.423-8.456-25.373-44.673-65.491-89.995-111.899-126.428-6.069-4.333-10.624-8.237-10.624-8.453zm106.692
29.492c1.085 0 5.856.651 11.708 1.951 36.866 8.24 103.008 20.818 145.293
27.975 7.157 1.083 12.797 2.387 12.797 2.818 0 .436-2.605 1.951-5.859
3.688-7.153 3.685-35.997 20.815-45.536 27.322-24.073 16.047-45.756
33.395-61.371 49.008-6.288 6.29-11.712 11.494-11.712
11.494s-1.297-3.685-2.386-8.242c-7.802-30.143-24.069-74.816-38.815-106.258-2.386-4.986-4.339-9.541-4.339-9.973
0 .433 0 .217.22.217zm187.795 35.781c1.301.432 3.47 7.806 7.806 24.069
8.025 31.446 11.712 66.576 10.411 99.321-.436 9.108-.868 17.564-1.304
18.651l-.649
2.166-11.276-3.685c-23.204-7.373-60.935-18.435-93.245-27.541-18.436-4.988-33.395-9.542-33.395-9.975
0-1.303 26.891-28.192 38.383-38.383 21.898-19.303 81.316-65.275
83.269-64.623zm14.963 2.166c.652-.647 89.779 14.746 130.331 22.554 30.145
5.854 73.948 14.963 76.549 16.049 1.301.432-3.254 3.034-17.784
9.539-57.248 25.808-99.754 49.008-142.036 77.202-11.06 7.373-20.386
13.444-20.602 13.444-.216 0-.433-6.287-.433-13.878
0-41.201-8.241-82.838-23.424-117.968-1.517-3.47-2.818-6.722-2.601-6.942zm230.516
45.542c.652.65-2.169 18.217-4.771 28.624-7.806 32.312-28.84 80.24-54.643
125.343-4.558 8.024-8.677 14.53-9.114
14.746-.429.216-6.285-3.038-13.009-6.941-25.154-14.746-53.778-28.624-85.007-41.637-8.671-3.685-16.263-6.723-16.48-7.153-1.521-1.303
68.308-47.493 105.174-69.612 29.276-17.781 76.982-44.239
77.85-43.37zm16.48 2.601c1.953 0 41.421 10.844 62.019 16.916 50.963 15.181
109.512 36.648 147.679 53.996l15.828 7.159-11.056 2.6c-93.245
21.467-173.049 46.192-250.034 77.418-6.289 2.602-11.928 4.771-12.357
4.771-.436 0 1.733-4.987 4.552-11.061 23.204-49.225 38.167-100.62
41.85-144.427.221-4.121.867-7.372 1.519-7.372zm-392.938 90.213c.649-.652
30.793 6.506 47.057 11.056 24.721 6.942 77.198 24.505 77.198 25.808 0
.216-5.853 5.204-12.79 11.278-28.408 23.637-55.734 48.572-88.481
80.234-9.759 9.328-17.997 16.917-18.429 16.917-.436
0-.649-1.304-.436-3.038 4.987-36.433
3.906-83.272-3.034-130.763-.653-6.074-1.302-11.276-1.085-11.492zm633.433.652c.429.431-13.881
22.984-22.988 35.777-13.009 18.649-32.098 43.375-75.252 97.588-22.765
28.622-48.358 60.936-56.812 71.778-8.678 10.842-15.831 19.948-16.051
19.948-.216
0-3.031-3.901-6.069-8.671-24.289-36.433-53.349-68.311-87.829-96.935-6.505-5.423-13.658-11.278-16.044-13.013-2.386-1.734-4.339-3.469-4.339-3.685
0-.649 36.862-16.483 64.841-27.757 49.01-19.952 115.794-43.805
165.892-59.203 26.24-8.239 54.215-16.263 54.651-15.827zm16.696
4.334c.865-.215 6.072 2.387 12.361 6.07 52.697 30.143 104.305 68.962
145.077 108.864 11.492 11.278 39.9 40.77 39.464 40.986 0
0-9.975.867-21.683 1.733-91.296 6.942-208.178 26.239-320.511 53.345-7.589
1.733-14.31 3.252-14.746 3.252-.429 0 8.025-8.456 18.653-18.647
65.922-63.538 96.067-103.656 131.628-175.22 4.986-10.623 9.325-19.731
9.757-20.383-.216 0-.216 0 0 0zm-482.936 49.446c3.038.647 31.229 13.88
52.48 24.503 19.517 9.755 48.794 25.372 50.311 26.671.216.216-10.195
5.638-22.984 11.928-40.772 20.384-75.684 39.682-112.118 61.802-10.408
6.29-19.082 11.497-19.298 11.497-.868 0-.652-.872 5.204-11.497
19.518-35.561 35.129-78.065 44.023-119.486.864-3.252 1.733-5.418
2.382-5.418zm-28.192 5.202c.652.652-6.721 27.323-11.273 41.853-8.894
27.541-23.856 62.02-38.383 88.043-3.474 6.069-8.677 14.961-11.496
19.948l-5.42
8.674-12.144-11.707c-14.094-13.663-25.59-22.12-40.333-29.712-5.859-3.033-10.411-5.638-10.411-6.069
0-1.735 37.082-35.347 65.49-59.635 20.383-17.566 63.321-52.045
63.97-51.395zm172.404 70.913l10.627 6.937c24.282 15.833 52.906 36.866
74.813 55.298 12.357 10.19 36.21 31.662 40.985 36.866l2.598 2.822-17.561
4.986c-99.321 27.538-176.087 52.043-265.649 85.007-9.975 3.685-18.433
6.721-19.085 6.721-1.297 0-2.385 1.083 19.954-19.519 57.251-52.691
107.992-110.812 145.726-167.411l7.592-11.707zm-45.324
11.276c.432.432-29.276 42.284-47.06 65.922-21.251 28.192-58.985
75.465-85.007 106.256-10.84 12.797-20.163 23.422-20.599
23.64-.652.216-.868-3.036-.868-8.024
0-26.242-6.721-54.216-18.433-78.068-4.988-9.975-5.856-12.361-4.768-13.444
4.119-3.688 67.223-39.686 107.123-61.153 26.89-14.312 68.956-35.563
69.612-35.129zm-274.107 67.225c.652 0 5.64 2.6 11.279 5.638 13.878 7.589
26.239 16.046 37.298 25.156.432.432-5.204 4.988-12.577 10.406-20.602
14.746-51.828 38.385-70.041 52.915-19.088 15.18-19.734 15.613-17.568
12.361 14.314-21.903 21.467-34.264 29.06-50.093 6.721-14.094 13.442-30.793
18.213-45.323 1.734-6.289 3.904-11.06 4.336-11.06zm73.083
57.248c1.081-.214 2.386 1.735 8.238 10.411 12.361 18.429 21.903 43.154
24.292 63.104l.429 4.339-29.705 11.494c-53.133 20.599-102.139
40.985-135.322 56.162-9.322 4.339-25.587 12.144-36.211 17.352-10.627
5.418-19.301 9.539-19.301 9.323s6.721-5.204 14.961-11.278c64.844-47.055
121.007-98.669 163.076-150.279 4.555-5.423 8.677-10.411
9.107-10.627l.436-.001zm-33.612 8.242c.868.867-23.853 28.84-40.768
45.971-41.853 42.723-83.273 76.12-134.669 108.649-6.505 4.119-12.359
7.804-13.011 8.24-1.519.867.432-1.303 22.986-25.808 14.314-15.397
25.155-28.408 37.516-44.453 8.24-10.624 9.759-12.143 21.688-20.604
31.878-22.987 105.39-72.864 106.258-71.995z"
/>
</g>
<path
fill="#231F1F"
d="M265.747 900.102c-2.276 0-4.553.217-6.809.217-45.975 2.45-76.983
22.683-95.113 62.195-15.506 35.735-13.813 82.446.174 118.4 16.265 35.131
42.547 53.672 86.416 60.675 9.282 1.52 15.506 6.616 33.483 27.606l22.12
25.915h40.118l-26.676-26.892c-14.746-14.745-26.673-27.584-26.673-28.712
0-1.127 5.641-3.599 12.469-5.68 22.51-6.812 41.203-24.202 54.279-50.854
10.583-21.402 12.102-28.018 13.619-54.646
3.969-79.26-37.82-128.813-107.409-128.247l.002.023zm35.173 207.27c-19.517
9.453-47.857 11.34-66.356
4.553-19.127-7.025-37.646-26.889-45.975-49.377-9.259-24.591-7.937-69.956
2.646-90.386 17.023-32.528 39.534-47.49 72.43-47.49 48.792 0 76.549 29.884
80.171 86.048 2.863 46.885-12.838 82.058-42.895
96.632l-.021.02zm693.025-139.568c-16.828 0-29.709 6.811-38.385 20.231l-6.809
10.627v-27.628h-29.123v165.678h29.104v-52.956c0-48.424.604-54.084
7.371-67.335 9.326-18.172 25.371-27.234 40.879-22.897l10.408
3.036v-28.712h-13.445v-.044zm-171.098-1.519c-5.705 0-11.756.76-17.781
2.084-38.971 10.19-60.938 47.489-59.594 85.873 0 32.139 6.244 48.206 21.752
65.057 31.77 26.065 60.502 28.146 99.275 14.161 6.615-2.819 13.814-6.072
13.814-6.072v-26.065l-13.814 7.156c-31.379 13.661-55.016
13.661-73.949-2.43-12.076-12.296-17.391-27.042-19.84-43.868h117.426v-22.339c0-45.539-27.41-74.294-67.313-73.557h.024zm-47.492
72.647s4.338-28.407 20.428-39.554c7.744-5.466 16.633-8.11 25.328-8.11 8.719
0 17.414 2.818 24.592 8.306 14.748 11.341 17.219 39.143 17.219
39.143h-87.566v.215h-.001zm-702.111-29.881c-31.573-19.128-45.582-32.921-43.869-49.185
4.9-44.997 60.503-38.773
91.295-21.749l.219-30.272s-17.024-7.373-41.421-7.764c-37.429-.564-61.63
11.709-72.97 36.691-16.656 36.865-1.908 64.665 51.396 95.677 29.925 17.412
43.152 32.528 43.152 49.008 0 34.047-41.05 45.931-83.401
24.57-8.716-4.337-16.09-7.959-16.48-7.959-1.519 9.651-.736 32.745-.736
32.745s13.012 5.466 32.527 9.236c48.4 9.65 92.445-13.054 96.608-49.919
3.622-34.609-8.893-52.761-56.318-81.104l-.002.025zm1178.454-43.155c-5.682
0-11.711.78-18 2.103-38.924 10.192-60.85 47.492-59.354 85.876 0 32.095 6.225
48.011 21.729 64.838 31.771 26.089 60.504 28.191 99.473 14.184 6.592-2.818
13.77-6.026 13.77-6.026v-26.109l-13.791 7.197c-31.443 13.619-55.082
13.619-73.947-2.471-12.145-12.274-17.414-26.847-19.865-43.871h117.232v-22.336c0-45.321-27.412-74.099-67.313-73.339l.066-.046zm-47.492
72.646s4.381-28.365 20.449-39.729c7.721-5.485 16.611-8.132 25.307-8.132
8.674 0 17.414 2.819 24.594 8.327 14.746 11.342 17.219 39.338 17.219
39.338h-87.545l-.024.196zm-533.809-29.123c-31.573-19.083-45.54-32.92-43.848-49.185
4.9-45.02 60.504-38.773
91.296-21.749l.218-30.272s-17.024-7.374-41.421-7.722c-37.429-.563-61.63
11.711-72.991 36.692-16.633 36.864-1.692 64.666 51.437 95.677 29.884 17.393
43.111 32.312 43.111 48.792 0 34.047-41.029 46.126-83.381
24.569-8.674-4.337-16.046-7.916-16.48-7.916-1.519 9.649-.736 32.746-.736
32.746s12.858 5.27 32.31 9.237c48.445 9.672 92.51-13.012 96.653-49.877
3.6-34.437-8.891-52.587-56.167-80.952v-.04zm752.421-42.005c-16.828 0-29.859
6.829-38.383 20.254l-6.811
10.582v-27.583h-29.123V1136.3h29.102v-52.954c0-48.403.584-54.085
7.375-67.313 9.324-18.15 25.369-27.235 40.875-22.878l10.408
3.035v-28.775h-13.443zm-984.021
41.05V902.941h-29.361v233.728h123.478v-27.604h-94.116v-100.601zm679.015
32.896l-24.201 62.975-23.27-63.322-23.637-70.173h-30.055c19.475 55.212
40.658 111.376 62.02 165.829 9.26.216 18.541 0 27.799 0l32.682-82.058
33.287-83.75h-28.732s-12.688 33.266-25.914 70.521l.021-.022zM506.455
839.251c4.728 0 8.674-1.516 11.927-4.769 3.208-3.211 4.9-6.984 4.9-11.711
0-4.728-1.692-8.675-4.9-11.711-3.253-3.035-7.005-4.555-11.711-4.555-4.769
0-8.717 1.52-11.927 4.728-3.252 3.211-4.727 7.158-4.727 11.712 0 4.771 1.519
8.716 4.727 11.711 3.037 3.034 6.984 4.553 11.711
4.553v.042zm-10.408-26.889c2.818-2.818 6.245-4.121 10.625-4.121 4.121 0
7.548 1.303 10.411 4.121 2.819 2.819 4.337 6.245 4.337 10.409 0 4.163-1.518
7.764-4.337 10.582-2.862 2.817-6.29 4.163-10.411 4.163-4.185
0-7.59-1.301-10.408-4.163-2.819-2.818-4.337-6.419-4.337-10.582 0-4.164
1.301-7.589 4.12-10.409zm7.003 11.928h1.908c1.346 0 2.668 1.3 3.795
3.773l2.279 5.116h3.577l-2.818-5.683c-1.149-2.275-2.276-3.598-3.6-3.969
1.67-.39 2.992-.953 3.947-2.082.952-.974 1.3-2.298 1.3-3.795
0-1.734-.542-3.034-1.69-3.989-1.302-1.084-3.384-1.669-6.074-1.669h-6.026v21.187h3.035v-8.891l.367.002zm0-9.846h2.647c1.908
0 3.253.39 3.99.953.716.564.911 1.303.911 2.646 0 2.45-1.52 3.601-4.337
3.601h-3.252v-7.2h.041zm-485.018
7.958c0-7.373-.216-12.858-.39-16.09h.174c.758 3.814 1.691 6.657 2.45
8.543l28.19 62.975h4.728l28.19-63.538c.761-1.733 1.52-4.337
2.452-7.959h.216c-.563 6.29-.758 11.754-.758
16.112v55.581h9.648v-82.622h-12.1L54.919 852.87c-.955 2.276-2.278
5.683-3.969
10.193h-.392c-.563-2.234-1.886-5.639-3.772-9.803l-25.33-58.053H8.598v82.621h9.281v-55.385l.153-.041zm96.045.154h8.329v51.458h-8.329v-51.458zm4.164-18.868c1.736
0 3.21-.587 4.337-1.734 1.15-1.129 1.91-2.603 1.91-4.337
0-1.692-.565-3.211-1.887-4.337-1.171-1.15-2.668-1.737-4.381-1.737-1.69
0-3.208.587-4.338 1.737-1.146 1.126-1.907 2.645-1.907 4.337 0 1.887.586
3.208 1.907 4.337 1.304 1.147 2.647 1.734 4.338 1.734h.021zm63.54
71.455v-9.066c-4.555 3.405-9.456 5.098-14.53 5.098-6.07
0-10.995-2.081-14.595-6.07-3.577-3.947-5.485-9.436-5.485-16.266 0-7.156
1.908-12.84 5.854-17.177 3.795-4.163 8.719-6.245 14.748-6.245 4.922 0 9.647
1.52 14.009 4.557v-9.65c-3.968-2.082-8.5-3.037-13.619-3.037-9.456 0-16.827
3.037-22.335 8.894-5.466 5.854-8.285 13.813-8.285 23.42 0 8.543 2.45 15.722
7.548 21.209 5.312 5.637 12.102 8.5 20.428 8.5 6.438-.178 11.707-1.523
16.262-4.167zm23.831-27.433c0-6.788 1.518-12.273 4.337-16.049 2.647-3.403
5.855-5.116 9.65-5.116 3.21 0 5.486.585 7.155
1.908v-9.846c-1.3-.563-3.187-.758-5.637-.758-3.405 0-6.439 1.146-9.107
3.253-2.819 2.231-5.074 5.638-6.397
9.975h-.216v-12.08h-9.433v58.985h9.454V847.71h.194zm54.279 31.443c8.892 0
16.048-2.863 21.36-8.543 5.29-5.641 7.936-13.229 7.936-22.686
0-9.647-2.427-17.021-7.372-22.51-4.9-5.483-11.711-8.132-20.603-8.132s-16.048
2.647-21.36 7.764c-5.681 5.641-8.674 13.599-8.674 23.813 0 8.891 2.429
16.265 7.548 21.751 5.29 5.68 12.295 8.521 21.165
8.521v.022zm-13.445-48.055c3.6-3.795 8.329-5.683 14.182-5.683 6.074 0 10.627
1.888 14.01 5.683 3.404 3.969 5.097 9.63 5.097 17.197 0 7.198-1.519
12.859-4.729 16.654-3.208 3.969-7.936 6.071-14.183 6.071-6.071
0-10.777-2.104-14.377-6.071-3.577-3.99-5.291-9.456-5.291-16.654-.368-7.156
1.519-13.01 5.291-17.197zm84.141 42.916c3.599-3.208 5.509-7.155 5.509-12.102
0-4.337-1.52-7.936-4.338-10.777-2.3-2.275-5.854-4.337-10.994-6.419-4.556-1.906-7.374-3.6-8.893-4.923-1.517-1.517-2.45-3.402-2.45-6.071
0-2.45.955-4.337 2.821-5.855 1.908-1.516 4.337-2.253 7.59-2.253 5.096 0
9.454 1.343 13.443 4.185v-9.456c-3.816-1.906-7.958-2.817-12.686-2.817-6.071
0-11.189 1.671-14.964 4.899-3.969 3.212-5.854 7.375-5.854 12.274 0 4.337 1.3
7.938 3.771 10.582 2.082 2.256 5.641 4.556 10.583 6.614 4.729 2.083 7.938
3.968 9.65 5.485 1.691 1.52 2.45 3.405 2.45 5.641 0 5.506-3.772 8.349-11.146
8.349-5.682 0-10.776-1.866-15.333-5.638v10.189c4.121 2.475 9.066 3.601 14.53
3.601 7.005-.368 12.49-2.081 16.264-5.486l.047-.022zm45.019-56.73c-8.893
0-16.048 2.647-21.361 7.764-5.638 5.641-8.674 13.599-8.674 23.813 0 8.891
2.452 16.265 7.547 21.751 5.313 5.68 12.295 8.521 21.187 8.521 9.107 0
16.048-2.861 21.36-8.545 5.313-5.637 7.958-13.227 7.958-22.683
0-9.65-2.472-17.022-7.374-22.509-5.115-5.487-11.927-8.133-20.601-8.133l-.042.021zm18.345
31.012c0 7.198-1.518 12.859-4.727 16.654-3.21 3.969-7.938 6.071-14.184
6.071-6.074 0-10.778-2.104-14.379-6.071-3.577-3.99-5.29-9.456-5.29-16.654
0-7.59 1.888-13.444 5.683-17.393 3.576-3.773 8.306-5.682 14.182-5.682 5.854
0 10.561 1.907 13.964 5.682 3.037 4.163 4.729 9.824 4.729
17.393h.022zm25.547 29.513h9.433v-51.068h13.813v-7.938H428.93v-9.108c0-8.282
3.208-12.446 9.845-12.446 2.234 0 4.511.563 6.203
1.518v-8.521c-1.692-.759-3.969-.932-6.812-.932-5.095 0-9.258 1.519-12.664
4.727-3.969 3.773-6.071 8.674-6.071
15.312v9.672h-9.978v7.936h9.978v50.876l.067-.028zm38.75-16.091c0 11.538
5.098 17.414 15.506 17.414 3.774 0 6.614-.606 8.891-1.951v-8.11c-1.734
1.302-3.795 1.91-6.071 1.91-3.208
0-5.464-.762-6.788-2.475-1.345-1.689-2.103-4.554-2.103-8.501v-33.286h14.961v-7.938h-14.961v-17.39c-3.253
1.127-6.44 2.082-9.456
3.034v14.355h-10.192v7.938h10.192v34.979l.021.021zm1014.88
108.73c-3.209-3.034-7.004-4.553-11.709-4.553-4.77 0-8.719 1.519-11.928
4.771-3.209 3.188-4.729 7.155-4.729 11.711 0 4.728 1.52 8.675 4.705 11.709
3.211 3.036 7.156 4.556 11.928 4.556 4.705 0 8.674-1.52 11.928-4.729
3.188-3.253 4.879-7.004
4.879-11.709-.174-4.771-1.887-8.719-5.096-11.754l.022-.002zm-1.517
22.338c-2.82 2.818-6.246 4.119-10.41 4.119-4.119
0-7.545-1.301-10.408-4.119-2.818-2.863-4.338-6.441-4.338-10.627 0-4.121
1.301-7.545 4.164-10.408 2.818-2.817 6.225-4.121 10.582-4.121 4.121 0 7.549
1.304 10.41 4.121 2.818 2.863 4.336 6.287 4.336 10.408 0 4.382-1.301
7.764-4.336 10.627zm-8.502-9.651c1.691-.39 3.037-1.149 3.969-2.081.955-.977
1.303-2.301 1.303-3.815
0-1.692-.543-3.037-1.691-3.969-1.301-1.085-3.404-1.671-6.07-1.671h-6.029v21.164h3.037v-8.891h1.885c1.303
0 2.604 1.3 3.773 3.773l2.254
5.096h3.602l-2.818-5.683c-.977-2.472-2.105-3.601-3.252-3.97l.037.047zm-2.082-1.907h-3.252v-7.155h2.668c1.887
0 3.209.345 3.969.932.758.563.932 1.301.932 2.646 0 2.45-1.518 3.579-4.336
3.579l.019-.002zM933.443
816.353h2.646v-21.187h7.002v-2.646h-16.652v2.646h7.006v21.187h-.002zm16.047-15.917c0-2.062
0-3.753-.152-4.705.174 1.126.564 1.887.738 2.45l8.133
18.172h1.301l8.152-18.347c.219-.563.393-1.301.76-2.275-.174 1.887-.174
3.401-.174 4.553v16.048h2.82V792.52h-3.406l-7.371 16.438c-.174.587-.762
1.734-1.129
3.037h-.217c-.152-.761-.541-1.519-1.084-2.818l-7.373-16.655h-3.816v23.854h2.666v-15.917l.152-.023z"
/>
<g
><path
style="opacity:1"
fill="#f44336"
d="M 0.5,-0.5 C 20.1667,-0.5 39.8333,-0.5 59.5,-0.5C 59.5,19.5 59.5,39.5 59.5,59.5C 39.5,59.5 19.5,59.5 -0.5,59.5C -0.5,39.8333 -0.5,20.1667 -0.5,0.5C 0.166667,0.5 0.5,0.166667 0.5,-0.5 Z"
/></g
>
<g
><path
style="opacity:0.999"
fill="#4caf4f"
d="M 67.5,-0.5 C 87.1667,-0.5 106.833,-0.5 126.5,-0.5C 126.5,0.166667 126.833,0.5 127.5,0.5C 127.5,20.1667 127.5,39.8333 127.5,59.5C 107.5,59.5 87.5,59.5 67.5,59.5C 67.5,39.5 67.5,19.5 67.5,-0.5 Z"
/></g
>
<g
><path
style="opacity:1"
fill="#2095f3"
d="M -0.5,67.5 C 19.5,67.5 39.5,67.5 59.5,67.5C 59.5,87.5 59.5,107.5 59.5,127.5C 39.8333,127.5 20.1667,127.5 0.5,127.5C 0.5,126.833 0.166667,126.5 -0.5,126.5C -0.5,106.833 -0.5,87.1667 -0.5,67.5 Z"
/></g
>
<g
><path
style="opacity:1"
fill="#fec107"
d="M 127.5,67.5 C 127.5,87.1667 127.5,106.833 127.5,126.5C 126.833,126.5 126.5,126.833 126.5,127.5C 106.833,127.5 87.1667,127.5 67.5,127.5C 67.5,107.5 67.5,87.5 67.5,67.5C 87.5,67.5 107.5,67.5 127.5,67.5 Z"
/></g
>
</svg>

68
packages/builder/src/components/common/bindings/BindableCombobox.svelte

@ -0,0 +1,68 @@
<script>
import { Combobox } from "@budibase/bbui"
import {
readableToRuntimeBinding,
runtimeToReadableBinding,
} from "builderStore/dataBinding"
import { createEventDispatcher } from "svelte"
import { isJSBinding } from "@budibase/string-templates"
export let value = ""
export let bindings = []
export let placeholder
export let label
export let disabled = false
export let options
export let appendBindingsAsOptions = true
export let error
const dispatch = createEventDispatcher()
$: readableValue = runtimeToReadableBinding(bindings, value)
$: isJS = isJSBinding(value)
$: allOptions = buildOptions(options, bindings, appendBindingsAsOptions)
const onChange = (value, optionPicked) => {
// Add HBS braces if picking binding
if (optionPicked && !options?.includes(value)) {
value = `{{ ${value} }}`
}
dispatch("change", readableToRuntimeBinding(bindings, value))
}
const buildOptions = (options, bindings, appendBindingsAsOptions) => {
if (!appendBindingsAsOptions) {
return options
}
return []
.concat(options || [])
.concat(bindings?.map(binding => binding.readableBinding) || [])
}
</script>
<div class="control" class:disabled>
<Combobox
{label}
{disabled}
readonly={isJS}
value={isJS ? "(JavaScript function)" : readableValue}
on:type={e => onChange(e.detail, false)}
on:pick={e => onChange(e.detail, true)}
on:blur={() => dispatch("blur")}
{placeholder}
options={allOptions}
{error}
/>
</div>
<style>
.control {
flex: 1;
position: relative;
}
.control:not(.disabled) :global(.spectrum-Textfield-input) {
padding-right: 40px;
}
</style>

4
packages/builder/src/components/common/bindings/DrawerBindableCombobox.svelte

@ -18,6 +18,7 @@
export let options
export let allowJS = true
export let appendBindingsAsOptions = true
export let error
const dispatch = createEventDispatcher()
let bindingDrawer
@ -59,8 +60,10 @@
value={isJS ? "(JavaScript function)" : readableValue}
on:type={e => onChange(e.detail, false)}
on:pick={e => onChange(e.detail, true)}
on:blur={() => dispatch("blur")}
{placeholder}
options={allOptions}
{error}
/>
{#if !disabled}
<div
@ -72,6 +75,7 @@
</div>
{/if}
</div>
<Drawer bind:this={bindingDrawer} {title}>
<svelte:fragment slot="description">
Add the objects on the left to enrich your text.

6
packages/builder/src/components/common/renderers/DateTimeRenderer.svelte

@ -0,0 +1,6 @@
<script>
import dayjs from "dayjs"
export let value
</script>
{new dayjs(value).format("MMM D, YYYY HH:mm")}

2
packages/builder/src/components/deploy/DeployNavigation.svelte

@ -52,7 +52,7 @@
reviewPendingDeployments(deployments, newDeployments)
return newDeployments
} catch (err) {
notifications.error("Error fetching deployment history")
notifications.error("Error fetching deployment overview")
}
}

2
packages/builder/src/components/deploy/DeploymentHistory.svelte

@ -55,7 +55,7 @@
deployments = newDeployments
} catch (err) {
clearInterval(poll)
notifications.error("Error fetching deployment history")
notifications.error("Error fetching deployment overview")
}
}

2
packages/builder/src/components/design/settings/controls/ColumnEditor/ColumnDrawer.svelte

@ -178,7 +178,7 @@
.column {
gap: var(--spacing-l);
display: grid;
grid-template-columns: 20px 1fr 1fr auto auto;
grid-template-columns: 20px 1fr 1fr 16px 16px;
align-items: center;
border-radius: var(--border-radius-s);
transition: background-color ease-in-out 130ms;

12
packages/builder/src/components/integration/KeyValueBuilder.svelte

@ -11,6 +11,7 @@
} from "@budibase/bbui"
import { createEventDispatcher } from "svelte"
import { lowercase } from "helpers"
import DrawerBindableInput from "components/common/bindings/DrawerBindableInput.svelte"
let dispatch = createEventDispatcher()
@ -30,6 +31,7 @@
export let tooltip
export let menuItems
export let showMenu = false
export let bindings = []
let fields = Object.entries(object || {}).map(([name, value]) => ({
name,
@ -108,6 +110,16 @@
/>
{#if options}
<Select bind:value={field.value} on:change={changed} {options} />
{:else if bindings && bindings.length}
<DrawerBindableInput
{bindings}
placeholder="Value"
on:change={e => (field.value = e.detail)}
disabled={readOnly}
value={field.value}
allowJS={false}
fillWidth={true}
/>
{:else}
<Input
placeholder={valuePlaceholder}

3
packages/builder/src/components/integration/QueryBindingBuilder.svelte

@ -57,7 +57,8 @@
placeholder="Default"
thin
disabled={bindable}
bind:value={binding.default}
on:change={evt => onBindingChange(binding.name, evt.detail)}
value={runtimeToReadableBinding(bindings, binding.default)}
/>
{#if bindable}
<DrawerBindableInput

91
packages/builder/src/components/portal/overview/automation/HistoryDetailsPanel.svelte

@ -0,0 +1,91 @@
<script>
import { Layout, Icon, ActionButton } from "@budibase/bbui"
import StatusRenderer from "./StatusRenderer.svelte"
import DateTimeRenderer from "components/common/renderers/DateTimeRenderer.svelte"
import TestDisplay from "components/automation/AutomationBuilder/TestDisplay.svelte"
import { goto } from "@roxi/routify"
import { automationStore } from "builderStore"
export let history
export let appId
export let close
$: exists = $automationStore.automations?.find(
auto => auto._id === history?.automationId
)
</script>
{#if history}
<div class="body">
<div class="top">
<div class="controls">
<StatusRenderer value={history.status} />
<ActionButton noPadding size="S" icon="Close" quiet on:click={close} />
</div>
</div>
<Layout paddingX="XL" gap="S">
<div class="icon">
<Icon name="Clock" />
<DateTimeRenderer value={history.createdAt} />
</div>
<div class="icon">
<Icon name="JourneyVoyager" />
<div>{history.automationName}</div>
</div>
<div>
{#if exists}
<ActionButton
icon="Edit"
fullWidth={false}
on:click={() =>
$goto(`../../../app/${appId}/automate/${history.automationId}`)}
>Edit automation</ActionButton
>
{/if}
</div>
</Layout>
<div class="bottom">
{#key history}
<TestDisplay testResults={history} width="100%" />
{/key}
</div>
</div>
{:else}
<div>No details found</div>
{/if}
<style>
.body {
right: 0;
background-color: var(--background);
border-left: var(--border-light);
width: 420px;
height: calc(100vh - 240px);
position: fixed;
overflow: auto;
}
.top {
padding: var(--spacing-m) 0 var(--spacing-m) 0;
border-bottom: var(--border-light);
}
.bottom {
margin-top: var(--spacing-m);
border-top: var(--border-light);
padding-top: calc(var(--spacing-xl) * 2);
padding-bottom: calc(var(--spacing-xl) * 2);
}
.icon {
display: flex;
gap: var(--spacing-m);
}
.controls {
padding: 0 var(--spacing-l) 0 var(--spacing-l);
display: grid;
grid-template-columns: 1fr auto;
gap: var(--spacing-s);
}
</style>

219
packages/builder/src/components/portal/overview/automation/HistoryTab.svelte

@ -0,0 +1,219 @@
<script>
import { Layout, Table, Select, Pagination } from "@budibase/bbui"
import DateTimeRenderer from "components/common/renderers/DateTimeRenderer.svelte"
import StatusRenderer from "./StatusRenderer.svelte"
import HistoryDetailsPanel from "./HistoryDetailsPanel.svelte"
import { automationStore } from "builderStore"
import { createPaginationStore } from "helpers/pagination"
import { onMount } from "svelte"
import dayjs from "dayjs"
const ERROR = "error",
SUCCESS = "success",
STOPPED = "stopped"
export let app
let pageInfo = createPaginationStore()
let runHistory = null
let showPanel = false
let selectedHistory = null
let automationOptions = []
let automationId = null
let status = null
let timeRange = null
$: page = $pageInfo.page
$: fetchLogs(automationId, status, page, timeRange)
const timeOptions = [
{ value: "1-w", label: "Past week" },
{ value: "1-d", label: "Past day" },
{ value: "1-h", label: "Past 1 hour" },
{ value: "15-m", label: "Past 15 mins" },
{ value: "5-m", label: "Past 5 mins" },
]
const statusOptions = [
{ value: SUCCESS, label: "Success" },
{ value: ERROR, label: "Error" },
{ value: STOPPED, label: "Stopped" },
]
const runHistorySchema = {
status: { displayName: "Status" },
createdAt: { displayName: "Time" },
automationName: { displayName: "Automation" },
}
const customRenderers = [
{ column: "createdAt", component: DateTimeRenderer },
{ column: "status", component: StatusRenderer },
]
async function fetchLogs(automationId, status, page, timeRange) {
let startDate = null
if (timeRange) {
const [length, units] = timeRange.split("-")
startDate = dayjs().subtract(length, units)
}
const response = await automationStore.actions.getLogs({
automationId,
status,
page,
startDate,
})
pageInfo.fetched(response.hasNextPage, response.nextPage)
runHistory = enrichHistory($automationStore.blockDefinitions, response.data)
}
function enrichHistory(definitions, runHistory) {
if (!definitions) {
return []
}
const finalHistory = []
for (let history of runHistory) {
if (!history.steps) {
continue
}
let notFound = false
for (let step of history.steps) {
const trigger = definitions.TRIGGER[step.stepId],
action = definitions.ACTION[step.stepId]
if (!trigger && !action) {
notFound = true
break
}
step.icon = trigger ? trigger.icon : action.icon
step.name = trigger ? trigger.name : action.name
}
if (!notFound) {
finalHistory.push(history)
}
}
return finalHistory
}
function viewDetails({ detail }) {
selectedHistory = detail
showPanel = true
}
onMount(async () => {
await automationStore.actions.fetch()
const params = new URLSearchParams(window.location.search)
const shouldOpen = params.get("open") === ERROR
// open with errors, open panel for latest
if (shouldOpen) {
status = ERROR
}
await automationStore.actions.fetch()
await fetchLogs(null, status)
if (shouldOpen) {
viewDetails({ detail: runHistory[0] })
}
automationOptions = []
for (let automation of $automationStore.automations) {
automationOptions.push({ value: automation._id, label: automation.name })
}
})
</script>
<div class="root" class:panelOpen={showPanel}>
<Layout paddingX="XL" gap="S" alignContent="start">
<div class="search">
<div class="select">
<Select
placeholder="All automations"
label="Automation"
bind:value={automationId}
options={automationOptions}
/>
</div>
<div class="select">
<Select
placeholder="Past 30 days"
label="Date range"
bind:value={timeRange}
options={timeOptions}
/>
</div>
<div class="select">
<Select
placeholder="All status"
label="Status"
bind:value={status}
options={statusOptions}
/>
</div>
</div>
{#if runHistory}
<Table
on:click={viewDetails}
schema={runHistorySchema}
allowSelectRows={false}
allowEditColumns={false}
allowEditRows={false}
data={runHistory}
{customRenderers}
placeholderText="No history found"
/>
{/if}
</Layout>
<div class="panel" class:panelShow={showPanel}>
<HistoryDetailsPanel
appId={app.devId}
bind:history={selectedHistory}
close={() => {
showPanel = false
}}
/>
</div>
</div>
<div class="pagination">
<Pagination
page={$pageInfo.pageNumber}
hasPrevPage={$pageInfo.loading ? false : $pageInfo.hasPrevPage}
hasNextPage={$pageInfo.loading ? false : $pageInfo.hasNextPage}
goToPrevPage={pageInfo.prevPage}
goToNextPage={pageInfo.nextPage}
/>
</div>
<style>
.root {
display: grid;
grid-template-columns: 1fr;
height: 100%;
}
.search {
display: flex;
gap: var(--spacing-l);
width: 100%;
align-items: flex-end;
}
.select {
flex-basis: 150px;
}
.pagination {
position: absolute;
bottom: 0;
margin-bottom: var(--spacing-xl);
margin-left: var(--spacing-l);
}
.panel {
display: none;
background-color: var(--background);
}
.panelShow {
display: block;
}
.panelOpen {
grid-template-columns: auto 420px;
}
</style>

38
packages/builder/src/components/portal/overview/automation/StatusRenderer.svelte

@ -0,0 +1,38 @@
<script>
import { Icon } from "@budibase/bbui"
export let value
$: isError = !value || value.toLowerCase() === "error"
$: isStopped = value?.toLowerCase() === "stopped"
$: status = getStatus(isError, isStopped)
function getStatus(error, stopped) {
if (error) {
return { color: "var(--red)", message: "Error", icon: "Alert" }
} else if (stopped) {
return { color: "var(--yellow)", message: "Stopped", icon: "StopCircle" }
} else {
return {
color: "var(--green)",
message: "Success",
icon: "CheckmarkCircle",
}
}
}
</script>
<div class="cell">
<Icon color={status.color} name={status.icon} />
<div style={`color: ${status.color};`}>
{status.message}
</div>
</div>
<style>
.cell {
display: flex;
flex-direction: row;
gap: var(--spacing-m);
align-items: center;
}
</style>

4
packages/builder/src/constants/backend/index.js

@ -158,13 +158,9 @@ export const ALLOWABLE_NUMBER_TYPES = ALLOWABLE_NUMBER_OPTIONS.map(
opt => opt.type
)
export const ALLOWABLE_JSON_OPTIONS = [FIELDS.JSON, FIELDS.ARRAY]
export const ALLOWABLE_JSON_TYPES = ALLOWABLE_JSON_OPTIONS.map(opt => opt.type)
export const SWITCHABLE_TYPES = [
...ALLOWABLE_STRING_TYPES,
...ALLOWABLE_NUMBER_TYPES,
...ALLOWABLE_JSON_TYPES,
]
export const IntegrationTypes = {

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

@ -5,6 +5,7 @@
import CreateAutomationModal from "components/automation/AutomationPanel/CreateAutomationModal.svelte"
import CreateWebhookModal from "components/automation/Shared/CreateWebhookModal.svelte"
import TestPanel from "components/automation/AutomationBuilder/TestPanel.svelte"
import { onMount } from "svelte"
$: automation =
$automationStore.selectedAutomation?.automation ||
@ -12,6 +13,9 @@
let modal
let webhookModal
onMount(() => {
$automationStore.showTestPanel = false
})
</script>
<!-- routify:options index=3 -->
@ -45,7 +49,7 @@
{/if}
</div>
{#if automation?.showTestPanel}
{#if $automationStore.showTestPanel}
<div class="setup">
<TestPanel {automation} />
</div>

4
packages/builder/src/pages/builder/app/[application]/data/datasource/[selectedDatasource]/_layout.svelte

@ -12,4 +12,6 @@
}
</script>
<slot />
{#key $params.selectedDatasource}
<slot />
{/key}

116
packages/builder/src/pages/builder/app/[application]/data/datasource/[selectedDatasource]/rest/[query]/index.svelte

@ -40,13 +40,39 @@
import { cloneDeep } from "lodash/fp"
import { RawRestBodyTypes } from "constants/backend"
import {
getRestBindings,
toBindingsArray,
runtimeToReadableBinding,
readableToRuntimeBinding,
runtimeToReadableMap,
readableToRuntimeMap,
} from "builderStore/dataBinding"
let query, datasource
let breakQs = {},
bindings = {}
requestBindings = {}
let saveId, url
let response, schema, enabledHeaders
let authConfigId
let dynamicVariables, addVariableModal, varBinding
let restBindings = getRestBindings()
$: staticVariables = datasource?.config?.staticVariables || {}
$: customRequestBindings = toBindingsArray(requestBindings, "Binding")
$: dynamicRequestBindings = toBindingsArray(dynamicVariables, "Dynamic")
$: dataSourceStaticBindings = toBindingsArray(
staticVariables,
"Datasource.Static"
)
$: mergedBindings = [
...restBindings,
...customRequestBindings,
...dynamicRequestBindings,
...dataSourceStaticBindings,
]
$: datasourceType = datasource?.source
$: integrationInfo = $integrations[datasourceType]
@ -63,8 +89,10 @@
Object.keys(schema || {}).length !== 0 ||
Object.keys(query?.schema || {}).length !== 0
$: runtimeUrlQueries = readableToRuntimeMap(mergedBindings, breakQs)
function getSelectedQuery() {
return cloneDeep(
const cloneQuery = cloneDeep(
$queries.list.find(q => q._id === $queries.selected) || {
datasourceId: $params.selectedDatasource,
parameters: [],
@ -76,6 +104,7 @@
queryVerb: "read",
}
)
return cloneQuery
}
function checkQueryName(inputUrl = null) {
@ -89,7 +118,9 @@
if (!base) {
return base
}
const qs = restUtils.buildQueryString(qsObj)
const qs = restUtils.buildQueryString(
runtimeToReadableMap(mergedBindings, qsObj)
)
let newUrl = base
if (base.includes("?")) {
newUrl = base.split("?")[0]
@ -98,14 +129,21 @@
}
function buildQuery() {
const newQuery = { ...query }
const queryString = restUtils.buildQueryString(breakQs)
const newQuery = cloneDeep(query)
const queryString = restUtils.buildQueryString(runtimeUrlQueries)
newQuery.parameters = restUtils.keyValueToQueryParameters(requestBindings)
newQuery.fields.requestBody =
typeof newQuery.fields.requestBody === "object"
? readableToRuntimeMap(mergedBindings, newQuery.fields.requestBody)
: readableToRuntimeBinding(mergedBindings, newQuery.fields.requestBody)
newQuery.fields.path = url.split("?")[0]
newQuery.fields.queryString = queryString
newQuery.fields.authConfigId = authConfigId
newQuery.fields.disabledHeaders = restUtils.flipHeaderState(enabledHeaders)
newQuery.schema = restUtils.fieldsToSchema(schema)
newQuery.parameters = restUtils.keyValueToQueryParameters(bindings)
return newQuery
}
@ -120,6 +158,13 @@
datasource.config.dynamicVariables = rebuildVariables(saveId)
datasource = await datasources.save(datasource)
}
prettifyQueryRequestBody(
query,
requestBindings,
dynamicVariables,
staticVariables,
restBindings
)
} catch (err) {
notifications.error(`Error saving query`)
}
@ -127,7 +172,7 @@
async function runQuery() {
try {
response = await queries.preview(buildQuery(query))
response = await queries.preview(buildQuery())
if (response.rows.length === 0) {
notifications.info("Request did not return any data")
} else {
@ -236,6 +281,36 @@
}
}
const prettifyQueryRequestBody = (
query,
requestBindings,
dynamicVariables,
staticVariables,
restBindings
) => {
let customRequestBindings = toBindingsArray(requestBindings, "Binding")
let dynamicRequestBindings = toBindingsArray(dynamicVariables, "Dynamic")
let dataSourceStaticBindings = toBindingsArray(
staticVariables,
"Datasource.Static"
)
const prettyBindings = [
...restBindings,
...customRequestBindings,
...dynamicRequestBindings,
...dataSourceStaticBindings,
]
//Parse the body here as now all bindings have been updated.
if (query?.fields?.requestBody) {
query.fields.requestBody =
typeof query.fields.requestBody === "object"
? runtimeToReadableMap(prettyBindings, query.fields.requestBody)
: runtimeToReadableBinding(prettyBindings, query.fields.requestBody)
}
}
onMount(async () => {
query = getSelectedQuery()
@ -250,6 +325,8 @@
const datasourceUrl = datasource?.config.url
const qs = query?.fields.queryString
breakQs = restUtils.breakQueryString(qs)
breakQs = runtimeToReadableMap(mergedBindings, breakQs)
const path = query.fields.path
if (
datasourceUrl &&
@ -260,7 +337,7 @@
}
url = buildUrl(query.fields.path, breakQs)
schema = restUtils.schemaToFields(query.schema)
bindings = restUtils.queryParametersToKeyValue(query.parameters)
requestBindings = restUtils.queryParametersToKeyValue(query.parameters)
authConfigId = getAuthConfigId()
if (!query.fields.disabledHeaders) {
query.fields.disabledHeaders = {}
@ -291,6 +368,14 @@
query.fields.pagination = {}
}
dynamicVariables = getDynamicVariables(datasource, query._id)
prettifyQueryRequestBody(
query,
requestBindings,
dynamicVariables,
staticVariables,
restBindings
)
})
</script>
@ -344,16 +429,26 @@
<Tabs selected="Bindings" quiet noPadding noHorizPadding onTop>
<Tab title="Bindings">
<KeyValueBuilder
bind:object={bindings}
bind:object={requestBindings}
tooltip="Set the name of the binding which can be used in Handlebars statements throughout your query"
name="binding"
headings
keyPlaceholder="Binding name"
valuePlaceholder="Default"
bindings={[
...restBindings,
...dynamicRequestBindings,
...dataSourceStaticBindings,
]}
/>
</Tab>
<Tab title="Params">
<KeyValueBuilder bind:object={breakQs} name="param" headings />
<KeyValueBuilder
bind:object={breakQs}
name="param"
headings
bindings={mergedBindings}
/>
</Tab>
<Tab title="Headers">
<KeyValueBuilder
@ -362,6 +457,7 @@
toggle
name="header"
headings
bindings={mergedBindings}
/>
</Tab>
<Tab title="Body">

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

@ -7,6 +7,7 @@
Modal,
Page,
notifications,
Notification,
Body,
Search,
} from "@budibase/bbui"
@ -37,6 +38,7 @@
let searchTerm = ""
let cloud = $admin.cloud
let creatingFromTemplate = false
let automationErrors
const resolveWelcomeMessage = (auth, apps) => {
const userWelcome = auth?.user?.firstName
@ -59,7 +61,8 @@
)
$: lockedApps = filteredApps.filter(app => app?.lockedYou || app?.lockedOther)
$: unlocked = lockedApps?.length == 0
$: unlocked = lockedApps?.length === 0
$: automationErrors = getAutomationErrors(enrichedApps)
const enrichApps = (apps, user, sortBy) => {
const enrichedApps = apps.map(app => ({
@ -89,6 +92,36 @@
}
}
const getAutomationErrors = apps => {
const automationErrors = {}
for (let app of apps) {
if (app.automationErrors) {
if (errorCount(app.automationErrors) > 0) {
automationErrors[app.devId] = app.automationErrors
}
}
}
return automationErrors
}
const goToAutomationError = appId => {
const params = new URLSearchParams({
tab: "Automation History",
open: "error",
})
$goto(`../overview/${appId}?${params.toString()}`)
}
const errorCount = errors => {
return Object.values(errors).reduce((acc, next) => acc + next.length, 0)
}
const automationErrorMessage = appId => {
const app = enrichedApps.find(app => app.devId === appId)
const errors = automationErrors[appId]
return `${app.name} - Automation error (${errorCount(errors)})`
}
const initiateAppCreation = () => {
if ($apps?.length) {
$goto("/builder/portal/apps/create")
@ -208,6 +241,23 @@
<Page wide>
<Layout noPadding gap="M">
{#if loaded}
{#each Object.keys(automationErrors || {}) as appId}
<Notification
wide
dismissable
action={() => goToAutomationError(appId)}
type="error"
icon="Alert"
actionMessage={errorCount(automationErrors[appId]) > 1
? "View errors"
: "View error"}
on:dismiss={async () => {
await automationStore.actions.clearLogErrors({ appId })
await apps.load()
}}
message={automationErrorMessage(appId)}
/>
{/each}
<div class="title">
<div class="welcome">
<Layout noPadding gap="XS">

5
packages/builder/src/pages/builder/portal/manage/email/index.svelte

@ -74,7 +74,10 @@
rev: smtpConfig._rev,
})
smtpConfig = {
config: {},
type: ConfigTypes.SMTP,
config: {
secure: true,
},
}
await admin.getChecklist()
notifications.success(`Settings cleared`)

12
packages/builder/src/pages/builder/portal/manage/users/index.svelte

@ -27,13 +27,23 @@
}
let pageInfo = createPaginationStore()
let search = undefined
let prevSearch = undefined,
search = undefined
$: page = $pageInfo.page
$: fetchUsers(page, search)
let createUserModal
async function fetchUsers(page, search) {
if ($pageInfo.loading) {
return
}
// need to remove the page if they've started searching
if (search && !prevSearch) {
pageInfo.reset()
page = undefined
}
prevSearch = search
try {
pageInfo.loading()
await users.search({ page, search })

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

@ -28,6 +28,7 @@
import EditableIcon from "components/common/EditableIcon.svelte"
import ConfirmDialog from "components/common/ConfirmDialog.svelte"
import BetaTab from "components/beta/BetaTab.svelte"
import HistoryTab from "components/portal/overview/automation/HistoryTab.svelte"
import { checkIncomingDeploymentStatus } from "components/deploy/utils"
import { onDestroy, onMount } from "svelte"
@ -188,6 +189,10 @@
})
onMount(async () => {
const params = new URLSearchParams(window.location.search)
if (params.get("tab")) {
selectedTab = params.get("tab")
}
try {
if (!apps.length) {
await apps.load()
@ -212,7 +217,7 @@
<ProgressCircle size="XL" />
</div>
{:then _}
<Layout paddingX="XXL" paddingY="XXL" gap="XL">
<Layout paddingX="XXL" paddingY="XL" gap="L">
<span class="page-header" class:loaded>
<ActionButton secondary icon={"ArrowLeft"} on:click={backToAppList}>
Back
@ -300,10 +305,12 @@
on:unpublish={e => unpublishApp(e.detail)}
/>
</Tab>
{#if false}
{#if isPublished}
<Tab title="Automation History">
<div class="container">Automation History contents</div>
<HistoryTab app={selectedApp} />
</Tab>
{/if}
{#if false}
<Tab title="Backups">
<div class="container">Backups contents</div>
</Tab>

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

@ -197,6 +197,7 @@
.overview-tab .top {
grid-template-columns: 1fr 1fr;
}
.overview-tab .bottom {
grid-template-columns: 1fr;
}
@ -214,29 +215,35 @@
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 {

2
packages/builder/src/stores/portal/auth.js

@ -64,6 +64,8 @@ export function createAuthStore() {
name: user.account?.name,
user_id: user._id,
tenant: user.tenantId,
admin: user?.admin?.global,
builder: user?.builder?.global,
"Company size": user.account?.size,
"Job role": user.account?.profession,
})

23
packages/builder/yarn.lock

@ -2467,6 +2467,11 @@ dayjs@^1.10.4:
resolved "https://registry.yarnpkg.com/dayjs/-/dayjs-1.10.7.tgz#2cf5f91add28116748440866a0a1d26f3a6ce468"
integrity sha512-P6twpd70BcPK34K26uJ1KT3wlhpuOAPoMwJzpsIWUxHZ7wpmbdZL/hQqBDfz7hGurYSa5PhzdhDHtt319hL3ig==
dayjs@^1.11.2:
version "1.11.2"
resolved "https://registry.yarnpkg.com/dayjs/-/dayjs-1.11.2.tgz#fa0f5223ef0d6724b3d8327134890cfe3d72fbe5"
integrity sha512-F4LXf1OeU9hrSYRPTTj/6FbO4HTjPKXvEIC1P2kcnFurViINCVk3ZV0xAS3XVx9MkMsXbbqlK6hjseaYbgKEHw==
debug@4, debug@4.3.2, debug@^4.1.0, debug@^4.1.1, debug@^4.3.2:
version "4.3.2"
resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.2.tgz#f0a49c18ac8779e31d4a0c6029dfb76873c7428b"
@ -2568,9 +2573,9 @@ diff@^4.0.1:
integrity sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==
diff@^5.0.0:
version "5.0.0"
resolved "https://registry.yarnpkg.com/diff/-/diff-5.0.0.tgz#7ed6ad76d859d030787ec35855f5b1daf31d852b"
integrity sha512-/VTCrvm5Z0JGty/BWHljh+BAiw3IK+2j87NGMu8Nwc/f48WoDAC395uomO9ZD117ZOBaHmkX1oyLvkVM/aIT3w==
version "5.1.0"
resolved "https://registry.yarnpkg.com/diff/-/diff-5.1.0.tgz#bc52d298c5ea8df9194800224445ed43ffc87e40"
integrity sha512-D+mk+qE8VC/PAUrlAU34N+VfXev0ghe5ywmpqrawphmVZc1bEfn56uo9qpyGp1p4xpzOHkSW4ztBd6L7Xx4ACw==
dir-glob@^3.0.1:
version "3.0.1"
@ -3246,9 +3251,9 @@ glob@^7.1.1, glob@^7.1.2, glob@^7.1.3, glob@^7.1.4:
path-is-absolute "^1.0.0"
glob@^7.1.6:
version "7.2.2"
resolved "https://registry.yarnpkg.com/glob/-/glob-7.2.2.tgz#29deb38e1ef90f132d5958abe9c3ee8e87f3c318"
integrity sha512-NzDgHDiJwKYByLrL5lONmQFpK/2G78SMMfo+E9CuGlX4IkvfKDsiQSNPwAYxEy+e6p7ZQ3uslSLlwlJcqezBmQ==
version "7.2.3"
resolved "https://registry.yarnpkg.com/glob/-/glob-7.2.3.tgz#b8df0fb802bbfa8e89bd1d938b4e16578ed44f2b"
integrity sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==
dependencies:
fs.realpath "^1.0.0"
inflight "^1.0.4"
@ -6282,9 +6287,9 @@ yargs@^15.3.1, yargs@^15.4.1:
yargs-parser "^18.1.2"
yargs@^17.2.1:
version "17.5.0"
resolved "https://registry.yarnpkg.com/yargs/-/yargs-17.5.0.tgz#2706c5431f8c119002a2b106fc9f58b9bb9097a3"
integrity sha512-3sLxVhbAB5OC8qvVRebCLWuouhwh/rswsiDYx3WGxajUk/l4G20SKfrKKFeNIHboUFt2JFgv2yfn+5cgOr/t5A==
version "17.5.1"
resolved "https://registry.yarnpkg.com/yargs/-/yargs-17.5.1.tgz#e109900cab6fcb7fd44b1d8249166feb0b36e58e"
integrity sha512-t6YAJcxDkNX7NFYiVtKvWUz8l+PaKTLiL63mJYWR2GnHq2gjEWISzsLp9wg3aY36dY1j+gfIEL3pIF+XlJJfbA==
dependencies:
cliui "^7.0.2"
escalade "^3.1.1"

4
packages/cli/package.json

@ -1,6 +1,6 @@
{
"name": "@budibase/cli",
"version": "1.0.212-alpha.8",
"version": "1.0.219",
"description": "Budibase CLI, for developers, self hosting and migrations.",
"main": "src/index.js",
"bin": {
@ -33,4 +33,4 @@
"devDependencies": {
"eslint": "^7.20.0"
}
}
}

10
packages/client/package.json

@ -1,6 +1,6 @@
{
"name": "@budibase/client",
"version": "1.0.212-alpha.8",
"version": "1.0.219",
"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.212-alpha.8",
"@budibase/frontend-core": "^1.0.212-alpha.8",
"@budibase/string-templates": "^1.0.212-alpha.8",
"@budibase/bbui": "^1.0.219",
"@budibase/frontend-core": "^1.0.219",
"@budibase/string-templates": "^1.0.219",
"@spectrum-css/button": "^3.0.3",
"@spectrum-css/card": "^3.0.3",
"@spectrum-css/divider": "^1.0.3",
@ -58,4 +58,4 @@
"rollup-plugin-visualizer": "^5.5.4"
},
"gitHead": "d1836a898cab3f8ab80ee6d8f42be1a9eed7dcdc"
}
}

4
packages/client/src/components/app/dynamic-filter/DynamicFilter.svelte

@ -61,7 +61,9 @@
clonedSchema = schema
} else {
allowedFields?.forEach(field => {
clonedSchema[field] = schema[field]
if (schema[field]) {
clonedSchema[field] = schema[field]
}
})
}
return Object.values(clonedSchema || {})

3
packages/client/src/components/app/forms/validation.js

@ -278,6 +278,9 @@ const notEqualHandler = (value, rule) => {
// Evaluates a regex constraint
const regexHandler = (value, rule) => {
const regex = parseType(rule.value, "string")
if (!value) {
value = ""
}
return new RegExp(regex).test(value)
}

1293
packages/client/yarn.lock

File diff suppressed because it is too large

6
packages/frontend-core/package.json

@ -1,13 +1,13 @@
{
"name": "@budibase/frontend-core",
"version": "1.0.212-alpha.8",
"version": "1.0.219",
"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.212-alpha.8",
"@budibase/bbui": "^1.0.219",
"lodash": "^4.17.21",
"svelte": "^3.46.2"
}
}
}

35
packages/frontend-core/src/api/automations.js

@ -73,4 +73,39 @@ export const buildAutomationEndpoints = API => ({
url: `/api/automations/${automationId}/${automationRev}`,
})
},
/**
* Get the logs for the app, or by automation ID.
* @param automationId The ID of the automation to get logs for.
* @param startDate An ISO date string to state the start of the date range.
* @param status The status, error or success.
* @param page The page to retrieve.
*/
getAutomationLogs: async ({ automationId, startDate, status, page }) => {
return await API.post({
url: "/api/automations/logs/search",
body: {
automationId,
startDate,
status,
page,
},
})
},
/**
* Clears automation log errors (which are creating notification) for
* automation or the app.
* @param automationId optional - the ID of the automation to clear errors for.
* @param appId The app ID to clear errors for.
*/
clearAutomationLogErrors: async ({ automationId, appId }) => {
return await API.delete({
url: "/api/automations/logs",
body: {
appId,
automationId,
},
})
},
})

9
packages/server/__mocks__/node-fetch.ts

@ -15,6 +15,15 @@ module FetchMock {
},
},
json: async () => {
//x-www-form-encoded body is a URLSearchParams
//The call to stringify it leaves it blank
if (body?.opts?.body instanceof URLSearchParams) {
const paramArray = Array.from(body.opts.body.entries())
body.opts.body = paramArray.reduce((acc: any, pair: any) => {
acc[pair[0]] = pair[1]
return acc
}, {})
}
return body
},
}

14
packages/server/package.json

@ -1,7 +1,7 @@
{
"name": "@budibase/server",
"email": "hi@budibase.com",
"version": "1.0.212-alpha.8",
"version": "1.0.219",
"description": "Budibase Web Server",
"main": "src/index.ts",
"repository": {
@ -77,11 +77,10 @@
"license": "GPL-3.0",
"dependencies": {
"@apidevtools/swagger-parser": "10.0.3",
"@budibase/backend-core": "^1.0.212-alpha.8",
"@budibase/client": "^1.0.212-alpha.8",
"@budibase/pro": "1.0.212-alpha.8",
"@budibase/string-templates": "^1.0.212-alpha.8",
"@budibase/types": "^1.0.212-alpha.8",
"@budibase/backend-core": "^1.0.219",
"@budibase/client": "^1.0.219",
"@budibase/pro": "1.0.219",
"@budibase/string-templates": "^1.0.219",
"@bull-board/api": "3.7.0",
"@bull-board/koa": "3.9.4",
"@elastic/elasticsearch": "7.10.0",
@ -152,6 +151,7 @@
"@babel/core": "7.17.4",
"@babel/preset-env": "7.16.11",
"@budibase/standard-components": "^0.9.139",
"@budibase/types": "^1.0.219",
"@jest/test-sequencer": "24.9.0",
"@types/apidoc": "0.50.0",
"@types/bson": "4.2.0",
@ -196,4 +196,4 @@
"oracledb": "5.3.0"
},
"gitHead": "d1836a898cab3f8ab80ee6d8f42be1a9eed7dcdc"
}
}

2
packages/server/scripts/dev/manage.js

@ -56,6 +56,8 @@ async function init() {
DISABLE_THREADING: 1,
SERVICE: "app-service",
DEPLOYMENT_ENVIRONMENT: "development",
BB_ADMIN_USER_EMAIL: "",
BB_ADMIN_USER_PASSWORD: "",
}
let envFile = ""
Object.keys(envFileJson).forEach(key => {

14
packages/server/specs/openapi.json

@ -2137,6 +2137,10 @@
"query": {
"type": "object",
"properties": {
"allOr": {
"type": "boolean",
"description": "Specifies that a row should be returned if it satisfies any of the specified options, rather than requiring it to fulfill all the search parameters. This defaults to false, meaning AND logic will be used."
},
"string": {
"type": "object",
"example": {
@ -2155,12 +2159,12 @@
},
"range": {
"type": "object",
"description": "Searches within a range, the format of this must be columnName -> [low, high].",
"description": "Searches within a range, the format of this must be in the format of an object with a \"low\" and \"high\" property.",
"example": {
"columnName1": [
10,
20
]
"columnName1": {
"low": 10,
"high": 20
}
}
},
"equal": {

14
packages/server/specs/openapi.yaml

@ -1551,6 +1551,12 @@ paths:
query:
type: object
properties:
allOr:
type: boolean
description: Specifies that a row should be returned if it satisfies any of the
specified options, rather than requiring it to fulfill
all the search parameters. This defaults to false,
meaning AND logic will be used.
string:
type: object
example:
@ -1566,12 +1572,12 @@ paths:
description: A fuzzy search, only supported by internal tables.
range:
type: object
description: Searches within a range, the format of this must be columnName ->
[low, high].
description: Searches within a range, the format of this must be in the format
of an object with a "low" and "high" property.
example:
columnName1:
- 10
- 20
low: 10
high: 20
equal:
type: object
description: Searches for rows that have a column value that is exactly the

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

@ -45,10 +45,12 @@ const { getTenantId, isMultiTenant } = require("@budibase/backend-core/tenancy")
import { syncGlobalUsers } from "./user"
const { app: appCache } = require("@budibase/backend-core/cache")
import { cleanupAutomations } from "../../automations/utils"
import { checkAppMetadata } from "../../automations/logging"
const {
getAppDB,
getProdAppDB,
updateAppId,
doInAppContext,
} = require("@budibase/backend-core/context")
import { getUniqueRows } from "../../utilities/usageQuota/rows"
import { quotas } from "@budibase/pro"
@ -192,7 +194,7 @@ export const fetch = async (ctx: any) => {
}
}
ctx.body = apps
ctx.body = await checkAppMetadata(apps)
}
export const fetchAppDefinition = async (ctx: any) => {
@ -558,20 +560,22 @@ export const sync = async (ctx: any, next: any) => {
}
const updateAppPackage = async (appPackage: any, appId: any) => {
const db = getAppDB()
const application = await db.get(DocumentTypes.APP_METADATA)
return doInAppContext(appId, async () => {
const db = getAppDB()
const application = await db.get(DocumentTypes.APP_METADATA)
const newAppPackage = { ...application, ...appPackage }
if (appPackage._rev !== application._rev) {
newAppPackage._rev = application._rev
}
const newAppPackage = { ...application, ...appPackage }
if (appPackage._rev !== application._rev) {
newAppPackage._rev = application._rev
}
// the locked by property is attached by server but generated from
// Redis, shouldn't ever store it
delete newAppPackage.lockedBy
// the locked by property is attached by server but generated from
// Redis, shouldn't ever store it
delete newAppPackage.lockedBy
await db.put(newAppPackage)
// remove any cached metadata, so that it will be updated
await appCache.invalidateAppMetadata(appId)
return newAppPackage
await db.put(newAppPackage)
// remove any cached metadata, so that it will be updated
await appCache.invalidateAppMetadata(appId)
return newAppPackage
})
}

37
packages/server/src/api/controllers/automation.js

@ -1,6 +1,10 @@
const actions = require("../../automations/actions")
const triggers = require("../../automations/triggers")
const { getAutomationParams, generateAutomationID } = require("../../db/utils")
const {
getAutomationParams,
generateAutomationID,
DocumentTypes,
} = require("../../db/utils")
const {
checkForWebhooks,
updateTestHistory,
@ -9,8 +13,14 @@ const {
const { deleteEntityMetadata } = require("../../utilities")
const { MetadataTypes } = require("../../constants")
const { setTestFlag, clearTestFlag } = require("../../utilities/redis")
const { getAppDB } = require("@budibase/backend-core/context")
const {
getAppDB,
getProdAppDB,
doInAppContext,
} = require("@budibase/backend-core/context")
const { events } = require("@budibase/backend-core")
const { app } = require("@budibase/backend-core/cache")
const { automations } = require("@budibase/pro")
const ACTION_DEFS = removeDeprecated(actions.ACTION_DEFINITIONS)
const TRIGGER_DEFS = removeDeprecated(triggers.TRIGGER_DEFINITIONS)
@ -183,6 +193,29 @@ exports.destroy = async function (ctx) {
await events.automation.deleted(oldAutomation)
}
exports.logSearch = async function (ctx) {
ctx.body = await automations.logs.logSearch(ctx.request.body)
}
exports.clearLogError = async function (ctx) {
const { automationId, appId } = ctx.request.body
await doInAppContext(appId, async () => {
const db = getProdAppDB()
const metadata = await db.get(DocumentTypes.APP_METADATA)
if (!automationId) {
delete metadata.automationErrors
} else if (
metadata.automationErrors &&
metadata.automationErrors[automationId]
) {
delete metadata.automationErrors[automationId]
}
await db.put(metadata)
await app.invalidateAppMetadata(metadata.appId, metadata)
ctx.body = { message: `Error logs cleared.` }
})
}
exports.getActionList = async function (ctx) {
ctx.body = ACTION_DEFS
}

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

Loading…
Cancel
Save