Merge branch 'develop' of github.com:Budibase/budibase into new-design-ui
This commit is contained in:
commit
16116f2877
|
@ -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>/**"
|
||||
]
|
||||
},
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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=
|
|
@ -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
|
||||
|
|
|
@ -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=
|
|
@ -1,5 +1,5 @@
|
|||
{
|
||||
"version": "1.0.212-alpha.8",
|
||||
"version": "1.0.219",
|
||||
"npmClient": "yarn",
|
||||
"packages": [
|
||||
"packages/*"
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -5,4 +5,5 @@ module.exports = {
|
|||
app: require("./src/cache/appMetadata"),
|
||||
writethrough: require("./src/cache/writethrough"),
|
||||
...generic,
|
||||
cache: generic,
|
||||
}
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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,
|
||||
}
|
||||
|
|
|
@ -314,6 +314,7 @@ function getContextDB(key, opts) {
|
|||
toUseAppId = getDevelopmentAppID(appId)
|
||||
break
|
||||
}
|
||||
|
||||
db = dangerousGetDB(toUseAppId, opts)
|
||||
try {
|
||||
cls.setOnContext(key, db)
|
||||
|
|
|
@ -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
|
|
@ -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
|
|
@ -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(),
|
||||
},
|
||||
},
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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,
|
||||
}
|
||||
|
||||
|
|
|
@ -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,
|
||||
}
|
|
@ -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) {
|
||||
|
|
|
@ -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,
|
||||
},
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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(
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
||||
|
|
|
@ -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}`
|
||||
}
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -40,5 +40,6 @@
|
|||
on:change={onChange}
|
||||
on:pick
|
||||
on:type
|
||||
on:blur
|
||||
/>
|
||||
</Field>
|
||||
|
|
|
@ -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 || ""}
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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,
|
||||
}
|
||||
|
|
|
@ -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>
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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()
|
||||
})
|
||||
})
|
||||
})
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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")
|
||||
})
|
||||
})
|
||||
|
||||
|
|
|
@ -103,6 +103,8 @@ filterTests(["smoke", "all"], () => {
|
|||
}
|
||||
cy.get("button").contains("Update password").click({ force: true })
|
||||
})
|
||||
// Remove users name
|
||||
cy.updateUserInformation()
|
||||
})
|
||||
})
|
||||
})
|
||||
|
|
|
@ -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 })
|
||||
|
|
|
@ -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")
|
||||
|
||||
})
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -5,7 +5,6 @@ filterTests(['smoke', 'all'], () => {
|
|||
context("Create a View", () => {
|
||||
before(() => {
|
||||
cy.login()
|
||||
|
||||
cy.createTestApp()
|
||||
cy.createTable("data")
|
||||
cy.addColumn("data", "group", "Text")
|
||||
|
|
|
@ -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 })
|
||||
|
|
|
@ -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)
|
||||
})
|
||||
|
||||
|
|
|
@ -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)
|
||||
})
|
||||
|
||||
|
|
|
@ -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
|
||||
})
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
||||
})
|
||||
},
|
||||
|
|
|
@ -65,7 +65,7 @@
|
|||
<ActionButton
|
||||
disabled={!$automationStore.selectedAutomation?.testResults}
|
||||
on:click={() => {
|
||||
$automationStore.selectedAutomation.automation.showTestPanel = true
|
||||
$automationStore.showTestPanel = true
|
||||
}}
|
||||
size="M">Test Details</ActionButton
|
||||
>
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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}
|
||||
|
|
|
@ -51,7 +51,7 @@
|
|||
$automationStore.selectedAutomation?.automation,
|
||||
testData
|
||||
)
|
||||
$automationStore.selectedAutomation.automation.showTestPanel = true
|
||||
$automationStore.showTestPanel = true
|
||||
} catch (error) {
|
||||
notifications.error("Error testing notification")
|
||||
}
|
||||
|
|
|
@ -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>
|
|
@ -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>
|
||||
|
|
|
@ -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),
|
||||
|
|
|
@ -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}
|
||||
|
|
|
@ -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()}>
|
||||
|
|
|
@ -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}
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
|
@ -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.
|
||||
|
|
|
@ -0,0 +1,6 @@
|
|||
<script>
|
||||
import dayjs from "dayjs"
|
||||
export let value
|
||||
</script>
|
||||
|
||||
{new dayjs(value).format("MMM D, YYYY HH:mm")}
|
|
@ -52,7 +52,7 @@
|
|||
reviewPendingDeployments(deployments, newDeployments)
|
||||
return newDeployments
|
||||
} catch (err) {
|
||||
notifications.error("Error fetching deployment history")
|
||||
notifications.error("Error fetching deployment overview")
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -55,7 +55,7 @@
|
|||
deployments = newDeployments
|
||||
} catch (err) {
|
||||
clearInterval(poll)
|
||||
notifications.error("Error fetching deployment history")
|
||||
notifications.error("Error fetching deployment overview")
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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>
|
|
@ -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>
|
|
@ -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>
|
|
@ -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 = {
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -12,4 +12,6 @@
|
|||
}
|
||||
</script>
|
||||
|
||||
<slot />
|
||||
{#key $params.selectedDatasource}
|
||||
<slot />
|
||||
{/key}
|
||||
|
|
|
@ -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">
|
||||
|
|
|
@ -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">
|
||||
|
|
|
@ -74,7 +74,10 @@
|
|||
rev: smtpConfig._rev,
|
||||
})
|
||||
smtpConfig = {
|
||||
config: {},
|
||||
type: ConfigTypes.SMTP,
|
||||
config: {
|
||||
secure: true,
|
||||
},
|
||||
}
|
||||
await admin.getChecklist()
|
||||
notifications.success(`Settings cleared`)
|
||||
|
|
|
@ -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 })
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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,
|
||||
})
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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": {
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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 || {})
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -1,12 +1,12 @@
|
|||
{
|
||||
"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"
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
},
|
||||
})
|
||||
},
|
||||
})
|
||||
|
|
|
@ -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
|
||||
},
|
||||
}
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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 => {
|
||||
|
|
|
@ -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": {
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
})
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
const { getAllApps } = require("@budibase/backend-core/db")
|
||||
const { updateAppId } = require("@budibase/backend-core/context")
|
||||
import { search as stringSearch } from "./utils"
|
||||
import { search as stringSearch, addRev } from "./utils"
|
||||
import * as controller from "../application"
|
||||
import { Application } from "../../../definitions/common"
|
||||
|
||||
|
@ -47,7 +47,7 @@ export async function read(ctx: any, next: any) {
|
|||
}
|
||||
|
||||
export async function update(ctx: any, next: any) {
|
||||
ctx.request.body = fixAppID(ctx.request.body, ctx.params)
|
||||
ctx.request.body = await addRev(fixAppID(ctx.request.body, ctx.params))
|
||||
updateAppId(ctx.params.appId)
|
||||
await controller.update(ctx)
|
||||
await setResponseApp(ctx)
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue