diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md index 457d2c1451..6ace2303d9 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.md +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -6,6 +6,8 @@ labels: bug assignees: '' --- +**Checklist** +- [ ] I have searched budibase discussions and github issues to check if my issue already exists **Hosting** diff --git a/lerna.json b/lerna.json index aaa41afdd1..0606169dd3 100644 --- a/lerna.json +++ b/lerna.json @@ -1,5 +1,5 @@ { - "version": "2.2.12-alpha.6", + "version": "2.2.12-alpha.32", "npmClient": "yarn", "packages": [ "packages/*" diff --git a/packages/backend-core/package.json b/packages/backend-core/package.json index 86d4869c2d..e83a7084b1 100644 --- a/packages/backend-core/package.json +++ b/packages/backend-core/package.json @@ -1,6 +1,6 @@ { "name": "@budibase/backend-core", - "version": "2.2.12-alpha.6", + "version": "2.2.12-alpha.32", "description": "Budibase backend core libraries used in server and worker", "main": "dist/src/index.js", "types": "dist/src/index.d.ts", @@ -23,7 +23,7 @@ }, "dependencies": { "@budibase/nano": "10.1.1", - "@budibase/types": "2.2.12-alpha.6", + "@budibase/types": "2.2.12-alpha.32", "@shopify/jest-koa-mocks": "5.0.1", "@techpass/passport-openidconnect": "0.3.2", "aws-cloudfront-sign": "2.2.0", @@ -31,6 +31,7 @@ "bcrypt": "5.0.1", "bcryptjs": "2.4.3", "bull": "4.10.1", + "correlation-id": "4.0.0", "dotenv": "16.0.1", "emitter-listener": "1.1.2", "ioredis": "4.28.0", @@ -63,15 +64,17 @@ "@types/ioredis": "4.28.0", "@types/jest": "27.5.1", "@types/koa": "2.13.4", + "@types/koa-pino-logger": "3.0.0", "@types/lodash": "4.14.180", "@types/node": "14.18.20", "@types/node-fetch": "2.6.1", + "@types/pino-http": "5.8.1", "@types/pouchdb": "6.4.0", "@types/redlock": "4.0.3", "@types/semver": "7.3.7", "@types/tar-fs": "2.0.1", "@types/uuid": "8.3.4", - "chance": "1.1.3", + "chance": "1.1.8", "ioredis-mock": "5.8.0", "jest": "28.1.1", "koa": "2.13.4", diff --git a/packages/backend-core/src/cloud/api.ts b/packages/backend-core/src/cloud/api.ts index b601b2e64b..ef9b9d88e6 100644 --- a/packages/backend-core/src/cloud/api.ts +++ b/packages/backend-core/src/cloud/api.ts @@ -1,4 +1,5 @@ import fetch from "node-fetch" +import * as logging from "../logging" export default class API { host: string @@ -22,6 +23,9 @@ export default class API { let json = options.headers["Content-Type"] === "application/json" + // add x-budibase-correlation-id header + logging.correlation.setHeader(options.headers) + const requestOptions = { method: method, body: json ? JSON.stringify(options.body) : options.body, diff --git a/packages/backend-core/src/constants/misc.ts b/packages/backend-core/src/constants/misc.ts index 61b3cea1f6..0bf3df4094 100644 --- a/packages/backend-core/src/constants/misc.ts +++ b/packages/backend-core/src/constants/misc.ts @@ -22,6 +22,7 @@ export enum Header { TENANT_ID = "x-budibase-tenant-id", TOKEN = "x-budibase-token", CSRF_TOKEN = "x-csrf-token", + CORRELATION_ID = "x-budibase-correlation-id", } export enum GlobalRole { diff --git a/packages/backend-core/src/events/processors/LoggingProcessor.ts b/packages/backend-core/src/events/processors/LoggingProcessor.ts index d41a82fbb4..6bb691a83a 100644 --- a/packages/backend-core/src/events/processors/LoggingProcessor.ts +++ b/packages/backend-core/src/events/processors/LoggingProcessor.ts @@ -23,7 +23,7 @@ export default class LoggingProcessor implements EventProcessor { return } let timestampString = getTimestampString(timestamp) - let message = `[audit] [tenant=${identity.tenantId}] [identityType=${identity.type}] [identity=${identity.id}] ${timestampString} ${event} ` + let message = `[audit] [identityType=${identity.type}] ${timestampString} ${event} ` if (env.isDev()) { message = message + `[debug: [properties=${JSON.stringify(properties)}] ]` } diff --git a/packages/backend-core/src/events/publishers/rows.ts b/packages/backend-core/src/events/publishers/rows.ts index b1180fd936..9608613e89 100644 --- a/packages/backend-core/src/events/publishers/rows.ts +++ b/packages/backend-core/src/events/publishers/rows.ts @@ -3,7 +3,6 @@ import { Event, RowsImportedEvent, RowsCreatedEvent, - RowImportFormat, Table, } from "@budibase/types" @@ -16,14 +15,9 @@ const created = async (count: number, timestamp?: string | number) => { await publishEvent(Event.ROWS_CREATED, properties, timestamp) } -const imported = async ( - table: Table, - format: RowImportFormat, - count: number -) => { +const imported = async (table: Table, count: number) => { const properties: RowsImportedEvent = { tableId: table._id as string, - format, count, } await publishEvent(Event.ROWS_IMPORTED, properties) diff --git a/packages/backend-core/src/events/publishers/table.ts b/packages/backend-core/src/events/publishers/table.ts index 878885c33b..d50f4df0e1 100644 --- a/packages/backend-core/src/events/publishers/table.ts +++ b/packages/backend-core/src/events/publishers/table.ts @@ -2,7 +2,6 @@ import { publishEvent } from "../events" import { Event, TableExportFormat, - TableImportFormat, Table, TableCreatedEvent, TableUpdatedEvent, @@ -40,10 +39,9 @@ async function exported(table: Table, format: TableExportFormat) { await publishEvent(Event.TABLE_EXPORTED, properties) } -async function imported(table: Table, format: TableImportFormat) { +async function imported(table: Table) { const properties: TableImportedEvent = { tableId: table._id as string, - format, } await publishEvent(Event.TABLE_IMPORTED, properties) } diff --git a/packages/backend-core/src/logging.ts b/packages/backend-core/src/logging.ts index beffc98e8d..8f2ae6e619 100644 --- a/packages/backend-core/src/logging.ts +++ b/packages/backend-core/src/logging.ts @@ -1,4 +1,8 @@ +import { Header } from "./constants" import env from "./environment" +const correlator = require("correlation-id") +import { Options } from "pino-http" +import { IncomingMessage } from "http" const NonErrors = ["AccountError"] @@ -31,14 +35,26 @@ export function logWarn(message: string) { console.warn(`bb-warn: ${message}`) } -export function pinoSettings() { +export function pinoSettings(): Options { return { prettyPrint: { levelFirst: true, }, + genReqId: correlator.getId, level: env.LOG_LEVEL || "error", autoLogging: { - ignore: (req: { url: string }) => req.url.includes("/health"), + ignore: (req: IncomingMessage) => !!req.url?.includes("/health"), }, } } + +const setCorrelationHeader = (headers: any) => { + const correlationId = correlator.getId() + if (correlationId) { + headers[Header.CORRELATION_ID] = correlationId + } +} + +export const correlation = { + setHeader: setCorrelationHeader, +} diff --git a/packages/backend-core/src/middleware/index.ts b/packages/backend-core/src/middleware/index.ts index 91de59d0f8..4986cde64b 100644 --- a/packages/backend-core/src/middleware/index.ts +++ b/packages/backend-core/src/middleware/index.ts @@ -15,4 +15,5 @@ export { default as csrf } from "./csrf" export { default as adminOnly } from "./adminOnly" export { default as builderOrAdmin } from "./builderOrAdmin" export { default as builderOnly } from "./builderOnly" +export { default as logging } from "./logging" export * as joiValidator from "./joi-validator" diff --git a/packages/backend-core/src/middleware/logging.ts b/packages/backend-core/src/middleware/logging.ts new file mode 100644 index 0000000000..d1f2d6566b --- /dev/null +++ b/packages/backend-core/src/middleware/logging.ts @@ -0,0 +1,88 @@ +const correlator = require("correlation-id") +import { Header } from "../constants" +import { v4 as uuid } from "uuid" +import * as context from "../context" + +const debug = console.warn +const trace = console.trace +const log = console.log +const info = console.info +const warn = console.warn +const error = console.error + +const getTenantId = () => { + let tenantId + try { + tenantId = context.getTenantId() + } catch (e: any) { + // do nothing + } + return tenantId +} + +const getAppId = () => { + let appId + try { + appId = context.getAppId() + } catch (e) { + // do nothing + } + return appId +} + +const getIdentityId = () => { + let identityId + try { + const identity = context.getIdentity() + identityId = identity?._id + } catch (e) { + // do nothing + } + return identityId +} + +const print = (fn: any, data: any[]) => { + let message = "" + + const correlationId = correlator.getId() + if (correlationId) { + message = message + `[correlationId=${correlator.getId()}]` + } + + const tenantId = getTenantId() + if (tenantId) { + message = message + ` [tenantId=${tenantId}]` + } + + const appId = getAppId() + if (appId) { + message = message + ` [appId=${appId}]` + } + + const identityId = getIdentityId() + if (identityId) { + message = message + ` [identityId=${identityId}]` + } + + fn(message, data) +} + +const logging = (ctx: any, next: any) => { + // use the provided correlation id header if present + let correlationId = ctx.headers[Header.CORRELATION_ID] + if (!correlationId) { + correlationId = uuid() + } + + return correlator.withId(correlationId, () => { + console.debug = data => print(debug, data) + console.trace = data => print(trace, data) + console.log = data => print(log, data) + console.info = data => print(info, data) + console.warn = data => print(warn, data) + console.error = data => print(error, data) + return next() + }) +} + +export default logging diff --git a/packages/backend-core/src/utils/tests/utils.spec.ts b/packages/backend-core/src/utils/tests/utils.spec.ts index bb76a93653..498aff1555 100644 --- a/packages/backend-core/src/utils/tests/utils.spec.ts +++ b/packages/backend-core/src/utils/tests/utils.spec.ts @@ -1,7 +1,8 @@ import { structures } from "../../../tests" import * as utils from "../../utils" import * as events from "../../events" -import { DEFAULT_TENANT_ID } from "../../constants" +import * as db from "../../db" +import { DEFAULT_TENANT_ID, Header } from "../../constants" import { doInTenant } from "../../context" describe("utils", () => { @@ -14,4 +15,95 @@ describe("utils", () => { }) }) }) + + describe("getAppIdFromCtx", () => { + it("gets appId from header", async () => { + const ctx = structures.koa.newContext() + const expected = db.generateAppID() + ctx.request.headers = { + [Header.APP_ID]: expected, + } + + const actual = await utils.getAppIdFromCtx(ctx) + expect(actual).toBe(expected) + }) + + it("gets appId from body", async () => { + const ctx = structures.koa.newContext() + const expected = db.generateAppID() + ctx.request.body = { + appId: expected, + } + + const actual = await utils.getAppIdFromCtx(ctx) + expect(actual).toBe(expected) + }) + + it("gets appId from path", async () => { + const ctx = structures.koa.newContext() + const expected = db.generateAppID() + ctx.path = `/apps/${expected}` + + const actual = await utils.getAppIdFromCtx(ctx) + expect(actual).toBe(expected) + }) + + it("gets appId from url", async () => { + const ctx = structures.koa.newContext() + const expected = db.generateAppID() + const app = structures.apps.app(expected) + + // set custom url + const appUrl = "custom-url" + app.url = `/${appUrl}` + ctx.path = `/app/${appUrl}` + + // save the app + const database = db.getDB(expected) + await database.put(app) + + const actual = await utils.getAppIdFromCtx(ctx) + expect(actual).toBe(expected) + }) + + it("doesn't get appId from url when previewing", async () => { + const ctx = structures.koa.newContext() + const appId = db.generateAppID() + const app = structures.apps.app(appId) + + // set custom url + const appUrl = "preview" + app.url = `/${appUrl}` + ctx.path = `/app/${appUrl}` + + // save the app + const database = db.getDB(appId) + await database.put(app) + + const actual = await utils.getAppIdFromCtx(ctx) + expect(actual).toBe(undefined) + }) + + it("gets appId from referer", async () => { + const ctx = structures.koa.newContext() + const expected = db.generateAppID() + ctx.request.headers = { + referer: `http://test.com/builder/app/${expected}/design/screen_123/screens`, + } + + const actual = await utils.getAppIdFromCtx(ctx) + expect(actual).toBe(expected) + }) + + it("doesn't get appId from referer when not builder", async () => { + const ctx = structures.koa.newContext() + const appId = db.generateAppID() + ctx.request.headers = { + referer: `http://test.com/foo/app/${appId}/bar`, + } + + const actual = await utils.getAppIdFromCtx(ctx) + expect(actual).toBe(undefined) + }) + }) }) diff --git a/packages/backend-core/src/utils/utils.ts b/packages/backend-core/src/utils/utils.ts index fd8d31b13f..c608686431 100644 --- a/packages/backend-core/src/utils/utils.ts +++ b/packages/backend-core/src/utils/utils.ts @@ -25,13 +25,16 @@ const jwt = require("jsonwebtoken") const APP_PREFIX = DocumentType.APP + SEPARATOR const PROD_APP_PREFIX = "/app/" +const BUILDER_PREVIEW_PATH = "/app/preview" +const BUILDER_REFERER_PREFIX = "/builder/app/" + function confirmAppId(possibleAppId: string | undefined) { return possibleAppId && possibleAppId.startsWith(APP_PREFIX) ? possibleAppId : undefined } -async function resolveAppUrl(ctx: Ctx) { +export async function resolveAppUrl(ctx: Ctx) { const appUrl = ctx.path.split("/")[2] let possibleAppUrl = `/${appUrl.toLowerCase()}` @@ -75,7 +78,7 @@ export function isServingApp(ctx: Ctx) { */ export async function getAppIdFromCtx(ctx: Ctx) { // look in headers - const options = [ctx.headers[Header.APP_ID]] + const options = [ctx.request.headers[Header.APP_ID]] let appId for (let option of options) { appId = confirmAppId(option as string) @@ -95,15 +98,23 @@ export async function getAppIdFromCtx(ctx: Ctx) { appId = confirmAppId(pathId) } - // look in the referer - const refererId = parseAppIdFromUrl(ctx.request.headers.referer) - if (!appId && refererId) { - appId = confirmAppId(refererId) + // lookup using custom url - prod apps only + // filter out the builder preview path which collides with the prod app path + // to ensure we don't load all apps excessively + const isBuilderPreview = ctx.path.startsWith(BUILDER_PREVIEW_PATH) + const isViewingProdApp = + ctx.path.startsWith(PROD_APP_PREFIX) && !isBuilderPreview + if (!appId && isViewingProdApp) { + appId = confirmAppId(await resolveAppUrl(ctx)) } - // look in the url - prod app - if (!appId && ctx.path.startsWith(PROD_APP_PREFIX)) { - appId = confirmAppId(await resolveAppUrl(ctx)) + // look in the referer - builder only + // make sure this is performed after prod app url resolution, in case the + // referer header is present from a builder redirect + const referer = ctx.request.headers.referer + if (!appId && referer?.includes(BUILDER_REFERER_PREFIX)) { + const refererId = parseAppIdFromUrl(ctx.request.headers.referer) + appId = confirmAppId(refererId) } return appId diff --git a/packages/backend-core/tests/utilities/structures/apps.ts b/packages/backend-core/tests/utilities/structures/apps.ts new file mode 100644 index 0000000000..f3743d99b2 --- /dev/null +++ b/packages/backend-core/tests/utilities/structures/apps.ts @@ -0,0 +1,21 @@ +import { generator } from "." +import { App } from "@budibase/types" +import { DEFAULT_TENANT_ID, DocumentType } from "../../../src/constants" + +export function app(id: string): App { + return { + _id: DocumentType.APP_METADATA, + appId: id, + type: "", + version: "0.0.1", + componentLibraries: [], + name: generator.name(), + url: `/custom-url`, + instance: { + _id: id, + }, + tenantId: DEFAULT_TENANT_ID, + status: "", + template: undefined, + } +} diff --git a/packages/backend-core/tests/utilities/structures/index.ts b/packages/backend-core/tests/utilities/structures/index.ts index e0ed4df9c4..0d0f0c507f 100644 --- a/packages/backend-core/tests/utilities/structures/index.ts +++ b/packages/backend-core/tests/utilities/structures/index.ts @@ -3,7 +3,8 @@ export * from "./common" import Chance from "chance" export const generator = new Chance() -export * as koa from "./koa" export * as accounts from "./accounts" +export * as apps from "./apps" +export * as koa from "./koa" export * as licenses from "./licenses" export * as plugins from "./plugins" diff --git a/packages/backend-core/tests/utilities/structures/koa.ts b/packages/backend-core/tests/utilities/structures/koa.ts index a33dca1546..102fe029de 100644 --- a/packages/backend-core/tests/utilities/structures/koa.ts +++ b/packages/backend-core/tests/utilities/structures/koa.ts @@ -5,9 +5,11 @@ export const newContext = (): BBContext => { const ctx = createMockContext() return { ...ctx, + path: "/", cookies: createMockCookies(), request: { ...ctx.request, + headers: {}, body: {}, }, } diff --git a/packages/backend-core/tsconfig.build.json b/packages/backend-core/tsconfig.build.json index 25cfb70899..12f8255a7c 100644 --- a/packages/backend-core/tsconfig.build.json +++ b/packages/backend-core/tsconfig.build.json @@ -12,6 +12,7 @@ "declaration": true, "types": [ "node", "jest" ], "outDir": "dist", + "skipLibCheck": true }, "include": [ "**/*.js", diff --git a/packages/bbui/package.json b/packages/bbui/package.json index 856465a0b7..6b67ca77d0 100644 --- a/packages/bbui/package.json +++ b/packages/bbui/package.json @@ -1,7 +1,7 @@ { "name": "@budibase/bbui", "description": "A UI solution used in the different Budibase projects.", - "version": "2.2.12-alpha.6", + "version": "2.2.12-alpha.32", "license": "MPL-2.0", "svelte": "src/index.js", "module": "dist/bbui.es.js", @@ -38,7 +38,7 @@ ], "dependencies": { "@adobe/spectrum-css-workflow-icons": "1.2.1", - "@budibase/string-templates": "2.2.12-alpha.6", + "@budibase/string-templates": "2.2.12-alpha.32", "@spectrum-css/actionbutton": "1.0.1", "@spectrum-css/actiongroup": "1.0.1", "@spectrum-css/avatar": "3.0.2", diff --git a/packages/bbui/src/Actions/click_outside.js b/packages/bbui/src/Actions/click_outside.js index 3a08484635..6842b94a32 100644 --- a/packages/bbui/src/Actions/click_outside.js +++ b/packages/bbui/src/Actions/click_outside.js @@ -1,11 +1,11 @@ -const ignoredClasses = [".flatpickr-calendar", ".modal-container"] +const ignoredClasses = [".flatpickr-calendar"] let clickHandlers = [] /** * Handle a body click event */ const handleClick = event => { - // Ignore click if needed + // Ignore click if this is an ignored class for (let className of ignoredClasses) { if (event.target.closest(className)) { return @@ -14,9 +14,18 @@ const handleClick = event => { // Process handlers clickHandlers.forEach(handler => { - if (!handler.element.contains(event.target)) { - handler.callback?.(event) + if (handler.element.contains(event.target)) { + return } + + // Ignore clicks for modals, unless the handler is registered from a modal + const sourceInModal = handler.element.closest(".spectrum-Modal") != null + const clickInModal = event.target.closest(".spectrum-Modal") != null + if (clickInModal && !sourceInModal) { + return + } + + handler.callback?.(event) }) } document.documentElement.addEventListener("click", handleClick, true) diff --git a/packages/bbui/src/Actions/position_dropdown.js b/packages/bbui/src/Actions/position_dropdown.js index 7570a39c8c..463b69169f 100644 --- a/packages/bbui/src/Actions/position_dropdown.js +++ b/packages/bbui/src/Actions/position_dropdown.js @@ -1,75 +1,68 @@ -export default function positionDropdown(element, { anchor, align, maxWidth }) { - let positionSide = "top" - let maxHeight = 0 - let dimensions = getDimensions(anchor) +export default function positionDropdown( + element, + { anchor, align, maxWidth, useAnchorWidth } +) { + const update = () => { + const anchorBounds = anchor.getBoundingClientRect() + const elementBounds = element.getBoundingClientRect() + let styles = { + maxHeight: null, + minWidth: null, + maxWidth, + left: null, + top: null, + } - function getDimensions() { - const { - bottom, - top: spaceAbove, - left, - width, - } = anchor.getBoundingClientRect() - const spaceBelow = window.innerHeight - bottom - const containerRect = element.getBoundingClientRect() - - let y - - if (spaceAbove > spaceBelow) { - positionSide = "bottom" - maxHeight = spaceAbove - 20 - y = window.innerHeight - spaceAbove + 5 + // Determine vertical styles + if (window.innerHeight - anchorBounds.bottom < 100) { + styles.top = anchorBounds.top - elementBounds.height - 5 } else { - positionSide = "top" - y = bottom + 5 - maxHeight = spaceBelow - 20 + styles.top = anchorBounds.bottom + 5 + styles.maxHeight = window.innerHeight - anchorBounds.bottom - 20 } - return { - [positionSide]: y, - left, - width, - containerWidth: containerRect.width, + // Determine horizontal styles + if (!maxWidth && useAnchorWidth) { + styles.maxWidth = anchorBounds.width } + if (useAnchorWidth) { + styles.minWidth = anchorBounds.width + } + if (align === "right") { + styles.left = anchorBounds.left + anchorBounds.width - elementBounds.width + } else if (align === "right-side") { + styles.left = anchorBounds.left + anchorBounds.width + } else { + styles.left = anchorBounds.left + } + + // Apply styles + Object.entries(styles).forEach(([style, value]) => { + if (value) { + element.style[style] = `${value.toFixed(0)}px` + } else { + element.style[style] = null + } + }) } - function calcLeftPosition() { - let left - - if (align == "right") { - left = dimensions.left + dimensions.width - dimensions.containerWidth - } else if (align == "right-side") { - left = dimensions.left + dimensions.width - } else { - left = dimensions.left - } - - return left - } - + // Apply initial styles which don't need to change element.style.position = "absolute" element.style.zIndex = "9999" - if (maxWidth) { - element.style.maxWidth = `${maxWidth}px` - } - element.style.minWidth = `${dimensions.width}px` - element.style.maxHeight = `${maxHeight.toFixed(0)}px` - element.style.transformOrigin = `center ${positionSide}` - element.style[positionSide] = `${dimensions[positionSide]}px` - element.style.left = `${calcLeftPosition(dimensions).toFixed(0)}px` + // Observe both anchor and element and resize the popover as appropriate const resizeObserver = new ResizeObserver(entries => { - entries.forEach(() => { - dimensions = getDimensions() - element.style[positionSide] = `${dimensions[positionSide]}px` - element.style.left = `${calcLeftPosition(dimensions).toFixed(0)}px` - }) + entries.forEach(update) }) resizeObserver.observe(anchor) resizeObserver.observe(element) + + document.addEventListener("scroll", update, true) + return { destroy() { resizeObserver.disconnect() + document.removeEventListener("scroll", update, true) }, } } diff --git a/packages/bbui/src/Avatar/Avatar.svelte b/packages/bbui/src/Avatar/Avatar.svelte index 136a4fe24b..1e4cefd8ce 100644 --- a/packages/bbui/src/Avatar/Avatar.svelte +++ b/packages/bbui/src/Avatar/Avatar.svelte @@ -58,5 +58,6 @@ overflow: hidden; user-select: none; text-transform: uppercase; + flex-shrink: 0; } diff --git a/packages/bbui/src/Button/Button.svelte b/packages/bbui/src/Button/Button.svelte index 36abcbf4da..979ec6a728 100644 --- a/packages/bbui/src/Button/Button.svelte +++ b/packages/bbui/src/Button/Button.svelte @@ -14,7 +14,7 @@ export let active = false export let tooltip = undefined export let dataCy - export let newStyles = false + export let newStyles = true let showTooltip = false @@ -28,6 +28,7 @@ class:spectrum-Button--quiet={quiet} class:new-styles={newStyles} class:active + class:disabled class="spectrum-Button spectrum-Button--size{size.toUpperCase()}" {disabled} data-cy={dataCy} @@ -108,7 +109,10 @@ border-color: transparent; color: var(--spectrum-global-color-gray-900); } - .spectrum-Button--secondary.new-styles:hover { + .spectrum-Button--secondary.new-styles:not(.disabled):hover { background: var(--spectrum-global-color-gray-300); } + .spectrum-Button--secondary.new-styles.disabled { + color: var(--spectrum-global-color-gray-500); + } diff --git a/packages/bbui/src/Drawer/DrawerContent.svelte b/packages/bbui/src/Drawer/DrawerContent.svelte index 28e0975d82..944a3f4313 100644 --- a/packages/bbui/src/Drawer/DrawerContent.svelte +++ b/packages/bbui/src/Drawer/DrawerContent.svelte @@ -34,7 +34,6 @@ display: none; } .main { - font-family: var(--font-sans); padding: var(--spacing-xl); } .main :global(textarea) { diff --git a/packages/bbui/src/Form/Core/DatePicker.svelte b/packages/bbui/src/Form/Core/DatePicker.svelte index 6996525a76..10aae67ec6 100644 --- a/packages/bbui/src/Form/Core/DatePicker.svelte +++ b/packages/bbui/src/Form/Core/DatePicker.svelte @@ -264,7 +264,7 @@ max-height: 100%; } :global(.flatpickr-calendar) { - font-family: "Source Sans Pro", sans-serif; + font-family: var(--font-sans); } .is-disabled { pointer-events: none !important; diff --git a/packages/bbui/src/Form/Core/Picker.svelte b/packages/bbui/src/Form/Core/Picker.svelte index 97bd1394b4..32cfcf3310 100644 --- a/packages/bbui/src/Form/Core/Picker.svelte +++ b/packages/bbui/src/Form/Core/Picker.svelte @@ -2,12 +2,12 @@ import "@spectrum-css/picker/dist/index-vars.css" import "@spectrum-css/popover/dist/index-vars.css" import "@spectrum-css/menu/dist/index-vars.css" - import { fly } from "svelte/transition" import { createEventDispatcher } from "svelte" import clickOutside from "../../Actions/click_outside" import Search from "./Search.svelte" import Icon from "../../Icon/Icon.svelte" import StatusLight from "../../StatusLight/StatusLight.svelte" + import Popover from "../../Popover/Popover.svelte" export let id = null export let disabled = false @@ -33,7 +33,10 @@ export let sort = false const dispatch = createEventDispatcher() + let searchTerm = null + let button + let popover $: sortedOptions = getSortedOptions(options, getOptionLabel, sort) $: filteredOptions = getFilteredOptions( @@ -42,7 +45,9 @@ getOptionLabel ) - const onClick = () => { + const onClick = e => { + e.preventDefault() + e.stopPropagation() dispatch("click") if (readonly) { return @@ -76,77 +81,119 @@ } -
(open = false)}> - - {#if open} -
- {#if autocomplete} - (searchTerm = event.detail)} - {disabled} - placeholder="Search" - /> + {/if} + + + + (open = false)} + useAnchorWidth={!autoWidth} + maxWidth={autoWidth ? 400 : null} +> +
(open = false)} + > + {#if autocomplete} + (searchTerm = event.detail)} + {disabled} + placeholder="Search" + /> + {/if} +
    + {#if placeholderOption} +
  • onSelectOption(null)} + > + {placeholderOption} + +
  • {/if} -
      - {#if placeholderOption} + {#if filteredOptions.length} + {#each filteredOptions as option, idx}
    • onSelectOption(null)} + on:click={() => onSelectOption(getOptionValue(option, idx))} + class:is-disabled={!isOptionEnabled(option)} > - {placeholderOption} + {#if getOptionIcon(option, idx)} + + + + {/if} + {#if getOptionColour(option, idx)} + + + + {/if} + + {getOptionLabel(option, idx)} +
    • - {/if} - {#if filteredOptions.length} - {#each filteredOptions as option, idx} -
    • onSelectOption(getOptionValue(option, idx))} - class:is-disabled={!isOptionEnabled(option)} - > - {#if getOptionIcon(option, idx)} - - - - {/if} - {#if getOptionColour(option, idx)} - - - - {/if} - - {getOptionLabel(option, idx)} - - -
    • - {/each} - {/if} -
    -
- {/if} -
+ {/each} + {/if} + +
+ diff --git a/packages/bbui/src/Form/Core/TextField.svelte b/packages/bbui/src/Form/Core/TextField.svelte index 0a723c140a..6a64876a2c 100644 --- a/packages/bbui/src/Form/Core/TextField.svelte +++ b/packages/bbui/src/Form/Core/TextField.svelte @@ -112,8 +112,4 @@ .spectrum-Textfield { width: 100%; } - input:disabled { - color: var(--spectrum-global-color-gray-600) !important; - -webkit-text-fill-color: var(--spectrum-global-color-gray-600) !important; - } diff --git a/packages/bbui/src/Icon/IconAvatar.svelte b/packages/bbui/src/Icon/IconAvatar.svelte index b404cdea9f..add51f5bdc 100644 --- a/packages/bbui/src/Icon/IconAvatar.svelte +++ b/packages/bbui/src/Icon/IconAvatar.svelte @@ -19,6 +19,7 @@ .icon { width: 28px; height: 28px; + flex: 0 0 28px; display: grid; place-items: center; border-radius: 50%; @@ -34,6 +35,7 @@ .icon.size--S { width: 22px; height: 22px; + flex: 0 0 22px; } .icon.size--S :global(.spectrum-Icon) { width: 16px; @@ -46,6 +48,7 @@ .icon.size--L { width: 40px; height: 40px; + flex: 0 0 40px; } .icon.size--L :global(.spectrum-Icon) { width: 28px; diff --git a/packages/bbui/src/InlineAlert/InlineAlert.svelte b/packages/bbui/src/InlineAlert/InlineAlert.svelte index 94ac6b2c2a..57e7296234 100644 --- a/packages/bbui/src/InlineAlert/InlineAlert.svelte +++ b/packages/bbui/src/InlineAlert/InlineAlert.svelte @@ -56,5 +56,6 @@ --spectrum-semantic-positive-icon-color: #2d9d78; --spectrum-semantic-negative-icon-color: #e34850; min-width: 100px; + margin: 0; } diff --git a/packages/bbui/src/Label/Label.svelte b/packages/bbui/src/Label/Label.svelte index 6b3392ce2d..ee6d9adf76 100644 --- a/packages/bbui/src/Label/Label.svelte +++ b/packages/bbui/src/Label/Label.svelte @@ -21,6 +21,7 @@ label { padding: 0; white-space: nowrap; + color: var(--spectrum-global-color-gray-600); } .muted { diff --git a/packages/bbui/src/Layout/Page.svelte b/packages/bbui/src/Layout/Page.svelte index 2996bcc613..15aabd2c61 100644 --- a/packages/bbui/src/Layout/Page.svelte +++ b/packages/bbui/src/Layout/Page.svelte @@ -1,32 +1,95 @@ -
- +
+
+
+ +
+
+
{ + sidePanelVisble = false + }} + > + +
diff --git a/packages/bbui/src/List/ListItem.svelte b/packages/bbui/src/List/ListItem.svelte index 40d3c5541c..28015c4c57 100644 --- a/packages/bbui/src/List/ListItem.svelte +++ b/packages/bbui/src/List/ListItem.svelte @@ -30,9 +30,11 @@ {/if}
-
- -
+ {#if $$slots.default} +
+ +
+ {/if} diff --git a/packages/bbui/src/Table/Table.svelte b/packages/bbui/src/Table/Table.svelte index cec270486a..19d361c8b1 100644 --- a/packages/bbui/src/Table/Table.svelte +++ b/packages/bbui/src/Table/Table.svelte @@ -21,6 +21,8 @@ * template: a HBS or JS binding to use as the value * background: the background color * color: the text color + * borderLeft: show a left border + * borderRight: show a right border */ export let data = [] export let schema = {} @@ -31,6 +33,7 @@ export let allowSelectRows export let allowEditRows = true export let allowEditColumns = true + export let allowClickRows = true export let selectedRows = [] export let customRenderers = [] export let disableSorting = false @@ -270,6 +273,17 @@ if (schema[field].align === "Right") { styles[field] += "justify-content: flex-end; text-align: right;" } + if (schema[field].borderLeft) { + styles[field] += + "border-left: 1px solid var(--spectrum-global-color-gray-200);" + } + if (schema[field].borderLeft) { + styles[field] += + "border-right: 1px solid var(--spectrum-global-color-gray-200);" + } + if (schema[field].minWidth) { + styles[field] += `min-width: ${schema[field].minWidth};` + } }) return styles } @@ -290,7 +304,11 @@ {:else} -
+
{#if fields.length}
{#if showEditColumn} @@ -356,7 +374,7 @@ {/if} {#if sortedRows?.length} {#each sortedRows as row, idx} -
+
{#if showEditColumn}
diff --git a/packages/bbui/src/Tags/Tag.svelte b/packages/bbui/src/Tags/Tag.svelte index f7089decdb..0cdd6c385d 100644 --- a/packages/bbui/src/Tags/Tag.svelte +++ b/packages/bbui/src/Tags/Tag.svelte @@ -37,7 +37,7 @@ diff --git a/packages/bbui/src/Tags/Tags.svelte b/packages/bbui/src/Tags/Tags.svelte index 36d34507f3..ebca64b774 100644 --- a/packages/bbui/src/Tags/Tags.svelte +++ b/packages/bbui/src/Tags/Tags.svelte @@ -5,3 +5,13 @@
+ + diff --git a/packages/bbui/src/Typography/Heading.svelte b/packages/bbui/src/Typography/Heading.svelte index 077c0a4878..5f243ad5a6 100644 --- a/packages/bbui/src/Typography/Heading.svelte +++ b/packages/bbui/src/Typography/Heading.svelte @@ -15,3 +15,9 @@ > + + diff --git a/packages/bbui/src/bbui.css b/packages/bbui/src/bbui.css index f98f27bf58..ef3483d3df 100644 --- a/packages/bbui/src/bbui.css +++ b/packages/bbui/src/bbui.css @@ -40,12 +40,14 @@ --rounded-medium: 8px; --rounded-large: 16px; - --font-sans: Source Sans Pro, -apple-system, BlinkMacSystemFont, Segoe UI, "Inter", - "Helvetica Neue", Arial, "Noto Sans", sans-serif, "Apple Color Emoji", - "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji"; + --font-sans: "Source Sans Pro", -apple-system, BlinkMacSystemFont, Segoe UI, "Inter", + "Helvetica Neue", Arial, "Noto Sans", sans-serif; + --font-accent: "Source Sans Pro", -apple-system, BlinkMacSystemFont, Segoe UI, "Inter", + "Helvetica Neue", Arial, "Noto Sans", sans-serif; --font-serif: "Georgia", Cambria, Times New Roman, Times, serif; --font-mono: Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace; + --spectrum-alias-body-text-font-family: var(--font-sans); font-size: 16px; --font-size-xs: 0.75rem; @@ -89,6 +91,8 @@ --border-light-2: 2px var(--grey-3) solid; --border-blue: 2px var(--blue) solid; --border-transparent: 2px transparent solid; + + --spectrum-alias-text-color-disabled: var(--spectrum-global-color-gray-600); } a { diff --git a/packages/bbui/src/context.js b/packages/bbui/src/context.js index bb95f8b425..919f2140d6 100644 --- a/packages/bbui/src/context.js +++ b/packages/bbui/src/context.js @@ -1,3 +1,4 @@ export default { Modal: "bbui-modal", + PopoverRoot: "bbui-popover-root", } diff --git a/packages/builder/assets/bb-emblem.svg b/packages/builder/assets/bb-emblem.svg index 9f4f3690d5..7d499e4862 100644 --- a/packages/builder/assets/bb-emblem.svg +++ b/packages/builder/assets/bb-emblem.svg @@ -3,7 +3,7 @@ diff --git a/packages/builder/cypress.json b/packages/builder/cypress.json index f1eada481f..b779794543 100644 --- a/packages/builder/cypress.json +++ b/packages/builder/cypress.json @@ -11,9 +11,5 @@ "WORKER_PORT": "4200", "JWT_SECRET": "test", "HOST_IP": "" - }, - "retries": { - "runMode": 1, - "openMode": 0 } } \ No newline at end of file diff --git a/packages/builder/cypress/integration/adminAndManagement/authentication.spec.js b/packages/builder/cypress/integration/adminAndManagement/authentication.spec.js index 5cc42cb59a..140dfb9ff8 100644 --- a/packages/builder/cypress/integration/adminAndManagement/authentication.spec.js +++ b/packages/builder/cypress/integration/adminAndManagement/authentication.spec.js @@ -2,7 +2,7 @@ import filterTests from "../../support/filterTests" // const interact = require("../support/interact") filterTests(["smoke", "all"], () => { - context("Auth Configuration", () => { + xcontext("Auth Configuration", () => { before(() => { cy.login() }) @@ -21,7 +21,7 @@ filterTests(["smoke", "all"], () => { cy.get("[data-cy=oidc-active]").should('not.be.checked') cy.intercept("POST", "/api/global/configs").as("updateAuth") - cy.get("button[data-cy=oidc-save]").contains("Save").click({force: true}) + cy.get("button[data-cy=oidc-save]").contains("Save").click({ force: true }) cy.wait("@updateAuth") cy.get("@updateAuth").its("response.statusCode").should("eq", 200) @@ -45,7 +45,7 @@ filterTests(["smoke", "all"], () => { cy.get("button[data-cy=oidc-save]").should("not.be.disabled"); cy.intercept("POST", "/api/global/configs").as("updateAuth") - cy.get("button[data-cy=oidc-save]").contains("Save").click({force: true}) + cy.get("button[data-cy=oidc-save]").contains("Save").click({ force: true }) cy.wait("@updateAuth") cy.get("@updateAuth").its("response.statusCode").should("eq", 200) @@ -85,11 +85,11 @@ filterTests(["smoke", "all"], () => { cy.get(".auth-form input.spectrum-Textfield-input").type("Another ") cy.get(".spectrum-Tags").find(".spectrum-Tags-item").its("length").should("eq", 6) cy.get(".spectrum-Tags-item").contains("Another") - + cy.get("button[data-cy=oidc-save]").should("not.be.disabled"); cy.intercept("POST", "/api/global/configs").as("updateAuth") - cy.get("button[data-cy=oidc-save]").contains("Save").click({force: true}) + cy.get("button[data-cy=oidc-save]").contains("Save").click({ force: true }) cy.wait("@updateAuth") cy.get("@updateAuth").its("response.statusCode").should("eq", 200) @@ -123,7 +123,7 @@ filterTests(["smoke", "all"], () => { cy.get("button[data-cy=oidc-save]").should("not.be.disabled"); cy.intercept("POST", "/api/global/configs").as("updateAuth") - cy.get("button[data-cy=oidc-save]").contains("Save").click({force: true}) + cy.get("button[data-cy=oidc-save]").contains("Save").click({ force: true }) cy.wait("@updateAuth") cy.get("@updateAuth").its("response.statusCode").should("eq", 200) @@ -144,7 +144,7 @@ filterTests(["smoke", "all"], () => { cy.get("div.content").scrollTo("bottom") - cy.get("[data-cy=restore-oidc-default-scopes]").click({force: true}) + cy.get("[data-cy=restore-oidc-default-scopes]").click({ force: true }) cy.get(".spectrum-Tags").find(".spectrum-Tags-item").its("length").should("eq", 4) diff --git a/packages/builder/cypress/integration/adminAndManagement/userSettings.spec.js b/packages/builder/cypress/integration/adminAndManagement/userSettings.spec.js index a2b0d32d02..18362031c9 100644 --- a/packages/builder/cypress/integration/adminAndManagement/userSettings.spec.js +++ b/packages/builder/cypress/integration/adminAndManagement/userSettings.spec.js @@ -3,107 +3,112 @@ 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" + const fname = "test" + const lname = "user" - cy.visit(`${Cypress.config().baseUrl}/builder`) - cy.updateUserInformation(fname, lname) + 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() + // 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(1).within(() => { - cy.get(interact.SPECTRUM_TEXTFIELD_INPUT).should('have.value', fname) - }) - cy.get(interact.FIELD).eq(2).within(() => { - cy.get(interact.SPECTRUM_TEXTFIELD_INPUT).should('have.value', lname) - }) + cy.get(interact.FIELD, { timeout: 1000 }).eq(1).within(() => { + cy.get(interact.SPECTRUM_TEXTFIELD_INPUT).should('have.value', fname) + }) + cy.get(interact.FIELD).eq(2).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") + xit("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) + cy.get(".user-dropdown .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 }) + }) + // 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 }) + // Click re-generate key button + cy.get("button").contains("Regenerate 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) - }) + // 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() + }) + 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 }) + // Access Update password modal + cy.get(".user-dropdown .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 }) - }) + // 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") + // 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 + xit("should open and close developer mode", () => { + cy.get(".user-dropdown .icon", { timeout: 2000 }).click({ force: true }) - // 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 + // 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(".app-table").should('exist') // config sections available + cy.get(interact.CREATE_APP_BUTTON).should('exist') // create app button 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 }) - }) - // Remove users name - cy.updateUserInformation() + // Change password back to original value + cy.get(".user-dropdown .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 }) }) + // Remove users name + cy.updateUserInformation() + }) }) }) diff --git a/packages/builder/cypress/integration/appOverview.spec.js b/packages/builder/cypress/integration/appOverview.spec.js index 2afc0af277..60d0f2a06d 100644 --- a/packages/builder/cypress/integration/appOverview.spec.js +++ b/packages/builder/cypress/integration/appOverview.spec.js @@ -2,7 +2,7 @@ import filterTests from "../support/filterTests" import clientPackage from "@budibase/client/package.json" filterTests(["all"], () => { - context("Application Overview screen", () => { + xcontext("Application Overview screen", () => { before(() => { cy.login() cy.deleteAllApps() diff --git a/packages/builder/cypress/integration/createApp.spec.js b/packages/builder/cypress/integration/createApp.spec.js index d37b0806c4..897c7f8b91 100644 --- a/packages/builder/cypress/integration/createApp.spec.js +++ b/packages/builder/cypress/integration/createApp.spec.js @@ -14,15 +14,15 @@ filterTests(['smoke', 'all'], () => { cy.visit(`${Cypress.config().baseUrl}/builder/portal/apps/create`, { timeout: 5000 }) //added /portal/apps/create cy.wait(1000) cy.get(interact.CREATE_APP_BUTTON, { timeout: 10000 }).contains('Start from scratch').should("exist") - + cy.get(interact.TEMPLATE_CATEGORY_FILTER).should("exist") cy.get(interact.TEMPLATE_CATEGORY).should("exist") - + cy.get(interact.APP_TABLE).should("not.exist") }) } - it("should provide filterable templates", () => { + xit("should provide filterable templates", () => { cy.visit(`${Cypress.config().baseUrl}/builder`, { timeout: 5000 }) cy.wait(500) @@ -30,16 +30,16 @@ filterTests(['smoke', 'all'], () => { .its("body") .then(val => { if (val.length > 0) { - cy.get(interact.SPECTRUM_BUTTON).contains("Templates").click({force: true}) + cy.get(interact.SPECTRUM_BUTTON).contains("View Templates").click({ force: true }) } }) cy.get(interact.TEMPLATE_CATEGORY_FILTER).should("exist") cy.get(interact.TEMPLATE_CATEGORY).should("exist") - + cy.get(interact.TEMPLATE_CATEGORY_ACTIONGROUP).its('length').should('be.gt', 1) cy.get(interact.TEMPLATE_CATEGORY_FILTER_ACTIONBUTTON).its('length').should('be.gt', 2) - + cy.get(interact.TEMPLATE_CATEGORY_FILTER_ACTIONBUTTON).eq(1).click() cy.get(interact.TEMPLATE_CATEGORY_ACTIONGROUP).should('have.length', 1) @@ -104,14 +104,14 @@ filterTests(['smoke', 'all'], () => { cy.visit(`${Cypress.config().baseUrl}/builder`, { timeout: 5000 }) cy.updateUserInformation("Ted", "Userman") - + cy.createApp("", false) cy.applicationInAppTable("Teds app") cy.deleteApp("Teds app") // Accomodate names that end in 'S' cy.updateUserInformation("Chris", "Userman") - + cy.createApp("", false) cy.applicationInAppTable("Chris app") cy.deleteApp("Chris app") @@ -123,35 +123,49 @@ filterTests(['smoke', 'all'], () => { const exportedApp = 'cypress/fixtures/exported-app.txt' cy.importApp(exportedApp, "") - cy.visit(`${Cypress.config().baseUrl}/builder`, { timeout: 2000 }) - cy.applicationInAppTable("My app") - - cy.get(".appTable .name").eq(0).click() - - cy.deleteApp("My app") + cy.get(".app-table .name").eq(0).click() + cy.closeModal() + cy.get(`[aria-label="ShowMenu"]`).click() + cy.get(".spectrum-Menu").within(() => { + cy.contains("Overview").click() + }) + cy.get(".app-overview-actions-icon").within(() => { + cy.get(".spectrum-Icon").click({ force: true }) + }) + cy.get(".spectrum-Menu").contains("Delete").click({ force: true }) + cy.get(".spectrum-Dialog-grid").within(() => { + cy.get("input").type("My app") + }) + cy.get(".spectrum-Button--warning").click() }) it("should create an application from an export, using the users first name as the default app name", () => { const exportedApp = 'cypress/fixtures/exported-app.txt' cy.updateUserInformation("Ted", "Userman") - cy.importApp(exportedApp, "") - cy.visit(`${Cypress.config().baseUrl}/builder`) - cy.applicationInAppTable("Teds app") - - cy.get(".appTable .name").eq(0).click() - - cy.deleteApp("Teds app") - + cy.get(".app-table .name").eq(0).click() + cy.closeModal() + cy.get(`[aria-label="ShowMenu"]`).click() + cy.get(".spectrum-Menu").within(() => { + cy.contains("Overview").click() + }) + cy.get(".app-overview-actions-icon").within(() => { + cy.get(".spectrum-Icon").click({ force: true }) + }) + cy.get(".spectrum-Menu").contains("Delete").click({ force: true }) + cy.get(".spectrum-Dialog-grid").within(() => { + cy.get("input").type("Teds app") + }) + cy.get(".spectrum-Button--warning").click() cy.updateUserInformation("", "") }) - it("should generate the first application from a template", () => { + xit("should generate the first application from a template", () => { cy.visit(`${Cypress.config().baseUrl}/builder`) cy.wait(500) @@ -172,28 +186,28 @@ filterTests(['smoke', 'all'], () => { const card = cy.get('.template-card').eq(0).should("exist"); const cardOverlay = card.get('.template-thumbnail-action-overlay').should("exist") cardOverlay.invoke("show") - cardOverlay.get("button").contains("Use template").should("exist").click({force: true}) + cardOverlay.get("button").contains("Use template").should("exist").click({ force: true }) }) // CMD Create app from theme card cy.get(".spectrum-Modal").should('be.visible') - + const templateName = cy.get(".spectrum-Modal .template-thumbnail-text") templateName.invoke('text') - .then(templateNameText => { - const templateNameParsed = "/"+templateNameText.toLowerCase().replace(/\s+/g, "-") - cy.get(interact.SPECTRUM_MODAL_INPUT).eq(0).should("have.value", templateNameText) - cy.get(interact.SPECTRUM_MODAL_INPUT).eq(1).should("have.value", templateNameParsed) + .then(templateNameText => { + const templateNameParsed = "/" + templateNameText.toLowerCase().replace(/\s+/g, "-") + cy.get(interact.SPECTRUM_MODAL_INPUT).eq(0).should("have.value", templateNameText) + cy.get(interact.SPECTRUM_MODAL_INPUT).eq(1).should("have.value", templateNameParsed) - cy.get(".spectrum-Modal .spectrum-ButtonGroup").contains("Create app").click() - cy.wait(5000) - - cy.visit(`${Cypress.config().baseUrl}/builder`) - cy.wait(2000) + cy.get(".spectrum-Modal .spectrum-ButtonGroup").contains("Create app").click() + cy.wait(5000) - cy.applicationInAppTable(templateNameText) - cy.deleteApp(templateNameText) - }); + cy.visit(`${Cypress.config().baseUrl}/builder`) + cy.wait(2000) + + cy.applicationInAppTable(templateNameText) + cy.deleteApp(templateNameText) + }); }) @@ -217,5 +231,5 @@ filterTests(['smoke', 'all'], () => { cy.deleteApp(secondAppName) }) - }) + }) }) diff --git a/packages/builder/cypress/integration/createScreen.spec.js b/packages/builder/cypress/integration/createScreen.spec.js index c4b237279d..09d8485386 100644 --- a/packages/builder/cypress/integration/createScreen.spec.js +++ b/packages/builder/cypress/integration/createScreen.spec.js @@ -2,7 +2,7 @@ import filterTests from "../support/filterTests" const interact = require('../support/interact') filterTests(["smoke", "all"], () => { - context("Screen Tests", () => { + xcontext("Screen Tests", () => { before(() => { cy.login() cy.createTestApp() @@ -25,7 +25,7 @@ filterTests(["smoke", "all"], () => { it.skip("should delete all screens then create first screen via button", () => { cy.deleteAllScreens() - + cy.contains("Create first screen").click() cy.get(interact.BODY, { timeout: 2000 }).should('contain', '/home') }) @@ -33,7 +33,7 @@ filterTests(["smoke", "all"], () => { it("Should create and filter screens by access level", () => { const accessLevels = ["Basic", "Admin", "Public", "Power"] - for (const access of accessLevels){ + for (const access of accessLevels) { // Create screen with specified access level cy.createScreen(access, access) // Filter by access level and confirm screen visible @@ -46,9 +46,9 @@ filterTests(["smoke", "all"], () => { // Filter by All screens - Confirm all screens visible cy.filterScreensAccessLevel("All screens") cy.get(interact.BODY).should('contain', accessLevels[0]) - .and('contain', accessLevels[1]) - .and('contain', accessLevels[2]) - .and('contain', accessLevels[3]) + .and('contain', accessLevels[1]) + .and('contain', accessLevels[2]) + .and('contain', accessLevels[3]) }) }) }) diff --git a/packages/builder/cypress/integration/queryLevelTransformers.spec.js b/packages/builder/cypress/integration/queryLevelTransformers.spec.js index 00cae6f417..7357bbe905 100644 --- a/packages/builder/cypress/integration/queryLevelTransformers.spec.js +++ b/packages/builder/cypress/integration/queryLevelTransformers.spec.js @@ -13,9 +13,9 @@ filterTests(["smoke", "all"], () => { const datasource = "REST" const restUrl = "https://api.openbrewerydb.org/breweries" cy.selectExternalDatasource(datasource) - cy.createRestQuery("GET", restUrl, "/breweries") + cy.createRestQuery("GET", restUrl, "breweries") cy.reload() - cy.contains(".nav-item-content", "/breweries", { timeout: 20000 }).click() + cy.contains(".nav-item-content", "breweries", { timeout: 20000 }).click() cy.contains(interact.SPECTRUM_TABS_ITEM, "Transformer", { timeout: 5000 }).click({ force: true }) // Get Transformer Function from file cy.readFile("cypress/support/queryLevelTransformerFunction.js").then( @@ -44,9 +44,9 @@ filterTests(["smoke", "all"], () => { const datasource = "REST" const restUrl = "https://api.openbrewerydb.org/breweries" cy.selectExternalDatasource(datasource) - cy.createRestQuery("GET", restUrl, "/breweries") + cy.createRestQuery("GET", restUrl, "breweries") cy.reload() - cy.contains(".nav-item-content", "/breweries", { timeout: 2000 }).click() + cy.contains(".nav-item-content", "breweries", { timeout: 2000 }).click() cy.contains(interact.SPECTRUM_TABS_ITEM, "Transformer", { timeout: 5000 }).click({ force: true }) // Get Transformer Function with Data from file cy.readFile( @@ -75,9 +75,9 @@ filterTests(["smoke", "all"], () => { const datasource = "REST" const restUrl = "https://api.openbrewerydb.org/breweries" cy.selectExternalDatasource(datasource) - cy.createRestQuery("GET", restUrl, "/breweries") + cy.createRestQuery("GET", restUrl, "breweries") cy.reload() - cy.contains(".nav-item-content", "/breweries", { timeout: 2000 }).click() + cy.contains(".nav-item-content", "breweries", { timeout: 10000 }).click() cy.contains(interact.SPECTRUM_TABS_ITEM, "Transformer", { timeout: 5000 }).click({ force: true }) // Clear the code box and add "test" cy.get(interact.CODEMIRROR_TEXTAREA) diff --git a/packages/builder/cypress/support/commands.js b/packages/builder/cypress/support/commands.js index 4c44fd6672..e63fd41591 100644 --- a/packages/builder/cypress/support/commands.js +++ b/packages/builder/cypress/support/commands.js @@ -101,7 +101,7 @@ Cypress.Commands.add("deleteUser", email => { }) Cypress.Commands.add("updateUserInformation", (firstName, lastName) => { - cy.get(".user-dropdown .avatar > .icon", { timeout: 2000 }).click({ + cy.get(".user-dropdown .icon", { timeout: 2000 }).click({ force: true, }) @@ -132,7 +132,7 @@ Cypress.Commands.add("updateUserInformation", (firstName, lastName) => { .blur() } cy.get(".confirm-wrap").within(() => { - cy.get("button").contains("Update information").click({ force: true }) + cy.get("button").contains("Save").click({ force: true }) }) cy.get(".spectrum-Dialog-grid").should("not.exist") }) @@ -222,9 +222,12 @@ Cypress.Commands.add("deleteApp", name => { // Go to app overview const appIdParsed = appId.split("_").pop() const actionEleId = `[data-cy=row_actions_${appIdParsed}]` - cy.get(actionEleId).within(() => { - cy.contains("Manage").click({ force: true }) + cy.get(actionEleId).click() + cy.get(`[aria-label="ShowMenu"]`).click() + cy.get(".spectrum-Menu").within(() => { + cy.contains("Overview").click() }) + cy.wait(500) // Unpublish first if needed @@ -400,7 +403,7 @@ Cypress.Commands.add("searchForApplication", appName => { return } else { // Searches for the app - cy.get(".filter").then(() => { + cy.get(".spectrum-Search").then(() => { cy.get(".spectrum-Textfield").within(() => { cy.get("input").eq(0).clear({ force: true }) cy.get("input").eq(0).type(appName, { force: true }) @@ -413,7 +416,7 @@ Cypress.Commands.add("searchForApplication", appName => { // Assumes there are no others Cypress.Commands.add("applicationInAppTable", appName => { cy.visit(`${Cypress.config().baseUrl}/builder`, { timeout: 30000 }) - cy.get(".appTable", { timeout: 30000 }).within(() => { + cy.get(".app-table", { timeout: 30000 }).within(() => { cy.get(".title").contains(appName).should("exist") }) }) diff --git a/packages/builder/package.json b/packages/builder/package.json index 9701e1ad94..82c939d1f4 100644 --- a/packages/builder/package.json +++ b/packages/builder/package.json @@ -1,6 +1,6 @@ { "name": "@budibase/builder", - "version": "2.2.12-alpha.6", + "version": "2.2.12-alpha.32", "license": "GPL-3.0", "private": true, "scripts": { @@ -71,10 +71,10 @@ } }, "dependencies": { - "@budibase/bbui": "2.2.12-alpha.6", - "@budibase/client": "2.2.12-alpha.6", - "@budibase/frontend-core": "2.2.12-alpha.6", - "@budibase/string-templates": "2.2.12-alpha.6", + "@budibase/bbui": "2.2.12-alpha.32", + "@budibase/client": "2.2.12-alpha.32", + "@budibase/frontend-core": "2.2.12-alpha.32", + "@budibase/string-templates": "2.2.12-alpha.32", "@sentry/browser": "5.19.1", "@spectrum-css/page": "^3.0.1", "@spectrum-css/vars": "^3.0.1", @@ -87,7 +87,7 @@ "shortid": "2.2.15", "svelte-dnd-action": "^0.9.8", "svelte-loading-spinners": "^0.1.1", - "svelte-portal": "0.1.0", + "svelte-portal": "1.0.0", "uuid": "8.3.1", "yup": "0.29.2" }, diff --git a/packages/builder/src/App.svelte b/packages/builder/src/App.svelte index 4d193df104..04846e01bf 100644 --- a/packages/builder/src/App.svelte +++ b/packages/builder/src/App.svelte @@ -11,11 +11,8 @@