From 96fbc8fff093fb5d49e6fa5a99441246c45b389e Mon Sep 17 00:00:00 2001 From: Martin McKeaveney Date: Mon, 9 Sep 2024 18:07:47 +0100 Subject: [PATCH] feature flag support --- packages/backend-core/src/features/index.ts | 1 + .../tests/core/utilities/structures/quotas.ts | 4 +++ .../builder/portal/settings/ai/index.svelte | 4 ++- packages/builder/src/stores/portal/menu.js | 15 ++++++--- packages/types/src/sdk/featureFlag.ts | 1 + .../src/api/controllers/global/configs.ts | 4 +-- .../controllers/global/tests/configs.spec.ts | 31 ++++++++++++++++++- 7 files changed, 52 insertions(+), 8 deletions(-) diff --git a/packages/backend-core/src/features/index.ts b/packages/backend-core/src/features/index.ts index b84237b1f0..f855daf8a5 100644 --- a/packages/backend-core/src/features/index.ts +++ b/packages/backend-core/src/features/index.ts @@ -268,4 +268,5 @@ export class FlagSet, T extends { [key: string]: V }> { export const flags = new FlagSet({ DEFAULT_VALUES: Flag.boolean(env.isDev()), SQS: Flag.boolean(env.isDev()), + AI_CUSTOM_CONFIGS: Flag.boolean(env.isDev()), }) diff --git a/packages/backend-core/tests/core/utilities/structures/quotas.ts b/packages/backend-core/tests/core/utilities/structures/quotas.ts index 8d0b05fe1e..58817e4831 100644 --- a/packages/backend-core/tests/core/utilities/structures/quotas.ts +++ b/packages/backend-core/tests/core/utilities/structures/quotas.ts @@ -17,6 +17,7 @@ export const usage = (users: number = 0, creators: number = 0): QuotaUsage => { automations: 0, dayPasses: 0, queries: 0, + budibaseAICredits: 0, triggers: {}, breakdown: { rowQueries: { @@ -46,12 +47,14 @@ export const usage = (users: number = 0, creators: number = 0): QuotaUsage => { automations: 0, dayPasses: 0, queries: 0, + budibaseAICredits: 0, triggers: {}, }, current: { automations: 0, dayPasses: 0, queries: 0, + budibaseAICredits: 0, triggers: {}, }, }, @@ -62,6 +65,7 @@ export const usage = (users: number = 0, creators: number = 0): QuotaUsage => { creators, userGroups: 0, rows: 0, + aiCustomConfigs: 0, triggers: {}, }, } diff --git a/packages/builder/src/pages/builder/portal/settings/ai/index.svelte b/packages/builder/src/pages/builder/portal/settings/ai/index.svelte index 77d6aa952a..c4403e5045 100644 --- a/packages/builder/src/pages/builder/portal/settings/ai/index.svelte +++ b/packages/builder/src/pages/builder/portal/settings/ai/index.svelte @@ -80,7 +80,9 @@ } async function deleteConfig(key) { - // Delete a configuration + // We don't store the default BB AI config in the DB + delete fullAIConfig.config.budibase_ai + // Delete the configuration delete fullAIConfig.config[key] try { diff --git a/packages/builder/src/stores/portal/menu.js b/packages/builder/src/stores/portal/menu.js index ec40f6d4ee..1e6282894e 100644 --- a/packages/builder/src/stores/portal/menu.js +++ b/packages/builder/src/stores/portal/menu.js @@ -1,7 +1,9 @@ import { derived } from "svelte/store" import { admin } from "./admin" import { auth } from "./auth" +import { isEnabled } from "helpers/featureFlags" import { sdk } from "@budibase/shared-core" +import { FeatureFlag } from "@budibase/types"; export const menu = derived([admin, auth], ([$admin, $auth]) => { const user = $auth?.user @@ -45,10 +47,6 @@ export const menu = derived([admin, auth], ([$admin, $auth]) => { title: "Auth", href: "/builder/portal/settings/auth", }, - { - title: "AI", - href: "/builder/portal/settings/ai", - }, { title: "Email", href: "/builder/portal/settings/email", @@ -66,6 +64,15 @@ export const menu = derived([admin, auth], ([$admin, $auth]) => { href: "/builder/portal/settings/environment", }, ] + if (isEnabled(FeatureFlag.AI_CUSTOM_CONFIGS)) { + settingsSubPages.push( + { + title: "AI", + href: "/builder/portal/settings/ai", + } + ) + } + if (!cloud) { settingsSubPages.push({ title: "Version", diff --git a/packages/types/src/sdk/featureFlag.ts b/packages/types/src/sdk/featureFlag.ts index 257b4ee576..69b2a3b1b2 100644 --- a/packages/types/src/sdk/featureFlag.ts +++ b/packages/types/src/sdk/featureFlag.ts @@ -1,6 +1,7 @@ export enum FeatureFlag { PER_CREATOR_PER_USER_PRICE = "PER_CREATOR_PER_USER_PRICE", PER_CREATOR_PER_USER_PRICE_ALERT = "PER_CREATOR_PER_USER_PRICE_ALERT", + AI_CUSTOM_CONFIGS = "AI_CUSTOM_CONFIGS", } export interface TenantFeatureFlags { diff --git a/packages/worker/src/api/controllers/global/configs.ts b/packages/worker/src/api/controllers/global/configs.ts index b8feb6158f..ffe7a1c717 100644 --- a/packages/worker/src/api/controllers/global/configs.ts +++ b/packages/worker/src/api/controllers/global/configs.ts @@ -135,7 +135,6 @@ const getEventFns = async (config: Config, existing?: Config) => { } } } - return fns } @@ -344,11 +343,12 @@ async function enrichAIConfig(aiConfig: AIConfig) { // Return the Budibase AI data source as part of the response if licensing allows const budibaseAIEnabled = await pro.features.isBudibaseAIEnabled() + const defaultConfigExists = Object.keys(aiConfig.config).some(key => aiConfig.config[key].isDefault) if (budibaseAIEnabled) { aiConfig.config["budibase_ai"] = { provider: "OpenAI", active: true, - isDefault: true, + isDefault: !defaultConfigExists, defaultModel: env.BUDIBASE_AI_DEFAULT_MODEL, name: "Budibase AI", } diff --git a/packages/worker/src/api/controllers/global/tests/configs.spec.ts b/packages/worker/src/api/controllers/global/tests/configs.spec.ts index 2ef00252ff..4bb3d598a6 100644 --- a/packages/worker/src/api/controllers/global/tests/configs.spec.ts +++ b/packages/worker/src/api/controllers/global/tests/configs.spec.ts @@ -1,4 +1,3 @@ -import { expect } from "vitest" import { configs } from "@budibase/backend-core" import { UserCtx } from "@budibase/types" import * as pro from "@budibase/pro" @@ -81,6 +80,35 @@ describe("Global configs controller", () => { }) }) + it("Should not not return the default Budibase AI config when on self host", async () => { + pro.features.isBudibaseAIEnabled = jest.fn(() => false) + configs.getConfig.mockResolvedValue({ + config: { + ai: { + apiKey: "abc123APIKey", + baseUrl: "https://api.example.com", + }, + }, + }) + const ctx = { + params: { + type: "ai", + }, + throw: jest.fn(), + } as UserCtx + + await find(ctx) + + expect(ctx.body).toEqual({ + config: { + ai: { + apiKey: "--secret-value--", + baseUrl: "https://api.example.com", + }, + }, + }) + }) + it("Should not update existing secrets when updating an existing AI Config", async () => { const newConfig = { type: "ai", @@ -114,4 +142,5 @@ describe("Global configs controller", () => { // should be unchanged expect(newConfig.config.aiconfig.apiKey === "myapikey") }) + })