diff --git a/.github/workflows/budibase_ci.yml b/.github/workflows/budibase_ci.yml index ea9da5ea8c..af88dbb38f 100644 --- a/.github/workflows/budibase_ci.yml +++ b/.github/workflows/budibase_ci.yml @@ -239,6 +239,7 @@ jobs: - name: Test server env: DATASOURCE: ${{ matrix.datasource }} + # DEBUG: testcontainers* run: | if ${{ env.ONLY_AFFECTED_TASKS }}; then AFFECTED=$(yarn --silent nx show projects --affected -t test --base=${{ env.NX_BASE_BRANCH }} -p @budibase/server) diff --git a/lerna.json b/lerna.json index 832759259f..87f0731cda 100644 --- a/lerna.json +++ b/lerna.json @@ -1,6 +1,6 @@ { "$schema": "node_modules/lerna/schemas/lerna-schema.json", - "version": "3.9.3", + "version": "3.9.5", "npmClient": "yarn", "concurrency": 20, "command": { diff --git a/packages/backend-core/src/constants/misc.ts b/packages/backend-core/src/constants/misc.ts index e2fd975e40..1fa8d6e926 100644 --- a/packages/backend-core/src/constants/misc.ts +++ b/packages/backend-core/src/constants/misc.ts @@ -9,6 +9,7 @@ export enum Cookie { ACCOUNT_RETURN_URL = "budibase:account:returnurl", DatasourceAuth = "budibase:datasourceauth", OIDC_CONFIG = "budibase:oidc:config", + FeatureFlags = "budibase:featureflags", } export { Header } from "@budibase/shared-core" diff --git a/packages/backend-core/src/context/mainContext.ts b/packages/backend-core/src/context/mainContext.ts index 22b937db88..9bea94de29 100644 --- a/packages/backend-core/src/context/mainContext.ts +++ b/packages/backend-core/src/context/mainContext.ts @@ -460,6 +460,17 @@ export function setFeatureFlags(key: string, value: Record) { context.featureFlagCache[key] = value } +export function getFeatureFlagOverrides(): Record { + return getCurrentContext()?.featureFlagOverrides || {} +} + +export async function doInFeatureFlagOverrideContext( + value: Record, + callback: () => Promise +) { + return await newContext({ featureFlagOverrides: value }, callback) +} + export function getTableForView(viewId: string): Table | undefined { const context = getCurrentContext() if (!context) { diff --git a/packages/backend-core/src/context/types.ts b/packages/backend-core/src/context/types.ts index c2cb966731..b34ef8c1b7 100644 --- a/packages/backend-core/src/context/types.ts +++ b/packages/backend-core/src/context/types.ts @@ -24,5 +24,6 @@ export type ContextMap = { featureFlagCache?: { [key: string]: Record } + featureFlagOverrides?: Record viewToTableCache?: Record } diff --git a/packages/backend-core/src/errors/errors.ts b/packages/backend-core/src/errors/errors.ts index ef6a3038ba..ceff6945af 100644 --- a/packages/backend-core/src/errors/errors.ts +++ b/packages/backend-core/src/errors/errors.ts @@ -1,5 +1,7 @@ // BASE +import { ErrorCode } from "@budibase/types" + export abstract class BudibaseError extends Error { code: string @@ -13,13 +15,6 @@ export abstract class BudibaseError extends Error { // ERROR HANDLING -export enum ErrorCode { - USAGE_LIMIT_EXCEEDED = "usage_limit_exceeded", - FEATURE_DISABLED = "feature_disabled", - INVALID_API_KEY = "invalid_api_key", - HTTP = "http", -} - /** * For the given error, build the public representation that is safe * to be exposed over an api. diff --git a/packages/backend-core/src/features/features.ts b/packages/backend-core/src/features/features.ts index 7106777084..a76ffe8a7b 100644 --- a/packages/backend-core/src/features/features.ts +++ b/packages/backend-core/src/features/features.ts @@ -175,6 +175,21 @@ export class FlagSet { } } + const overrides = context.getFeatureFlagOverrides() + for (const [key, value] of Object.entries(overrides)) { + if (!this.isFlagName(key)) { + continue + } + + if (typeof value !== "boolean") { + continue + } + + // @ts-expect-error - TS does not like you writing into a generic type. + flagValues[key] = value + tags[`flags.${key}.source`] = "override" + } + context.setFeatureFlags(this.setId, flagValues) for (const [key, value] of Object.entries(flagValues)) { tags[`flags.${key}.value`] = value diff --git a/packages/backend-core/src/middleware/authenticated.ts b/packages/backend-core/src/middleware/authenticated.ts index 54d808de86..86c0c687dc 100644 --- a/packages/backend-core/src/middleware/authenticated.ts +++ b/packages/backend-core/src/middleware/authenticated.ts @@ -16,11 +16,12 @@ import env from "../environment" import { Ctx, EndpointMatcher, + ErrorCode, LoginMethod, SessionCookie, User, } from "@budibase/types" -import { ErrorCode, InvalidAPIKeyError } from "../errors" +import { InvalidAPIKeyError } from "../errors" import tracer from "dd-trace" import type { Middleware, Next } from "koa" diff --git a/packages/backend-core/src/middleware/featureFlagCookie.ts b/packages/backend-core/src/middleware/featureFlagCookie.ts new file mode 100644 index 0000000000..070e2161af --- /dev/null +++ b/packages/backend-core/src/middleware/featureFlagCookie.ts @@ -0,0 +1,13 @@ +import { Ctx, FeatureFlagCookie } from "@budibase/types" +import { Middleware, Next } from "koa" +import { getCookie } from "../utils" +import { Cookie } from "../constants" +import { doInFeatureFlagOverrideContext } from "../context" + +export default (async (ctx: Ctx, next: Next) => { + const cookie = getCookie(ctx, Cookie.FeatureFlags) + const flags = cookie?.flags || {} + await doInFeatureFlagOverrideContext(flags, async () => { + await next() + }) +}) as Middleware diff --git a/packages/backend-core/src/middleware/index.ts b/packages/backend-core/src/middleware/index.ts index 9ee51db45b..0d16ae52e3 100644 --- a/packages/backend-core/src/middleware/index.ts +++ b/packages/backend-core/src/middleware/index.ts @@ -20,5 +20,6 @@ export { default as correlation } from "../logging/correlation/middleware" export { default as errorHandling } from "./errorHandling" export { default as querystringToBody } from "./querystringToBody" export { default as csp } from "./contentSecurityPolicy" +export { default as featureFlagCookie } from "./featureFlagCookie" export * as joiValidator from "./joi-validator" export { default as ip } from "./ip" diff --git a/packages/builder/src/components/backend/DataTable/modals/CreateEditColumn.svelte b/packages/builder/src/components/backend/DataTable/modals/CreateEditColumn.svelte index f6216a0c0b..58f27f563e 100644 --- a/packages/builder/src/components/backend/DataTable/modals/CreateEditColumn.svelte +++ b/packages/builder/src/components/backend/DataTable/modals/CreateEditColumn.svelte @@ -28,7 +28,6 @@ import { createEventDispatcher, getContext, onMount } from "svelte" import { cloneDeep } from "lodash/fp" import { datasources, tables } from "@/stores/builder" - import { licensing } from "@/stores/portal" import { TableNames, UNEDITABLE_USER_FIELDS } from "@/constants" import { DB_TYPE_EXTERNAL, @@ -137,8 +136,6 @@ ] $: rowGoldenSample = RowUtils.generateGoldenSample($rows) - $: aiEnabled = - $licensing.customAIConfigsEnabled || $licensing.budibaseAIEnabled $: if (hasPrimaryDisplay && editableColumn.constraints) { editableColumn.constraints.presence = { allowEmpty: false } } @@ -559,10 +556,8 @@ FIELDS.SIGNATURE_SINGLE, FIELDS.JSON, FIELDS.AUTO, + FIELDS.AI, ] - if (aiEnabled) { - fields.push(FIELDS.AI) - } return fields } if (isExternalTable && isSqlTable) { diff --git a/packages/builder/src/components/backend/DatasourceNavigator/datasourceUtils.js b/packages/builder/src/components/backend/DatasourceNavigator/datasourceUtils.js index 5e38cc9bfe..f23866c267 100644 --- a/packages/builder/src/components/backend/DatasourceNavigator/datasourceUtils.js +++ b/packages/builder/src/components/backend/DatasourceNavigator/datasourceUtils.js @@ -1,4 +1,5 @@ import { TableNames } from "@/constants" +import { INTERNAL_TABLE_SOURCE_ID } from "@budibase/types" const showDatasourceOpen = ({ selected, @@ -41,7 +42,7 @@ const containsActiveEntity = ( // Check for hardcoded datasource edge cases if ( isActive("./datasource/bb_internal") && - datasource._id === "bb_internal" + datasource._id === INTERNAL_TABLE_SOURCE_ID ) { return true } diff --git a/packages/builder/src/components/commandPalette/CommandPalette.svelte b/packages/builder/src/components/commandPalette/CommandPalette.svelte index 4598b44873..427b1e960f 100644 --- a/packages/builder/src/components/commandPalette/CommandPalette.svelte +++ b/packages/builder/src/components/commandPalette/CommandPalette.svelte @@ -8,7 +8,7 @@ notifications, } from "@budibase/bbui" import { API } from "@/api" - import { goto } from "@roxi/routify" + import { goto, params, isActive } from "@roxi/routify" import { automationStore, previewStore, @@ -19,54 +19,36 @@ queries, tables, views, + viewsV2, } from "@/stores/builder" - import { themeStore } from "@/stores/portal" + import { themeStore, featureFlags } from "@/stores/portal" import { getContext } from "svelte" import { ThemeOptions } from "@budibase/shared-core" + import { FeatureFlag } from "@budibase/types" const modalContext = getContext(Context.Modal) - const commands = [ + + let search + let selected = null + + $: inApp = $isActive("/builder/app/:application") + $: commands = [ { type: "Access", name: "Invite users and manage app access", description: "", icon: "User", action: () => builderStore.showBuilderSidePanel(), + requiresApp: true, }, - { - type: "Navigate", - name: "Portal", - description: "", - icon: "Compass", - action: () => $goto("../../portal"), - }, - { - type: "Navigate", - name: "Data", - description: "", - icon: "Compass", - action: () => $goto("./data"), - }, - { - type: "Navigate", - name: "Design", - description: "", - icon: "Compass", - action: () => $goto("./design"), - }, - { - type: "Navigate", - name: "Automations", - description: "", - icon: "Compass", - action: () => $goto("./automation"), - }, + ...navigationCommands(), { type: "Publish", name: "App", description: "Deploy your application", icon: "Box", action: deployApp, + requiresApp: true, }, { type: "Preview", @@ -74,12 +56,14 @@ description: "", icon: "Play", action: () => previewStore.showPreview(true), + requiresApp: true, }, { type: "Preview", name: "Published App", icon: "Play", action: () => window.open(`/app${$appStore.url}`), + requiresApp: true, }, { type: "Support", @@ -87,6 +71,7 @@ icon: "Help", action: () => window.open(`https://github.com/Budibase/budibase/discussions/new`), + requiresApp: true, }, { type: "Support", @@ -96,52 +81,166 @@ window.open( `https://github.com/Budibase/budibase/issues/new?assignees=&labels=bug&template=bug_report.md&title=` ), + requiresApp: true, }, - ...($datasources?.list?.map(datasource => ({ + ...datasourceCommands($datasources?.list || []), + ...tableCommands($tables?.list || []), + ...viewCommands($views?.list || []), + ...viewV2Commands($viewsV2?.list || []), + ...queryCommands($queries?.list || []), + ...screenCommands($sortedScreens), + ...automationCommands($automationStore?.automations || []), + ...themeCommands(), + ...featureFlagCommands($featureFlags), + ] + $: enrichedCommands = commands.map(cmd => ({ + ...cmd, + searchValue: `${cmd.type} ${cmd.name}`.toLowerCase().replace(/_/g, " "), + })) + $: results = filterResults(enrichedCommands, search, inApp) + $: categories = groupResults(results) + + const navigationCommands = () => { + const routes = [ + { + name: "Portal", + url: "/builder/portal", + }, + { + name: "Data", + url: "/builder/app/:application/data", + }, + { + name: "Design", + url: "/builder/app/:application/design", + }, + { + name: "Automations", + url: "/builder/app/:application/automation", + }, + { + name: "Settings", + url: "/builder/app/:application/settings", + }, + ] + return routes.map(route => ({ + type: "Navigate", + name: route.name, + icon: "Compass", + action: () => { + const gotoParams = route.url.includes(":application") + ? { application: $params.application } + : {} + $goto(route.url, gotoParams) + }, + requiresApp: true, + })) + } + + const datasourceCommands = datasources => { + return datasources.map(datasource => ({ type: "Datasource", - name: `${datasource.name}`, + name: datasource.name, icon: "Data", - action: () => $goto(`./data/datasource/${datasource._id}`), - })) ?? []), - ...($tables?.list?.map(table => ({ + action: () => + $goto(`/builder/app/:application/data/datasource/:id`, { + application: $params.application, + id: datasource._id, + }), + requiresApp: true, + })) + } + + const tableCommands = tables => { + return tables.map(table => ({ type: "Table", name: table.name, icon: "Table", - action: () => $goto(`./data/table/${table._id}`), - })) ?? []), - ...($views?.list?.map(view => ({ + action: () => + $goto(`/builder/app/:application/data/table/:id`, { + application: $params.application, + id: table._id, + }), + requiresApp: true, + })) + } + + const viewCommands = views => { + return views.map(view => ({ type: "View", name: view.name, icon: "Remove", action: () => { - if (view.version === 2) { - $goto(`./data/view/v2/${view.id}`) - } else { - $goto(`./data/view/${view.name}`) - } + $goto(`/builder/app/:application/data/view/:name`, { + application: $params.application, + name: view.name, + }) }, - })) ?? []), - ...($queries?.list?.map(query => ({ + requiresApp: true, + })) + } + + const viewV2Commands = views => { + return views.map(view => ({ + type: "View", + name: view.name, + icon: "Remove", + action: () => { + $goto(`/builder/app/:application/data/table/:tableId/:viewId`, { + application: $params.application, + x: view.tableId, + viewId: view.id, + }) + }, + requiresApp: true, + })) + } + + const queryCommands = queries => { + return queries.map(query => ({ type: "Query", name: query.name, icon: "SQLQuery", - action: () => $goto(`./data/query/${query._id}`), - })) ?? []), - ...$sortedScreens.map(screen => ({ + action: () => + $goto(`/builder/app/:application/data/query/:id`, { + application: $params.application, + id: query._id, + }), + requiresApp: true, + })) + } + + const screenCommands = screens => { + return screens.map(screen => ({ type: "Screen", name: screen.routing.route, icon: "WebPage", - action: () => { - $goto(`./design/${screen._id}/${screen._id}-screen`) - }, - })), - ...($automationStore?.automations?.map(automation => ({ + action: () => + $goto(`/builder/app/:application/design/:screenId/:componentId`, { + application: $params.application, + screenId: screen._id, + componentId: `${screen._id}-screen`, + }), + requiresApp: true, + })) + } + + const automationCommands = automations => { + return automations.map(automation => ({ type: "Automation", name: automation.name, icon: "ShareAndroid", - action: () => $goto(`./automation/${automation._id}`), - })) ?? []), - ...ThemeOptions.map(themeMeta => ({ + action: () => + $goto(`/builder/app/:application/automation/:id`, { + application: $params.application, + id: automation._id, + }), + requiresApp: true, + })) + } + + const themeCommands = () => { + return ThemeOptions.map(themeMeta => ({ type: "Change Builder Theme", name: themeMeta.name, icon: "ColorPalette", @@ -150,28 +249,41 @@ state.theme = themeMeta.id return state }), - })), - ] + })) + } - let search - let selected = null - - $: enrichedCommands = commands.map(cmd => ({ - ...cmd, - searchValue: `${cmd.type} ${cmd.name}`.toLowerCase(), - })) - $: results = filterResults(enrichedCommands, search) - $: categories = groupResults(results) - - const filterResults = (commands, search) => { - if (!search) { - selected = null - return commands + const featureFlagCommands = flags => { + if (!flags.DEBUG_UI) { + return [] + } + return Object.entries(flags) + .filter(([flag]) => flag !== FeatureFlag.DEBUG_UI) + .map(([flag, value]) => ({ + type: "Feature Flag", + name: `${value ? "Disable" : "Enable"} ${flag}`, + icon: "Flag", + action: () => { + featureFlags.setFlag(flag, !value) + }, + })) + } + + const filterResults = (commands, search, inApp) => { + if (search) { + selected = 0 + search = search.toLowerCase().replace(/_/g, " ") + } else { + selected = null } - selected = 0 - search = search.toLowerCase() return commands - .filter(cmd => cmd.searchValue.includes(search)) + .filter(cmd => { + // Handle searching + if (search && !cmd.searchValue.includes(search)) { + return false + } + // Handle commands that require an app + return inApp || !cmd.requiresApp + }) .map((cmd, idx) => ({ ...cmd, idx, @@ -264,7 +376,8 @@ {command.type}: 
- {command.name} + + {@html command.name}
{/each} @@ -339,4 +452,10 @@ text-overflow: ellipsis; white-space: nowrap; } + .name :global(code) { + font-size: 12px; + background: var(--background-alt); + padding: 4px; + border-radius: 4px; + } diff --git a/packages/builder/src/components/common/CodeEditor/AIGen.svelte b/packages/builder/src/components/common/CodeEditor/AIGen.svelte index c7e3a0f1ce..1e2593bb62 100644 --- a/packages/builder/src/components/common/CodeEditor/AIGen.svelte +++ b/packages/builder/src/components/common/CodeEditor/AIGen.svelte @@ -1,9 +1,9 @@ -
+
{#if suggestedCode !== null}
- + +
{/if} @@ -103,7 +106,7 @@ placeholder="Generate with AI" onSubmit={generateJs} bind:expanded - on:collapse={rejectSuggestion} + bind:value={inputValue} readonly={!!suggestedCode} {expandedOnly} /> @@ -112,32 +115,27 @@ diff --git a/packages/builder/src/components/common/RoleSelect.svelte b/packages/builder/src/components/common/RoleSelect.svelte index 15d4bd815d..4316e339dc 100644 --- a/packages/builder/src/components/common/RoleSelect.svelte +++ b/packages/builder/src/components/common/RoleSelect.svelte @@ -28,6 +28,7 @@ $: isPremiumOrAbove = [ Constants.PlanType.PREMIUM, + Constants.PlanType.PREMIUM_PLUS, Constants.PlanType.ENTERPRISE, Constants.PlanType.ENTERPRISE_BASIC_TRIAL, Constants.PlanType.ENTERPRISE_BASIC, diff --git a/packages/builder/src/components/common/ai/AIInput.svelte b/packages/builder/src/components/common/ai/AIInput.svelte index 5a7992e2ec..3d1a2e25c2 100644 --- a/packages/builder/src/components/common/ai/AIInput.svelte +++ b/packages/builder/src/components/common/ai/AIInput.svelte @@ -14,7 +14,6 @@ export const submit = onPromptSubmit $: expanded = expandedOnly || expanded - const dispatch = createEventDispatcher() let promptInput: HTMLInputElement @@ -28,9 +27,11 @@ $: aiEnabled = $auth?.user?.llm $: creditsExceeded = $licensing.aiCreditsExceeded - $: disabled = !aiEnabled || creditsExceeded || readonly || promptLoading + $: disabled = !aiEnabled || creditsExceeded || readonly $: animateBorder = !disabled && expanded + $: canSubmit = !readonly && !!value + function collapse() { dispatch("collapse") expanded = expandedOnly @@ -62,7 +63,7 @@ } async function onPromptSubmit() { - if (readonly) { + if (!canSubmit) { return } promptLoading = true @@ -89,6 +90,7 @@ src={BBAI} alt="AI" class="ai-icon" + class:loading={promptLoading} class:disabled={expanded && disabled} on:click={e => { e.stopPropagation() @@ -103,7 +105,7 @@ class="prompt-input" {placeholder} on:keydown={handleKeyPress} - {disabled} + disabled={disabled || promptLoading} /> {:else} @@ -151,13 +153,14 @@ {:else} {/if} @@ -167,14 +170,12 @@ diff --git a/packages/builder/src/components/common/bindings/BindingPanel.svelte b/packages/builder/src/components/common/bindings/BindingPanel.svelte index 6ae0579f33..57061c420f 100644 --- a/packages/builder/src/components/common/bindings/BindingPanel.svelte +++ b/packages/builder/src/components/common/bindings/BindingPanel.svelte @@ -372,6 +372,7 @@ (sidePanel = "Evaluation")} {completions} {bindings} {validations} diff --git a/packages/builder/src/helpers/duplicate.ts b/packages/builder/src/helpers/duplicate.ts index eb705c6525..e0a281658f 100644 --- a/packages/builder/src/helpers/duplicate.ts +++ b/packages/builder/src/helpers/duplicate.ts @@ -1,113 +1,5 @@ -/** - * Duplicates a name with respect to a collection of existing names - * e.g. - * name all names result - * ------ ----------- -------- - * ("foo") ["foo"] "foo 1" - * ("foo") ["foo", "foo 1"] "foo 2" - * ("foo 1") ["foo", "foo 1"] "foo 2" - * ("foo") ["foo", "foo 2"] "foo 1" - * - * Repl - */ -export const duplicateName = (name: string, allNames: string[]) => { - const duplicatePattern = new RegExp(`\\s(\\d+)$`) - const baseName = name.split(duplicatePattern)[0] - const isDuplicate = new RegExp(`${baseName}\\s(\\d+)$`) +// export * from +import { helpers } from "@budibase/shared-core" - // get the sequence from matched names - const sequence: number[] = [] - allNames.filter(n => { - if (n === baseName) { - return true - } - const match = n.match(isDuplicate) - if (match) { - sequence.push(parseInt(match[1])) - return true - } - return false - }) - sequence.sort((a, b) => a - b) - // get the next number in the sequence - let number - if (sequence.length === 0) { - number = 1 - } else { - // get the next number in the sequence - for (let i = 0; i < sequence.length; i++) { - if (sequence[i] !== i + 1) { - number = i + 1 - break - } - } - if (!number) { - number = sequence.length + 1 - } - } - - return `${baseName} ${number}` -} - -/** - * More flexible alternative to the above function, which handles getting the - * next sequential name from an array of existing items while accounting for - * any type of prefix, and being able to deeply retrieve that name from the - * existing item array. - * - * Examples with a prefix of "foo": - * [] => "foo" - * ["foo"] => "foo2" - * ["foo", "foo6"] => "foo7" - * - * Examples with a prefix of "foo " (space at the end): - * [] => "foo" - * ["foo"] => "foo 2" - * ["foo", "foo 6"] => "foo 7" - * - * @param items the array of existing items - * @param prefix the string prefix of each name, including any spaces desired - * @param getName optional function to extract the name for an item, if not a - * flat array of strings - */ -export const getSequentialName = ( - items: T[] | null, - prefix: string | null, - { - getName, - numberFirstItem, - separator = "", - }: { - getName?: (item: T) => string - numberFirstItem?: boolean - separator?: string - } = {} -) => { - if (!prefix?.length) { - return "" - } - const trimmedPrefix = prefix.trim() - const firstName = numberFirstItem ? `${prefix}1` : trimmedPrefix - if (!items?.length) { - return firstName - } - let max = 0 - items.forEach(item => { - const name = getName?.(item) ?? item - if (typeof name !== "string" || !name.startsWith(trimmedPrefix)) { - return - } - const split = name.split(trimmedPrefix) - if (split.length !== 2) { - return - } - if (split[1].trim() === "") { - split[1] = "1" - } - const num = parseInt(split[1]) - if (num > max) { - max = num - } - }) - return max === 0 ? firstName : `${prefix}${separator}${max + 1}` -} +export const duplicateName = helpers.duplicateName +export const getSequentialName = helpers.getSequentialName diff --git a/packages/builder/src/pages/builder/_layout.svelte b/packages/builder/src/pages/builder/_layout.svelte index 7e765d7366..7bb97be71d 100644 --- a/packages/builder/src/pages/builder/_layout.svelte +++ b/packages/builder/src/pages/builder/_layout.svelte @@ -6,8 +6,11 @@ import { API } from "@/api" import Branding from "./Branding.svelte" import ContextMenu from "@/components/ContextMenu.svelte" + import CommandPalette from "@/components/commandPalette/CommandPalette.svelte" + import { Modal } from "@budibase/bbui" let loaded = false + let commandPaletteModal $: multiTenancyEnabled = $admin.multiTenancy $: hasAdminUser = $admin?.checklist?.adminUser?.checked @@ -157,12 +160,25 @@ } } } + + // Event handler for the command palette + const handleKeyDown = e => { + if (e.key === "k" && (e.ctrlKey || e.metaKey)) { + e.preventDefault() + commandPaletteModal.toggle() + } + } + + + + + {#if loaded} {/if} diff --git a/packages/builder/src/pages/builder/app/[application]/_layout.svelte b/packages/builder/src/pages/builder/app/[application]/_layout.svelte index 5077d939d4..e2538d1ec0 100644 --- a/packages/builder/src/pages/builder/app/[application]/_layout.svelte +++ b/packages/builder/src/pages/builder/app/[application]/_layout.svelte @@ -14,7 +14,6 @@ Tabs, Tab, Heading, - Modal, notifications, TooltipPosition, } from "@budibase/bbui" @@ -24,7 +23,6 @@ import { capitalise } from "@/helpers" import { onMount, onDestroy } from "svelte" import VerificationPromptBanner from "@/components/common/VerificationPromptBanner.svelte" - import CommandPalette from "@/components/commandPalette/CommandPalette.svelte" import BuilderSidePanel from "./_components/BuilderSidePanel.svelte" import { UserAvatars } from "@budibase/frontend-core" import PreviewOverlay from "./_components/PreviewOverlay.svelte" @@ -35,7 +33,6 @@ let promise = getPackage() let hasSynced = false - let commandPaletteModal let loaded = false $: selected = capitalise( @@ -75,14 +72,6 @@ $goto($builderStore.previousTopNavPath[path] || path) } - // Event handler for the command palette - const handleKeyDown = e => { - if (e.key === "k" && (e.ctrlKey || e.metaKey)) { - e.preventDefault() - commandPaletteModal.toggle() - } - } - onMount(async () => { if (!hasSynced && application) { try { @@ -162,11 +151,6 @@ {/if} - - - - - diff --git a/packages/builder/src/pages/builder/app/[application]/data/_components/CreateInternalTableModal.svelte b/packages/builder/src/pages/builder/app/[application]/data/_components/CreateInternalTableModal.svelte index 6fee783363..a6080bf685 100644 --- a/packages/builder/src/pages/builder/app/[application]/data/_components/CreateInternalTableModal.svelte +++ b/packages/builder/src/pages/builder/app/[application]/data/_components/CreateInternalTableModal.svelte @@ -6,7 +6,7 @@ let modal let promptUpload = false - export function show({ promptUpload: newPromptUpload = false }) { + export function show({ promptUpload: newPromptUpload = false } = {}) { promptUpload = newPromptUpload modal.show() } diff --git a/packages/builder/src/pages/builder/app/[application]/data/new.svelte b/packages/builder/src/pages/builder/app/[application]/data/new.svelte index 3d4094d6b4..f79a292d6c 100644 --- a/packages/builder/src/pages/builder/app/[application]/data/new.svelte +++ b/packages/builder/src/pages/builder/app/[application]/data/new.svelte @@ -1,4 +1,4 @@ - - - -
-
- +
+
+
+ +
+
+ {config.name} +
+
+ {#if config.active} +
Enabled
+ {:else} +
Disabled
+ {/if} +
-
- {config.provider} - -
-
- {#if config.name !== "Budibase AI"} - - - {/if} - {#if config.active} -
Activated
- {:else if !config.active} -
Disabled
- {/if} - {#if config.isDefault} -
Default
+
+ {#if config.provider === "BudibaseAI"} + {#if config.active} + disableHandler && disableHandler()}> + Disable + + {:else} + editHandler && editHandler()}> + Enable + + {/if} + {:else} + + editHandler && editHandler()}> + {#if config.apiKey}Edit{:else}Set up{/if} + {/if}
@@ -71,10 +60,16 @@ padding: 16px; border-radius: 4px; cursor: pointer; - display: grid; - grid-template-columns: 6% 1fr auto; - grid-gap: 20px; + display: flex; align-items: center; + justify-content: space-between; + gap: 12px; + } + + .details { + display: flex; + align-items: center; + gap: 12px; } .option :global(label) { @@ -87,12 +82,13 @@ .header { align-items: center; + color: var(--spectrum-global-color-gray-900); } .icon { - background-color: white; - height: 38px; - width: 38px; + background-color: var(--spectrum-global-color-gray-200); + height: 40px; + width: 40px; display: flex; justify-content: center; align-items: center; @@ -103,33 +99,21 @@ pointer-events: none; } - .controls { - display: grid; - grid-auto-flow: column; - grid-gap: 10px; - align-items: center; - } - .tag { - display: flex; - color: #ffffff; padding: 4px 8px; justify-content: center; align-items: center; gap: 8px; font-size: 12px; border-radius: 5px; - } - - .default { - background: var(--grey-6); + color: #fff; } .active { - background: var(--spectrum-global-color-green-600); + background: #004c2e; } .disabled { - background: var(--spectrum-global-color-red-600); + background: var(--grey-3); } diff --git a/packages/builder/src/pages/builder/portal/settings/ai/AISettings.spec.js b/packages/builder/src/pages/builder/portal/settings/ai/AISettings.spec.js index 35f8859cd5..f6e579f7fc 100644 --- a/packages/builder/src/pages/builder/portal/settings/ai/AISettings.spec.js +++ b/packages/builder/src/pages/builder/portal/settings/ai/AISettings.spec.js @@ -56,66 +56,18 @@ describe("AISettings", () => { expect(instance).toBeDefined() }) - describe("Licensing", () => { - it("should show the premium label on self host for custom configs", async () => { - setupEnv(Hosting.Self) - setupDOM() - const premiumTag = instance.queryByText("Premium") - expect(premiumTag).toBeInTheDocument() - }) - - it("should show the enterprise label on cloud for custom configs", async () => { - setupEnv(Hosting.Cloud) - setupDOM() - const enterpriseTag = instance.queryByText("Enterprise") - expect(enterpriseTag).toBeInTheDocument() - }) - - it("the add configuration button should not do anything the user doesn't have the correct license on cloud", async () => { - let addConfigurationButton + describe("DOM Render tests", () => { + it("the enable bb ai button should not do anything if the user doesn't have the correct license on self host", async () => { + let addAiButton let configModal - setupEnv(Hosting.Cloud) + setupEnv(Hosting.Self, { customAIConfigsEnabled: false }) setupDOM() - addConfigurationButton = instance.queryByText("Add configuration") - expect(addConfigurationButton).toBeInTheDocument() - await fireEvent.click(addConfigurationButton) + addAiButton = instance.queryByText("Enable BB AI") + expect(addAiButton).toBeInTheDocument() + await fireEvent.click(addAiButton) configModal = instance.queryByText("Custom AI Configuration") expect(configModal).not.toBeInTheDocument() }) - - it("the add configuration button should open the config modal if the user has the correct license on cloud", async () => { - let addConfigurationButton - let configModal - - setupEnv( - Hosting.Cloud, - { customAIConfigsEnabled: true }, - { AI_CUSTOM_CONFIGS: true } - ) - setupDOM() - addConfigurationButton = instance.queryByText("Add configuration") - expect(addConfigurationButton).toBeInTheDocument() - await fireEvent.click(addConfigurationButton) - configModal = instance.queryByText("Custom AI Configuration") - expect(configModal).toBeInTheDocument() - }) - - it("the add configuration button should open the config modal if the user has the correct license on self host", async () => { - let addConfigurationButton - let configModal - - setupEnv( - Hosting.Self, - { customAIConfigsEnabled: true }, - { AI_CUSTOM_CONFIGS: true } - ) - setupDOM() - addConfigurationButton = instance.queryByText("Add configuration") - expect(addConfigurationButton).toBeInTheDocument() - await fireEvent.click(addConfigurationButton) - configModal = instance.queryByText("Custom AI Configuration") - expect(configModal).toBeInTheDocument() - }) }) }) diff --git a/packages/builder/src/pages/builder/portal/settings/ai/ConfigModal.svelte b/packages/builder/src/pages/builder/portal/settings/ai/ConfigModal.svelte index 26e7a1606c..ac6c578a1a 100644 --- a/packages/builder/src/pages/builder/portal/settings/ai/ConfigModal.svelte +++ b/packages/builder/src/pages/builder/portal/settings/ai/ConfigModal.svelte @@ -1,76 +1,42 @@ - updateHandler(config) + : () => enableHandler(config)} + onCancel={isEnabled + ? () => disableHandler(config) + : () => updateHandler(config)} + disabled={!complete} size="M" - title="Custom AI Configuration" + title={`Set up ${config.name}`} >
- - -
-
- - {#if config.provider !== Providers.Custom.name} - - {/if} + +
@@ -80,13 +46,14 @@ bind:value={config.baseUrl} />
+
- - -
-
- - + +