Merge branch 'develop' of github.com:Budibase/budibase into omnibus-action
This commit is contained in:
commit
83c9ab9788
|
@ -7,6 +7,7 @@ on:
|
||||||
branches:
|
branches:
|
||||||
- master
|
- master
|
||||||
- develop
|
- develop
|
||||||
|
- new-design-ui
|
||||||
pull_request:
|
pull_request:
|
||||||
branches:
|
branches:
|
||||||
- master
|
- master
|
||||||
|
@ -59,3 +60,19 @@ jobs:
|
||||||
with:
|
with:
|
||||||
install: false
|
install: false
|
||||||
command: yarn test:e2e:ci
|
command: yarn test:e2e:ci
|
||||||
|
|
||||||
|
- name: Configure AWS Credentials
|
||||||
|
uses: aws-actions/configure-aws-credentials@v1
|
||||||
|
with:
|
||||||
|
aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
|
||||||
|
aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
|
||||||
|
aws-region: eu-west-1
|
||||||
|
|
||||||
|
- name: Upload to S3
|
||||||
|
if: github.ref == 'refs/heads/new-design-ui'
|
||||||
|
run: |
|
||||||
|
tar -czvf new_ui.tar.gz packages/server/builder/assets packages/server/builder/index.html
|
||||||
|
aws s3 cp new_ui.tar.gz s3://prod-budi-app-assets/beta:design_ui/
|
||||||
|
aws s3 cp packages/client/dist/budibase-client.js s3://prod-budi-app-assets/beta:design_ui/budibase-client.js
|
||||||
|
aws cloudfront create-invalidation --distribution-id E3ELKP4RCEHVLW --paths "/beta:design_ui/*"
|
||||||
|
|
||||||
|
|
|
@ -21,7 +21,8 @@ env:
|
||||||
# Posthog token used by ui at build time
|
# Posthog token used by ui at build time
|
||||||
POSTHOG_TOKEN: phc_uDYOfnFt6wAbBAXkC6STjcrTpAFiWIhqgFcsC1UVO5F
|
POSTHOG_TOKEN: phc_uDYOfnFt6wAbBAXkC6STjcrTpAFiWIhqgFcsC1UVO5F
|
||||||
INTERCOM_TOKEN: ${{ secrets.INTERCOM_TOKEN }}
|
INTERCOM_TOKEN: ${{ secrets.INTERCOM_TOKEN }}
|
||||||
PERSONAL_ACCESS_TOKEN : ${{ secrets.PERSONAL_ACCESS_TOKEN }}
|
PERSONAL_ACCESS_TOKEN: ${{ secrets.PERSONAL_ACCESS_TOKEN }}
|
||||||
|
FEATURE_PREVIEW_URL: https://budirelease.live
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
release:
|
release:
|
||||||
|
@ -124,4 +125,4 @@ jobs:
|
||||||
with:
|
with:
|
||||||
webhook-url: ${{ secrets.PROD_DEPLOY_WEBHOOK_URL }}
|
webhook-url: ${{ secrets.PROD_DEPLOY_WEBHOOK_URL }}
|
||||||
content: "Release Env Deployment Complete: ${{ env.RELEASE_VERSION }} deployed to Budibase Release Env."
|
content: "Release Env Deployment Complete: ${{ env.RELEASE_VERSION }} deployed to Budibase Release Env."
|
||||||
embed-title: ${{ env.RELEASE_VERSION }}
|
embed-title: ${{ env.RELEASE_VERSION }}
|
||||||
|
|
|
@ -3,5 +3,17 @@
|
||||||
"editor.codeActionsOnSave": {
|
"editor.codeActionsOnSave": {
|
||||||
"source.fixAll": true
|
"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://github.com/Budibase/budibase
|
||||||
- https://budibase.com
|
- https://budibase.com
|
||||||
type: application
|
type: application
|
||||||
version: 0.2.10
|
version: 0.2.11
|
||||||
appVersion: 1.0.48
|
appVersion: 1.0.214
|
||||||
dependencies:
|
dependencies:
|
||||||
- name: couchdb
|
- name: couchdb
|
||||||
version: 3.6.1
|
version: 3.6.1
|
||||||
|
|
|
@ -103,7 +103,7 @@ globals:
|
||||||
google:
|
google:
|
||||||
clientId: ""
|
clientId: ""
|
||||||
secret: ""
|
secret: ""
|
||||||
automationMaxIterations: "500"
|
automationMaxIterations: "200"
|
||||||
|
|
||||||
createSecrets: true # creates an internal API key, JWT secrets and redis password for you
|
createSecrets: true # creates an internal API key, JWT secrets and redis password for you
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
{
|
{
|
||||||
"version": "1.0.212-alpha.0",
|
"version": "1.0.219-alpha.0",
|
||||||
"npmClient": "yarn",
|
"npmClient": "yarn",
|
||||||
"packages": [
|
"packages": [
|
||||||
"packages/*"
|
"packages/*"
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "@budibase/backend-core",
|
"name": "@budibase/backend-core",
|
||||||
"version": "1.0.212-alpha.0",
|
"version": "1.0.219-alpha.0",
|
||||||
"description": "Budibase backend core libraries used in server and worker",
|
"description": "Budibase backend core libraries used in server and worker",
|
||||||
"main": "dist/src/index.js",
|
"main": "dist/src/index.js",
|
||||||
"types": "dist/src/index.d.ts",
|
"types": "dist/src/index.d.ts",
|
||||||
|
@ -20,7 +20,7 @@
|
||||||
"test:watch": "jest --watchAll"
|
"test:watch": "jest --watchAll"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@budibase/types": "^1.0.212-alpha.0",
|
"@budibase/types": "^1.0.219-alpha.0",
|
||||||
"@techpass/passport-openidconnect": "0.3.2",
|
"@techpass/passport-openidconnect": "0.3.2",
|
||||||
"aws-sdk": "2.1030.0",
|
"aws-sdk": "2.1030.0",
|
||||||
"bcrypt": "5.0.1",
|
"bcrypt": "5.0.1",
|
||||||
|
@ -36,6 +36,7 @@
|
||||||
"passport-google-oauth": "2.0.0",
|
"passport-google-oauth": "2.0.0",
|
||||||
"passport-jwt": "4.0.0",
|
"passport-jwt": "4.0.0",
|
||||||
"passport-local": "1.0.0",
|
"passport-local": "1.0.0",
|
||||||
|
"passport-oauth2-refresh": "^2.1.0",
|
||||||
"posthog-node": "1.3.0",
|
"posthog-node": "1.3.0",
|
||||||
"pouchdb": "7.3.0",
|
"pouchdb": "7.3.0",
|
||||||
"pouchdb-find": "7.2.2",
|
"pouchdb-find": "7.2.2",
|
||||||
|
|
|
@ -2,6 +2,9 @@ const passport = require("koa-passport")
|
||||||
const LocalStrategy = require("passport-local").Strategy
|
const LocalStrategy = require("passport-local").Strategy
|
||||||
const JwtStrategy = require("passport-jwt").Strategy
|
const JwtStrategy = require("passport-jwt").Strategy
|
||||||
const { getGlobalDB } = require("./tenancy")
|
const { getGlobalDB } = require("./tenancy")
|
||||||
|
const refresh = require("passport-oauth2-refresh")
|
||||||
|
const { Configs } = require("./constants")
|
||||||
|
const { getScopedConfig } = require("./db/utils")
|
||||||
const {
|
const {
|
||||||
jwt,
|
jwt,
|
||||||
local,
|
local,
|
||||||
|
@ -12,6 +15,7 @@ const {
|
||||||
tenancy,
|
tenancy,
|
||||||
appTenancy,
|
appTenancy,
|
||||||
authError,
|
authError,
|
||||||
|
ssoCallbackUrl,
|
||||||
csrf,
|
csrf,
|
||||||
internalApi,
|
internalApi,
|
||||||
} = require("./middleware")
|
} = require("./middleware")
|
||||||
|
@ -34,6 +38,122 @@ 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)
|
||||||
|
} catch (e) {
|
||||||
|
console.error("Could not update OAuth details for current user", e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
buildAuthMiddleware: authenticated,
|
buildAuthMiddleware: authenticated,
|
||||||
passport,
|
passport,
|
||||||
|
@ -46,4 +166,7 @@ module.exports = {
|
||||||
authError,
|
authError,
|
||||||
buildCsrfMiddleware: csrf,
|
buildCsrfMiddleware: csrf,
|
||||||
internalApi,
|
internalApi,
|
||||||
|
refreshOAuthToken,
|
||||||
|
updateUserOAuth,
|
||||||
|
ssoCallbackUrl,
|
||||||
}
|
}
|
||||||
|
|
|
@ -314,6 +314,7 @@ function getContextDB(key, opts) {
|
||||||
toUseAppId = getDevelopmentAppID(appId)
|
toUseAppId = getDevelopmentAppID(appId)
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
|
||||||
db = dangerousGetDB(toUseAppId, opts)
|
db = dangerousGetDB(toUseAppId, opts)
|
||||||
try {
|
try {
|
||||||
cls.setOnContext(key, db)
|
cls.setOnContext(key, db)
|
||||||
|
|
|
@ -1,41 +0,0 @@
|
||||||
exports.SEPARATOR = "_"
|
|
||||||
|
|
||||||
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 { newid } from "../hashing"
|
||||||
import { DEFAULT_TENANT_ID, Configs } from "../constants"
|
import { DEFAULT_TENANT_ID, Configs } from "../constants"
|
||||||
import env from "../environment"
|
import env from "../environment"
|
||||||
import { SEPARATOR, DocumentTypes } from "./constants"
|
import { SEPARATOR, DocumentTypes, UNICODE_MAX, ViewNames } from "./constants"
|
||||||
import { getTenantId, getGlobalDBName, getGlobalDB } from "../tenancy"
|
import { getTenantId, getGlobalDBName, getGlobalDB } from "../tenancy"
|
||||||
import fetch from "node-fetch"
|
import fetch from "node-fetch"
|
||||||
import { doWithDB, allDbs } from "./index"
|
import { doWithDB, allDbs } from "./index"
|
||||||
|
@ -12,14 +12,6 @@ import { isDevApp, isDevAppID } from "./conversions"
|
||||||
import { APP_PREFIX } from "./constants"
|
import { APP_PREFIX } from "./constants"
|
||||||
import * as events from "../events"
|
import * as events from "../events"
|
||||||
|
|
||||||
const UNICODE_MAX = "\ufff0"
|
|
||||||
|
|
||||||
export const ViewNames = {
|
|
||||||
USER_BY_EMAIL: "by_email",
|
|
||||||
BY_API_KEY: "by_api_key",
|
|
||||||
USER_BY_BUILDERS: "by_builders",
|
|
||||||
}
|
|
||||||
|
|
||||||
export * from "./constants"
|
export * from "./constants"
|
||||||
export * from "./conversions"
|
export * from "./conversions"
|
||||||
export { default as Replication } from "./Replication"
|
export { default as Replication } from "./Replication"
|
||||||
|
@ -63,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.
|
* Generates a new workspace ID.
|
||||||
* @returns {string} The new workspace ID which the workspace doc can be stored under.
|
* @returns {string} The new workspace ID which the workspace doc can be stored under.
|
||||||
|
@ -93,13 +92,17 @@ export function generateGlobalUserID(id?: any) {
|
||||||
/**
|
/**
|
||||||
* Gets parameters for retrieving users.
|
* Gets parameters for retrieving users.
|
||||||
*/
|
*/
|
||||||
export function getGlobalUserParams(globalId: any, otherProps = {}) {
|
export function getGlobalUserParams(globalId: any, otherProps: any = {}) {
|
||||||
if (!globalId) {
|
if (!globalId) {
|
||||||
globalId = ""
|
globalId = ""
|
||||||
}
|
}
|
||||||
|
const startkey = otherProps?.startkey
|
||||||
return {
|
return {
|
||||||
...otherProps,
|
...otherProps,
|
||||||
startkey: `${DocumentTypes.USER}${SEPARATOR}${globalId}`,
|
// need to include this incase pagination
|
||||||
|
startkey: startkey
|
||||||
|
? startkey
|
||||||
|
: `${DocumentTypes.USER}${SEPARATOR}${globalId}`,
|
||||||
endkey: `${DocumentTypes.USER}${SEPARATOR}${globalId}${UNICODE_MAX}`,
|
endkey: `${DocumentTypes.USER}${SEPARATOR}${globalId}${UNICODE_MAX}`,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -384,7 +387,9 @@ export const getScopedFullConfig = async function (
|
||||||
if (type === Configs.SETTINGS) {
|
if (type === Configs.SETTINGS) {
|
||||||
if (scopedConfig && scopedConfig.doc) {
|
if (scopedConfig && scopedConfig.doc) {
|
||||||
// overrides affected by environment variables
|
// overrides affected by environment variables
|
||||||
scopedConfig.doc.config.platformUrl = await getPlatformUrl()
|
scopedConfig.doc.config.platformUrl = await getPlatformUrl({
|
||||||
|
tenantAware: true,
|
||||||
|
})
|
||||||
scopedConfig.doc.config.analyticsEnabled =
|
scopedConfig.doc.config.analyticsEnabled =
|
||||||
await events.analytics.enabled()
|
await events.analytics.enabled()
|
||||||
} else {
|
} else {
|
||||||
|
@ -393,7 +398,7 @@ export const getScopedFullConfig = async function (
|
||||||
doc: {
|
doc: {
|
||||||
_id: generateConfigID({ type, user, workspace }),
|
_id: generateConfigID({ type, user, workspace }),
|
||||||
config: {
|
config: {
|
||||||
platformUrl: await getPlatformUrl(),
|
platformUrl: await getPlatformUrl({ tenantAware: true }),
|
||||||
analyticsEnabled: await events.analytics.enabled(),
|
analyticsEnabled: await events.analytics.enabled(),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -434,6 +439,26 @@ export const getPlatformUrl = async (opts = { tenantAware: true }) => {
|
||||||
return platformUrl
|
return platformUrl
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function pagination(
|
||||||
|
data: any[],
|
||||||
|
pageSize: number,
|
||||||
|
{ paginate, property } = { paginate: true, property: "_id" }
|
||||||
|
) {
|
||||||
|
if (!paginate) {
|
||||||
|
return { data, hasNextPage: false }
|
||||||
|
}
|
||||||
|
const hasNextPage = data.length > pageSize
|
||||||
|
let nextPage = undefined
|
||||||
|
if (hasNextPage) {
|
||||||
|
nextPage = property ? data[pageSize]?.[property] : data[pageSize]?._id
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
data: data.slice(0, pageSize),
|
||||||
|
hasNextPage,
|
||||||
|
nextPage,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export async function getScopedConfig(db: any, params: any) {
|
export async function getScopedConfig(db: any, params: any) {
|
||||||
const configDoc = await getScopedFullConfig(db, params)
|
const configDoc = await getScopedFullConfig(db, params)
|
||||||
return configDoc && configDoc.config ? configDoc.config : configDoc
|
return configDoc && configDoc.config ? configDoc.config : configDoc
|
||||||
|
|
|
@ -13,6 +13,7 @@ import deprovisioning from "./context/deprovision"
|
||||||
import auth from "./auth"
|
import auth from "./auth"
|
||||||
import constants from "./constants"
|
import constants from "./constants"
|
||||||
import * as dbConstants from "./db/constants"
|
import * as dbConstants from "./db/constants"
|
||||||
|
import logging from "./logging"
|
||||||
|
|
||||||
// mimic the outer package exports
|
// mimic the outer package exports
|
||||||
import * as db from "./pkg/db"
|
import * as db from "./pkg/db"
|
||||||
|
@ -49,6 +50,7 @@ const core = {
|
||||||
deprovisioning,
|
deprovisioning,
|
||||||
installation,
|
installation,
|
||||||
errors,
|
errors,
|
||||||
|
logging,
|
||||||
...errorClasses,
|
...errorClasses,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -94,7 +94,6 @@ module.exports = (
|
||||||
user = await getUser(userId, session.tenantId)
|
user = await getUser(userId, session.tenantId)
|
||||||
}
|
}
|
||||||
user.csrfToken = session.csrfToken
|
user.csrfToken = session.csrfToken
|
||||||
delete user.password
|
|
||||||
authenticated = true
|
authenticated = true
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
error = err
|
error = err
|
||||||
|
@ -128,6 +127,8 @@ module.exports = (
|
||||||
}
|
}
|
||||||
if (!user && tenantId) {
|
if (!user && tenantId) {
|
||||||
user = { tenantId }
|
user = { tenantId }
|
||||||
|
} else {
|
||||||
|
delete user.password
|
||||||
}
|
}
|
||||||
// be explicit
|
// be explicit
|
||||||
if (authenticated !== true) {
|
if (authenticated !== true) {
|
||||||
|
|
|
@ -2,7 +2,7 @@ const jwt = require("./passport/jwt")
|
||||||
const local = require("./passport/local")
|
const local = require("./passport/local")
|
||||||
const google = require("./passport/google")
|
const google = require("./passport/google")
|
||||||
const oidc = require("./passport/oidc")
|
const oidc = require("./passport/oidc")
|
||||||
const { authError } = require("./passport/utils")
|
const { authError, ssoCallbackUrl } = require("./passport/utils")
|
||||||
const authenticated = require("./authenticated")
|
const authenticated = require("./authenticated")
|
||||||
const auditLog = require("./auditLog")
|
const auditLog = require("./auditLog")
|
||||||
const tenancy = require("./tenancy")
|
const tenancy = require("./tenancy")
|
||||||
|
@ -20,6 +20,7 @@ module.exports = {
|
||||||
tenancy,
|
tenancy,
|
||||||
authError,
|
authError,
|
||||||
internalApi,
|
internalApi,
|
||||||
|
ssoCallbackUrl,
|
||||||
datasource: {
|
datasource: {
|
||||||
google: datasourceGoogle,
|
google: datasourceGoogle,
|
||||||
},
|
},
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
const GoogleStrategy = require("passport-google-oauth").OAuth2Strategy
|
const GoogleStrategy = require("passport-google-oauth").OAuth2Strategy
|
||||||
|
const { ssoCallbackUrl } = require("./utils")
|
||||||
const { authenticateThirdParty } = require("./third-party-common")
|
const { authenticateThirdParty } = require("./third-party-common")
|
||||||
|
const { Configs } = require("../../../constants")
|
||||||
|
|
||||||
const buildVerifyFn = saveUserFn => {
|
const buildVerifyFn = saveUserFn => {
|
||||||
return (accessToken, refreshToken, profile, done) => {
|
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
|
// expose for testing
|
||||||
exports.buildVerifyFn = buildVerifyFn
|
exports.buildVerifyFn = buildVerifyFn
|
||||||
|
|
|
@ -55,6 +55,7 @@ exports.authenticate = async function (ctx, email, password, done) {
|
||||||
if (await compare(password, dbUser.password)) {
|
if (await compare(password, dbUser.password)) {
|
||||||
const sessionId = newid()
|
const sessionId = newid()
|
||||||
const tenantId = getTenantId()
|
const tenantId = getTenantId()
|
||||||
|
|
||||||
await createASession(dbUser._id, { sessionId, tenantId })
|
await createASession(dbUser._id, { sessionId, tenantId })
|
||||||
|
|
||||||
dbUser.token = jwt.sign(
|
dbUser.token = jwt.sign(
|
||||||
|
|
|
@ -1,6 +1,8 @@
|
||||||
const fetch = require("node-fetch")
|
const fetch = require("node-fetch")
|
||||||
const OIDCStrategy = require("@techpass/passport-openidconnect").Strategy
|
const OIDCStrategy = require("@techpass/passport-openidconnect").Strategy
|
||||||
const { authenticateThirdParty } = require("./third-party-common")
|
const { authenticateThirdParty } = require("./third-party-common")
|
||||||
|
const { ssoCallbackUrl } = require("./utils")
|
||||||
|
const { Configs } = require("../../../constants")
|
||||||
|
|
||||||
const buildVerifyFn = saveUserFn => {
|
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.
|
* from couchDB rather than environment variables, using this factory is necessary for dynamically configuring passport.
|
||||||
* @returns Dynamically configured Passport OIDC Strategy
|
* @returns Dynamically configured Passport OIDC Strategy
|
||||||
*/
|
*/
|
||||||
exports.strategyFactory = async function (config, callbackUrl, saveUserFn) {
|
exports.strategyFactory = async function (config, saveUserFn) {
|
||||||
try {
|
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) {
|
if (!clientID || !clientSecret || !callbackUrl || !configUrl) {
|
||||||
|
//check for remote config and all required elements
|
||||||
throw new Error(
|
throw new Error(
|
||||||
"Configuration invalid. Must contain clientID, clientSecret, callbackUrl and configUrl"
|
"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 body = await response.json()
|
||||||
|
|
||||||
const verify = buildVerifyFn(saveUserFn)
|
return {
|
||||||
return new OIDCStrategy(
|
issuer: body.issuer,
|
||||||
{
|
authorizationURL: body.authorization_endpoint,
|
||||||
issuer: body.issuer,
|
tokenURL: body.token_endpoint,
|
||||||
authorizationURL: body.authorization_endpoint,
|
userInfoURL: body.userinfo_endpoint,
|
||||||
tokenURL: body.token_endpoint,
|
clientID: clientID,
|
||||||
userInfoURL: body.userinfo_endpoint,
|
clientSecret: clientSecret,
|
||||||
clientID: clientID,
|
callbackURL: callbackUrl,
|
||||||
clientSecret: clientSecret,
|
}
|
||||||
callbackURL: callbackUrl,
|
|
||||||
},
|
|
||||||
verify
|
|
||||||
)
|
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error(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
|
// expose for testing
|
||||||
exports.buildVerifyFn = buildVerifyFn
|
exports.buildVerifyFn = buildVerifyFn
|
||||||
|
|
|
@ -48,8 +48,8 @@ describe("oidc", () => {
|
||||||
|
|
||||||
it("should create successfully create an oidc strategy", async () => {
|
it("should create successfully create an oidc strategy", async () => {
|
||||||
const oidc = require("../oidc")
|
const oidc = require("../oidc")
|
||||||
|
const enrichedConfig = await oidc.fetchStrategyConfig(oidcConfig, callbackUrl)
|
||||||
await oidc.strategyFactory(oidcConfig, callbackUrl)
|
await oidc.strategyFactory(enrichedConfig, callbackUrl)
|
||||||
|
|
||||||
expect(mockFetch).toHaveBeenCalledWith(oidcConfig.configUrl)
|
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.
|
* Utility to handle authentication errors.
|
||||||
*
|
*
|
||||||
|
@ -5,6 +9,7 @@
|
||||||
* @param {*} message Message that will be returned in the response body
|
* @param {*} message Message that will be returned in the response body
|
||||||
* @param {*} err (Optional) error that will be logged
|
* @param {*} err (Optional) error that will be logged
|
||||||
*/
|
*/
|
||||||
|
|
||||||
exports.authError = function (done, message, err = null) {
|
exports.authError = function (done, message, err = null) {
|
||||||
return done(
|
return done(
|
||||||
err,
|
err,
|
||||||
|
@ -12,3 +17,21 @@ exports.authError = function (done, message, err = null) {
|
||||||
{ message: message }
|
{ 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}`
|
||||||
|
}
|
||||||
|
|
|
@ -294,6 +294,16 @@ export const uploadDirectory = async (
|
||||||
await Promise.all(uploads)
|
await Promise.all(uploads)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
exports.downloadTarballDirect = async (url: string, path: string) => {
|
||||||
|
path = sanitizeKey(path)
|
||||||
|
const response = await fetch(url)
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new Error(`unexpected response ${response.statusText}`)
|
||||||
|
}
|
||||||
|
|
||||||
|
await streamPipeline(response.body, zlib.Unzip(), tar.extract(path))
|
||||||
|
}
|
||||||
|
|
||||||
export const downloadTarball = async (url: any, bucketName: any, path: any) => {
|
export const downloadTarball = async (url: any, bucketName: any, path: any) => {
|
||||||
bucketName = sanitizeBucket(bucketName)
|
bucketName = sanitizeBucket(bucketName)
|
||||||
path = sanitizeKey(path)
|
path = sanitizeKey(path)
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
const { ViewNames } = require("./db/utils")
|
const { ViewNames } = require("./db/utils")
|
||||||
const { queryGlobalView } = require("./db/views")
|
const { queryGlobalView } = require("./db/views")
|
||||||
|
const { UNICODE_MAX } = require("./db/constants")
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Given an email address this will use a view to search through
|
* Given an email address this will use a view to search through
|
||||||
|
@ -19,3 +20,24 @@ exports.getGlobalUserByEmail = async email => {
|
||||||
|
|
||||||
return response
|
return response
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Performs a starts with search on the global email view.
|
||||||
|
*/
|
||||||
|
exports.searchGlobalUsersByEmail = async (email, opts) => {
|
||||||
|
if (typeof email !== "string") {
|
||||||
|
throw new Error("Must provide a string to search by")
|
||||||
|
}
|
||||||
|
const lcEmail = email.toLowerCase()
|
||||||
|
// handle if passing up startkey for pagination
|
||||||
|
const startkey = opts && opts.startkey ? opts.startkey : lcEmail
|
||||||
|
let response = await queryGlobalView(ViewNames.USER_BY_EMAIL, {
|
||||||
|
...opts,
|
||||||
|
startkey,
|
||||||
|
endkey: `${lcEmail}${UNICODE_MAX}`,
|
||||||
|
})
|
||||||
|
if (!response) {
|
||||||
|
response = []
|
||||||
|
}
|
||||||
|
return Array.isArray(response) ? response : [response]
|
||||||
|
}
|
||||||
|
|
|
@ -4123,6 +4123,11 @@ passport-oauth1@1.x.x:
|
||||||
passport-strategy "1.x.x"
|
passport-strategy "1.x.x"
|
||||||
utils-merge "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:
|
passport-oauth2@1.x.x:
|
||||||
version "1.6.1"
|
version "1.6.1"
|
||||||
resolved "https://registry.yarnpkg.com/passport-oauth2/-/passport-oauth2-1.6.1.tgz#c5aee8f849ce8bd436c7f81d904a3cd1666f181b"
|
resolved "https://registry.yarnpkg.com/passport-oauth2/-/passport-oauth2-1.6.1.tgz#c5aee8f849ce8bd436c7f81d904a3cd1666f181b"
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
{
|
{
|
||||||
"name": "@budibase/bbui",
|
"name": "@budibase/bbui",
|
||||||
"description": "A UI solution used in the different Budibase projects.",
|
"description": "A UI solution used in the different Budibase projects.",
|
||||||
"version": "1.0.212-alpha.0",
|
"version": "1.0.219-alpha.0",
|
||||||
"license": "MPL-2.0",
|
"license": "MPL-2.0",
|
||||||
"svelte": "src/index.js",
|
"svelte": "src/index.js",
|
||||||
"module": "dist/bbui.es.js",
|
"module": "dist/bbui.es.js",
|
||||||
|
@ -38,7 +38,7 @@
|
||||||
],
|
],
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@adobe/spectrum-css-workflow-icons": "^1.2.1",
|
"@adobe/spectrum-css-workflow-icons": "^1.2.1",
|
||||||
"@budibase/string-templates": "^1.0.212-alpha.0",
|
"@budibase/string-templates": "^1.0.219-alpha.0",
|
||||||
"@spectrum-css/actionbutton": "^1.0.1",
|
"@spectrum-css/actionbutton": "^1.0.1",
|
||||||
"@spectrum-css/actiongroup": "^1.0.1",
|
"@spectrum-css/actiongroup": "^1.0.1",
|
||||||
"@spectrum-css/avatar": "^3.0.2",
|
"@spectrum-css/avatar": "^3.0.2",
|
||||||
|
|
|
@ -13,6 +13,7 @@
|
||||||
export let size = "M"
|
export let size = "M"
|
||||||
export let active = false
|
export let active = false
|
||||||
export let fullWidth = false
|
export let fullWidth = false
|
||||||
|
export let noPadding = false
|
||||||
|
|
||||||
function longPress(element) {
|
function longPress(element) {
|
||||||
if (!longPressable) return
|
if (!longPressable) return
|
||||||
|
@ -41,6 +42,7 @@
|
||||||
class:spectrum-ActionButton--quiet={quiet}
|
class:spectrum-ActionButton--quiet={quiet}
|
||||||
class:spectrum-ActionButton--emphasized={emphasized}
|
class:spectrum-ActionButton--emphasized={emphasized}
|
||||||
class:is-selected={selected}
|
class:is-selected={selected}
|
||||||
|
class:noPadding
|
||||||
class:fullWidth
|
class:fullWidth
|
||||||
class="spectrum-ActionButton spectrum-ActionButton--size{size}"
|
class="spectrum-ActionButton spectrum-ActionButton--size{size}"
|
||||||
class:active
|
class:active
|
||||||
|
@ -80,4 +82,8 @@
|
||||||
.active svg {
|
.active svg {
|
||||||
color: var(--spectrum-global-color-blue-600);
|
color: var(--spectrum-global-color-blue-600);
|
||||||
}
|
}
|
||||||
|
.noPadding {
|
||||||
|
padding: 0;
|
||||||
|
min-width: 0;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
|
@ -40,5 +40,6 @@
|
||||||
on:change={onChange}
|
on:change={onChange}
|
||||||
on:pick
|
on:pick
|
||||||
on:type
|
on:type
|
||||||
|
on:blur
|
||||||
/>
|
/>
|
||||||
</Field>
|
</Field>
|
||||||
|
|
|
@ -52,7 +52,10 @@
|
||||||
{id}
|
{id}
|
||||||
type="text"
|
type="text"
|
||||||
on:focus={() => (focus = true)}
|
on:focus={() => (focus = true)}
|
||||||
on:blur={() => (focus = false)}
|
on:blur={() => {
|
||||||
|
focus = false
|
||||||
|
dispatch("blur")
|
||||||
|
}}
|
||||||
on:change={onType}
|
on:change={onType}
|
||||||
value={value || ""}
|
value={value || ""}
|
||||||
placeholder={placeholder || ""}
|
placeholder={placeholder || ""}
|
||||||
|
|
|
@ -1,15 +1,20 @@
|
||||||
<script>
|
<script>
|
||||||
|
import { ActionButton } from "../"
|
||||||
|
|
||||||
import { createEventDispatcher } from "svelte"
|
import { createEventDispatcher } from "svelte"
|
||||||
|
|
||||||
export let type = "info"
|
export let type = "info"
|
||||||
export let icon = "Info"
|
export let icon = "Info"
|
||||||
export let message = ""
|
export let message = ""
|
||||||
export let dismissable = false
|
export let dismissable = false
|
||||||
|
export let actionMessage = null
|
||||||
|
export let action = null
|
||||||
|
export let wide = false
|
||||||
|
|
||||||
const dispatch = createEventDispatcher()
|
const dispatch = createEventDispatcher()
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="spectrum-Toast spectrum-Toast--{type}">
|
<div class="spectrum-Toast spectrum-Toast--{type}" class:wide>
|
||||||
{#if icon}
|
{#if icon}
|
||||||
<svg
|
<svg
|
||||||
class="spectrum-Icon spectrum-Icon--sizeM spectrum-Toast-typeIcon"
|
class="spectrum-Icon spectrum-Icon--sizeM spectrum-Toast-typeIcon"
|
||||||
|
@ -19,8 +24,13 @@
|
||||||
<use xlink:href="#spectrum-icon-18-{icon}" />
|
<use xlink:href="#spectrum-icon-18-{icon}" />
|
||||||
</svg>
|
</svg>
|
||||||
{/if}
|
{/if}
|
||||||
<div class="spectrum-Toast-body">
|
<div class="spectrum-Toast-body" class:actionBody={!!action}>
|
||||||
<div class="spectrum-Toast-content">{message || ""}</div>
|
<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>
|
</div>
|
||||||
{#if dismissable}
|
{#if dismissable}
|
||||||
<div class="spectrum-Toast-buttons">
|
<div class="spectrum-Toast-buttons">
|
||||||
|
@ -46,4 +56,15 @@
|
||||||
.spectrum-Toast {
|
.spectrum-Toast {
|
||||||
pointer-events: all;
|
pointer-events: all;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.wide {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.actionBody {
|
||||||
|
justify-content: space-between;
|
||||||
|
display: flex;
|
||||||
|
width: 100%;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
|
@ -8,13 +8,15 @@
|
||||||
|
|
||||||
<Portal target=".modal-container">
|
<Portal target=".modal-container">
|
||||||
<div class="notifications">
|
<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 }}>
|
<div transition:fly={{ y: -30 }}>
|
||||||
<Notification
|
<Notification
|
||||||
{type}
|
{type}
|
||||||
{icon}
|
{icon}
|
||||||
{message}
|
{message}
|
||||||
{dismissable}
|
{dismissable}
|
||||||
|
{action}
|
||||||
|
{wide}
|
||||||
on:dismiss={() => notifications.dismiss(id)}
|
on:dismiss={() => notifications.dismiss(id)}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -20,7 +20,16 @@ export const createNotificationStore = () => {
|
||||||
setTimeout(() => (block = false), timeout)
|
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) {
|
if (block) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -28,7 +37,15 @@ export const createNotificationStore = () => {
|
||||||
_notifications.update(state => {
|
_notifications.update(state => {
|
||||||
return [
|
return [
|
||||||
...state,
|
...state,
|
||||||
{ id: _id, type, message, icon, dismissable: !autoDismiss },
|
{
|
||||||
|
id: _id,
|
||||||
|
type,
|
||||||
|
message,
|
||||||
|
icon,
|
||||||
|
dismissable: !autoDismiss,
|
||||||
|
action,
|
||||||
|
wide,
|
||||||
|
},
|
||||||
]
|
]
|
||||||
})
|
})
|
||||||
if (autoDismiss) {
|
if (autoDismiss) {
|
||||||
|
@ -50,10 +67,11 @@ export const createNotificationStore = () => {
|
||||||
return {
|
return {
|
||||||
subscribe,
|
subscribe,
|
||||||
send,
|
send,
|
||||||
info: msg => send(msg, "info", "Info"),
|
info: msg => send(msg, { type: "info", icon: "Info" }),
|
||||||
error: msg => send(msg, "error", "Alert", false),
|
error: msg =>
|
||||||
warning: msg => send(msg, "warning", "Alert"),
|
send(msg, { type: "error", icon: "Alert", autoDismiss: false }),
|
||||||
success: msg => send(msg, "success", "CheckmarkCircle"),
|
warning: msg => send(msg, { type: "warning", icon: "Alert" }),
|
||||||
|
success: msg => send(msg, { type: "success", icon: "CheckmarkCircle" }),
|
||||||
blockNotifications,
|
blockNotifications,
|
||||||
dismiss: dismissNotification,
|
dismiss: dismissNotification,
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,7 +5,7 @@
|
||||||
|
|
||||||
const displayLimit = 5
|
const displayLimit = 5
|
||||||
|
|
||||||
$: badges = value?.slice(0, displayLimit) ?? []
|
$: badges = Array.isArray(value) ? value.slice(0, displayLimit) : []
|
||||||
$: leftover = (value?.length ?? 0) - badges.length
|
$: leftover = (value?.length ?? 0) - badges.length
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|
|
@ -26,12 +26,20 @@
|
||||||
array: ArrayRenderer,
|
array: ArrayRenderer,
|
||||||
internal: InternalRenderer,
|
internal: InternalRenderer,
|
||||||
}
|
}
|
||||||
$: type = schema?.type ?? "string"
|
$: type = getType(schema)
|
||||||
$: customRenderer = customRenderers?.find(x => x.column === schema?.name)
|
$: customRenderer = customRenderers?.find(x => x.column === schema?.name)
|
||||||
$: renderer = customRenderer?.component ?? typeMap[type] ?? StringRenderer
|
$: renderer = customRenderer?.component ?? typeMap[type] ?? StringRenderer
|
||||||
$: width = schema?.width || "150px"
|
$: width = schema?.width || "150px"
|
||||||
$: cellValue = getCellValue(value, schema.template)
|
$: 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) => {
|
const getCellValue = (value, template) => {
|
||||||
if (!template) {
|
if (!template) {
|
||||||
return value
|
return value
|
||||||
|
|
|
@ -37,6 +37,7 @@
|
||||||
export let autoSortColumns = true
|
export let autoSortColumns = true
|
||||||
export let compact = false
|
export let compact = false
|
||||||
export let customPlaceholder = false
|
export let customPlaceholder = false
|
||||||
|
export let placeholderText = "No rows found"
|
||||||
|
|
||||||
const dispatch = createEventDispatcher()
|
const dispatch = createEventDispatcher()
|
||||||
|
|
||||||
|
@ -405,7 +406,7 @@
|
||||||
>
|
>
|
||||||
<use xlink:href="#spectrum-icon-18-Table" />
|
<use xlink:href="#spectrum-icon-18-Table" />
|
||||||
</svg>
|
</svg>
|
||||||
<div>No rows found</div>
|
<div>{placeholderText}</div>
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -13,7 +13,7 @@
|
||||||
"HOST_IP": ""
|
"HOST_IP": ""
|
||||||
},
|
},
|
||||||
"retries": {
|
"retries": {
|
||||||
"runMode": 2,
|
"runMode": 1,
|
||||||
"openMode": 0
|
"openMode": 0
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,18 +16,15 @@ filterTests(['all'], () => {
|
||||||
|
|
||||||
it("should add form with multi select picker, containing 5 options", () => {
|
it("should add form with multi select picker, containing 5 options", () => {
|
||||||
cy.navigateToFrontend()
|
cy.navigateToFrontend()
|
||||||
cy.wait(500)
|
|
||||||
// Add data provider
|
// Add data provider
|
||||||
cy.get(interact.CATEGORY_DATA).click()
|
cy.get(interact.CATEGORY_DATA, { timeout: 500 }).click()
|
||||||
cy.get(interact.COMPONENT_DATA_PROVIDER).click()
|
cy.get(interact.COMPONENT_DATA_PROVIDER).click()
|
||||||
cy.get(interact.DATASOURCE_PROP_CONTROL).click()
|
cy.get(interact.DATASOURCE_PROP_CONTROL).click()
|
||||||
cy.get(interact.DROPDOWN).contains("Multi Data").click()
|
cy.get(interact.DROPDOWN).contains("Multi Data").click()
|
||||||
cy.wait(500)
|
|
||||||
// Add Form with schema to match table
|
// Add Form with schema to match table
|
||||||
cy.addComponent("Form", "Form")
|
cy.addComponent("Form", "Form")
|
||||||
cy.get(interact.DATASOURCE_PROP_CONTROL).click()
|
cy.get(interact.DATASOURCE_PROP_CONTROL).click()
|
||||||
cy.get(interact.DROPDOWN).contains("Multi Data").click()
|
cy.get(interact.DROPDOWN).contains("Multi Data").click()
|
||||||
cy.wait(500)
|
|
||||||
// Add multi-select picker to form
|
// Add multi-select picker to form
|
||||||
cy.addComponent("Form", "Multi-select Picker").then(componentId => {
|
cy.addComponent("Form", "Multi-select Picker").then(componentId => {
|
||||||
cy.get(interact.DATASOURCE_FIELD_CONTROL).type("Test Data").type("{enter}")
|
cy.get(interact.DATASOURCE_FIELD_CONTROL).type("Test Data").type("{enter}")
|
||||||
|
|
|
@ -0,0 +1,131 @@
|
||||||
|
import filterTests from "../../support/filterTests"
|
||||||
|
const interact = require('../../support/interact')
|
||||||
|
|
||||||
|
filterTests(["smoke", "all"], () => {
|
||||||
|
context("Account Portals", () => {
|
||||||
|
|
||||||
|
const bbUserEmail = "bbuser@test.com"
|
||||||
|
|
||||||
|
before(() => {
|
||||||
|
cy.login()
|
||||||
|
cy.deleteApp("Cypress Tests")
|
||||||
|
cy.createApp("Cypress Tests")
|
||||||
|
|
||||||
|
// Create new user
|
||||||
|
cy.visit(`${Cypress.config().baseUrl}/builder`, { timeout: 1000})
|
||||||
|
cy.createUser(bbUserEmail)
|
||||||
|
cy.contains("bbuser").click()
|
||||||
|
cy.wait(500)
|
||||||
|
|
||||||
|
// Reset password
|
||||||
|
cy.get(".spectrum-ActionButton-label", { timeout: 2000 }).contains("Force password reset").click({ force: true })
|
||||||
|
|
||||||
|
cy.get(".spectrum-Dialog-grid")
|
||||||
|
.find(interact.SPECTRUM_TEXTFIELD_INPUT).invoke('val').as('pwd')
|
||||||
|
|
||||||
|
cy.get(interact.SPECTRUM_BUTTON).contains("Reset password").click({ force: true })
|
||||||
|
|
||||||
|
// Login as new user and set password
|
||||||
|
cy.logOut()
|
||||||
|
cy.get('@pwd').then((pwd) => {
|
||||||
|
cy.login(bbUserEmail, pwd)
|
||||||
|
})
|
||||||
|
|
||||||
|
for (let i = 0; i < 2; i++) {
|
||||||
|
cy.get(interact.SPECTRUM_TEXTFIELD_INPUT).eq(i).type("test")
|
||||||
|
}
|
||||||
|
cy.get(interact.SPECTRUM_BUTTON).contains("Reset your password").click({ force: true })
|
||||||
|
cy.logoutNoAppGrid()
|
||||||
|
})
|
||||||
|
|
||||||
|
it("should verify Admin Portal", () => {
|
||||||
|
cy.login()
|
||||||
|
cy.contains("Users").click()
|
||||||
|
cy.contains("bbuser").click()
|
||||||
|
|
||||||
|
// Enable Development & Administration access
|
||||||
|
cy.wait(500)
|
||||||
|
for (let i = 4; i < 6; i++) {
|
||||||
|
cy.get(interact.FIELD).eq(i).within(() => {
|
||||||
|
cy.get(interact.SPECTRUM_SWITCH_INPUT).click({ force: true })
|
||||||
|
cy.get(interact.SPECTRUM_SWITCH_INPUT).should('be.enabled')
|
||||||
|
})
|
||||||
|
}
|
||||||
|
bbUserLogin()
|
||||||
|
|
||||||
|
// Verify available options for Admin portal
|
||||||
|
cy.get(".spectrum-SideNav")
|
||||||
|
.should('contain', 'Apps')
|
||||||
|
//.and('contain', 'Usage')
|
||||||
|
.and('contain', 'Users')
|
||||||
|
.and('contain', 'Auth')
|
||||||
|
.and('contain', 'Email')
|
||||||
|
.and('contain', 'Organisation')
|
||||||
|
.and('contain', 'Theming')
|
||||||
|
.and('contain', 'Update')
|
||||||
|
//.and('contain', 'Upgrade')
|
||||||
|
|
||||||
|
cy.logOut()
|
||||||
|
})
|
||||||
|
|
||||||
|
it("should verify Development Portal", () => {
|
||||||
|
// Only Development access should be enabled
|
||||||
|
cy.login()
|
||||||
|
cy.contains("Users").click()
|
||||||
|
cy.contains("bbuser").click()
|
||||||
|
cy.wait(500)
|
||||||
|
cy.get(interact.FIELD).eq(5).within(() => {
|
||||||
|
cy.get(interact.SPECTRUM_SWITCH_INPUT).click({ force: true })
|
||||||
|
})
|
||||||
|
|
||||||
|
bbUserLogin()
|
||||||
|
|
||||||
|
// Verify available options for Admin portal
|
||||||
|
cy.get(interact.SPECTRUM_SIDENAV)
|
||||||
|
.should('contain', 'Apps')
|
||||||
|
//.and('contain', 'Usage')
|
||||||
|
.and('not.contain', 'Users')
|
||||||
|
.and('not.contain', 'Auth')
|
||||||
|
.and('not.contain', 'Email')
|
||||||
|
.and('not.contain', 'Organisation')
|
||||||
|
.and('contain', 'Theming')
|
||||||
|
.and('not.contain', 'Update')
|
||||||
|
.and('not.contain', 'Upgrade')
|
||||||
|
|
||||||
|
cy.logOut()
|
||||||
|
})
|
||||||
|
|
||||||
|
it("should verify Standard Portal", () => {
|
||||||
|
// Development access should be disabled (Admin access is already disabled)
|
||||||
|
cy.login()
|
||||||
|
cy.contains("Users").click()
|
||||||
|
cy.contains("bbuser").click()
|
||||||
|
cy.wait(500)
|
||||||
|
cy.get(interact.FIELD).eq(4).within(() => {
|
||||||
|
cy.get(interact.SPECTRUM_SWITCH_INPUT).click({ force: true })
|
||||||
|
})
|
||||||
|
|
||||||
|
bbUserLogin()
|
||||||
|
|
||||||
|
// Verify Standard Portal
|
||||||
|
cy.get(interact.SPECTRUM_SIDENAV).should('not.exist') // No config sections
|
||||||
|
cy.get(interact.CREATE_APP_BUTTON).should('not.exist') // No create app button
|
||||||
|
cy.get(".app").should('not.exist') // No apps -> no roles assigned to user
|
||||||
|
cy.get(interact.CONTAINER).should('contain', bbUserEmail) // Message containing users email
|
||||||
|
|
||||||
|
cy.logoutNoAppGrid()
|
||||||
|
})
|
||||||
|
|
||||||
|
const bbUserLogin = () => {
|
||||||
|
// Login as bbuser
|
||||||
|
cy.logOut()
|
||||||
|
cy.login(bbUserEmail, "test")
|
||||||
|
}
|
||||||
|
|
||||||
|
after(() => {
|
||||||
|
cy.login()
|
||||||
|
// Delete BB user
|
||||||
|
cy.deleteUser(bbUserEmail)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
|
@ -1,28 +1,34 @@
|
||||||
import filterTests from "../support/filterTests"
|
import filterTests from "../../support/filterTests"
|
||||||
const interact = require('../support/interact')
|
const interact = require('../../support/interact')
|
||||||
|
|
||||||
filterTests(["smoke", "all"], () => {
|
filterTests(["smoke", "all"], () => {
|
||||||
context("Create a User and Assign Roles", () => {
|
context("User Management", () => {
|
||||||
before(() => {
|
before(() => {
|
||||||
cy.login()
|
cy.login()
|
||||||
cy.deleteApp("Cypress Tests")
|
cy.deleteApp("Cypress Tests")
|
||||||
cy.createApp("Cypress Tests")
|
cy.createApp("Cypress Tests")
|
||||||
})
|
})
|
||||||
|
|
||||||
it("should create a user", () => {
|
it("should create a user via basic onboarding", () => {
|
||||||
cy.visit(`${Cypress.config().baseUrl}/builder`)
|
cy.visit(`${Cypress.config().baseUrl}/builder`, { timeout: 1000})
|
||||||
cy.wait(1000)
|
|
||||||
cy.createUser("bbuser@test.com")
|
cy.createUser("bbuser@test.com")
|
||||||
cy.get(interact.SPECTRUM_TABLE).should("contain", "bbuser")
|
cy.get(interact.SPECTRUM_TABLE).should("contain", "bbuser")
|
||||||
})
|
})
|
||||||
|
|
||||||
it("should confirm there is No Access for a New User", () => {
|
it("should confirm basic permission for a New User", () => {
|
||||||
// Click into the user
|
// Basic permission = development & administraton disabled
|
||||||
cy.contains("bbuser").click()
|
cy.contains("bbuser").click()
|
||||||
cy.wait(500)
|
// Confirm development and admin access are disabled
|
||||||
// Get No Access table - Confirm it has apps in it
|
for (let i = 4; i < 6; i++) {
|
||||||
cy.get(interact.SPECTRUM_TABLE).eq(1).should("not.contain", "No rows found")
|
cy.wait(500)
|
||||||
// Get Configure Roles table - Confirm it has no apps
|
cy.get(interact.FIELD).eq(i).within(() => {
|
||||||
|
//cy.get(interact.SPECTRUM_SWITCH_INPUT).should('be.disabled')
|
||||||
|
cy.get(".spectrum-Switch-switch").should('not.be.checked')
|
||||||
|
})
|
||||||
|
}
|
||||||
|
// Existing apps appear within the No Access table
|
||||||
|
cy.get(interact.SPECTRUM_TABLE, { timeout: 500 }).eq(1).should("not.contain", "No rows found")
|
||||||
|
// Configure roles table should not contain apps
|
||||||
cy.get(interact.SPECTRUM_TABLE).eq(0).contains("No rows found")
|
cy.get(interact.SPECTRUM_TABLE).eq(0).contains("No rows found")
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@ -40,20 +46,16 @@ filterTests(["smoke", "all"], () => {
|
||||||
cy.createApp(name)
|
cy.createApp(name)
|
||||||
} else {
|
} else {
|
||||||
cy.visit(`${Cypress.config().baseUrl}/builder`)
|
cy.visit(`${Cypress.config().baseUrl}/builder`)
|
||||||
cy.wait(500)
|
cy.get(interact.CREATE_APP_BUTTON, { timeout: 1000 }).click({ force: true })
|
||||||
cy.get(interact.CREATE_APP_BUTTON).click({ force: true })
|
|
||||||
cy.createAppFromScratch(name)
|
cy.createAppFromScratch(name)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
// Navigate back to the user
|
// Navigate back to the user
|
||||||
cy.visit(`${Cypress.config().baseUrl}/builder`)
|
cy.visit(`${Cypress.config().baseUrl}/builder`, { timeout: 500})
|
||||||
cy.wait(500)
|
|
||||||
cy.get(interact.SPECTRUM_SIDENAV).contains("Users").click()
|
cy.get(interact.SPECTRUM_SIDENAV).contains("Users").click()
|
||||||
cy.wait(500)
|
cy.get(interact.SPECTRUM_TABLE, { timeout: 500 }).contains("bbuser").click()
|
||||||
cy.get(interact.SPECTRUM_TABLE).contains("bbuser").click()
|
|
||||||
cy.wait(1000)
|
|
||||||
for (let i = 0; i < 3; i++) {
|
for (let i = 0; i < 3; i++) {
|
||||||
cy.get(interact.SPECTRUM_TABLE, { timeout: 3000})
|
cy.get(interact.SPECTRUM_TABLE, { timeout: 3000})
|
||||||
.eq(1)
|
.eq(1)
|
||||||
|
@ -62,31 +64,28 @@ filterTests(["smoke", "all"], () => {
|
||||||
.find(interact.SPECTRUM_TABLE_CELL)
|
.find(interact.SPECTRUM_TABLE_CELL)
|
||||||
.eq(0)
|
.eq(0)
|
||||||
.click()
|
.click()
|
||||||
cy.wait(500)
|
cy.get(interact.SPECTRUM_DIALOG_GRID, { timeout: 500 })
|
||||||
cy.get(interact.SPECTRUM_DIALOG_GRID)
|
|
||||||
.contains("Choose an option")
|
.contains("Choose an option")
|
||||||
.click()
|
.click()
|
||||||
.then(() => {
|
.then(() => {
|
||||||
cy.wait(1000)
|
|
||||||
if (i == 0) {
|
if (i == 0) {
|
||||||
cy.get(interact.SPECTRUM_MENU).contains("Admin").click({ force: true })
|
cy.get(interact.SPECTRUM_MENU, { timeout: 1000 }).contains("Admin").click({ force: true })
|
||||||
}
|
}
|
||||||
else if (i == 1) {
|
else if (i == 1) {
|
||||||
cy.get(interact.SPECTRUM_MENU).contains("Power").click({ force: true })
|
cy.get(interact.SPECTRUM_MENU, { timeout: 1000 }).contains("Power").click({ force: true })
|
||||||
}
|
}
|
||||||
else if (i == 2) {
|
else if (i == 2) {
|
||||||
cy.get(interact.SPECTRUM_MENU).contains("Basic").click({ force: true })
|
cy.get(interact.SPECTRUM_MENU, { timeout: 1000 }).contains("Basic").click({ force: true })
|
||||||
}
|
}
|
||||||
cy.wait(1000)
|
cy.get(interact.SPECTRUM_BUTTON, { timeout: 1000 })
|
||||||
cy.get(interact.SPECTRUM_BUTTON)
|
|
||||||
.contains("Update role")
|
.contains("Update role")
|
||||||
.click({ force: true })
|
.click({ force: true })
|
||||||
})
|
})
|
||||||
cy.reload()
|
cy.reload()
|
||||||
|
cy.wait(1000)
|
||||||
}
|
}
|
||||||
// Confirm roles exist within Configure roles table
|
// Confirm roles exist within Configure roles table
|
||||||
cy.wait(2000)
|
cy.get(interact.SPECTRUM_TABLE, { timeout: 2000 })
|
||||||
cy.get(interact.SPECTRUM_TABLE)
|
|
||||||
.eq(0)
|
.eq(0)
|
||||||
.within(assginedRoles => {
|
.within(assginedRoles => {
|
||||||
expect(assginedRoles).to.contain("Admin")
|
expect(assginedRoles).to.contain("Admin")
|
||||||
|
@ -112,21 +111,19 @@ filterTests(["smoke", "all"], () => {
|
||||||
.click()
|
.click()
|
||||||
.then(() => {
|
.then(() => {
|
||||||
cy.get(interact.SPECTRUM_PICKER).eq(1).click({ force: true })
|
cy.get(interact.SPECTRUM_PICKER).eq(1).click({ force: true })
|
||||||
cy.wait(500)
|
cy.get(interact.SPECTRUM_POPOVER, { timeout: 500 }).contains("No Access").click()
|
||||||
cy.get(interact.SPECTRUM_POPOVER).contains("No Access").click()
|
|
||||||
})
|
})
|
||||||
cy.get(interact.SPECTRUM_BUTTON)
|
cy.get(interact.SPECTRUM_BUTTON)
|
||||||
.contains("Update role")
|
.contains("Update role")
|
||||||
.click({ force: true })
|
.click({ force: true })
|
||||||
cy.wait(1000)
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
// Confirm Configure roles table no longer has any apps in it
|
// Confirm Configure roles table no longer has any apps in it
|
||||||
cy.get(interact.SPECTRUM_TABLE).eq(0).contains("No rows found")
|
cy.get(interact.SPECTRUM_TABLE, { timeout: 1000 }).eq(0).contains("No rows found")
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
it("should enable Developer access", () => {
|
it("should enable Developer access and verify application access", () => {
|
||||||
// Enable Developer access
|
// Enable Developer access
|
||||||
cy.get(interact.FIELD)
|
cy.get(interact.FIELD)
|
||||||
.eq(4)
|
.eq(4)
|
||||||
|
@ -158,15 +155,15 @@ filterTests(["smoke", "all"], () => {
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
it("should disable Developer access", () => {
|
it("should disable Developer access and verify application access", () => {
|
||||||
// Disable Developer access
|
// Disable Developer access
|
||||||
cy.get(".field")
|
cy.get(interact.FIELD)
|
||||||
.eq(4)
|
.eq(4)
|
||||||
.within(() => {
|
.within(() => {
|
||||||
cy.get(".spectrum-Switch-input").click({ force: true })
|
cy.get(".spectrum-Switch-input").click({ force: true })
|
||||||
})
|
})
|
||||||
// Configure roles table should now be empty
|
// Configure roles table should now be empty
|
||||||
cy.get(".container")
|
cy.get(interact.CONTAINER)
|
||||||
.contains("Configure roles")
|
.contains("Configure roles")
|
||||||
.parent()
|
.parent()
|
||||||
.within(() => {
|
.within(() => {
|
||||||
|
@ -174,22 +171,64 @@ 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")
|
||||||
|
})
|
||||||
|
// Add Last name
|
||||||
|
cy.get(interact.FIELD).eq(3).within(() => {
|
||||||
|
cy.get(interact.SPECTRUM_TEXTFIELD_INPUT).type("test")
|
||||||
|
})
|
||||||
|
cy.get(interact.FIELD).eq(0).click()
|
||||||
|
// Reload page
|
||||||
|
cy.reload()
|
||||||
|
|
||||||
|
// Confirm details have been saved
|
||||||
|
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")
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
it("should reset the users password", () => {
|
||||||
|
cy.get(interact.REGENERATE, { timeout: 500 }).contains("Force password reset").click({ force: true })
|
||||||
|
|
||||||
|
// Reset password modal
|
||||||
|
cy.get(interact.SPECTRUM_DIALOG_GRID)
|
||||||
|
.find(interact.SPECTRUM_TEXTFIELD_INPUT).invoke('val').as('pwd')
|
||||||
|
cy.get(interact.SPECTRUM_BUTTON).contains("Reset password").click({ force: true })
|
||||||
|
|
||||||
|
// Logout, then login with new password
|
||||||
|
cy.logOut()
|
||||||
|
cy.get('@pwd').then((pwd) => {
|
||||||
|
cy.login("bbuser@test.com", pwd)
|
||||||
|
})
|
||||||
|
|
||||||
|
// Reset password screen
|
||||||
|
for (let i = 0; i < 2; i++) {
|
||||||
|
cy.get(interact.SPECTRUM_TEXTFIELD_INPUT).eq(i).type("test")
|
||||||
|
}
|
||||||
|
cy.get(interact.SPECTRUM_BUTTON).contains("Reset your password").click({ force: true })
|
||||||
|
|
||||||
|
// Confirm user logged in afer password change
|
||||||
|
cy.get(".avatar > .icon").click({ force: true })
|
||||||
|
|
||||||
|
cy.get(".spectrum-Menu-item").contains("Update user information").click({ force: true })
|
||||||
|
cy.get(interact.SPECTRUM_TEXTFIELD_INPUT)
|
||||||
|
.eq(0)
|
||||||
|
.invoke('val').should('eq', 'bbuser@test.com')
|
||||||
|
|
||||||
|
// Logout and login as previous user
|
||||||
|
cy.logoutNoAppGrid()
|
||||||
|
cy.login()
|
||||||
|
})
|
||||||
|
|
||||||
it("should delete a user", () => {
|
it("should delete a user", () => {
|
||||||
// Click Delete user button
|
cy.deleteUser("bbuser@test.com")
|
||||||
cy.get(interact.SPECTRUM_BUTTON)
|
cy.get(interact.SPECTRUM_TABLE, { timeout: 4000 }).should("not.have.text", "bbuser")
|
||||||
.contains("Delete user")
|
|
||||||
.click({ force: true })
|
|
||||||
.then(() => {
|
|
||||||
// Confirm deletion within modal
|
|
||||||
cy.wait(500)
|
|
||||||
cy.get(interact.SPECTRUM_DIALOG_GRID).within(() => {
|
|
||||||
cy.get(interact.SPECTRUM_BUTTON)
|
|
||||||
.contains("Delete user")
|
|
||||||
.click({ force: true })
|
|
||||||
cy.wait(4000)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
cy.get(interact.SPECTRUM_TABLE).should("not.have.text", "bbuser")
|
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
})
|
})
|
|
@ -0,0 +1,108 @@
|
||||||
|
import filterTests from "../../support/filterTests"
|
||||||
|
const interact = require('../../support/interact')
|
||||||
|
|
||||||
|
filterTests(["smoke", "all"], () => {
|
||||||
|
context("User Settings Menu", () => {
|
||||||
|
|
||||||
|
before(() => {
|
||||||
|
cy.login()
|
||||||
|
})
|
||||||
|
|
||||||
|
it("should update user information via user settings menu", () => {
|
||||||
|
const fname = "test"
|
||||||
|
const lname = "user"
|
||||||
|
|
||||||
|
cy.visit(`${Cypress.config().baseUrl}/builder`)
|
||||||
|
cy.updateUserInformation(fname, lname)
|
||||||
|
|
||||||
|
// Go to user info and confirm name update
|
||||||
|
cy.contains("Users").click()
|
||||||
|
cy.contains("test@test.com").click()
|
||||||
|
|
||||||
|
cy.get(interact.FIELD, { timeout: 1000 }).eq(2).within(() => {
|
||||||
|
cy.get(interact.SPECTRUM_TEXTFIELD_INPUT).should('have.value', fname)
|
||||||
|
})
|
||||||
|
cy.get(interact.FIELD).eq(3).within(() => {
|
||||||
|
cy.get(interact.SPECTRUM_TEXTFIELD_INPUT).should('have.value', lname)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
it("should allow copying of the users API key", () => {
|
||||||
|
cy.get(".user-dropdown .avatar > .icon", { timeout: 2000 }).click({ force: true })
|
||||||
|
cy.get(interact.SPECTRUM_MENU_ITEM).contains("View API key").click({ force: true })
|
||||||
|
cy.get(interact.SPECTRUM_DIALOG_CONTENT).within(() => {
|
||||||
|
cy.get(interact.SPECTRUM_ICON).click({force: true})
|
||||||
|
})
|
||||||
|
// There may be timing issues with this on the smoke build
|
||||||
|
cy.wait(500)
|
||||||
|
cy.get(".spectrum-Toast-content")
|
||||||
|
.contains("URL copied to clipboard")
|
||||||
|
.should("be.visible")
|
||||||
|
})
|
||||||
|
|
||||||
|
it("should allow API key regeneration", () => {
|
||||||
|
// Get initial API key value
|
||||||
|
cy.get(interact.SPECTRUM_DIALOG_CONTENT)
|
||||||
|
.find(interact.SPECTRUM_TEXTFIELD_INPUT).invoke('val').as('keyOne')
|
||||||
|
|
||||||
|
// Click re-generate key button
|
||||||
|
cy.get("button").contains("Re-generate key").click({ force: true })
|
||||||
|
|
||||||
|
// Verify API key was changed
|
||||||
|
cy.get(interact.SPECTRUM_DIALOG_CONTENT).within(() => {
|
||||||
|
cy.get('@keyOne').then((keyOne) => {
|
||||||
|
cy.get(interact.SPECTRUM_TEXTFIELD_INPUT).invoke('val').should('not.eq', keyOne)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
cy.closeModal()
|
||||||
|
})
|
||||||
|
|
||||||
|
it("should update password", () => {
|
||||||
|
// Access Update password modal
|
||||||
|
cy.get(".user-dropdown .avatar > .icon", { timeout: 2000 }).click({ force: true })
|
||||||
|
cy.get(interact.SPECTRUM_MENU_ITEM).contains("Update password").click({ force: true })
|
||||||
|
|
||||||
|
// Enter new password and update
|
||||||
|
cy.get(interact.SPECTRUM_DIALOG_GRID).within(() => {
|
||||||
|
for (let i = 0; i < 2; i++) {
|
||||||
|
// password set to 'newpwd'
|
||||||
|
cy.get(interact.SPECTRUM_TEXTFIELD_INPUT).eq(i).type("newpwd")
|
||||||
|
}
|
||||||
|
cy.get("button").contains("Update password").click({ force: true })
|
||||||
|
})
|
||||||
|
|
||||||
|
// Logout & in with new password
|
||||||
|
cy.logOut()
|
||||||
|
cy.login("test@test.com", "newpwd")
|
||||||
|
})
|
||||||
|
|
||||||
|
it("should open and close developer mode", () => {
|
||||||
|
cy.get(".user-dropdown .avatar > .icon", { timeout: 2000 }).click({ force: true })
|
||||||
|
|
||||||
|
// Close developer mode & verify
|
||||||
|
cy.get(interact.SPECTRUM_MENU_ITEM).contains("Close developer mode").click({ force: true })
|
||||||
|
cy.get(interact.SPECTRUM_SIDENAV).should('not.exist') // No config sections
|
||||||
|
cy.get(interact.CREATE_APP_BUTTON).should('not.exist') // No create app button
|
||||||
|
cy.get(".app").should('not.exist') // At least one app should be available
|
||||||
|
|
||||||
|
// Open developer mode & verify
|
||||||
|
cy.get(".avatar > .icon").click({ force: true })
|
||||||
|
cy.get(interact.SPECTRUM_MENU_ITEM).contains("Open developer mode").click({ force: true })
|
||||||
|
cy.get(interact.SPECTRUM_SIDENAV).should('exist') // config sections available
|
||||||
|
cy.get(interact.CREATE_APP_BUTTON).should('exist') // create app button available
|
||||||
|
cy.get(interact.APP_TABLE).should('exist') // App table available
|
||||||
|
})
|
||||||
|
|
||||||
|
after(() => {
|
||||||
|
// Change password back to original value
|
||||||
|
cy.get(".user-dropdown .avatar > .icon", { timeout: 2000 }).click({ force: true })
|
||||||
|
cy.get(interact.SPECTRUM_MENU_ITEM).contains("Update password").click({ force: true })
|
||||||
|
cy.get(interact.SPECTRUM_DIALOG_GRID).within(() => {
|
||||||
|
for (let i = 0; i < 2; i++) {
|
||||||
|
cy.get(interact.SPECTRUM_TEXTFIELD_INPUT).eq(i).type("test")
|
||||||
|
}
|
||||||
|
cy.get("button").contains("Update password").click({ force: true })
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
|
@ -136,7 +136,6 @@ filterTests(["all"], () => {
|
||||||
.within(() => {
|
.within(() => {
|
||||||
cy.get(".confirm-wrap button").click({ force: true })
|
cy.get(".confirm-wrap button").click({ force: true })
|
||||||
})
|
})
|
||||||
cy.wait(1000)
|
|
||||||
|
|
||||||
cy.visit(`${Cypress.config().baseUrl}/builder`)
|
cy.visit(`${Cypress.config().baseUrl}/builder`)
|
||||||
cy.get(".appTable .app-row-actions button")
|
cy.get(".appTable .app-row-actions button")
|
||||||
|
@ -158,12 +157,9 @@ filterTests(["all"], () => {
|
||||||
.contains("Manage")
|
.contains("Manage")
|
||||||
.eq(0)
|
.eq(0)
|
||||||
.click({ force: true })
|
.click({ force: true })
|
||||||
cy.wait(1000)
|
cy.get(".edit-hover", { timeout: 1000 }).eq(0).click({ force: true })
|
||||||
cy.get(".app-overview-actions-icon").within(() => {
|
|
||||||
cy.get(".spectrum-Icon").click({ force: true })
|
|
||||||
})
|
|
||||||
cy.get(".spectrum-Menu").contains("Edit icon").click()
|
|
||||||
// Select random icon
|
// Select random icon
|
||||||
|
cy.wait(400)
|
||||||
cy.get(".grid").within(() => {
|
cy.get(".grid").within(() => {
|
||||||
cy.get(".icon-item")
|
cy.get(".icon-item")
|
||||||
.eq(Math.floor(Math.random() * 23) + 1)
|
.eq(Math.floor(Math.random() * 23) + 1)
|
||||||
|
@ -182,6 +178,7 @@ filterTests(["all"], () => {
|
||||||
cy.get("@iconChange").its("response.statusCode").should("eq", 200)
|
cy.get("@iconChange").its("response.statusCode").should("eq", 200)
|
||||||
// Confirm icon has changed from default
|
// Confirm icon has changed from default
|
||||||
// Confirm colour has been applied
|
// Confirm colour has been applied
|
||||||
|
cy.get(".spectrum-ActionButton-label").contains("Back").click({ force: true })
|
||||||
cy.get(".appTable", { timeout: 2000 }).within(() => {
|
cy.get(".appTable", { timeout: 2000 }).within(() => {
|
||||||
cy.get("[aria-label]")
|
cy.get("[aria-label]")
|
||||||
.eq(0)
|
.eq(0)
|
||||||
|
@ -265,10 +262,9 @@ filterTests(["all"], () => {
|
||||||
//Downgrade the app for the test
|
//Downgrade the app for the test
|
||||||
cy.alterAppVersion(appId, "0.0.1-alpha.0").then(() => {
|
cy.alterAppVersion(appId, "0.0.1-alpha.0").then(() => {
|
||||||
cy.reload()
|
cy.reload()
|
||||||
cy.wait(1000)
|
|
||||||
cy.log("Current deployment version: " + clientPackage.version)
|
cy.log("Current deployment version: " + clientPackage.version)
|
||||||
|
|
||||||
cy.get(".version-status a").contains("Update").click()
|
cy.get(".version-status a", { timeout: 1000 }).contains("Update").click()
|
||||||
cy.get(".spectrum-Tabs-item.is-selected").contains("Settings")
|
cy.get(".spectrum-Tabs-item.is-selected").contains("Settings")
|
||||||
|
|
||||||
cy.get(".version-section .page-action button")
|
cy.get(".version-section .page-action button")
|
||||||
|
@ -336,8 +332,7 @@ filterTests(["all"], () => {
|
||||||
})
|
})
|
||||||
|
|
||||||
cy.visit(`${Cypress.config().baseUrl}/builder`)
|
cy.visit(`${Cypress.config().baseUrl}/builder`)
|
||||||
cy.wait(1000)
|
cy.get(".appTable .app-row-actions button", { timeout: 1000 })
|
||||||
cy.get(".appTable .app-row-actions button")
|
|
||||||
.contains("Manage")
|
.contains("Manage")
|
||||||
.eq(0)
|
.eq(0)
|
||||||
.click({ force: true })
|
.click({ force: true })
|
||||||
|
@ -345,8 +340,7 @@ filterTests(["all"], () => {
|
||||||
cy.get(".spectrum-Tabs-item.is-selected").contains("Settings")
|
cy.get(".spectrum-Tabs-item.is-selected").contains("Settings")
|
||||||
|
|
||||||
cy.get(".details-section .page-action .spectrum-Button").scrollIntoView()
|
cy.get(".details-section .page-action .spectrum-Button").scrollIntoView()
|
||||||
cy.wait(1000)
|
cy.get(".details-section .page-action .spectrum-Button", { timeout: 1000 }).should(
|
||||||
cy.get(".details-section .page-action .spectrum-Button").should(
|
|
||||||
"be.disabled"
|
"be.disabled"
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
@ -375,27 +369,21 @@ filterTests(["all"], () => {
|
||||||
.contains("Copy App ID")
|
.contains("Copy App ID")
|
||||||
.click({ force: true })
|
.click({ force: true })
|
||||||
})
|
})
|
||||||
|
|
||||||
cy.get(".spectrum-Toast-content")
|
cy.get(".spectrum-Toast-content")
|
||||||
.contains("App ID copied to clipboard.")
|
.contains("App ID copied to clipboard.")
|
||||||
.should("be.visible")
|
.should("be.visible")
|
||||||
})
|
})
|
||||||
|
|
||||||
it("Should allow unpublishing of the application", () => {
|
it("Should allow unpublishing of the application via the Unpublish link", () => {
|
||||||
cy.visit(`${Cypress.config().baseUrl}/builder`)
|
cy.visit(`${Cypress.config().baseUrl}/builder`)
|
||||||
cy.get(".appTable .app-row-actions button")
|
cy.get(".appTable .app-row-actions button")
|
||||||
.contains("Manage")
|
.contains("Manage")
|
||||||
.eq(0)
|
.eq(0)
|
||||||
.click({ force: true })
|
.click({ force: true })
|
||||||
cy.get(".app-overview-actions-icon > .icon").click({ force: true })
|
|
||||||
|
|
||||||
cy.get("[data-cy='app-overview-menu-popover']")
|
cy.get(`[data-cy="app-status"]`).within(() => {
|
||||||
.eq(0)
|
cy.contains("Unpublish").click({ force: true })
|
||||||
.within(() => {
|
|
||||||
cy.get(".spectrum-Menu-item")
|
|
||||||
.contains("Unpublish")
|
|
||||||
.click({ force: true })
|
|
||||||
cy.wait(500)
|
|
||||||
})
|
})
|
||||||
|
|
||||||
cy.get("[data-cy='unpublish-modal']")
|
cy.get("[data-cy='unpublish-modal']")
|
||||||
|
@ -406,9 +394,8 @@ filterTests(["all"], () => {
|
||||||
|
|
||||||
cy.get(".overview-tab [data-cy='app-status']").within(() => {
|
cy.get(".overview-tab [data-cy='app-status']").within(() => {
|
||||||
cy.get(".status-display").contains("Unpublished")
|
cy.get(".status-display").contains("Unpublished")
|
||||||
cy.get(".status-display .icon svg[aria-label='GlobeStrike']").should(
|
cy.get(".status-display .icon svg[aria-label='GlobeStrike']")
|
||||||
"exist"
|
.should("exist")
|
||||||
)
|
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
|
@ -11,9 +11,8 @@ filterTests(['all'], () => {
|
||||||
|
|
||||||
it("Should reflect the unpublished status correctly", () => {
|
it("Should reflect the unpublished status correctly", () => {
|
||||||
cy.visit(`${Cypress.config().baseUrl}/builder`)
|
cy.visit(`${Cypress.config().baseUrl}/builder`)
|
||||||
cy.wait(1000)
|
|
||||||
|
|
||||||
cy.get(interact.APP_TABLE_STATUS).eq(0)
|
cy.get(interact.APP_TABLE_STATUS, { timeout: 3000 }).eq(0)
|
||||||
.within(() => {
|
.within(() => {
|
||||||
cy.contains("Unpublished")
|
cy.contains("Unpublished")
|
||||||
cy.get(interact.GLOBESTRIKE).should("exist")
|
cy.get(interact.GLOBESTRIKE).should("exist")
|
||||||
|
@ -35,11 +34,10 @@ filterTests(['all'], () => {
|
||||||
cy.get(interact.DEPLOY_APP_MODAL).should("be.visible")
|
cy.get(interact.DEPLOY_APP_MODAL).should("be.visible")
|
||||||
.within(() => {
|
.within(() => {
|
||||||
cy.get(interact.SPECTRUM_BUTTON).contains("Publish").click({ force : true })
|
cy.get(interact.SPECTRUM_BUTTON).contains("Publish").click({ force : true })
|
||||||
cy.wait(1000)
|
|
||||||
});
|
});
|
||||||
|
|
||||||
//Verify that the app url is presented correctly to the user
|
//Verify that the app url is presented correctly to the user
|
||||||
cy.get(interact.DEPLOY_SUCCESS_MODAL)
|
cy.get(interact.DEPLOY_SUCCESS_MODAL, { timeout: 1000 })
|
||||||
.should("be.visible")
|
.should("be.visible")
|
||||||
.within(() => {
|
.within(() => {
|
||||||
let appUrl = Cypress.config().baseUrl + '/app/cypress-tests'
|
let appUrl = Cypress.config().baseUrl + '/app/cypress-tests'
|
||||||
|
@ -48,9 +46,8 @@ filterTests(['all'], () => {
|
||||||
})
|
})
|
||||||
|
|
||||||
cy.visit(`${Cypress.config().baseUrl}/builder`)
|
cy.visit(`${Cypress.config().baseUrl}/builder`)
|
||||||
cy.wait(1000)
|
|
||||||
|
|
||||||
cy.get(interact.APP_TABLE_STATUS).eq(0)
|
cy.get(interact.APP_TABLE_STATUS, { timeout: 3000 }).eq(0)
|
||||||
.within(() => {
|
.within(() => {
|
||||||
cy.contains("Published")
|
cy.contains("Published")
|
||||||
cy.get(interact.GLOBE).should("exist")
|
cy.get(interact.GLOBE).should("exist")
|
||||||
|
@ -58,7 +55,7 @@ filterTests(['all'], () => {
|
||||||
|
|
||||||
cy.get(interact.APP_TABLE_ROW_ACTION).eq(0)
|
cy.get(interact.APP_TABLE_ROW_ACTION).eq(0)
|
||||||
.within(() => {
|
.within(() => {
|
||||||
cy.get(interact.SPECTRUM_BUTTON).contains("View")
|
cy.get(interact.SPECTRUM_BUTTON).contains("Manage")
|
||||||
cy.get(interact.SPECTRUM_BUTTON).contains("Edit").click({ force: true })
|
cy.get(interact.SPECTRUM_BUTTON).contains("Edit").click({ force: true })
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@ -83,24 +80,25 @@ filterTests(['all'], () => {
|
||||||
cy.get("svg[aria-label='Globe']").should("exist")
|
cy.get("svg[aria-label='Globe']").should("exist")
|
||||||
})
|
})
|
||||||
|
|
||||||
cy.get(interact.APP_TABLE_ROW_ACTION).eq(0)
|
cy.get(interact.APP_TABLE).eq(0)
|
||||||
.within(() => {
|
.within(() => {
|
||||||
cy.get(interact.SPECTRUM_BUTTON).contains("View")
|
|
||||||
cy.get(interact.APP_TABLE_APP_NAME).click({ force: true })
|
cy.get(interact.APP_TABLE_APP_NAME).click({ force: true })
|
||||||
})
|
})
|
||||||
|
|
||||||
cy.get(interact.SPECTRUM_LINK).contains('Unpublish').click();
|
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")
|
cy.get(interact.UNPUBLISH_MODAL).should("be.visible")
|
||||||
.within(() => {
|
.within(() => {
|
||||||
cy.get(interact.CONFIRM_WRAP_BUTTON).click({ force: true }
|
cy.get(interact.CONFIRM_WRAP_BUTTON).click({ force: true }
|
||||||
)})
|
)})
|
||||||
|
|
||||||
cy.get(interact.DEPLOYMENT_TOP_NAV_GLOBESTRIKE).should("exist")
|
|
||||||
|
|
||||||
cy.visit(`${Cypress.config().baseUrl}/builder`)
|
cy.visit(`${Cypress.config().baseUrl}/builder`)
|
||||||
|
cy.get(interact.APP_TABLE_STATUS, { timeout: 1000 }).eq(0).contains("Unpublished")
|
||||||
cy.get(interact.APP_TABLE_STATUS).eq(0).contains("Unpublished")
|
|
||||||
|
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
|
@ -5,6 +5,7 @@ filterTests(['smoke', 'all'], () => {
|
||||||
context("Auto Screens UI", () => {
|
context("Auto Screens UI", () => {
|
||||||
before(() => {
|
before(() => {
|
||||||
cy.login()
|
cy.login()
|
||||||
|
cy.deleteAllApps()
|
||||||
})
|
})
|
||||||
|
|
||||||
it("should disable the autogenerated screen options if no sources are available", () => {
|
it("should disable the autogenerated screen options if no sources are available", () => {
|
||||||
|
|
|
@ -6,14 +6,14 @@ filterTests(['smoke', 'all'], () => {
|
||||||
|
|
||||||
before(() => {
|
before(() => {
|
||||||
cy.login()
|
cy.login()
|
||||||
cy.deleteApp("Cypress Tests")
|
cy.deleteAllApps()
|
||||||
})
|
})
|
||||||
|
|
||||||
if (!(Cypress.env("TEST_ENV"))) {
|
if (!(Cypress.env("TEST_ENV"))) {
|
||||||
it("should show the new user UI/UX", () => {
|
it("should show the new user UI/UX", () => {
|
||||||
cy.visit(`${Cypress.config().baseUrl}/builder/portal/apps/create`) //added /portal/apps/create
|
cy.visit(`${Cypress.config().baseUrl}/builder/portal/apps/create`, { timeout: 5000 }) //added /portal/apps/create
|
||||||
|
cy.wait(1000)
|
||||||
cy.get(interact.CREATE_APP_BUTTON).contains('Start from scratch').should("exist")
|
cy.get(interact.CREATE_APP_BUTTON).contains('Start from scratch').should("exist")
|
||||||
cy.get(interact.CREATE_APP_BUTTON).should("exist")
|
|
||||||
|
|
||||||
cy.get(interact.TEMPLATE_CATEGORY_FILTER).should("exist")
|
cy.get(interact.TEMPLATE_CATEGORY_FILTER).should("exist")
|
||||||
cy.get(interact.TEMPLATE_CATEGORY).should("exist")
|
cy.get(interact.TEMPLATE_CATEGORY).should("exist")
|
||||||
|
@ -23,7 +23,7 @@ filterTests(['smoke', 'all'], () => {
|
||||||
}
|
}
|
||||||
|
|
||||||
it("should provide filterable templates", () => {
|
it("should provide filterable templates", () => {
|
||||||
cy.visit(`${Cypress.config().baseUrl}/builder`)
|
cy.visit(`${Cypress.config().baseUrl}/builder`, { timeout: 5000 })
|
||||||
cy.wait(500)
|
cy.wait(500)
|
||||||
|
|
||||||
cy.request(`${Cypress.config().baseUrl}/api/applications?status=all`)
|
cy.request(`${Cypress.config().baseUrl}/api/applications?status=all`)
|
||||||
|
@ -48,18 +48,10 @@ filterTests(['smoke', 'all'], () => {
|
||||||
})
|
})
|
||||||
|
|
||||||
it("should enforce a valid url before submission", () => {
|
it("should enforce a valid url before submission", () => {
|
||||||
cy.visit(`${Cypress.config().baseUrl}/builder`)
|
cy.visit(`${Cypress.config().baseUrl}/builder`, { timeout: 10000 })
|
||||||
cy.wait(500)
|
|
||||||
|
|
||||||
// Start create app process. If apps already exist, click second button
|
// Start create app process. If apps already exist, click second button
|
||||||
cy.get(interact.CREATE_APP_BUTTON).click({ force: true })
|
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 })
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
const appName = "Cypress Tests"
|
const appName = "Cypress Tests"
|
||||||
cy.get(interact.SPECTRUM_MODAL).within(() => {
|
cy.get(interact.SPECTRUM_MODAL).within(() => {
|
||||||
|
@ -92,21 +84,16 @@ filterTests(['smoke', 'all'], () => {
|
||||||
|
|
||||||
it("should create the first application from scratch", () => {
|
it("should create the first application from scratch", () => {
|
||||||
const appName = "Cypress Tests"
|
const appName = "Cypress Tests"
|
||||||
cy.createApp(appName)
|
cy.createApp(appName, false)
|
||||||
|
|
||||||
cy.visit(`${Cypress.config().baseUrl}/builder`)
|
cy.visit(`${Cypress.config().baseUrl}/builder`)
|
||||||
cy.wait(1000)
|
|
||||||
|
|
||||||
cy.applicationInAppTable(appName)
|
cy.applicationInAppTable(appName)
|
||||||
cy.deleteApp(appName)
|
cy.deleteApp(appName)
|
||||||
})
|
})
|
||||||
|
|
||||||
it("should create the first application from scratch with a default name", () => {
|
it("should create the first application from scratch with a default name", () => {
|
||||||
cy.createApp()
|
cy.createApp("", false)
|
||||||
|
|
||||||
cy.visit(`${Cypress.config().baseUrl}/builder`)
|
|
||||||
cy.wait(1000)
|
|
||||||
|
|
||||||
cy.applicationInAppTable("My app")
|
cy.applicationInAppTable("My app")
|
||||||
cy.deleteApp("My app")
|
cy.deleteApp("My app")
|
||||||
})
|
})
|
||||||
|
@ -116,26 +103,22 @@ filterTests(['smoke', 'all'], () => {
|
||||||
|
|
||||||
cy.updateUserInformation("Ted", "Userman")
|
cy.updateUserInformation("Ted", "Userman")
|
||||||
|
|
||||||
cy.createApp()
|
cy.createApp("", false)
|
||||||
|
|
||||||
cy.visit(`${Cypress.config().baseUrl}/builder`)
|
cy.visit(`${Cypress.config().baseUrl}/builder`)
|
||||||
cy.wait(1000)
|
|
||||||
|
|
||||||
cy.applicationInAppTable("Teds app")
|
cy.applicationInAppTable("Teds app")
|
||||||
cy.deleteApp("Teds app")
|
cy.deleteApp("Teds app")
|
||||||
cy.wait(2000)
|
|
||||||
|
|
||||||
//Accomodate names that end in 'S'
|
//Accomodate names that end in 'S'
|
||||||
cy.updateUserInformation("Chris", "Userman")
|
cy.updateUserInformation("Chris", "Userman")
|
||||||
|
|
||||||
cy.createApp()
|
cy.createApp("", false)
|
||||||
|
|
||||||
cy.visit(`${Cypress.config().baseUrl}/builder`)
|
cy.visit(`${Cypress.config().baseUrl}/builder`)
|
||||||
cy.wait(1000)
|
|
||||||
|
|
||||||
cy.applicationInAppTable("Chris app")
|
cy.applicationInAppTable("Chris app")
|
||||||
cy.deleteApp("Chris app")
|
cy.deleteApp("Chris app")
|
||||||
cy.wait(2000)
|
|
||||||
|
|
||||||
cy.updateUserInformation("", "")
|
cy.updateUserInformation("", "")
|
||||||
})
|
})
|
||||||
|
@ -145,7 +128,7 @@ filterTests(['smoke', 'all'], () => {
|
||||||
|
|
||||||
cy.importApp(exportedApp, "")
|
cy.importApp(exportedApp, "")
|
||||||
|
|
||||||
cy.visit(`${Cypress.config().baseUrl}/builder`)
|
cy.visit(`${Cypress.config().baseUrl}/builder`, { timeout: 2000 })
|
||||||
|
|
||||||
cy.applicationInAppTable("My app")
|
cy.applicationInAppTable("My app")
|
||||||
|
|
||||||
|
@ -224,14 +207,12 @@ filterTests(['smoke', 'all'], () => {
|
||||||
cy.createApp(appName)
|
cy.createApp(appName)
|
||||||
|
|
||||||
cy.visit(`${Cypress.config().baseUrl}/builder`)
|
cy.visit(`${Cypress.config().baseUrl}/builder`)
|
||||||
cy.wait(500)
|
|
||||||
|
|
||||||
// Create second app
|
// Create second app
|
||||||
const secondAppName = "Second App Demo"
|
const secondAppName = "Second App Demo"
|
||||||
cy.createApp(secondAppName)
|
cy.createApp(secondAppName)
|
||||||
|
|
||||||
cy.visit(`${Cypress.config().baseUrl}/builder`)
|
cy.visit(`${Cypress.config().baseUrl}/builder`)
|
||||||
cy.wait(500)
|
|
||||||
|
|
||||||
//Both applications should exist and be searchable
|
//Both applications should exist and be searchable
|
||||||
cy.searchForApplication(appName)
|
cy.searchForApplication(appName)
|
||||||
|
|
|
@ -16,17 +16,15 @@ filterTests(['smoke', 'all'], () => {
|
||||||
cy.get(interact.MODAL_INNER_WRAPPER).within(() => {
|
cy.get(interact.MODAL_INNER_WRAPPER).within(() => {
|
||||||
cy.get("input").type("Add Row")
|
cy.get("input").type("Add Row")
|
||||||
cy.contains("Row Created").click({ force: true })
|
cy.contains("Row Created").click({ force: true })
|
||||||
cy.wait(500)
|
cy.get(interact.SPECTRUM_BUTTON_CTA, { timeout: 500 }).click()
|
||||||
cy.get(interact.SPECTRUM_BUTTON_CTA).click()
|
|
||||||
})
|
})
|
||||||
|
|
||||||
// Setup trigger
|
// Setup trigger
|
||||||
cy.get(interact.SPECTRUM_PICKER_LABEL).click()
|
cy.get(interact.SPECTRUM_PICKER_LABEL).click()
|
||||||
cy.wait(500)
|
cy.wait(500)
|
||||||
cy.contains("dog").click()
|
cy.contains("dog").click()
|
||||||
cy.wait(2000)
|
|
||||||
// Create action
|
// Create action
|
||||||
cy.get('[aria-label="AddCircle"]').eq(1).click()
|
cy.get('[aria-label="AddCircle"]', { timeout: 2000 }).eq(1).click()
|
||||||
cy.get(interact.MODAL_INNER_WRAPPER).within(() => {
|
cy.get(interact.MODAL_INNER_WRAPPER).within(() => {
|
||||||
cy.wait(1000)
|
cy.wait(1000)
|
||||||
cy.contains("Create Row").trigger('mouseover').click().click()
|
cy.contains("Create Row").trigger('mouseover').click().click()
|
||||||
|
@ -43,11 +41,9 @@ filterTests(['smoke', 'all'], () => {
|
||||||
cy.contains("Finish and test automation").click()
|
cy.contains("Finish and test automation").click()
|
||||||
|
|
||||||
cy.get(interact.MODAL_INNER_WRAPPER).within(() => {
|
cy.get(interact.MODAL_INNER_WRAPPER).within(() => {
|
||||||
cy.wait(1000)
|
cy.get(interact.SPECTRUM_PICKER_LABEL, { timeout: 1000 }).click()
|
||||||
cy.get(interact.SPECTRUM_PICKER_LABEL).click()
|
|
||||||
cy.contains("dog").click()
|
cy.contains("dog").click()
|
||||||
cy.wait(1000)
|
cy.get(interact.SPECTRUM_TEXTFIELD_INPUT, { timeout: 1000 })
|
||||||
cy.get(interact.SPECTRUM_TEXTFIELD_INPUT)
|
|
||||||
.first()
|
.first()
|
||||||
.type("automationGoodboy")
|
.type("automationGoodboy")
|
||||||
cy.get(interact.SPECTRUM_TEXTFIELD_INPUT)
|
cy.get(interact.SPECTRUM_TEXTFIELD_INPUT)
|
||||||
|
|
|
@ -10,9 +10,8 @@ filterTests(["smoke", "all"], () => {
|
||||||
|
|
||||||
it("should create a new Table", () => {
|
it("should create a new Table", () => {
|
||||||
cy.createTable("dog")
|
cy.createTable("dog")
|
||||||
cy.wait(1000)
|
|
||||||
// Check if Table exists
|
// Check if Table exists
|
||||||
cy.get(interact.TABLE_TITLE_H1).should("have.text", "dog")
|
cy.get(interact.TABLE_TITLE_H1, { timeout: 1000 }).should("have.text", "dog")
|
||||||
})
|
})
|
||||||
|
|
||||||
it("adds a new column to the table", () => {
|
it("adds a new column to the table", () => {
|
||||||
|
@ -40,7 +39,7 @@ filterTests(["smoke", "all"], () => {
|
||||||
|
|
||||||
it("edits a row", () => {
|
it("edits a row", () => {
|
||||||
cy.contains("button", "Edit").click({ force: true })
|
cy.contains("button", "Edit").click({ force: true })
|
||||||
cy.wait(1000)
|
cy.wait(500)
|
||||||
cy.get(interact.SPECTRUM_MODAL_INPUT).clear()
|
cy.get(interact.SPECTRUM_MODAL_INPUT).clear()
|
||||||
cy.get(interact.SPECTRUM_MODAL_INPUT).type("Updated")
|
cy.get(interact.SPECTRUM_MODAL_INPUT).type("Updated")
|
||||||
cy.contains("Save").click()
|
cy.contains("Save").click()
|
||||||
|
@ -63,8 +62,7 @@ filterTests(["smoke", "all"], () => {
|
||||||
cy.addRow([i])
|
cy.addRow([i])
|
||||||
}
|
}
|
||||||
cy.reload()
|
cy.reload()
|
||||||
cy.wait(2000)
|
cy.get(interact.SPECTRUM_PAGINATION, { timeout: 2000 }).within(() => {
|
||||||
cy.get(interact.SPECTRUM_PAGINATION).within(() => {
|
|
||||||
cy.get(interact.SPECTRUM_ACTION_BUTTON).eq(1).click()
|
cy.get(interact.SPECTRUM_ACTION_BUTTON).eq(1).click()
|
||||||
})
|
})
|
||||||
cy.get(interact.SPECTRUM_PAGINATION).within(() => {
|
cy.get(interact.SPECTRUM_PAGINATION).within(() => {
|
||||||
|
@ -79,10 +77,9 @@ filterTests(["smoke", "all"], () => {
|
||||||
cy.get(interact.SPECTRUM_BUTTON).click({ force: true })
|
cy.get(interact.SPECTRUM_BUTTON).click({ force: true })
|
||||||
})
|
})
|
||||||
cy.get(interact.SPECTRUM_DIALOG_GRID).contains("Delete").click({ force: true })
|
cy.get(interact.SPECTRUM_DIALOG_GRID).contains("Delete").click({ force: true })
|
||||||
cy.wait(1000)
|
|
||||||
|
|
||||||
// Confirm table only has one page
|
// Confirm table only has one page
|
||||||
cy.get(interact.SPECTRUM_PAGINATION).within(() => {
|
cy.get(interact.SPECTRUM_PAGINATION, { timeout: 1000 }).within(() => {
|
||||||
cy.get(interact.SPECTRUM_ACTION_BUTTON).eq(1).should("not.be.enabled")
|
cy.get(interact.SPECTRUM_ACTION_BUTTON).eq(1).should("not.be.enabled")
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
|
@ -69,8 +69,8 @@ filterTests(['smoke', 'all'], () => {
|
||||||
|
|
||||||
cy.get(interact.SPECTRUM_BUTTON).contains("Save").click({ force: true })
|
cy.get(interact.SPECTRUM_BUTTON).contains("Save").click({ force: true })
|
||||||
})
|
})
|
||||||
cy.wait(1000)
|
|
||||||
|
|
||||||
|
cy.wait(1000)
|
||||||
cy.get(interact.TITLE).then($headers => {
|
cy.get(interact.TITLE).then($headers => {
|
||||||
expect($headers).to.have.length(7)
|
expect($headers).to.have.length(7)
|
||||||
const headers = Array.from($headers).map(header =>
|
const headers = Array.from($headers).map(header =>
|
||||||
|
|
|
@ -34,7 +34,6 @@ filterTests(['all'], () => {
|
||||||
Large = 16px */
|
Large = 16px */
|
||||||
it("should test button roundness", () => {
|
it("should test button roundness", () => {
|
||||||
const buttonRoundnessValues = ["0", "4px", "8px", "16px"]
|
const buttonRoundnessValues = ["0", "4px", "8px", "16px"]
|
||||||
cy.wait(1000)
|
|
||||||
// Add button, change roundness and confirm value
|
// Add button, change roundness and confirm value
|
||||||
cy.addComponent("Button", null).then((componentId) => {
|
cy.addComponent("Button", null).then((componentId) => {
|
||||||
buttonRoundnessValues.forEach(function (item, index){
|
buttonRoundnessValues.forEach(function (item, index){
|
||||||
|
|
|
@ -17,11 +17,10 @@ filterTests(['all'], () => {
|
||||||
// Navigate back within datasource wizard
|
// Navigate back within datasource wizard
|
||||||
cy.get(".spectrum-Dialog-grid").within(() => {
|
cy.get(".spectrum-Dialog-grid").within(() => {
|
||||||
cy.get(".spectrum-Button").contains("Back").click({ force: true })
|
cy.get(".spectrum-Button").contains("Back").click({ force: true })
|
||||||
cy.wait(1000)
|
|
||||||
})
|
})
|
||||||
|
|
||||||
// Select PostgreSQL datasource again
|
// Select PostgreSQL datasource again
|
||||||
cy.get(".item-list").contains(datasource).click()
|
cy.get(".item-list", { timeout: 1000 }).contains(datasource).click()
|
||||||
cy.get(".spectrum-Dialog-grid").within(() => {
|
cy.get(".spectrum-Dialog-grid").within(() => {
|
||||||
cy.get(".spectrum-Button").contains("Continue").click({ force: true })
|
cy.get(".spectrum-Button").contains("Continue").click({ force: true })
|
||||||
})
|
})
|
||||||
|
|
|
@ -111,10 +111,9 @@ filterTests(["all"], () => {
|
||||||
// Save relationship & reload page
|
// Save relationship & reload page
|
||||||
cy.get(".spectrum-Button").contains("Save").click({ force: true })
|
cy.get(".spectrum-Button").contains("Save").click({ force: true })
|
||||||
cy.reload()
|
cy.reload()
|
||||||
cy.wait(1000)
|
|
||||||
})
|
})
|
||||||
// Confirm table length & relationship name
|
// Confirm table length & relationship name
|
||||||
cy.get(".spectrum-Table")
|
cy.get(".spectrum-Table", { timeout: 1000 })
|
||||||
.eq(1)
|
.eq(1)
|
||||||
.find(".spectrum-Table-row")
|
.find(".spectrum-Table-row")
|
||||||
.its("length")
|
.its("length")
|
||||||
|
@ -136,15 +135,15 @@ filterTests(["all"], () => {
|
||||||
cy.get(".spectrum-Table")
|
cy.get(".spectrum-Table")
|
||||||
.eq(1)
|
.eq(1)
|
||||||
.within(() => {
|
.within(() => {
|
||||||
cy.get(".spectrum-Table-row").eq(0).click({ force: true })
|
cy.get(".spectrum-Table-cell").eq(0).click({ force: true })
|
||||||
cy.wait(500)
|
|
||||||
})
|
})
|
||||||
cy.get(".spectrum-Dialog-grid").within(() => {
|
cy.get(".spectrum-Dialog-grid", { timeout: 500 }).within(() => {
|
||||||
cy.get(".spectrum-Button")
|
cy.get(".spectrum-Button")
|
||||||
.contains("Delete")
|
.contains("Delete")
|
||||||
.click({ force: true })
|
.click({ force: true })
|
||||||
})
|
})
|
||||||
cy.reload()
|
cy.reload()
|
||||||
|
cy.wait(500)
|
||||||
}
|
}
|
||||||
// Confirm relationships no longer exist
|
// Confirm relationships no longer exist
|
||||||
cy.get(".spectrum-Body").should(
|
cy.get(".spectrum-Body").should(
|
||||||
|
@ -217,9 +216,8 @@ filterTests(["all"], () => {
|
||||||
cy.get(".spectrum-Button")
|
cy.get(".spectrum-Button")
|
||||||
.contains("Delete Query")
|
.contains("Delete Query")
|
||||||
.click({ force: true })
|
.click({ force: true })
|
||||||
cy.wait(1000)
|
|
||||||
// Confirm deletion
|
// Confirm deletion
|
||||||
cy.get(".nav-item").should("not.contain", queryName)
|
cy.get(".nav-item", { timeout: 1000 }).should("not.contain", queryName)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
|
@ -20,7 +20,7 @@ filterTests(["all"], () => {
|
||||||
.click({ force: true })
|
.click({ force: true })
|
||||||
cy.wait(500)
|
cy.wait(500)
|
||||||
// Confirm config contains localhost
|
// Confirm config contains localhost
|
||||||
cy.get(".spectrum-Textfield-input")
|
cy.get(".spectrum-Textfield-input", { timeout: 500 })
|
||||||
.eq(1)
|
.eq(1)
|
||||||
.should("have.value", "localhost")
|
.should("have.value", "localhost")
|
||||||
// Add another Oracle data source, configure & skip table fetch
|
// Add another Oracle data source, configure & skip table fetch
|
||||||
|
@ -140,9 +140,8 @@ filterTests(["all"], () => {
|
||||||
.eq(1)
|
.eq(1)
|
||||||
.within(() => {
|
.within(() => {
|
||||||
cy.get(".spectrum-Table-row").eq(0).click()
|
cy.get(".spectrum-Table-row").eq(0).click()
|
||||||
cy.wait(500)
|
|
||||||
})
|
})
|
||||||
cy.get(".spectrum-Dialog-grid").within(() => {
|
cy.get(".spectrum-Dialog-grid", { timeout: 500 }).within(() => {
|
||||||
cy.get(".spectrum-Button")
|
cy.get(".spectrum-Button")
|
||||||
.contains("Delete")
|
.contains("Delete")
|
||||||
.click({ force: true })
|
.click({ force: true })
|
||||||
|
@ -221,10 +220,9 @@ filterTests(["all"], () => {
|
||||||
cy.get(".spectrum-Button")
|
cy.get(".spectrum-Button")
|
||||||
.contains("Delete Query")
|
.contains("Delete Query")
|
||||||
.click({ force: true })
|
.click({ force: true })
|
||||||
cy.wait(1000)
|
|
||||||
|
|
||||||
// Confirm deletion
|
// Confirm deletion
|
||||||
cy.get(".nav-item").should("not.contain", queryName)
|
cy.get(".nav-item", { timeout: 1000 }).should("not.contain", queryName)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
|
@ -35,6 +35,7 @@ filterTests(["all"], () => {
|
||||||
// Check response from datasource after adding configuration
|
// Check response from datasource after adding configuration
|
||||||
cy.wait("@datasource")
|
cy.wait("@datasource")
|
||||||
cy.get("@datasource").its("response.statusCode").should("eq", 200)
|
cy.get("@datasource").its("response.statusCode").should("eq", 200)
|
||||||
|
cy.wait(2000)
|
||||||
// Confirm fetch tables was successful
|
// Confirm fetch tables was successful
|
||||||
cy.get(".spectrum-Table")
|
cy.get(".spectrum-Table")
|
||||||
.eq(0)
|
.eq(0)
|
||||||
|
@ -113,13 +114,13 @@ filterTests(["all"], () => {
|
||||||
cy.get(".spectrum-Table")
|
cy.get(".spectrum-Table")
|
||||||
.eq(1)
|
.eq(1)
|
||||||
.within(() => {
|
.within(() => {
|
||||||
cy.get(".spectrum-Table-row").eq(0).click({ force: true })
|
cy.get(".spectrum-Table-cell").eq(0).click({ force: true })
|
||||||
cy.wait(500)
|
|
||||||
})
|
})
|
||||||
cy.get(".spectrum-Dialog-grid").within(() => {
|
cy.get(".spectrum-Dialog-grid", { timeout: 500 }).within(() => {
|
||||||
cy.get(".spectrum-Button").contains("Delete").click({ force: true })
|
cy.get(".spectrum-Button").contains("Delete").click({ force: true })
|
||||||
})
|
})
|
||||||
cy.reload()
|
cy.reload()
|
||||||
|
cy.wait(500)
|
||||||
// Confirm relationship was deleted
|
// Confirm relationship was deleted
|
||||||
cy.get(".spectrum-Table")
|
cy.get(".spectrum-Table")
|
||||||
.eq(1)
|
.eq(1)
|
||||||
|
@ -159,7 +160,7 @@ filterTests(["all"], () => {
|
||||||
switchSchema("randomText")
|
switchSchema("randomText")
|
||||||
|
|
||||||
// No tables displayed
|
// No tables displayed
|
||||||
cy.get(".spectrum-Body").eq(2).should("contain", "No tables found")
|
cy.get(".spectrum-Body", { timeout: 5000 }).eq(2).should("contain", "No tables found")
|
||||||
|
|
||||||
// Previously created query should be visible
|
// Previously created query should be visible
|
||||||
cy.get(".spectrum-Table").should("contain", queryName)
|
cy.get(".spectrum-Table").should("contain", queryName)
|
||||||
|
@ -170,7 +171,7 @@ filterTests(["all"], () => {
|
||||||
switchSchema("1")
|
switchSchema("1")
|
||||||
|
|
||||||
// Confirm tables exist - Check for specific one
|
// Confirm tables exist - Check for specific one
|
||||||
cy.get(".spectrum-Table").eq(0).should("contain", "test")
|
cy.get(".spectrum-Table", { timeout: 5000 }).eq(0).should("contain", "test")
|
||||||
cy.get(".spectrum-Table")
|
cy.get(".spectrum-Table")
|
||||||
.eq(0)
|
.eq(0)
|
||||||
.find(".spectrum-Table-row")
|
.find(".spectrum-Table-row")
|
||||||
|
@ -184,7 +185,7 @@ filterTests(["all"], () => {
|
||||||
switchSchema("public")
|
switchSchema("public")
|
||||||
|
|
||||||
// Confirm tables exist - again
|
// Confirm tables exist - again
|
||||||
cy.get(".spectrum-Table").eq(0).should("contain", "REGIONS")
|
cy.get(".spectrum-Table", { timeout: 5000 }).eq(0).should("contain", "REGIONS")
|
||||||
cy.get(".spectrum-Table")
|
cy.get(".spectrum-Table")
|
||||||
.eq(0)
|
.eq(0)
|
||||||
.find(".spectrum-Table-row")
|
.find(".spectrum-Table-row")
|
||||||
|
@ -230,7 +231,9 @@ filterTests(["all"], () => {
|
||||||
// Run and Save query
|
// Run and Save query
|
||||||
cy.get(".spectrum-Button").contains("Run Query").click({ force: true })
|
cy.get(".spectrum-Button").contains("Run Query").click({ force: true })
|
||||||
cy.wait(500)
|
cy.wait(500)
|
||||||
cy.get(".spectrum-Button").contains("Save Query").click({ force: true })
|
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(".nav-item").should("contain", queryRename)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@ -247,9 +250,8 @@ filterTests(["all"], () => {
|
||||||
cy.get(".spectrum-Button")
|
cy.get(".spectrum-Button")
|
||||||
.contains("Delete Query")
|
.contains("Delete Query")
|
||||||
.click({ force: true })
|
.click({ force: true })
|
||||||
cy.wait(1000)
|
|
||||||
// Confirm deletion
|
// Confirm deletion
|
||||||
cy.get(".nav-item").should("not.contain", queryName)
|
cy.get(".nav-item", { timeout: 1000 }).should("not.contain", queryName)
|
||||||
})
|
})
|
||||||
|
|
||||||
const switchSchema = schema => {
|
const switchSchema = schema => {
|
||||||
|
@ -271,7 +273,7 @@ filterTests(["all"], () => {
|
||||||
.click({ force: true })
|
.click({ force: true })
|
||||||
})
|
})
|
||||||
cy.reload()
|
cy.reload()
|
||||||
cy.wait(5000)
|
cy.wait(1000)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
|
@ -14,8 +14,7 @@ filterTests(["smoke", "all"], () => {
|
||||||
// Select REST data source
|
// Select REST data source
|
||||||
cy.selectExternalDatasource(datasource)
|
cy.selectExternalDatasource(datasource)
|
||||||
// Enter incorrect api & attempt to send query
|
// Enter incorrect api & attempt to send query
|
||||||
cy.wait(500)
|
cy.get(".spectrum-Button", { timeout: 500 }).contains("Add query").click({ force: true })
|
||||||
cy.get(".spectrum-Button").contains("Add query").click({ force: true })
|
|
||||||
cy.intercept("**/preview").as("queryError")
|
cy.intercept("**/preview").as("queryError")
|
||||||
cy.get("input").clear().type("random text")
|
cy.get("input").clear().type("random text")
|
||||||
cy.get(".spectrum-Button").contains("Send").click({ force: true })
|
cy.get(".spectrum-Button").contains("Send").click({ force: true })
|
||||||
|
@ -36,8 +35,7 @@ filterTests(["smoke", "all"], () => {
|
||||||
// createRestQuery confirms query creation
|
// createRestQuery confirms query creation
|
||||||
cy.createRestQuery("GET", restUrl, "/breweries")
|
cy.createRestQuery("GET", restUrl, "/breweries")
|
||||||
// Confirm status code response within REST datasource
|
// Confirm status code response within REST datasource
|
||||||
cy.wait(1000)
|
cy.get(".stats", { timeout: 1000 }).within(() => {
|
||||||
cy.get(".stats").within(() => {
|
|
||||||
cy.get(".spectrum-FieldLabel")
|
cy.get(".spectrum-FieldLabel")
|
||||||
.eq(0)
|
.eq(0)
|
||||||
.should("contain", 200)
|
.should("contain", 200)
|
||||||
|
|
|
@ -13,16 +13,13 @@ filterTests(["all"], () => {
|
||||||
const appRename = "Cypress Renamed"
|
const appRename = "Cypress Renamed"
|
||||||
// Rename app, Search for app, Confirm name was changed
|
// Rename app, Search for app, Confirm name was changed
|
||||||
cy.visit(`${Cypress.config().baseUrl}/builder`)
|
cy.visit(`${Cypress.config().baseUrl}/builder`)
|
||||||
cy.wait(500)
|
|
||||||
renameApp(appName, appRename)
|
renameApp(appName, appRename)
|
||||||
cy.reload()
|
cy.reload()
|
||||||
cy.wait(1000)
|
|
||||||
cy.searchForApplication(appRename)
|
cy.searchForApplication(appRename)
|
||||||
cy.get(interact.APP_TABLE).find(interact.TITLE).should("have.length", 1)
|
cy.get(interact.APP_TABLE).find(interact.TITLE).should("have.length", 1)
|
||||||
cy.applicationInAppTable(appRename)
|
cy.applicationInAppTable(appRename)
|
||||||
// Set app name back to Cypress Tests
|
// Set app name back to Cypress Tests
|
||||||
cy.reload()
|
cy.reload()
|
||||||
cy.wait(1000)
|
|
||||||
renameApp(appRename, appName)
|
renameApp(appRename, appName)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@ -43,7 +40,6 @@ filterTests(["all"], () => {
|
||||||
})
|
})
|
||||||
// Rename app, Search for app, Confirm name was changed
|
// Rename app, Search for app, Confirm name was changed
|
||||||
cy.visit(`${Cypress.config().baseUrl}/builder`)
|
cy.visit(`${Cypress.config().baseUrl}/builder`)
|
||||||
cy.wait(500)
|
|
||||||
renameApp(appName, appRename, true)
|
renameApp(appName, appRename, true)
|
||||||
cy.get(interact.APP_TABLE).find(interact.WRAPPER).should("have.length", 1)
|
cy.get(interact.APP_TABLE).find(interact.WRAPPER).should("have.length", 1)
|
||||||
cy.applicationInAppTable(appRename)
|
cy.applicationInAppTable(appRename)
|
||||||
|
@ -52,13 +48,9 @@ filterTests(["all"], () => {
|
||||||
it("Should try to rename an application to have no name", () => {
|
it("Should try to rename an application to have no name", () => {
|
||||||
const appName = "Cypress Tests"
|
const appName = "Cypress Tests"
|
||||||
cy.visit(`${Cypress.config().baseUrl}/builder`)
|
cy.visit(`${Cypress.config().baseUrl}/builder`)
|
||||||
cy.wait(500)
|
|
||||||
renameApp(appName, " ", false, true)
|
renameApp(appName, " ", false, true)
|
||||||
cy.wait(500)
|
|
||||||
// Close modal and confirm name has not been changed
|
// Close modal and confirm name has not been changed
|
||||||
cy.get(interact.SPECTRUM_DIALOG_GRID).contains("Cancel").click()
|
cy.get(interact.SPECTRUM_DIALOG_GRID, { timeout: 1000 }).contains("Cancel").click()
|
||||||
cy.reload()
|
|
||||||
cy.wait(1000)
|
|
||||||
cy.applicationInAppTable(appName)
|
cy.applicationInAppTable(appName)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@ -66,8 +58,7 @@ filterTests(["all"], () => {
|
||||||
// It is not possible to have applications with the same name
|
// It is not possible to have applications with the same name
|
||||||
const appName = "Cypress Tests"
|
const appName = "Cypress Tests"
|
||||||
cy.visit(`${Cypress.config().baseUrl}/builder`)
|
cy.visit(`${Cypress.config().baseUrl}/builder`)
|
||||||
cy.wait(500)
|
cy.get(interact.SPECTRUM_BUTTON), { timeout: 500 }
|
||||||
cy.get(interact.SPECTRUM_BUTTON)
|
|
||||||
.contains("Create app")
|
.contains("Create app")
|
||||||
.click({ force: true })
|
.click({ force: true })
|
||||||
cy.contains(/Start from scratch/).click()
|
cy.contains(/Start from scratch/).click()
|
||||||
|
@ -90,13 +81,10 @@ filterTests(["all"], () => {
|
||||||
const numberName = 12345
|
const numberName = 12345
|
||||||
const specialCharName = "£$%^"
|
const specialCharName = "£$%^"
|
||||||
cy.visit(`${Cypress.config().baseUrl}/builder`)
|
cy.visit(`${Cypress.config().baseUrl}/builder`)
|
||||||
cy.wait(500)
|
|
||||||
renameApp(appName, numberName)
|
renameApp(appName, numberName)
|
||||||
cy.reload()
|
cy.reload()
|
||||||
cy.wait(1000)
|
|
||||||
cy.applicationInAppTable(numberName)
|
cy.applicationInAppTable(numberName)
|
||||||
cy.reload()
|
cy.reload()
|
||||||
cy.wait(1000)
|
|
||||||
renameApp(numberName, specialCharName)
|
renameApp(numberName, specialCharName)
|
||||||
cy.get(interact.ERROR).should(
|
cy.get(interact.ERROR).should(
|
||||||
"have.text",
|
"have.text",
|
||||||
|
@ -104,13 +92,12 @@ filterTests(["all"], () => {
|
||||||
)
|
)
|
||||||
// Set app name back to Cypress Tests
|
// Set app name back to Cypress Tests
|
||||||
cy.reload()
|
cy.reload()
|
||||||
cy.wait(1000)
|
|
||||||
renameApp(numberName, appName)
|
renameApp(numberName, appName)
|
||||||
})
|
})
|
||||||
|
|
||||||
const renameApp = (originalName, changedName, published, noName) => {
|
const renameApp = (originalName, changedName, published, noName) => {
|
||||||
cy.searchForApplication(originalName)
|
cy.searchForApplication(originalName)
|
||||||
cy.get(interact.APP_TABLE).within(() => {
|
cy.get(interact.APP_TABLE, { timeout: 1000 }).within(() => {
|
||||||
cy.get(".app-row-actions button")
|
cy.get(".app-row-actions button")
|
||||||
.contains("Manage")
|
.contains("Manage")
|
||||||
.eq(0)
|
.eq(0)
|
||||||
|
|
|
@ -36,8 +36,8 @@ filterTests(['smoke', 'all'], () => {
|
||||||
cy.get(interact.SPECTRUM_BUTTON_GROUP).within(() => {
|
cy.get(interact.SPECTRUM_BUTTON_GROUP).within(() => {
|
||||||
cy.get(interact.SPECTRUM_BUTTON).contains("Publish").click({ force: true })
|
cy.get(interact.SPECTRUM_BUTTON).contains("Publish").click({ force: true })
|
||||||
})
|
})
|
||||||
cy.wait(1000)
|
cy.wait(1000) // Wait for next modal to finish loading
|
||||||
cy.get(interact.SPECTRUM_BUTTON_GROUP).within(() => {
|
cy.get(interact.SPECTRUM_BUTTON_GROUP, { timeout: 1000 }).within(() => {
|
||||||
cy.get(interact.SPECTRUM_BUTTON).contains("Done").click({ force: true })
|
cy.get(interact.SPECTRUM_BUTTON).contains("Done").click({ force: true })
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@ -50,18 +50,17 @@ filterTests(['smoke', 'all'], () => {
|
||||||
cy.get(interact.SPECTRUM_DIALOG_GRID).within(() => {
|
cy.get(interact.SPECTRUM_DIALOG_GRID).within(() => {
|
||||||
// Click Revert
|
// Click Revert
|
||||||
cy.get(interact.SPECTRUM_BUTTON).contains("Revert").click({ force: true })
|
cy.get(interact.SPECTRUM_BUTTON).contains("Revert").click({ force: true })
|
||||||
cy.wait(1000)
|
cy.wait(2000) // Wait for app to finish reverting
|
||||||
})
|
})
|
||||||
// Confirm Paragraph component is still visible
|
// Confirm Paragraph component is still visible
|
||||||
cy.get(interact.ROOT).contains("New Paragraph")
|
cy.get(interact.ROOT, { timeout: 1000 }).contains("New Paragraph")
|
||||||
// Confirm Button component is not visible
|
// Confirm Button component is not visible
|
||||||
cy.get(interact.ROOT).should("not.have.text", "New Button")
|
cy.get(interact.ROOT, { timeout: 1000 }).should("not.have.text", "New Button")
|
||||||
cy.wait(500)
|
|
||||||
})
|
})
|
||||||
|
|
||||||
it("should enter incorrect app name when reverting", () => {
|
it("should enter incorrect app name when reverting", () => {
|
||||||
// Click Revert
|
// Click Revert
|
||||||
cy.get(interact.TOP_RIGHT_NAV).within(() => {
|
cy.get(interact.TOP_RIGHT_NAV, { timeout: 1000 }).within(() => {
|
||||||
cy.get(interact.AREA_LABEL_REVERT).click({ force: true })
|
cy.get(interact.AREA_LABEL_REVERT).click({ force: true })
|
||||||
})
|
})
|
||||||
// Enter incorrect app name
|
// Enter incorrect app name
|
||||||
|
|
|
@ -3,7 +3,7 @@ Cypress.on("uncaught:exception", () => {
|
||||||
})
|
})
|
||||||
|
|
||||||
// ACCOUNTS & USERS
|
// ACCOUNTS & USERS
|
||||||
Cypress.Commands.add("login", () => {
|
Cypress.Commands.add("login", (email, password) => {
|
||||||
cy.visit(`${Cypress.config().baseUrl}/builder`)
|
cy.visit(`${Cypress.config().baseUrl}/builder`)
|
||||||
cy.wait(2000)
|
cy.wait(2000)
|
||||||
cy.url().then(url => {
|
cy.url().then(url => {
|
||||||
|
@ -17,8 +17,13 @@ Cypress.Commands.add("login", () => {
|
||||||
if (url.includes("builder/auth/login") || url.includes("builder/admin")) {
|
if (url.includes("builder/auth/login") || url.includes("builder/admin")) {
|
||||||
// login
|
// login
|
||||||
cy.contains("Sign in to Budibase").then(() => {
|
cy.contains("Sign in to Budibase").then(() => {
|
||||||
cy.get("input").first().type("test@test.com")
|
if (email == null) {
|
||||||
cy.get('input[type="password"]').type("test")
|
cy.get("input").first().type("test@test.com")
|
||||||
|
cy.get('input[type="password"]').type("test")
|
||||||
|
} else {
|
||||||
|
cy.get("input").first().type(email)
|
||||||
|
cy.get('input[type="password"]').type(password)
|
||||||
|
}
|
||||||
cy.get("button").first().click({ force: true })
|
cy.get("button").first().click({ force: true })
|
||||||
cy.wait(1000)
|
cy.wait(1000)
|
||||||
})
|
})
|
||||||
|
@ -27,7 +32,7 @@ Cypress.Commands.add("login", () => {
|
||||||
})
|
})
|
||||||
|
|
||||||
Cypress.Commands.add("logOut", () => {
|
Cypress.Commands.add("logOut", () => {
|
||||||
cy.visit(`${Cypress.config().baseUrl}/builder`)
|
cy.visit(`${Cypress.config().baseUrl}/builder`, { timeout: 2000 })
|
||||||
cy.get(".user-dropdown .avatar > .icon").click({ force: true })
|
cy.get(".user-dropdown .avatar > .icon").click({ force: true })
|
||||||
cy.get(".spectrum-Popover[data-cy='user-menu']").within(() => {
|
cy.get(".spectrum-Popover[data-cy='user-menu']").within(() => {
|
||||||
cy.get("li[data-cy='user-logout']").click({ force: true })
|
cy.get("li[data-cy='user-logout']").click({ force: true })
|
||||||
|
@ -35,24 +40,58 @@ Cypress.Commands.add("logOut", () => {
|
||||||
cy.wait(2000)
|
cy.wait(2000)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
Cypress.Commands.add("logoutNoAppGrid", () => {
|
||||||
|
// Logs user out when app grid is not present
|
||||||
|
cy.visit(`${Cypress.config().baseUrl}/builder`)
|
||||||
|
cy.get(".avatar > .icon").click({ force: true })
|
||||||
|
cy.get(".spectrum-Popover[data-cy='user-menu']").within(() => {
|
||||||
|
cy.get(".spectrum-Menu-item").contains("Log out").click({ force: true })
|
||||||
|
})
|
||||||
|
cy.wait(2000)
|
||||||
|
})
|
||||||
|
|
||||||
Cypress.Commands.add("createUser", email => {
|
Cypress.Commands.add("createUser", email => {
|
||||||
// quick hacky recorded way to create a user
|
// quick hacky recorded way to create a user
|
||||||
cy.contains("Users").click()
|
cy.contains("Users").click()
|
||||||
cy.get(`[data-cy="add-user"]`).click()
|
cy.get(`[data-cy="add-user"]`).click()
|
||||||
cy.get(".spectrum-Picker-label").click()
|
cy.get(".spectrum-Dialog-grid").within(() => {
|
||||||
cy.get(".spectrum-Menu-item:nth-child(2) > .spectrum-Menu-itemLabel").click()
|
cy.get(".spectrum-Picker-label").click()
|
||||||
|
cy.get(
|
||||||
|
".spectrum-Menu-item:nth-child(2) > .spectrum-Menu-itemLabel"
|
||||||
|
).click()
|
||||||
|
|
||||||
//Onboarding type selector
|
// Onboarding type selector
|
||||||
cy.get(
|
cy.get(".spectrum-Textfield-input")
|
||||||
":nth-child(2) > .spectrum-Form-itemField > .spectrum-Textfield > .spectrum-Textfield-input"
|
.eq(0)
|
||||||
)
|
.first()
|
||||||
.first()
|
.type(email, { force: true })
|
||||||
.type(email, { force: true })
|
cy.get(".spectrum-Button--cta").click({ force: true })
|
||||||
cy.get(".spectrum-Button--cta").click({ force: true })
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
Cypress.Commands.add("deleteUser", email => {
|
||||||
|
// Assumes user has access to Users section
|
||||||
|
cy.contains("Users", { timeout: 2000 }).click()
|
||||||
|
cy.contains(email).click()
|
||||||
|
|
||||||
|
// Click Delete user button
|
||||||
|
cy.get(".spectrum-Button")
|
||||||
|
.contains("Delete user")
|
||||||
|
.click({ force: true })
|
||||||
|
.then(() => {
|
||||||
|
// Confirm deletion within modal
|
||||||
|
cy.get(".spectrum-Dialog-grid", { timeout: 500 }).within(() => {
|
||||||
|
cy.get(".spectrum-Button")
|
||||||
|
.contains("Delete user")
|
||||||
|
.click({ force: true })
|
||||||
|
})
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
Cypress.Commands.add("updateUserInformation", (firstName, lastName) => {
|
Cypress.Commands.add("updateUserInformation", (firstName, lastName) => {
|
||||||
cy.get(".user-dropdown .avatar > .icon").click({ force: true })
|
cy.get(".user-dropdown .avatar > .icon", { timeout: 2000 }).click({
|
||||||
|
force: true,
|
||||||
|
})
|
||||||
|
|
||||||
cy.get(".spectrum-Popover[data-cy='user-menu']").within(() => {
|
cy.get(".spectrum-Popover[data-cy='user-menu']").within(() => {
|
||||||
cy.get("li[data-cy='user-info']").click({ force: true })
|
cy.get("li[data-cy='user-info']").click({ force: true })
|
||||||
|
@ -95,9 +134,8 @@ Cypress.Commands.add("createApp", (name, addDefaultTable) => {
|
||||||
const shouldCreateDefaultTable =
|
const shouldCreateDefaultTable =
|
||||||
typeof addDefaultTable != "boolean" ? true : addDefaultTable
|
typeof addDefaultTable != "boolean" ? true : addDefaultTable
|
||||||
|
|
||||||
cy.visit(`${Cypress.config().baseUrl}/builder`)
|
cy.visit(`${Cypress.config().baseUrl}/builder`, { timeout: 5000 })
|
||||||
cy.wait(1000)
|
cy.get(`[data-cy="create-app-btn"]`, { timeout: 2000 }).click({ force: true })
|
||||||
cy.get(`[data-cy="create-app-btn"]`).click({ force: true })
|
|
||||||
|
|
||||||
// If apps already exist
|
// If apps already exist
|
||||||
cy.request(`${Cypress.config().baseUrl}/api/applications?status=all`)
|
cy.request(`${Cypress.config().baseUrl}/api/applications?status=all`)
|
||||||
|
@ -117,7 +155,7 @@ Cypress.Commands.add("createApp", (name, addDefaultTable) => {
|
||||||
cy.get(".spectrum-ButtonGroup")
|
cy.get(".spectrum-ButtonGroup")
|
||||||
.contains("Create app")
|
.contains("Create app")
|
||||||
.click({ force: true })
|
.click({ force: true })
|
||||||
cy.wait(10000)
|
cy.wait(2000)
|
||||||
})
|
})
|
||||||
if (shouldCreateDefaultTable) {
|
if (shouldCreateDefaultTable) {
|
||||||
cy.createTable("Cypress Tests", true)
|
cy.createTable("Cypress Tests", true)
|
||||||
|
@ -125,7 +163,7 @@ Cypress.Commands.add("createApp", (name, addDefaultTable) => {
|
||||||
})
|
})
|
||||||
|
|
||||||
Cypress.Commands.add("deleteApp", name => {
|
Cypress.Commands.add("deleteApp", name => {
|
||||||
cy.visit(`${Cypress.config().baseUrl}/builder`)
|
cy.visit(`${Cypress.config().baseUrl}/builder`, { timeout: 5000 })
|
||||||
cy.wait(2000)
|
cy.wait(2000)
|
||||||
cy.request(`${Cypress.config().baseUrl}/api/applications?status=all`)
|
cy.request(`${Cypress.config().baseUrl}/api/applications?status=all`)
|
||||||
.its("body")
|
.its("body")
|
||||||
|
@ -150,14 +188,15 @@ Cypress.Commands.add("deleteApp", name => {
|
||||||
cy.get(actionEleId).within(() => {
|
cy.get(actionEleId).within(() => {
|
||||||
cy.contains("Manage").click({ force: true })
|
cy.contains("Manage").click({ force: true })
|
||||||
})
|
})
|
||||||
cy.wait(1000)
|
cy.wait(500)
|
||||||
|
|
||||||
// Unpublish first if needed
|
// Unpublish first if needed
|
||||||
cy.get(`[data-cy="app-status"]`).then($status => {
|
cy.get(`[data-cy="app-status"]`).then($status => {
|
||||||
if ($status.text().includes("Last published")) {
|
if ($status.text().includes("- Unpublish")) {
|
||||||
cy.contains("Unpublish").click()
|
// Exact match for Unpublish
|
||||||
|
cy.contains("Unpublish").click({ force: true })
|
||||||
cy.get(".spectrum-Modal").within(() => {
|
cy.get(".spectrum-Modal").within(() => {
|
||||||
cy.contains("Unpublish app").click()
|
cy.contains("Unpublish app").click({ force: true })
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
@ -276,16 +315,18 @@ Cypress.Commands.add("alterAppVersion", (appId, version) => {
|
||||||
})
|
})
|
||||||
|
|
||||||
Cypress.Commands.add("importApp", (exportFilePath, name) => {
|
Cypress.Commands.add("importApp", (exportFilePath, name) => {
|
||||||
cy.visit(`${Cypress.config().baseUrl}/builder`)
|
cy.visit(`${Cypress.config().baseUrl}/builder`, { timeout: 5000 })
|
||||||
|
|
||||||
cy.request(`${Cypress.config().baseUrl}/api/applications?status=all`)
|
cy.request(`${Cypress.config().baseUrl}/api/applications?status=all`)
|
||||||
.its("body")
|
.its("body")
|
||||||
.then(val => {
|
.then(val => {
|
||||||
if (val.length > 0) {
|
if (val.length > 0) {
|
||||||
cy.get(`[data-cy="create-app-btn"]`).click({ force: true })
|
cy.get(`[data-cy="create-app-btn"]`).click({ force: true })
|
||||||
cy.wait(500)
|
|
||||||
}
|
}
|
||||||
cy.get(`[data-cy="import-app-btn"]`).click({ force: true })
|
cy.wait(500)
|
||||||
|
cy.get(`[data-cy="import-app-btn"]`).click({
|
||||||
|
force: true,
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
cy.get(".spectrum-Modal").within(() => {
|
cy.get(".spectrum-Modal").within(() => {
|
||||||
|
@ -303,7 +344,7 @@ Cypress.Commands.add("importApp", (exportFilePath, name) => {
|
||||||
cy.get(".confirm-wrap button")
|
cy.get(".confirm-wrap button")
|
||||||
.should("not.be.disabled")
|
.should("not.be.disabled")
|
||||||
.click({ force: true })
|
.click({ force: true })
|
||||||
cy.wait(5000)
|
cy.wait(3000)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@ -332,7 +373,8 @@ Cypress.Commands.add("searchForApplication", appName => {
|
||||||
|
|
||||||
// Assumes there are no others
|
// Assumes there are no others
|
||||||
Cypress.Commands.add("applicationInAppTable", appName => {
|
Cypress.Commands.add("applicationInAppTable", appName => {
|
||||||
cy.get(".appTable").within(() => {
|
cy.visit(`${Cypress.config().baseUrl}/builder`, { timeout: 10000 })
|
||||||
|
cy.get(".appTable", { timeout: 2000 }).within(() => {
|
||||||
cy.get(".title").contains(appName).should("exist")
|
cy.get(".title").contains(appName).should("exist")
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
@ -360,7 +402,7 @@ Cypress.Commands.add("createTable", (tableName, initialTable) => {
|
||||||
cy.navigateToDataSection()
|
cy.navigateToDataSection()
|
||||||
cy.get(`[data-cy="new-table"]`).click()
|
cy.get(`[data-cy="new-table"]`).click()
|
||||||
}
|
}
|
||||||
cy.wait(5000)
|
cy.wait(2000)
|
||||||
cy.get(".item")
|
cy.get(".item")
|
||||||
.contains("Budibase DB")
|
.contains("Budibase DB")
|
||||||
.click({ force: true })
|
.click({ force: true })
|
||||||
|
@ -368,8 +410,7 @@ Cypress.Commands.add("createTable", (tableName, initialTable) => {
|
||||||
cy.get(".spectrum-Button").contains("Continue").click({ force: true })
|
cy.get(".spectrum-Button").contains("Continue").click({ force: true })
|
||||||
})
|
})
|
||||||
cy.get(".spectrum-Modal").within(() => {
|
cy.get(".spectrum-Modal").within(() => {
|
||||||
cy.wait(1000)
|
cy.get("input", { timeout: 1000 }).first().type(tableName).blur()
|
||||||
cy.get("input").first().type(tableName).blur()
|
|
||||||
cy.get(".spectrum-ButtonGroup").contains("Create").click()
|
cy.get(".spectrum-ButtonGroup").contains("Create").click()
|
||||||
})
|
})
|
||||||
cy.contains(tableName).should("be.visible")
|
cy.contains(tableName).should("be.visible")
|
||||||
|
@ -451,8 +492,7 @@ Cypress.Commands.add("addCustomSourceOptions", totalOptions => {
|
||||||
.contains("Add Option")
|
.contains("Add Option")
|
||||||
.click({ force: true })
|
.click({ force: true })
|
||||||
.then(() => {
|
.then(() => {
|
||||||
cy.wait(500)
|
cy.get("[placeholder='Label']", { timeout: 500 }).eq(i).type(i)
|
||||||
cy.get("[placeholder='Label']").eq(i).type(i)
|
|
||||||
cy.get("[placeholder='Value']").eq(i).type(i)
|
cy.get("[placeholder='Value']").eq(i).type(i)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -464,10 +504,14 @@ Cypress.Commands.add("addCustomSourceOptions", totalOptions => {
|
||||||
// DESIGN AREA
|
// DESIGN AREA
|
||||||
Cypress.Commands.add("addComponent", (category, component) => {
|
Cypress.Commands.add("addComponent", (category, component) => {
|
||||||
if (category) {
|
if (category) {
|
||||||
cy.get(`[data-cy="category-${category}"]`).click({ force: true })
|
cy.get(`[data-cy="category-${category}"]`, { timeout: 1000 }).click({
|
||||||
|
force: true,
|
||||||
|
})
|
||||||
}
|
}
|
||||||
if (component) {
|
if (component) {
|
||||||
cy.get(`[data-cy="component-${component}"]`).click({ force: true })
|
cy.get(`[data-cy="component-${component}"]`, { timeout: 1000 }).click({
|
||||||
|
force: true,
|
||||||
|
})
|
||||||
}
|
}
|
||||||
cy.wait(1000)
|
cy.wait(1000)
|
||||||
cy.location().then(loc => {
|
cy.location().then(loc => {
|
||||||
|
@ -496,15 +540,14 @@ Cypress.Commands.add("createScreen", (route, accessLevelLabel) => {
|
||||||
cy.get(".spectrum-Modal").within(() => {
|
cy.get(".spectrum-Modal").within(() => {
|
||||||
cy.get("[data-cy='blank-screen']").click()
|
cy.get("[data-cy='blank-screen']").click()
|
||||||
cy.get(".spectrum-Button").contains("Continue").click({ force: true })
|
cy.get(".spectrum-Button").contains("Continue").click({ force: true })
|
||||||
cy.wait(500)
|
|
||||||
})
|
})
|
||||||
cy.get(".spectrum-Dialog-grid").within(() => {
|
cy.wait(500)
|
||||||
|
cy.get(".spectrum-Dialog-grid", { timeout: 500 }).within(() => {
|
||||||
cy.get(".spectrum-Form-itemField").eq(0).type(route)
|
cy.get(".spectrum-Form-itemField").eq(0).type(route)
|
||||||
cy.get(".spectrum-Button").contains("Continue").click({ force: true })
|
cy.get(".confirm-wrap").contains("Continue").click({ force: true })
|
||||||
cy.wait(1000)
|
|
||||||
})
|
})
|
||||||
|
|
||||||
cy.get(".spectrum-Modal").within(() => {
|
cy.get(".spectrum-Modal", { timeout: 1000 }).within(() => {
|
||||||
if (accessLevelLabel) {
|
if (accessLevelLabel) {
|
||||||
cy.get(".spectrum-Picker-label").click()
|
cy.get(".spectrum-Picker-label").click()
|
||||||
cy.wait(500)
|
cy.wait(500)
|
||||||
|
@ -522,10 +565,12 @@ Cypress.Commands.add(
|
||||||
cy.get(".spectrum-Modal").within(() => {
|
cy.get(".spectrum-Modal").within(() => {
|
||||||
cy.get(".item").contains("Autogenerated screens").click()
|
cy.get(".item").contains("Autogenerated screens").click()
|
||||||
cy.get(".spectrum-Button").contains("Continue").click({ force: true })
|
cy.get(".spectrum-Button").contains("Continue").click({ force: true })
|
||||||
cy.wait(500)
|
|
||||||
})
|
})
|
||||||
cy.get(".spectrum-Modal [data-cy='data-source-modal']").within(() => {
|
cy.get(".spectrum-Modal [data-cy='data-source-modal']", {
|
||||||
|
timeout: 500,
|
||||||
|
}).within(() => {
|
||||||
for (let i = 0; i < datasourceNames.length; i++) {
|
for (let i = 0; i < datasourceNames.length; i++) {
|
||||||
|
cy.wait(500)
|
||||||
cy.get(".data-source-entry").contains(datasourceNames[i]).click()
|
cy.get(".data-source-entry").contains(datasourceNames[i]).click()
|
||||||
//Ensure the check mark is visible
|
//Ensure the check mark is visible
|
||||||
cy.get(".data-source-entry")
|
cy.get(".data-source-entry")
|
||||||
|
@ -574,7 +619,7 @@ Cypress.Commands.add(
|
||||||
// NAVIGATION
|
// NAVIGATION
|
||||||
Cypress.Commands.add("navigateToFrontend", () => {
|
Cypress.Commands.add("navigateToFrontend", () => {
|
||||||
// Clicks on Design tab and then the Home nav item
|
// Clicks on Design tab and then the Home nav item
|
||||||
cy.wait(1000)
|
cy.wait(500)
|
||||||
cy.contains("Design").click()
|
cy.contains("Design").click()
|
||||||
cy.get(".spectrum-Search").type("/")
|
cy.get(".spectrum-Search").type("/")
|
||||||
cy.get(".nav-item").contains("home").click()
|
cy.get(".nav-item").contains("home").click()
|
||||||
|
@ -606,11 +651,11 @@ Cypress.Commands.add("selectExternalDatasource", datasourceName => {
|
||||||
cy.get(".add-button").click()
|
cy.get(".add-button").click()
|
||||||
})
|
})
|
||||||
// Clicks specified datasource & continue
|
// Clicks specified datasource & continue
|
||||||
cy.wait(1000)
|
cy.get(".item-list", { timeout: 1000 }).contains(datasourceName).click()
|
||||||
cy.get(".item-list").contains(datasourceName).click()
|
|
||||||
cy.get(".spectrum-Dialog-grid").within(() => {
|
cy.get(".spectrum-Dialog-grid").within(() => {
|
||||||
cy.get(".spectrum-Button").contains("Continue").click({ force: true })
|
cy.get(".spectrum-Button").contains("Continue").click({ force: true })
|
||||||
})
|
})
|
||||||
|
cy.wait(500)
|
||||||
})
|
})
|
||||||
|
|
||||||
Cypress.Commands.add("addDatasourceConfig", (datasource, skipFetch) => {
|
Cypress.Commands.add("addDatasourceConfig", (datasource, skipFetch) => {
|
||||||
|
@ -618,8 +663,7 @@ Cypress.Commands.add("addDatasourceConfig", (datasource, skipFetch) => {
|
||||||
// Adds the config for specified datasource & fetches tables
|
// Adds the config for specified datasource & fetches tables
|
||||||
// Currently supports MySQL, PostgreSQL, Oracle
|
// Currently supports MySQL, PostgreSQL, Oracle
|
||||||
// Host IP Address
|
// Host IP Address
|
||||||
cy.wait(500)
|
cy.get(".spectrum-Dialog-grid", { timeout: 500 }).within(() => {
|
||||||
cy.get(".spectrum-Dialog-grid").within(() => {
|
|
||||||
cy.get(".form-row")
|
cy.get(".form-row")
|
||||||
.eq(0)
|
.eq(0)
|
||||||
.within(() => {
|
.within(() => {
|
||||||
|
@ -719,16 +763,18 @@ Cypress.Commands.add("addDatasourceConfig", (datasource, skipFetch) => {
|
||||||
Cypress.Commands.add("createRestQuery", (method, restUrl, queryPrettyName) => {
|
Cypress.Commands.add("createRestQuery", (method, restUrl, queryPrettyName) => {
|
||||||
// addExternalDatasource should be called prior to this
|
// addExternalDatasource should be called prior to this
|
||||||
// Configures REST datasource & sends query
|
// Configures REST datasource & sends query
|
||||||
cy.wait(1000)
|
cy.get(".spectrum-Button", { timeout: 1000 })
|
||||||
cy.get(".spectrum-Button").contains("Add query").click({ force: true })
|
.contains("Add query")
|
||||||
|
.click({ force: true })
|
||||||
// Select Method & add Rest URL
|
// Select Method & add Rest URL
|
||||||
cy.get(".spectrum-Picker-label").eq(1).click()
|
cy.get(".spectrum-Picker-label").eq(1).click()
|
||||||
cy.get(".spectrum-Menu").contains(method).click()
|
cy.get(".spectrum-Menu").contains(method).click()
|
||||||
cy.get("input").clear().type(restUrl)
|
cy.get("input").clear().type(restUrl)
|
||||||
// Send query
|
// Send query
|
||||||
cy.get(".spectrum-Button").contains("Send").click({ force: true })
|
cy.get(".spectrum-Button").contains("Send").click({ force: true })
|
||||||
cy.wait(500)
|
cy.get(".spectrum-Button", { timeout: 500 })
|
||||||
cy.get(".spectrum-Button").contains("Save").click({ force: true })
|
.contains("Save")
|
||||||
|
.click({ force: true })
|
||||||
cy.get(".hierarchy-items-container")
|
cy.get(".hierarchy-items-container")
|
||||||
.should("contain", method)
|
.should("contain", method)
|
||||||
.and("contain", queryPrettyName)
|
.and("contain", queryPrettyName)
|
||||||
|
|
|
@ -62,6 +62,7 @@ export const GLOBESTRIKE = "svg[aria-label=GlobeStrike]"
|
||||||
export const GLOBE = "svg[aria-label=Globe]"
|
export const GLOBE = "svg[aria-label=Globe]"
|
||||||
export const UNPUBLISH_MODAL = "[data-cy=unpublish-modal]"
|
export const UNPUBLISH_MODAL = "[data-cy=unpublish-modal]"
|
||||||
export const CONFIRM_WRAP_BUTTON = ".confirm-wrap button"
|
export const CONFIRM_WRAP_BUTTON = ".confirm-wrap button"
|
||||||
|
export const DEPLOYMENT_TOP_NAV = ".deployment-top-nav"
|
||||||
|
|
||||||
//changeAppiconAndColour
|
//changeAppiconAndColour
|
||||||
export const APP_ROW_ACTION = ".app-row-actions-icon"
|
export const APP_ROW_ACTION = ".app-row-actions-icon"
|
||||||
|
@ -97,13 +98,16 @@ export const ACTION_SPECTRUM_ICON = ".actions .spectrum-Icon"
|
||||||
export const SPECTRUM_MENU_CHILD2 = ".spectrum-Menu > :nth-child(2)"
|
export const SPECTRUM_MENU_CHILD2 = ".spectrum-Menu > :nth-child(2)"
|
||||||
export const DELETE_TABLE_CONFIRM = '[data-cy="delete-table-confirm"]'
|
export const DELETE_TABLE_CONFIRM = '[data-cy="delete-table-confirm"]'
|
||||||
|
|
||||||
//createUSerAndRoles
|
//adminAndManagement Folder
|
||||||
export const SPECTRUM_TABLE = ".spectrum-Table"
|
export const SPECTRUM_TABLE = ".spectrum-Table"
|
||||||
export const SPECTRUM_SIDENAV = ".spectrum-SideNav"
|
export const SPECTRUM_SIDENAV = ".spectrum-SideNav"
|
||||||
export const SPECTRUM_TABLE_ROW = ".spectrum-Table-row"
|
export const SPECTRUM_TABLE_ROW = ".spectrum-Table-row"
|
||||||
export const SPECTRUM_TABLE_CELL = ".spectrum-Table-cell"
|
export const SPECTRUM_TABLE_CELL = ".spectrum-Table-cell"
|
||||||
export const FIELD = ".field"
|
export const FIELD = ".field"
|
||||||
export const CONTAINER = ".container"
|
export const CONTAINER = ".container"
|
||||||
|
export const REGENERATE = ".regenerate"
|
||||||
|
export const SPECTRUM_DIALOG_CONTENT = ".spectrum-Dialog-content"
|
||||||
|
export const SPECTRUM_ICON = ".spectrum-Icon"
|
||||||
|
|
||||||
//createView
|
//createView
|
||||||
export const SPECTRUM_MENU_ITEM_LABEL = ".spectrum-Menu-itemLabel"
|
export const SPECTRUM_MENU_ITEM_LABEL = ".spectrum-Menu-itemLabel"
|
||||||
|
@ -113,14 +117,17 @@ export const TOP_RIGHT_NAV = ".toprightnav"
|
||||||
export const AREA_LABEL_REVERT = "[aria-label=Revert]"
|
export const AREA_LABEL_REVERT = "[aria-label=Revert]"
|
||||||
export const ROOT = ".root"
|
export const ROOT = ".root"
|
||||||
|
|
||||||
//quertLevelTransformers
|
//queryLevelTransformers
|
||||||
export const SPECTRUM_TABS_ITEM = ".spectrum-Tabs-itemLabel"
|
export const SPECTRUM_TABS_ITEM = ".spectrum-Tabs-itemLabel"
|
||||||
export const CODEMIRROR_TEXTAREA = ".CodeMirror textarea"
|
export const CODEMIRROR_TEXTAREA = ".CodeMirror textarea"
|
||||||
|
|
||||||
//renemaApplication
|
//renameApplication
|
||||||
export const WRAPPER = ".wrapper"
|
export const WRAPPER = ".wrapper"
|
||||||
export const ERROR = ".error"
|
export const ERROR = ".error"
|
||||||
export const AREA_LABEL_MORE = "[aria-label=More]"
|
export const AREA_LABEL_MORE = "[aria-label=More]"
|
||||||
export const APP_ROW_ACTION_MENU_POPOVER =
|
export const APP_ROW_ACTION_MENU_POPOVER =
|
||||||
'[data-cy="app-row-actions-menu-popover"]'
|
'[data-cy="app-row-actions-menu-popover"]'
|
||||||
export const SPECTRUM_MENU_ITEMM = ".spectrum-Menu-item"
|
export const SPECTRUM_MENU_ITEM = ".spectrum-Menu-item"
|
||||||
|
|
||||||
|
//commands
|
||||||
|
export const HOME_LOGO = ".home-logo"
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "@budibase/builder",
|
"name": "@budibase/builder",
|
||||||
"version": "1.0.212-alpha.0",
|
"version": "1.0.219-alpha.0",
|
||||||
"license": "GPL-3.0",
|
"license": "GPL-3.0",
|
||||||
"private": true,
|
"private": true,
|
||||||
"scripts": {
|
"scripts": {
|
||||||
|
@ -69,14 +69,15 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@budibase/bbui": "^1.0.212-alpha.0",
|
"@budibase/bbui": "^1.0.219-alpha.0",
|
||||||
"@budibase/client": "^1.0.212-alpha.0",
|
"@budibase/client": "^1.0.219-alpha.0",
|
||||||
"@budibase/frontend-core": "^1.0.212-alpha.0",
|
"@budibase/frontend-core": "^1.0.219-alpha.0",
|
||||||
"@budibase/string-templates": "^1.0.212-alpha.0",
|
"@budibase/string-templates": "^1.0.219-alpha.0",
|
||||||
"@sentry/browser": "5.19.1",
|
"@sentry/browser": "5.19.1",
|
||||||
"@spectrum-css/page": "^3.0.1",
|
"@spectrum-css/page": "^3.0.1",
|
||||||
"@spectrum-css/vars": "^3.0.1",
|
"@spectrum-css/vars": "^3.0.1",
|
||||||
"codemirror": "^5.59.0",
|
"codemirror": "^5.59.0",
|
||||||
|
"dayjs": "^1.11.2",
|
||||||
"downloadjs": "1.4.7",
|
"downloadjs": "1.4.7",
|
||||||
"lodash": "4.17.21",
|
"lodash": "4.17.21",
|
||||||
"posthog-js": "1.4.5",
|
"posthog-js": "1.4.5",
|
||||||
|
|
|
@ -53,7 +53,7 @@ export default class IntercomClient {
|
||||||
* @returns Intercom global object
|
* @returns Intercom global object
|
||||||
*/
|
*/
|
||||||
show(user = {}) {
|
show(user = {}) {
|
||||||
if (!this.initialised) return
|
if (!this.initialised || !user?.admin) return
|
||||||
|
|
||||||
return window.Intercom("boot", {
|
return window.Intercom("boot", {
|
||||||
app_id: this.token,
|
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.
|
* Gets the bindable properties exposed by a certain component.
|
||||||
*/
|
*/
|
||||||
|
@ -298,7 +387,6 @@ const getUserBindings = () => {
|
||||||
providerId: "user",
|
providerId: "user",
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
return bindings
|
return bindings
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -5,6 +5,7 @@ import { cloneDeep } from "lodash/fp"
|
||||||
|
|
||||||
const initialAutomationState = {
|
const initialAutomationState = {
|
||||||
automations: [],
|
automations: [],
|
||||||
|
showTestPanel: false,
|
||||||
blockDefinitions: {
|
blockDefinitions: {
|
||||||
TRIGGER: [],
|
TRIGGER: [],
|
||||||
ACTION: [],
|
ACTION: [],
|
||||||
|
@ -19,6 +20,17 @@ export const getAutomationStore = () => {
|
||||||
}
|
}
|
||||||
|
|
||||||
const automationActions = store => ({
|
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 () => {
|
fetch: async () => {
|
||||||
const responses = await Promise.all([
|
const responses = await Promise.all([
|
||||||
API.getAutomations(),
|
API.getAutomations(),
|
||||||
|
@ -109,6 +121,20 @@ const automationActions = store => ({
|
||||||
return state
|
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 => {
|
addTestDataToAutomation: data => {
|
||||||
store.update(state => {
|
store.update(state => {
|
||||||
state.selectedAutomation.addTestData(data)
|
state.selectedAutomation.addTestData(data)
|
||||||
|
@ -117,11 +143,10 @@ const automationActions = store => ({
|
||||||
},
|
},
|
||||||
addBlockToAutomation: (block, blockIdx) => {
|
addBlockToAutomation: (block, blockIdx) => {
|
||||||
store.update(state => {
|
store.update(state => {
|
||||||
const newBlock = state.selectedAutomation.addBlock(
|
state.selectedBlock = state.selectedAutomation.addBlock(
|
||||||
cloneDeep(block),
|
cloneDeep(block),
|
||||||
blockIdx
|
blockIdx
|
||||||
)
|
)
|
||||||
state.selectedBlock = newBlock
|
|
||||||
return state
|
return state
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
|
|
|
@ -65,7 +65,7 @@
|
||||||
<ActionButton
|
<ActionButton
|
||||||
disabled={!$automationStore.selectedAutomation?.testResults}
|
disabled={!$automationStore.selectedAutomation?.testResults}
|
||||||
on:click={() => {
|
on:click={() => {
|
||||||
$automationStore.selectedAutomation.automation.showTestPanel = true
|
$automationStore.showTestPanel = true
|
||||||
}}
|
}}
|
||||||
size="M">Test Details</ActionButton
|
size="M">Test Details</ActionButton
|
||||||
>
|
>
|
||||||
|
|
|
@ -1,6 +1,4 @@
|
||||||
<script>
|
<script>
|
||||||
import FlowItemHeader from "./FlowItemHeader.svelte"
|
|
||||||
|
|
||||||
import { automationStore } from "builderStore"
|
import { automationStore } from "builderStore"
|
||||||
import {
|
import {
|
||||||
Icon,
|
Icon,
|
||||||
|
@ -16,6 +14,7 @@
|
||||||
import AutomationBlockSetup from "../../SetupPanel/AutomationBlockSetup.svelte"
|
import AutomationBlockSetup from "../../SetupPanel/AutomationBlockSetup.svelte"
|
||||||
import CreateWebhookModal from "components/automation/Shared/CreateWebhookModal.svelte"
|
import CreateWebhookModal from "components/automation/Shared/CreateWebhookModal.svelte"
|
||||||
import ActionModal from "./ActionModal.svelte"
|
import ActionModal from "./ActionModal.svelte"
|
||||||
|
import FlowItemHeader from "./FlowItemHeader.svelte"
|
||||||
|
|
||||||
export let block
|
export let block
|
||||||
export let testDataModal
|
export let testDataModal
|
||||||
|
|
|
@ -7,12 +7,19 @@
|
||||||
export let blockComplete
|
export let blockComplete
|
||||||
export let showTestStatus = false
|
export let showTestStatus = false
|
||||||
export let showParameters = {}
|
export let showParameters = {}
|
||||||
|
export let testResult
|
||||||
|
export let isTrigger
|
||||||
|
|
||||||
$: testResult =
|
$: {
|
||||||
$automationStore.selectedAutomation?.testResults?.steps.filter(step =>
|
if (!testResult) {
|
||||||
block.id ? step.id === block.id : step.stepId === block.stepId
|
testResult =
|
||||||
)
|
$automationStore.selectedAutomation?.testResults?.steps.filter(step =>
|
||||||
$: isTrigger = block.type === "TRIGGER"
|
block.id ? step.id === block.id : step.stepId === block.stepId
|
||||||
|
)[0]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$: isTrigger = isTrigger || block.type === "TRIGGER"
|
||||||
|
$: status = updateStatus(testResult, isTrigger)
|
||||||
|
|
||||||
async function onSelect(block) {
|
async function onSelect(block) {
|
||||||
await automationStore.update(state => {
|
await automationStore.update(state => {
|
||||||
|
@ -20,6 +27,19 @@
|
||||||
return state
|
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>
|
</script>
|
||||||
|
|
||||||
<div class="blockSection">
|
<div class="blockSection">
|
||||||
|
@ -60,16 +80,13 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="blockTitle">
|
<div class="blockTitle">
|
||||||
{#if showTestStatus && testResult && testResult[0]}
|
{#if showTestStatus && testResult}
|
||||||
<div style="float: right;">
|
<div style="float: right;">
|
||||||
<StatusLight
|
<StatusLight
|
||||||
positive={isTrigger || testResult[0].outputs?.success}
|
positive={status?.positive}
|
||||||
negative={!testResult[0].outputs?.success}
|
yellow={status?.yellow}
|
||||||
><Body size="XS"
|
negative={status?.negative}
|
||||||
>{testResult[0].outputs?.success || isTrigger
|
><Body size="XS">{status?.message}</Body></StatusLight
|
||||||
? "Success"
|
|
||||||
: "Error"}</Body
|
|
||||||
></StatusLight
|
|
||||||
>
|
>
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
|
|
|
@ -51,7 +51,7 @@
|
||||||
$automationStore.selectedAutomation?.automation,
|
$automationStore.selectedAutomation?.automation,
|
||||||
testData
|
testData
|
||||||
)
|
)
|
||||||
$automationStore.selectedAutomation.automation.showTestPanel = true
|
$automationStore.showTestPanel = true
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
notifications.error("Error testing notification")
|
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>
|
<script>
|
||||||
import { Icon, Divider, Tabs, Tab, TextArea, Label } from "@budibase/bbui"
|
import { Icon, Divider } from "@budibase/bbui"
|
||||||
import FlowItemHeader from "./FlowChart/FlowItemHeader.svelte"
|
import TestDisplay from "./TestDisplay.svelte"
|
||||||
import { automationStore } from "builderStore"
|
import { automationStore } from "builderStore"
|
||||||
|
|
||||||
export let automation
|
export let automation
|
||||||
|
export let testResults
|
||||||
|
|
||||||
let showParameters
|
|
||||||
let blocks
|
let blocks
|
||||||
|
|
||||||
$: {
|
$: {
|
||||||
|
@ -17,13 +17,15 @@
|
||||||
blocks = blocks
|
blocks = blocks
|
||||||
.concat(automation.definition.steps || [])
|
.concat(automation.definition.steps || [])
|
||||||
.filter(x => x.stepId !== "LOOP")
|
.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>
|
</script>
|
||||||
|
|
||||||
<div class="title">
|
<div class="title">
|
||||||
|
@ -34,7 +36,7 @@
|
||||||
<div style="padding-right: var(--spacing-xl)">
|
<div style="padding-right: var(--spacing-xl)">
|
||||||
<Icon
|
<Icon
|
||||||
on:click={async () => {
|
on:click={async () => {
|
||||||
$automationStore.selectedAutomation.automation.showTestPanel = false
|
$automationStore.showTestPanel = false
|
||||||
}}
|
}}
|
||||||
hoverable
|
hoverable
|
||||||
name="Close"
|
name="Close"
|
||||||
|
@ -44,59 +46,9 @@
|
||||||
|
|
||||||
<Divider />
|
<Divider />
|
||||||
|
|
||||||
<div class="container">
|
<TestDisplay {automation} {testResults} />
|
||||||
{#each blocks as block, idx}
|
|
||||||
<div class="block">
|
|
||||||
{#if block.stepId !== "LOOP"}
|
|
||||||
<FlowItemHeader showTestStatus={true} bind:showParameters {block} />
|
|
||||||
{#if showParameters && showParameters[block.id]}
|
|
||||||
<Divider noMargin />
|
|
||||||
{#if testResults?.[idx]?.outputs.iterations}
|
|
||||||
<div style="display: flex; padding: 10px 10px 0px 12px;">
|
|
||||||
<Icon name="Reuse" />
|
|
||||||
<div style="margin-left: 10px;">
|
|
||||||
<Label>
|
|
||||||
This loop ran {testResults?.[idx]?.outputs.iterations} times.</Label
|
|
||||||
>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{/if}
|
|
||||||
|
|
||||||
<div class="tabs">
|
|
||||||
<Tabs quiet noPadding selected="Input">
|
|
||||||
<Tab title="Input">
|
|
||||||
<div style="padding: 10px 10px 10px 10px;">
|
|
||||||
<TextArea
|
|
||||||
minHeight="80px"
|
|
||||||
disabled
|
|
||||||
value={JSON.stringify(testResults?.[idx]?.inputs, null, 2)}
|
|
||||||
/>
|
|
||||||
</div></Tab
|
|
||||||
>
|
|
||||||
<Tab title="Output">
|
|
||||||
<div style="padding: 10px 10px 10px 10px;">
|
|
||||||
<TextArea
|
|
||||||
minHeight="100px"
|
|
||||||
disabled
|
|
||||||
value={JSON.stringify(testResults?.[idx]?.outputs, null, 2)}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</Tab>
|
|
||||||
</Tabs>
|
|
||||||
</div>
|
|
||||||
{/if}
|
|
||||||
{/if}
|
|
||||||
</div>
|
|
||||||
{#if blocks.length - 1 !== idx}
|
|
||||||
<div class="separator" />
|
|
||||||
{/if}
|
|
||||||
{/each}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
.container {
|
|
||||||
padding: 0px 30px 0px 30px;
|
|
||||||
}
|
|
||||||
.title {
|
.title {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: row;
|
flex-direction: row;
|
||||||
|
@ -106,15 +58,6 @@
|
||||||
justify-content: space-between;
|
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 {
|
.title-text {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: row;
|
flex-direction: row;
|
||||||
|
@ -124,23 +67,4 @@
|
||||||
.title :global(h1) {
|
.title :global(h1) {
|
||||||
flex: 1 1 auto;
|
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>
|
</style>
|
||||||
|
|
|
@ -22,10 +22,8 @@
|
||||||
RelationshipTypes,
|
RelationshipTypes,
|
||||||
ALLOWABLE_STRING_OPTIONS,
|
ALLOWABLE_STRING_OPTIONS,
|
||||||
ALLOWABLE_NUMBER_OPTIONS,
|
ALLOWABLE_NUMBER_OPTIONS,
|
||||||
ALLOWABLE_JSON_OPTIONS,
|
|
||||||
ALLOWABLE_STRING_TYPES,
|
ALLOWABLE_STRING_TYPES,
|
||||||
ALLOWABLE_NUMBER_TYPES,
|
ALLOWABLE_NUMBER_TYPES,
|
||||||
ALLOWABLE_JSON_TYPES,
|
|
||||||
SWITCHABLE_TYPES,
|
SWITCHABLE_TYPES,
|
||||||
} from "constants/backend"
|
} from "constants/backend"
|
||||||
import { getAutoColumnInformation, buildAutoColumn } from "builderStore/utils"
|
import { getAutoColumnInformation, buildAutoColumn } from "builderStore/utils"
|
||||||
|
@ -255,11 +253,6 @@
|
||||||
ALLOWABLE_NUMBER_TYPES.indexOf(field.type) !== -1
|
ALLOWABLE_NUMBER_TYPES.indexOf(field.type) !== -1
|
||||||
) {
|
) {
|
||||||
return ALLOWABLE_NUMBER_OPTIONS
|
return ALLOWABLE_NUMBER_OPTIONS
|
||||||
} else if (
|
|
||||||
originalName &&
|
|
||||||
ALLOWABLE_JSON_TYPES.indexOf(field.type) !== -1
|
|
||||||
) {
|
|
||||||
return ALLOWABLE_JSON_OPTIONS
|
|
||||||
} else if (!external) {
|
} else if (!external) {
|
||||||
return [
|
return [
|
||||||
...Object.values(fieldDefinitions),
|
...Object.values(fieldDefinitions),
|
||||||
|
|
|
@ -10,11 +10,31 @@
|
||||||
import KeyValueBuilder from "components/integration/KeyValueBuilder.svelte"
|
import KeyValueBuilder from "components/integration/KeyValueBuilder.svelte"
|
||||||
import RestAuthenticationBuilder from "./auth/RestAuthenticationBuilder.svelte"
|
import RestAuthenticationBuilder from "./auth/RestAuthenticationBuilder.svelte"
|
||||||
import ViewDynamicVariables from "./variables/ViewDynamicVariables.svelte"
|
import ViewDynamicVariables from "./variables/ViewDynamicVariables.svelte"
|
||||||
|
import {
|
||||||
|
getRestBindings,
|
||||||
|
readableToRuntimeBinding,
|
||||||
|
runtimeToReadableMap,
|
||||||
|
} from "builderStore/dataBinding"
|
||||||
|
import { cloneDeep } from "lodash/fp"
|
||||||
|
|
||||||
export let datasource
|
export let datasource
|
||||||
export let queries
|
export let queries
|
||||||
|
|
||||||
let addHeader
|
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>
|
</script>
|
||||||
|
|
||||||
<Divider size="S" />
|
<Divider size="S" />
|
||||||
|
@ -30,9 +50,10 @@
|
||||||
</Body>
|
</Body>
|
||||||
<KeyValueBuilder
|
<KeyValueBuilder
|
||||||
bind:this={addHeader}
|
bind:this={addHeader}
|
||||||
bind:object={datasource.config.defaultHeaders}
|
bind:object={parsedHeaders}
|
||||||
on:change
|
on:change={evt => onDefaultHeaderUpdate(evt.detail)}
|
||||||
noAddButton
|
noAddButton
|
||||||
|
bindings={getRestBindings()}
|
||||||
/>
|
/>
|
||||||
<div>
|
<div>
|
||||||
<ActionButton icon="Add" on:click={() => addHeader.addEntry()}>
|
<ActionButton icon="Add" on:click={() => addHeader.addEntry()}>
|
||||||
|
|
|
@ -2,6 +2,8 @@
|
||||||
import { onMount } from "svelte"
|
import { onMount } from "svelte"
|
||||||
import { ModalContent, Layout, Select, Body, Input } from "@budibase/bbui"
|
import { ModalContent, Layout, Select, Body, Input } from "@budibase/bbui"
|
||||||
import { AUTH_TYPE_LABELS, AUTH_TYPES } from "./authTypes"
|
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 configs
|
||||||
export let currentConfig
|
export let currentConfig
|
||||||
|
@ -203,11 +205,23 @@
|
||||||
/>
|
/>
|
||||||
{/if}
|
{/if}
|
||||||
{#if form.type === AUTH_TYPES.BEARER}
|
{#if form.type === AUTH_TYPES.BEARER}
|
||||||
<Input
|
<BindableCombobox
|
||||||
label="Token"
|
label="Token"
|
||||||
bind:value={form.bearer.token}
|
value={form.bearer.token}
|
||||||
on:change={onFieldChange}
|
bindings={getAuthBindings()}
|
||||||
on:blur={() => (blurred.bearer.token = true)}
|
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}
|
error={blurred.bearer.token ? errors.bearer.token : null}
|
||||||
/>
|
/>
|
||||||
{/if}
|
{/if}
|
||||||
|
|
|
@ -4,396 +4,40 @@
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<svg
|
<svg
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
version="1.1"
|
||||||
{width}
|
{width}
|
||||||
{height}
|
{height}
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
viewBox="-16 -16 150 150"
|
||||||
viewBox="0 0 1478.201 1195.111"
|
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)">
|
<g
|
||||||
<linearGradient
|
><path
|
||||||
id="a"
|
style="opacity:1"
|
||||||
gradientUnits="userSpaceOnUse"
|
fill="#f44336"
|
||||||
x1="-2901.952"
|
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"
|
||||||
y1="923.573"
|
/></g
|
||||||
x2="-2061.249"
|
>
|
||||||
y2="1420.331"
|
<g
|
||||||
gradientTransform="matrix(.1234 0 0 -.1234 1158.33 1550.273)"
|
><path
|
||||||
>
|
style="opacity:0.999"
|
||||||
<stop offset="0" stop-color="#909ca9" />
|
fill="#4caf4f"
|
||||||
<stop offset="1" stop-color="#ededee" />
|
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"
|
||||||
</linearGradient>
|
/></g
|
||||||
<path
|
>
|
||||||
fill="url(#a)"
|
<g
|
||||||
d="M1410.773 814.195l-286.9 93.683-249.599 110.161-69.829 18.435c-17.784
|
><path
|
||||||
16.916-36.431 34.049-56.599 51.397-22.119 19.082-42.72 36.433-58.553
|
style="opacity:1"
|
||||||
49.008-17.564 13.88-43.587 39.902-56.814 56.38-19.735 24.721-35.348
|
fill="#2095f3"
|
||||||
50.96-42.071 71.13-11.928 36.433-6.07 73.297 16.916 107.346 29.492 43.369
|
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"
|
||||||
88.261 87.606 156.785 117.749 34.916 15.4 93.683 35.132 137.92 46.19
|
/></g
|
||||||
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
|
<g
|
||||||
101.703-269.119 11.276-47.706 20.166-111.246 26.019-186.492 1.521-21.036
|
><path
|
||||||
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
|
style="opacity:1"
|
||||||
3.467-1.517 38.815-11.712l-7.153-16.912v-.005h.004zm-65.49 38.386c2.602 0
|
fill="#fec107"
|
||||||
9.539 66.573 11.273 108.646.436 8.89.216 14.745-.216 14.745-1.733
|
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"
|
||||||
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
|
/></g
|
||||||
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"
|
|
||||||
/>
|
|
||||||
</svg>
|
</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 options
|
||||||
export let allowJS = true
|
export let allowJS = true
|
||||||
export let appendBindingsAsOptions = true
|
export let appendBindingsAsOptions = true
|
||||||
|
export let error
|
||||||
|
|
||||||
const dispatch = createEventDispatcher()
|
const dispatch = createEventDispatcher()
|
||||||
let bindingDrawer
|
let bindingDrawer
|
||||||
|
@ -59,8 +60,10 @@
|
||||||
value={isJS ? "(JavaScript function)" : readableValue}
|
value={isJS ? "(JavaScript function)" : readableValue}
|
||||||
on:type={e => onChange(e.detail, false)}
|
on:type={e => onChange(e.detail, false)}
|
||||||
on:pick={e => onChange(e.detail, true)}
|
on:pick={e => onChange(e.detail, true)}
|
||||||
|
on:blur={() => dispatch("blur")}
|
||||||
{placeholder}
|
{placeholder}
|
||||||
options={allOptions}
|
options={allOptions}
|
||||||
|
{error}
|
||||||
/>
|
/>
|
||||||
{#if !disabled}
|
{#if !disabled}
|
||||||
<div
|
<div
|
||||||
|
@ -72,6 +75,7 @@
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<Drawer bind:this={bindingDrawer} {title}>
|
<Drawer bind:this={bindingDrawer} {title}>
|
||||||
<svelte:fragment slot="description">
|
<svelte:fragment slot="description">
|
||||||
Add the objects on the left to enrich your text.
|
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)
|
reviewPendingDeployments(deployments, newDeployments)
|
||||||
return newDeployments
|
return newDeployments
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
notifications.error("Error fetching deployment history")
|
notifications.error("Error fetching deployment overview")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -55,7 +55,7 @@
|
||||||
deployments = newDeployments
|
deployments = newDeployments
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
clearInterval(poll)
|
clearInterval(poll)
|
||||||
notifications.error("Error fetching deployment history")
|
notifications.error("Error fetching deployment overview")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -178,7 +178,7 @@
|
||||||
.column {
|
.column {
|
||||||
gap: var(--spacing-l);
|
gap: var(--spacing-l);
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: 20px 1fr 1fr auto auto;
|
grid-template-columns: 20px 1fr 1fr 16px 16px;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
border-radius: var(--border-radius-s);
|
border-radius: var(--border-radius-s);
|
||||||
transition: background-color ease-in-out 130ms;
|
transition: background-color ease-in-out 130ms;
|
||||||
|
|
|
@ -11,6 +11,7 @@
|
||||||
} from "@budibase/bbui"
|
} from "@budibase/bbui"
|
||||||
import { createEventDispatcher } from "svelte"
|
import { createEventDispatcher } from "svelte"
|
||||||
import { lowercase } from "helpers"
|
import { lowercase } from "helpers"
|
||||||
|
import DrawerBindableInput from "components/common/bindings/DrawerBindableInput.svelte"
|
||||||
|
|
||||||
let dispatch = createEventDispatcher()
|
let dispatch = createEventDispatcher()
|
||||||
|
|
||||||
|
@ -30,6 +31,7 @@
|
||||||
export let tooltip
|
export let tooltip
|
||||||
export let menuItems
|
export let menuItems
|
||||||
export let showMenu = false
|
export let showMenu = false
|
||||||
|
export let bindings = []
|
||||||
|
|
||||||
let fields = Object.entries(object || {}).map(([name, value]) => ({
|
let fields = Object.entries(object || {}).map(([name, value]) => ({
|
||||||
name,
|
name,
|
||||||
|
@ -108,6 +110,16 @@
|
||||||
/>
|
/>
|
||||||
{#if options}
|
{#if options}
|
||||||
<Select bind:value={field.value} on:change={changed} {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}
|
{:else}
|
||||||
<Input
|
<Input
|
||||||
placeholder={valuePlaceholder}
|
placeholder={valuePlaceholder}
|
||||||
|
|
|
@ -57,7 +57,8 @@
|
||||||
placeholder="Default"
|
placeholder="Default"
|
||||||
thin
|
thin
|
||||||
disabled={bindable}
|
disabled={bindable}
|
||||||
bind:value={binding.default}
|
on:change={evt => onBindingChange(binding.name, evt.detail)}
|
||||||
|
value={runtimeToReadableBinding(bindings, binding.default)}
|
||||||
/>
|
/>
|
||||||
{#if bindable}
|
{#if bindable}
|
||||||
<DrawerBindableInput
|
<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,218 @@
|
||||||
|
<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 () => {
|
||||||
|
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
|
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 = [
|
export const SWITCHABLE_TYPES = [
|
||||||
...ALLOWABLE_STRING_TYPES,
|
...ALLOWABLE_STRING_TYPES,
|
||||||
...ALLOWABLE_NUMBER_TYPES,
|
...ALLOWABLE_NUMBER_TYPES,
|
||||||
...ALLOWABLE_JSON_TYPES,
|
|
||||||
]
|
]
|
||||||
|
|
||||||
export const IntegrationTypes = {
|
export const IntegrationTypes = {
|
||||||
|
|
|
@ -0,0 +1,66 @@
|
||||||
|
import { writable } from "svelte/store"
|
||||||
|
|
||||||
|
function defaultValue() {
|
||||||
|
return {
|
||||||
|
nextPage: null,
|
||||||
|
page: undefined,
|
||||||
|
hasPrevPage: false,
|
||||||
|
hasNextPage: false,
|
||||||
|
loading: false,
|
||||||
|
pageNumber: 1,
|
||||||
|
pages: [],
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function createPaginationStore() {
|
||||||
|
const { subscribe, set, update } = writable(defaultValue())
|
||||||
|
|
||||||
|
function prevPage() {
|
||||||
|
update(state => {
|
||||||
|
state.pageNumber--
|
||||||
|
state.nextPage = state.pages.pop()
|
||||||
|
state.page = state.pages[state.pages.length - 1]
|
||||||
|
state.hasPrevPage = state.pageNumber > 1
|
||||||
|
return state
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
function nextPage() {
|
||||||
|
update(state => {
|
||||||
|
state.pageNumber++
|
||||||
|
state.page = state.nextPage
|
||||||
|
state.pages.push(state.page)
|
||||||
|
state.hasPrevPage = state.pageNumber > 1
|
||||||
|
return state
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
function fetched(hasNextPage, nextPage) {
|
||||||
|
update(state => {
|
||||||
|
state.hasNextPage = hasNextPage
|
||||||
|
state.nextPage = nextPage
|
||||||
|
state.loading = false
|
||||||
|
return state
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
function loading(loading = true) {
|
||||||
|
update(state => {
|
||||||
|
state.loading = loading
|
||||||
|
return state
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
function reset() {
|
||||||
|
set(defaultValue())
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
subscribe,
|
||||||
|
prevPage,
|
||||||
|
nextPage,
|
||||||
|
fetched,
|
||||||
|
loading,
|
||||||
|
reset,
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,7 +1,14 @@
|
||||||
<script>
|
<script>
|
||||||
import { store, automationStore } from "builderStore"
|
import { store, automationStore } from "builderStore"
|
||||||
import { roles, flags } from "stores/backend"
|
import { roles, flags } from "stores/backend"
|
||||||
import { Icon, ActionGroup, Tabs, Tab, notifications } from "@budibase/bbui"
|
import {
|
||||||
|
Icon,
|
||||||
|
ActionGroup,
|
||||||
|
Tabs,
|
||||||
|
Tab,
|
||||||
|
notifications,
|
||||||
|
Banner,
|
||||||
|
} from "@budibase/bbui"
|
||||||
import RevertModal from "components/deploy/RevertModal.svelte"
|
import RevertModal from "components/deploy/RevertModal.svelte"
|
||||||
import VersionModal from "components/deploy/VersionModal.svelte"
|
import VersionModal from "components/deploy/VersionModal.svelte"
|
||||||
import DeployNavigation from "components/deploy/DeployNavigation.svelte"
|
import DeployNavigation from "components/deploy/DeployNavigation.svelte"
|
||||||
|
@ -17,6 +24,7 @@
|
||||||
|
|
||||||
// Get Package and set store
|
// Get Package and set store
|
||||||
let promise = getPackage()
|
let promise = getPackage()
|
||||||
|
let betaAccess = false
|
||||||
|
|
||||||
// Sync once when you load the app
|
// Sync once when you load the app
|
||||||
let hasSynced = false
|
let hasSynced = false
|
||||||
|
@ -58,10 +66,18 @@
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function newDesignUi() {
|
||||||
|
await flags.toggleUiFeature("design_ui")
|
||||||
|
window.location.reload()
|
||||||
|
}
|
||||||
|
|
||||||
onMount(async () => {
|
onMount(async () => {
|
||||||
if (!hasSynced && application) {
|
if (!hasSynced && application) {
|
||||||
try {
|
try {
|
||||||
await API.syncApp(application)
|
await API.syncApp(application)
|
||||||
|
// check if user has beta access
|
||||||
|
const betaResponse = await API.checkBetaAccess($auth?.user?.email)
|
||||||
|
betaAccess = betaResponse.access
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
notifications.error("Failed to sync with production database")
|
notifications.error("Failed to sync with production database")
|
||||||
}
|
}
|
||||||
|
@ -79,6 +95,15 @@
|
||||||
<div class="loading" />
|
<div class="loading" />
|
||||||
{:then _}
|
{:then _}
|
||||||
<div class="root">
|
<div class="root">
|
||||||
|
{#if betaAccess}
|
||||||
|
<Banner
|
||||||
|
extraButtonText="Try New UI (Beta)"
|
||||||
|
extraButtonAction={newDesignUi}
|
||||||
|
>
|
||||||
|
Try the <b>all new</b> budibase design interface. (Not recommended for existing
|
||||||
|
budibase apps)
|
||||||
|
</Banner>
|
||||||
|
{/if}
|
||||||
<div class="top-nav">
|
<div class="top-nav">
|
||||||
<div class="topleftnav">
|
<div class="topleftnav">
|
||||||
<button class="home-logo">
|
<button class="home-logo">
|
||||||
|
|
|
@ -5,6 +5,7 @@
|
||||||
import CreateAutomationModal from "components/automation/AutomationPanel/CreateAutomationModal.svelte"
|
import CreateAutomationModal from "components/automation/AutomationPanel/CreateAutomationModal.svelte"
|
||||||
import CreateWebhookModal from "components/automation/Shared/CreateWebhookModal.svelte"
|
import CreateWebhookModal from "components/automation/Shared/CreateWebhookModal.svelte"
|
||||||
import TestPanel from "components/automation/AutomationBuilder/TestPanel.svelte"
|
import TestPanel from "components/automation/AutomationBuilder/TestPanel.svelte"
|
||||||
|
import { onMount } from "svelte"
|
||||||
|
|
||||||
$: automation =
|
$: automation =
|
||||||
$automationStore.selectedAutomation?.automation ||
|
$automationStore.selectedAutomation?.automation ||
|
||||||
|
@ -12,6 +13,9 @@
|
||||||
|
|
||||||
let modal
|
let modal
|
||||||
let webhookModal
|
let webhookModal
|
||||||
|
onMount(() => {
|
||||||
|
$automationStore.showTestPanel = false
|
||||||
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<!-- routify:options index=3 -->
|
<!-- routify:options index=3 -->
|
||||||
|
@ -45,7 +49,7 @@
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{#if automation?.showTestPanel}
|
{#if $automationStore.showTestPanel}
|
||||||
<div class="setup">
|
<div class="setup">
|
||||||
<TestPanel {automation} />
|
<TestPanel {automation} />
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -12,4 +12,6 @@
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<slot />
|
{#key $params.selectedDatasource}
|
||||||
|
<slot />
|
||||||
|
{/key}
|
||||||
|
|
|
@ -40,13 +40,39 @@
|
||||||
import { cloneDeep } from "lodash/fp"
|
import { cloneDeep } from "lodash/fp"
|
||||||
import { RawRestBodyTypes } from "constants/backend"
|
import { RawRestBodyTypes } from "constants/backend"
|
||||||
|
|
||||||
|
import {
|
||||||
|
getRestBindings,
|
||||||
|
toBindingsArray,
|
||||||
|
runtimeToReadableBinding,
|
||||||
|
readableToRuntimeBinding,
|
||||||
|
runtimeToReadableMap,
|
||||||
|
readableToRuntimeMap,
|
||||||
|
} from "builderStore/dataBinding"
|
||||||
|
|
||||||
let query, datasource
|
let query, datasource
|
||||||
let breakQs = {},
|
let breakQs = {},
|
||||||
bindings = {}
|
requestBindings = {}
|
||||||
let saveId, url
|
let saveId, url
|
||||||
let response, schema, enabledHeaders
|
let response, schema, enabledHeaders
|
||||||
let authConfigId
|
let authConfigId
|
||||||
let dynamicVariables, addVariableModal, varBinding
|
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
|
$: datasourceType = datasource?.source
|
||||||
$: integrationInfo = $integrations[datasourceType]
|
$: integrationInfo = $integrations[datasourceType]
|
||||||
|
@ -63,8 +89,10 @@
|
||||||
Object.keys(schema || {}).length !== 0 ||
|
Object.keys(schema || {}).length !== 0 ||
|
||||||
Object.keys(query?.schema || {}).length !== 0
|
Object.keys(query?.schema || {}).length !== 0
|
||||||
|
|
||||||
|
$: runtimeUrlQueries = readableToRuntimeMap(mergedBindings, breakQs)
|
||||||
|
|
||||||
function getSelectedQuery() {
|
function getSelectedQuery() {
|
||||||
return cloneDeep(
|
const cloneQuery = cloneDeep(
|
||||||
$queries.list.find(q => q._id === $queries.selected) || {
|
$queries.list.find(q => q._id === $queries.selected) || {
|
||||||
datasourceId: $params.selectedDatasource,
|
datasourceId: $params.selectedDatasource,
|
||||||
parameters: [],
|
parameters: [],
|
||||||
|
@ -76,6 +104,7 @@
|
||||||
queryVerb: "read",
|
queryVerb: "read",
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
return cloneQuery
|
||||||
}
|
}
|
||||||
|
|
||||||
function checkQueryName(inputUrl = null) {
|
function checkQueryName(inputUrl = null) {
|
||||||
|
@ -89,7 +118,9 @@
|
||||||
if (!base) {
|
if (!base) {
|
||||||
return base
|
return base
|
||||||
}
|
}
|
||||||
const qs = restUtils.buildQueryString(qsObj)
|
const qs = restUtils.buildQueryString(
|
||||||
|
runtimeToReadableMap(mergedBindings, qsObj)
|
||||||
|
)
|
||||||
let newUrl = base
|
let newUrl = base
|
||||||
if (base.includes("?")) {
|
if (base.includes("?")) {
|
||||||
newUrl = base.split("?")[0]
|
newUrl = base.split("?")[0]
|
||||||
|
@ -98,14 +129,21 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
function buildQuery() {
|
function buildQuery() {
|
||||||
const newQuery = { ...query }
|
const newQuery = cloneDeep(query)
|
||||||
const queryString = restUtils.buildQueryString(breakQs)
|
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.path = url.split("?")[0]
|
||||||
newQuery.fields.queryString = queryString
|
newQuery.fields.queryString = queryString
|
||||||
newQuery.fields.authConfigId = authConfigId
|
newQuery.fields.authConfigId = authConfigId
|
||||||
newQuery.fields.disabledHeaders = restUtils.flipHeaderState(enabledHeaders)
|
newQuery.fields.disabledHeaders = restUtils.flipHeaderState(enabledHeaders)
|
||||||
newQuery.schema = restUtils.fieldsToSchema(schema)
|
newQuery.schema = restUtils.fieldsToSchema(schema)
|
||||||
newQuery.parameters = restUtils.keyValueToQueryParameters(bindings)
|
|
||||||
return newQuery
|
return newQuery
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -120,6 +158,13 @@
|
||||||
datasource.config.dynamicVariables = rebuildVariables(saveId)
|
datasource.config.dynamicVariables = rebuildVariables(saveId)
|
||||||
datasource = await datasources.save(datasource)
|
datasource = await datasources.save(datasource)
|
||||||
}
|
}
|
||||||
|
prettifyQueryRequestBody(
|
||||||
|
query,
|
||||||
|
requestBindings,
|
||||||
|
dynamicVariables,
|
||||||
|
staticVariables,
|
||||||
|
restBindings
|
||||||
|
)
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
notifications.error(`Error saving query`)
|
notifications.error(`Error saving query`)
|
||||||
}
|
}
|
||||||
|
@ -127,7 +172,7 @@
|
||||||
|
|
||||||
async function runQuery() {
|
async function runQuery() {
|
||||||
try {
|
try {
|
||||||
response = await queries.preview(buildQuery(query))
|
response = await queries.preview(buildQuery())
|
||||||
if (response.rows.length === 0) {
|
if (response.rows.length === 0) {
|
||||||
notifications.info("Request did not return any data")
|
notifications.info("Request did not return any data")
|
||||||
} else {
|
} 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 () => {
|
onMount(async () => {
|
||||||
query = getSelectedQuery()
|
query = getSelectedQuery()
|
||||||
|
|
||||||
|
@ -250,6 +325,8 @@
|
||||||
const datasourceUrl = datasource?.config.url
|
const datasourceUrl = datasource?.config.url
|
||||||
const qs = query?.fields.queryString
|
const qs = query?.fields.queryString
|
||||||
breakQs = restUtils.breakQueryString(qs)
|
breakQs = restUtils.breakQueryString(qs)
|
||||||
|
breakQs = runtimeToReadableMap(mergedBindings, breakQs)
|
||||||
|
|
||||||
const path = query.fields.path
|
const path = query.fields.path
|
||||||
if (
|
if (
|
||||||
datasourceUrl &&
|
datasourceUrl &&
|
||||||
|
@ -260,7 +337,7 @@
|
||||||
}
|
}
|
||||||
url = buildUrl(query.fields.path, breakQs)
|
url = buildUrl(query.fields.path, breakQs)
|
||||||
schema = restUtils.schemaToFields(query.schema)
|
schema = restUtils.schemaToFields(query.schema)
|
||||||
bindings = restUtils.queryParametersToKeyValue(query.parameters)
|
requestBindings = restUtils.queryParametersToKeyValue(query.parameters)
|
||||||
authConfigId = getAuthConfigId()
|
authConfigId = getAuthConfigId()
|
||||||
if (!query.fields.disabledHeaders) {
|
if (!query.fields.disabledHeaders) {
|
||||||
query.fields.disabledHeaders = {}
|
query.fields.disabledHeaders = {}
|
||||||
|
@ -291,6 +368,14 @@
|
||||||
query.fields.pagination = {}
|
query.fields.pagination = {}
|
||||||
}
|
}
|
||||||
dynamicVariables = getDynamicVariables(datasource, query._id)
|
dynamicVariables = getDynamicVariables(datasource, query._id)
|
||||||
|
|
||||||
|
prettifyQueryRequestBody(
|
||||||
|
query,
|
||||||
|
requestBindings,
|
||||||
|
dynamicVariables,
|
||||||
|
staticVariables,
|
||||||
|
restBindings
|
||||||
|
)
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
@ -344,16 +429,26 @@
|
||||||
<Tabs selected="Bindings" quiet noPadding noHorizPadding onTop>
|
<Tabs selected="Bindings" quiet noPadding noHorizPadding onTop>
|
||||||
<Tab title="Bindings">
|
<Tab title="Bindings">
|
||||||
<KeyValueBuilder
|
<KeyValueBuilder
|
||||||
bind:object={bindings}
|
bind:object={requestBindings}
|
||||||
tooltip="Set the name of the binding which can be used in Handlebars statements throughout your query"
|
tooltip="Set the name of the binding which can be used in Handlebars statements throughout your query"
|
||||||
name="binding"
|
name="binding"
|
||||||
headings
|
headings
|
||||||
keyPlaceholder="Binding name"
|
keyPlaceholder="Binding name"
|
||||||
valuePlaceholder="Default"
|
valuePlaceholder="Default"
|
||||||
|
bindings={[
|
||||||
|
...restBindings,
|
||||||
|
...dynamicRequestBindings,
|
||||||
|
...dataSourceStaticBindings,
|
||||||
|
]}
|
||||||
/>
|
/>
|
||||||
</Tab>
|
</Tab>
|
||||||
<Tab title="Params">
|
<Tab title="Params">
|
||||||
<KeyValueBuilder bind:object={breakQs} name="param" headings />
|
<KeyValueBuilder
|
||||||
|
bind:object={breakQs}
|
||||||
|
name="param"
|
||||||
|
headings
|
||||||
|
bindings={mergedBindings}
|
||||||
|
/>
|
||||||
</Tab>
|
</Tab>
|
||||||
<Tab title="Headers">
|
<Tab title="Headers">
|
||||||
<KeyValueBuilder
|
<KeyValueBuilder
|
||||||
|
@ -362,6 +457,7 @@
|
||||||
toggle
|
toggle
|
||||||
name="header"
|
name="header"
|
||||||
headings
|
headings
|
||||||
|
bindings={mergedBindings}
|
||||||
/>
|
/>
|
||||||
</Tab>
|
</Tab>
|
||||||
<Tab title="Body">
|
<Tab title="Body">
|
||||||
|
|
|
@ -7,6 +7,7 @@
|
||||||
Modal,
|
Modal,
|
||||||
Page,
|
Page,
|
||||||
notifications,
|
notifications,
|
||||||
|
Notification,
|
||||||
Body,
|
Body,
|
||||||
Search,
|
Search,
|
||||||
} from "@budibase/bbui"
|
} from "@budibase/bbui"
|
||||||
|
@ -37,6 +38,7 @@
|
||||||
let searchTerm = ""
|
let searchTerm = ""
|
||||||
let cloud = $admin.cloud
|
let cloud = $admin.cloud
|
||||||
let creatingFromTemplate = false
|
let creatingFromTemplate = false
|
||||||
|
let automationErrors
|
||||||
|
|
||||||
const resolveWelcomeMessage = (auth, apps) => {
|
const resolveWelcomeMessage = (auth, apps) => {
|
||||||
const userWelcome = auth?.user?.firstName
|
const userWelcome = auth?.user?.firstName
|
||||||
|
@ -59,7 +61,8 @@
|
||||||
)
|
)
|
||||||
|
|
||||||
$: lockedApps = filteredApps.filter(app => app?.lockedYou || app?.lockedOther)
|
$: 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 enrichApps = (apps, user, sortBy) => {
|
||||||
const enrichedApps = apps.map(app => ({
|
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 = () => {
|
const initiateAppCreation = () => {
|
||||||
if ($apps?.length) {
|
if ($apps?.length) {
|
||||||
$goto("/builder/portal/apps/create")
|
$goto("/builder/portal/apps/create")
|
||||||
|
@ -208,6 +241,23 @@
|
||||||
<Page wide>
|
<Page wide>
|
||||||
<Layout noPadding gap="M">
|
<Layout noPadding gap="M">
|
||||||
{#if loaded}
|
{#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="title">
|
||||||
<div class="welcome">
|
<div class="welcome">
|
||||||
<Layout noPadding gap="XS">
|
<Layout noPadding gap="XS">
|
||||||
|
|
|
@ -10,7 +10,9 @@
|
||||||
} from "@budibase/bbui"
|
} from "@budibase/bbui"
|
||||||
import { createValidationStore, emailValidator } from "helpers/validation"
|
import { createValidationStore, emailValidator } from "helpers/validation"
|
||||||
import { users } from "stores/portal"
|
import { users } from "stores/portal"
|
||||||
|
import { createEventDispatcher } from "svelte"
|
||||||
|
|
||||||
|
const dispatch = createEventDispatcher()
|
||||||
const password = Math.random().toString(36).substring(2, 22)
|
const password = Math.random().toString(36).substring(2, 22)
|
||||||
const options = ["Email onboarding", "Basic onboarding"]
|
const options = ["Email onboarding", "Basic onboarding"]
|
||||||
const [email, error, touched] = createValidationStore("", emailValidator)
|
const [email, error, touched] = createValidationStore("", emailValidator)
|
||||||
|
@ -39,6 +41,7 @@
|
||||||
forceResetPassword: true,
|
forceResetPassword: true,
|
||||||
})
|
})
|
||||||
notifications.success("Successfully created user")
|
notifications.success("Successfully created user")
|
||||||
|
dispatch("created")
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
notifications.error("Error creating user")
|
notifications.error("Error creating user")
|
||||||
}
|
}
|
||||||
|
|
|
@ -12,41 +12,46 @@
|
||||||
Layout,
|
Layout,
|
||||||
Modal,
|
Modal,
|
||||||
notifications,
|
notifications,
|
||||||
|
Pagination,
|
||||||
} from "@budibase/bbui"
|
} from "@budibase/bbui"
|
||||||
import TagsRenderer from "./_components/TagsTableRenderer.svelte"
|
import TagsRenderer from "./_components/TagsTableRenderer.svelte"
|
||||||
import AddUserModal from "./_components/AddUserModal.svelte"
|
import AddUserModal from "./_components/AddUserModal.svelte"
|
||||||
import { users } from "stores/portal"
|
import { users } from "stores/portal"
|
||||||
import { onMount } from "svelte"
|
import { createPaginationStore } from "helpers/pagination"
|
||||||
|
|
||||||
const schema = {
|
const schema = {
|
||||||
email: {},
|
email: {},
|
||||||
developmentAccess: { displayName: "Development Access", type: "boolean" },
|
developmentAccess: { displayName: "Development Access", type: "boolean" },
|
||||||
adminAccess: { displayName: "Admin Access", type: "boolean" },
|
adminAccess: { displayName: "Admin Access", type: "boolean" },
|
||||||
// role: { type: "options" },
|
|
||||||
group: {},
|
group: {},
|
||||||
// access: {},
|
|
||||||
// group: {}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
let search
|
let pageInfo = createPaginationStore()
|
||||||
$: filteredUsers = $users
|
let prevSearch = undefined,
|
||||||
.filter(user => user.email.includes(search || ""))
|
search = undefined
|
||||||
.map(user => ({
|
$: page = $pageInfo.page
|
||||||
...user,
|
$: fetchUsers(page, search)
|
||||||
group: ["All users"],
|
|
||||||
developmentAccess: !!user.builder?.global,
|
|
||||||
adminAccess: !!user.admin?.global,
|
|
||||||
}))
|
|
||||||
|
|
||||||
let createUserModal
|
let createUserModal
|
||||||
|
|
||||||
onMount(async () => {
|
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 {
|
try {
|
||||||
await users.init()
|
pageInfo.loading()
|
||||||
|
await users.search({ page, search })
|
||||||
|
pageInfo.fetched($users.hasNextPage, $users.nextPage)
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
notifications.error("Error getting user list")
|
notifications.error("Error getting user list")
|
||||||
}
|
}
|
||||||
})
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<Layout noPadding>
|
<Layout noPadding>
|
||||||
|
@ -75,17 +80,31 @@
|
||||||
<Table
|
<Table
|
||||||
on:click={({ detail }) => $goto(`./${detail._id}`)}
|
on:click={({ detail }) => $goto(`./${detail._id}`)}
|
||||||
{schema}
|
{schema}
|
||||||
data={filteredUsers || $users}
|
data={$users.data}
|
||||||
allowEditColumns={false}
|
allowEditColumns={false}
|
||||||
allowEditRows={false}
|
allowEditRows={false}
|
||||||
allowSelectRows={false}
|
allowSelectRows={false}
|
||||||
customRenderers={[{ column: "group", component: TagsRenderer }]}
|
customRenderers={[{ column: "group", component: TagsRenderer }]}
|
||||||
/>
|
/>
|
||||||
|
<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>
|
||||||
</Layout>
|
</Layout>
|
||||||
</Layout>
|
</Layout>
|
||||||
|
|
||||||
<Modal bind:this={createUserModal}>
|
<Modal bind:this={createUserModal}>
|
||||||
<AddUserModal />
|
<AddUserModal
|
||||||
|
on:created={async () => {
|
||||||
|
pageInfo.reset()
|
||||||
|
await fetchUsers()
|
||||||
|
}}
|
||||||
|
/>
|
||||||
</Modal>
|
</Modal>
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
|
|
|
@ -27,6 +27,7 @@
|
||||||
import AppLockModal from "components/common/AppLockModal.svelte"
|
import AppLockModal from "components/common/AppLockModal.svelte"
|
||||||
import EditableIcon from "components/common/EditableIcon.svelte"
|
import EditableIcon from "components/common/EditableIcon.svelte"
|
||||||
import ConfirmDialog from "components/common/ConfirmDialog.svelte"
|
import ConfirmDialog from "components/common/ConfirmDialog.svelte"
|
||||||
|
import HistoryTab from "components/portal/overview/automation/HistoryTab.svelte"
|
||||||
import { checkIncomingDeploymentStatus } from "components/deploy/utils"
|
import { checkIncomingDeploymentStatus } from "components/deploy/utils"
|
||||||
import { onDestroy, onMount } from "svelte"
|
import { onDestroy, onMount } from "svelte"
|
||||||
|
|
||||||
|
@ -187,6 +188,10 @@
|
||||||
})
|
})
|
||||||
|
|
||||||
onMount(async () => {
|
onMount(async () => {
|
||||||
|
const params = new URLSearchParams(window.location.search)
|
||||||
|
if (params.get("tab")) {
|
||||||
|
selectedTab = params.get("tab")
|
||||||
|
}
|
||||||
try {
|
try {
|
||||||
if (!apps.length) {
|
if (!apps.length) {
|
||||||
await apps.load()
|
await apps.load()
|
||||||
|
@ -211,7 +216,7 @@
|
||||||
<ProgressCircle size="XL" />
|
<ProgressCircle size="XL" />
|
||||||
</div>
|
</div>
|
||||||
{:then _}
|
{:then _}
|
||||||
<Layout paddingX="XXL" paddingY="XXL" gap="XL">
|
<Layout paddingX="XXL" paddingY="XL" gap="L">
|
||||||
<span class="page-header" class:loaded>
|
<span class="page-header" class:loaded>
|
||||||
<ActionButton secondary icon={"ArrowLeft"} on:click={backToAppList}>
|
<ActionButton secondary icon={"ArrowLeft"} on:click={backToAppList}>
|
||||||
Back
|
Back
|
||||||
|
@ -299,10 +304,10 @@
|
||||||
on:unpublish={e => unpublishApp(e.detail)}
|
on:unpublish={e => unpublishApp(e.detail)}
|
||||||
/>
|
/>
|
||||||
</Tab>
|
</Tab>
|
||||||
|
<Tab title="Automation History">
|
||||||
|
<HistoryTab app={selectedApp} />
|
||||||
|
</Tab>
|
||||||
{#if false}
|
{#if false}
|
||||||
<Tab title="Automation History">
|
|
||||||
<div class="container">Automation History contents</div>
|
|
||||||
</Tab>
|
|
||||||
<Tab title="Backups">
|
<Tab title="Backups">
|
||||||
<div class="container">Backups contents</div>
|
<div class="container">Backups contents</div>
|
||||||
</Tab>
|
</Tab>
|
||||||
|
|
|
@ -1,14 +1,7 @@
|
||||||
<script>
|
<script>
|
||||||
import DashCard from "components/common/DashCard.svelte"
|
import DashCard from "components/common/DashCard.svelte"
|
||||||
import { AppStatus } from "constants"
|
import { AppStatus } from "constants"
|
||||||
import {
|
import { Icon, Heading, Link, Avatar, Layout } from "@budibase/bbui"
|
||||||
Icon,
|
|
||||||
Heading,
|
|
||||||
Link,
|
|
||||||
Avatar,
|
|
||||||
notifications,
|
|
||||||
Layout,
|
|
||||||
} from "@budibase/bbui"
|
|
||||||
import { store } from "builderStore"
|
import { store } from "builderStore"
|
||||||
import clientPackage from "@budibase/client/package.json"
|
import clientPackage from "@budibase/client/package.json"
|
||||||
import { processStringSync } from "@budibase/string-templates"
|
import { processStringSync } from "@budibase/string-templates"
|
||||||
|
@ -20,29 +13,22 @@
|
||||||
export let navigateTab
|
export let navigateTab
|
||||||
const dispatch = createEventDispatcher()
|
const dispatch = createEventDispatcher()
|
||||||
|
|
||||||
const userInit = async () => {
|
|
||||||
try {
|
|
||||||
await users.init()
|
|
||||||
} catch (error) {
|
|
||||||
notifications.error("Error getting user list")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const unpublishApp = () => {
|
const unpublishApp = () => {
|
||||||
dispatch("unpublish", app)
|
dispatch("unpublish", app)
|
||||||
}
|
}
|
||||||
|
|
||||||
let userPromise = userInit()
|
let appEditor, appEditorPromise
|
||||||
|
|
||||||
$: updateAvailable = clientPackage.version !== $store.version
|
$: updateAvailable = clientPackage.version !== $store.version
|
||||||
$: isPublished = app && app?.status === AppStatus.DEPLOYED
|
$: isPublished = app && app?.status === AppStatus.DEPLOYED
|
||||||
$: appEditorId = !app?.updatedBy ? $auth.user._id : app?.updatedBy
|
$: appEditorId = !app?.updatedBy ? $auth.user._id : app?.updatedBy
|
||||||
$: appEditorText = appEditor?.firstName || appEditor?.email
|
$: appEditorText = appEditor?.firstName || appEditor?.email
|
||||||
$: filteredUsers = !appEditorId
|
$: fetchAppEditor(appEditorId)
|
||||||
? []
|
|
||||||
: $users.filter(user => user._id === appEditorId)
|
|
||||||
|
|
||||||
$: appEditor = filteredUsers.length ? filteredUsers[0] : null
|
async function fetchAppEditor(editorId) {
|
||||||
|
appEditorPromise = users.get(editorId)
|
||||||
|
appEditor = await appEditorPromise
|
||||||
|
}
|
||||||
|
|
||||||
const getInitials = user => {
|
const getInitials = user => {
|
||||||
let initials = ""
|
let initials = ""
|
||||||
|
@ -90,7 +76,7 @@
|
||||||
</DashCard>
|
</DashCard>
|
||||||
<DashCard title={"Last Edited"} dataCy={"edited-by"}>
|
<DashCard title={"Last Edited"} dataCy={"edited-by"}>
|
||||||
<div class="last-edited-content">
|
<div class="last-edited-content">
|
||||||
{#await userPromise}
|
{#await appEditorPromise}
|
||||||
<Avatar size="M" initials={"-"} />
|
<Avatar size="M" initials={"-"} />
|
||||||
{:then _}
|
{:then _}
|
||||||
<div class="updated-by">
|
<div class="updated-by">
|
||||||
|
@ -211,6 +197,7 @@
|
||||||
.overview-tab .top {
|
.overview-tab .top {
|
||||||
grid-template-columns: 1fr 1fr;
|
grid-template-columns: 1fr 1fr;
|
||||||
}
|
}
|
||||||
|
|
||||||
.overview-tab .bottom {
|
.overview-tab .bottom {
|
||||||
grid-template-columns: 1fr;
|
grid-template-columns: 1fr;
|
||||||
}
|
}
|
||||||
|
@ -228,29 +215,35 @@
|
||||||
align-items: center;
|
align-items: center;
|
||||||
gap: var(--spacing-m);
|
gap: var(--spacing-m);
|
||||||
}
|
}
|
||||||
|
|
||||||
.status-text,
|
.status-text,
|
||||||
.last-edit-text {
|
.last-edit-text {
|
||||||
color: var(--spectrum-global-color-gray-600);
|
color: var(--spectrum-global-color-gray-600);
|
||||||
}
|
}
|
||||||
|
|
||||||
.updated-by {
|
.updated-by {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
gap: var(--spacing-m);
|
gap: var(--spacing-m);
|
||||||
}
|
}
|
||||||
|
|
||||||
.succeeded :global(.icon) {
|
.succeeded :global(.icon) {
|
||||||
color: var(--spectrum-global-color-green-600);
|
color: var(--spectrum-global-color-green-600);
|
||||||
}
|
}
|
||||||
|
|
||||||
.failed :global(.icon) {
|
.failed :global(.icon) {
|
||||||
color: var(
|
color: var(
|
||||||
--spectrum-semantic-negative-color-default,
|
--spectrum-semantic-negative-color-default,
|
||||||
var(--spectrum-global-color-red-500)
|
var(--spectrum-global-color-red-500)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
.metric-info {
|
.metric-info {
|
||||||
display: flex;
|
display: flex;
|
||||||
gap: var(--spacing-l);
|
gap: var(--spacing-l);
|
||||||
margin-top: var(--spacing-s);
|
margin-top: var(--spacing-s);
|
||||||
}
|
}
|
||||||
|
|
||||||
.version-status,
|
.version-status,
|
||||||
.last-edit-text,
|
.last-edit-text,
|
||||||
.status-text {
|
.status-text {
|
||||||
|
|
|
@ -16,6 +16,9 @@ export function createFlagsStore() {
|
||||||
})
|
})
|
||||||
await actions.fetch()
|
await actions.fetch()
|
||||||
},
|
},
|
||||||
|
toggleUiFeature: async feature => {
|
||||||
|
await API.toggleUiFeature({ value: feature })
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
|
|
@ -64,6 +64,8 @@ export function createAuthStore() {
|
||||||
name: user.account?.name,
|
name: user.account?.name,
|
||||||
user_id: user._id,
|
user_id: user._id,
|
||||||
tenant: user.tenantId,
|
tenant: user.tenantId,
|
||||||
|
admin: user?.admin?.global,
|
||||||
|
builder: user?.builder?.global,
|
||||||
"Company size": user.account?.size,
|
"Company size": user.account?.size,
|
||||||
"Job role": user.account?.profession,
|
"Job role": user.account?.profession,
|
||||||
})
|
})
|
||||||
|
|
|
@ -3,11 +3,24 @@ import { API } from "api"
|
||||||
import { update } from "lodash"
|
import { update } from "lodash"
|
||||||
|
|
||||||
export function createUsersStore() {
|
export function createUsersStore() {
|
||||||
const { subscribe, set } = writable([])
|
const { subscribe, set } = writable({})
|
||||||
|
|
||||||
async function init() {
|
// opts can contain page and search params
|
||||||
const users = await API.getUsers()
|
async function search(opts = {}) {
|
||||||
set(users)
|
const paged = await API.searchUsers(opts)
|
||||||
|
set({
|
||||||
|
...paged,
|
||||||
|
...opts,
|
||||||
|
})
|
||||||
|
return paged
|
||||||
|
}
|
||||||
|
|
||||||
|
async function get(userId) {
|
||||||
|
try {
|
||||||
|
return await API.getUser(userId)
|
||||||
|
} catch (err) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function invite({ email, builder, admin }) {
|
async function invite({ email, builder, admin }) {
|
||||||
|
@ -47,7 +60,8 @@ export function createUsersStore() {
|
||||||
body.admin = { global: true }
|
body.admin = { global: true }
|
||||||
}
|
}
|
||||||
await API.saveUser(body)
|
await API.saveUser(body)
|
||||||
await init()
|
// re-search from first page
|
||||||
|
await search()
|
||||||
}
|
}
|
||||||
|
|
||||||
async function del(id) {
|
async function del(id) {
|
||||||
|
@ -61,7 +75,8 @@ export function createUsersStore() {
|
||||||
|
|
||||||
return {
|
return {
|
||||||
subscribe,
|
subscribe,
|
||||||
init,
|
search,
|
||||||
|
get,
|
||||||
invite,
|
invite,
|
||||||
acceptInvite,
|
acceptInvite,
|
||||||
create,
|
create,
|
||||||
|
|
|
@ -2467,6 +2467,11 @@ dayjs@^1.10.4:
|
||||||
resolved "https://registry.yarnpkg.com/dayjs/-/dayjs-1.10.7.tgz#2cf5f91add28116748440866a0a1d26f3a6ce468"
|
resolved "https://registry.yarnpkg.com/dayjs/-/dayjs-1.10.7.tgz#2cf5f91add28116748440866a0a1d26f3a6ce468"
|
||||||
integrity sha512-P6twpd70BcPK34K26uJ1KT3wlhpuOAPoMwJzpsIWUxHZ7wpmbdZL/hQqBDfz7hGurYSa5PhzdhDHtt319hL3ig==
|
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:
|
debug@4, debug@4.3.2, debug@^4.1.0, debug@^4.1.1, debug@^4.3.2:
|
||||||
version "4.3.2"
|
version "4.3.2"
|
||||||
resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.2.tgz#f0a49c18ac8779e31d4a0c6029dfb76873c7428b"
|
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==
|
integrity sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==
|
||||||
|
|
||||||
diff@^5.0.0:
|
diff@^5.0.0:
|
||||||
version "5.0.0"
|
version "5.1.0"
|
||||||
resolved "https://registry.yarnpkg.com/diff/-/diff-5.0.0.tgz#7ed6ad76d859d030787ec35855f5b1daf31d852b"
|
resolved "https://registry.yarnpkg.com/diff/-/diff-5.1.0.tgz#bc52d298c5ea8df9194800224445ed43ffc87e40"
|
||||||
integrity sha512-/VTCrvm5Z0JGty/BWHljh+BAiw3IK+2j87NGMu8Nwc/f48WoDAC395uomO9ZD117ZOBaHmkX1oyLvkVM/aIT3w==
|
integrity sha512-D+mk+qE8VC/PAUrlAU34N+VfXev0ghe5ywmpqrawphmVZc1bEfn56uo9qpyGp1p4xpzOHkSW4ztBd6L7Xx4ACw==
|
||||||
|
|
||||||
dir-glob@^3.0.1:
|
dir-glob@^3.0.1:
|
||||||
version "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"
|
path-is-absolute "^1.0.0"
|
||||||
|
|
||||||
glob@^7.1.6:
|
glob@^7.1.6:
|
||||||
version "7.2.2"
|
version "7.2.3"
|
||||||
resolved "https://registry.yarnpkg.com/glob/-/glob-7.2.2.tgz#29deb38e1ef90f132d5958abe9c3ee8e87f3c318"
|
resolved "https://registry.yarnpkg.com/glob/-/glob-7.2.3.tgz#b8df0fb802bbfa8e89bd1d938b4e16578ed44f2b"
|
||||||
integrity sha512-NzDgHDiJwKYByLrL5lONmQFpK/2G78SMMfo+E9CuGlX4IkvfKDsiQSNPwAYxEy+e6p7ZQ3uslSLlwlJcqezBmQ==
|
integrity sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==
|
||||||
dependencies:
|
dependencies:
|
||||||
fs.realpath "^1.0.0"
|
fs.realpath "^1.0.0"
|
||||||
inflight "^1.0.4"
|
inflight "^1.0.4"
|
||||||
|
@ -6282,9 +6287,9 @@ yargs@^15.3.1, yargs@^15.4.1:
|
||||||
yargs-parser "^18.1.2"
|
yargs-parser "^18.1.2"
|
||||||
|
|
||||||
yargs@^17.2.1:
|
yargs@^17.2.1:
|
||||||
version "17.5.0"
|
version "17.5.1"
|
||||||
resolved "https://registry.yarnpkg.com/yargs/-/yargs-17.5.0.tgz#2706c5431f8c119002a2b106fc9f58b9bb9097a3"
|
resolved "https://registry.yarnpkg.com/yargs/-/yargs-17.5.1.tgz#e109900cab6fcb7fd44b1d8249166feb0b36e58e"
|
||||||
integrity sha512-3sLxVhbAB5OC8qvVRebCLWuouhwh/rswsiDYx3WGxajUk/l4G20SKfrKKFeNIHboUFt2JFgv2yfn+5cgOr/t5A==
|
integrity sha512-t6YAJcxDkNX7NFYiVtKvWUz8l+PaKTLiL63mJYWR2GnHq2gjEWISzsLp9wg3aY36dY1j+gfIEL3pIF+XlJJfbA==
|
||||||
dependencies:
|
dependencies:
|
||||||
cliui "^7.0.2"
|
cliui "^7.0.2"
|
||||||
escalade "^3.1.1"
|
escalade "^3.1.1"
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "@budibase/cli",
|
"name": "@budibase/cli",
|
||||||
"version": "1.0.212-alpha.0",
|
"version": "1.0.219-alpha.0",
|
||||||
"description": "Budibase CLI, for developers, self hosting and migrations.",
|
"description": "Budibase CLI, for developers, self hosting and migrations.",
|
||||||
"main": "src/index.js",
|
"main": "src/index.js",
|
||||||
"bin": {
|
"bin": {
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "@budibase/client",
|
"name": "@budibase/client",
|
||||||
"version": "1.0.212-alpha.0",
|
"version": "1.0.219-alpha.0",
|
||||||
"license": "MPL-2.0",
|
"license": "MPL-2.0",
|
||||||
"module": "dist/budibase-client.js",
|
"module": "dist/budibase-client.js",
|
||||||
"main": "dist/budibase-client.js",
|
"main": "dist/budibase-client.js",
|
||||||
|
@ -19,9 +19,9 @@
|
||||||
"dev:builder": "rollup -cw"
|
"dev:builder": "rollup -cw"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@budibase/bbui": "^1.0.212-alpha.0",
|
"@budibase/bbui": "^1.0.219-alpha.0",
|
||||||
"@budibase/frontend-core": "^1.0.212-alpha.0",
|
"@budibase/frontend-core": "^1.0.219-alpha.0",
|
||||||
"@budibase/string-templates": "^1.0.212-alpha.0",
|
"@budibase/string-templates": "^1.0.219-alpha.0",
|
||||||
"@spectrum-css/button": "^3.0.3",
|
"@spectrum-css/button": "^3.0.3",
|
||||||
"@spectrum-css/card": "^3.0.3",
|
"@spectrum-css/card": "^3.0.3",
|
||||||
"@spectrum-css/divider": "^1.0.3",
|
"@spectrum-css/divider": "^1.0.3",
|
||||||
|
|
|
@ -278,6 +278,9 @@ const notEqualHandler = (value, rule) => {
|
||||||
// Evaluates a regex constraint
|
// Evaluates a regex constraint
|
||||||
const regexHandler = (value, rule) => {
|
const regexHandler = (value, rule) => {
|
||||||
const regex = parseType(rule.value, "string")
|
const regex = parseType(rule.value, "string")
|
||||||
|
if (!value) {
|
||||||
|
value = ""
|
||||||
|
}
|
||||||
return new RegExp(regex).test(value)
|
return new RegExp(regex).test(value)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,12 +1,12 @@
|
||||||
{
|
{
|
||||||
"name": "@budibase/frontend-core",
|
"name": "@budibase/frontend-core",
|
||||||
"version": "1.0.212-alpha.0",
|
"version": "1.0.219-alpha.0",
|
||||||
"description": "Budibase frontend core libraries used in builder and client",
|
"description": "Budibase frontend core libraries used in builder and client",
|
||||||
"author": "Budibase",
|
"author": "Budibase",
|
||||||
"license": "MPL-2.0",
|
"license": "MPL-2.0",
|
||||||
"svelte": "src/index.js",
|
"svelte": "src/index.js",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@budibase/bbui": "^1.0.212-alpha.0",
|
"@budibase/bbui": "^1.0.219-alpha.0",
|
||||||
"lodash": "^4.17.21",
|
"lodash": "^4.17.21",
|
||||||
"svelte": "^3.46.2"
|
"svelte": "^3.46.2"
|
||||||
}
|
}
|
||||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue