Merge branch 'master' into ts/notification-store
This commit is contained in:
commit
3518d2ada2
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"$schema": "node_modules/lerna/schemas/lerna-schema.json",
|
"$schema": "node_modules/lerna/schemas/lerna-schema.json",
|
||||||
"version": "3.2.37",
|
"version": "3.2.38",
|
||||||
"npmClient": "yarn",
|
"npmClient": "yarn",
|
||||||
"concurrency": 20,
|
"concurrency": 20,
|
||||||
"command": {
|
"command": {
|
||||||
|
|
|
@ -1,9 +1,7 @@
|
||||||
import { derived, Readable } from "svelte/store"
|
import { derived, Readable } from "svelte/store"
|
||||||
import { admin } from "./admin"
|
import { admin } from "./admin"
|
||||||
import { auth } from "./auth"
|
import { auth } from "./auth"
|
||||||
import { isEnabled } from "@/helpers/featureFlags"
|
|
||||||
import { sdk } from "@budibase/shared-core"
|
import { sdk } from "@budibase/shared-core"
|
||||||
import { FeatureFlag } from "@budibase/types"
|
|
||||||
|
|
||||||
interface MenuItem {
|
interface MenuItem {
|
||||||
title: string
|
title: string
|
||||||
|
@ -73,13 +71,11 @@ export const menu: Readable<MenuItem[]> = derived(
|
||||||
title: "Environment",
|
title: "Environment",
|
||||||
href: "/builder/portal/settings/environment",
|
href: "/builder/portal/settings/environment",
|
||||||
},
|
},
|
||||||
]
|
{
|
||||||
if (isEnabled(FeatureFlag.AI_CUSTOM_CONFIGS)) {
|
|
||||||
settingsSubPages.push({
|
|
||||||
title: "AI",
|
title: "AI",
|
||||||
href: "/builder/portal/settings/ai",
|
href: "/builder/portal/settings/ai",
|
||||||
})
|
},
|
||||||
}
|
]
|
||||||
|
|
||||||
if (!cloud) {
|
if (!cloud) {
|
||||||
settingsSubPages.push({
|
settingsSubPages.push({
|
||||||
|
|
|
@ -31,7 +31,7 @@ const getDatasourceFetchInstance = datasource => {
|
||||||
if (!handler) {
|
if (!handler) {
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
return new handler({ API })
|
return new handler({ API, datasource })
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -52,7 +52,7 @@ export const fetchDatasourceSchema = async (
|
||||||
// Get the normal schema as long as we aren't wanting a form schema
|
// Get the normal schema as long as we aren't wanting a form schema
|
||||||
let schema
|
let schema
|
||||||
if (datasource?.type !== "query" || !options?.formSchema) {
|
if (datasource?.type !== "query" || !options?.formSchema) {
|
||||||
schema = instance.getSchema(datasource, definition)
|
schema = instance.getSchema(definition)
|
||||||
} else if (definition.parameters?.length) {
|
} else if (definition.parameters?.length) {
|
||||||
schema = {}
|
schema = {}
|
||||||
definition.parameters.forEach(param => {
|
definition.parameters.forEach(param => {
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import { FieldType } from "@budibase/types"
|
import { FieldType, UIColumn } from "@budibase/types"
|
||||||
|
|
||||||
import OptionsCell from "../cells/OptionsCell.svelte"
|
import OptionsCell from "../cells/OptionsCell.svelte"
|
||||||
import DateCell from "../cells/DateCell.svelte"
|
import DateCell from "../cells/DateCell.svelte"
|
||||||
|
@ -40,13 +40,23 @@ const TypeComponentMap = {
|
||||||
// Custom types for UI only
|
// Custom types for UI only
|
||||||
role: RoleCell,
|
role: RoleCell,
|
||||||
}
|
}
|
||||||
export const getCellRenderer = column => {
|
|
||||||
|
function getCellRendererByType(type: FieldType | "role" | undefined) {
|
||||||
|
if (!type) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
return TypeComponentMap[type as keyof typeof TypeComponentMap]
|
||||||
|
}
|
||||||
|
|
||||||
|
export const getCellRenderer = (column: UIColumn) => {
|
||||||
if (column.calculationType) {
|
if (column.calculationType) {
|
||||||
return NumberCell
|
return NumberCell
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
TypeComponentMap[column?.schema?.cellRenderType] ||
|
getCellRendererByType(column.schema?.cellRenderType) ||
|
||||||
TypeComponentMap[column?.schema?.type] ||
|
getCellRendererByType(column.schema?.type) ||
|
||||||
TextCell
|
TextCell
|
||||||
)
|
)
|
||||||
}
|
}
|
|
@ -1,32 +0,0 @@
|
||||||
// TODO: remove when all stores are typed
|
|
||||||
|
|
||||||
import { GeneratedIDPrefix, CellIDSeparator } from "./constants"
|
|
||||||
import { Helpers } from "@budibase/bbui"
|
|
||||||
|
|
||||||
export const parseCellID = cellId => {
|
|
||||||
if (!cellId) {
|
|
||||||
return { rowId: undefined, field: undefined }
|
|
||||||
}
|
|
||||||
const parts = cellId.split(CellIDSeparator)
|
|
||||||
const field = parts.pop()
|
|
||||||
return { rowId: parts.join(CellIDSeparator), field }
|
|
||||||
}
|
|
||||||
|
|
||||||
export const getCellID = (rowId, fieldName) => {
|
|
||||||
return `${rowId}${CellIDSeparator}${fieldName}`
|
|
||||||
}
|
|
||||||
|
|
||||||
export const parseEventLocation = e => {
|
|
||||||
return {
|
|
||||||
x: e.clientX ?? e.touches?.[0]?.clientX,
|
|
||||||
y: e.clientY ?? e.touches?.[0]?.clientY,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export const generateRowID = () => {
|
|
||||||
return `${GeneratedIDPrefix}${Helpers.uuid()}`
|
|
||||||
}
|
|
||||||
|
|
||||||
export const isGeneratedRowID = id => {
|
|
||||||
return id?.startsWith(GeneratedIDPrefix)
|
|
||||||
}
|
|
|
@ -1,12 +1,14 @@
|
||||||
import { get } from "svelte/store"
|
import { get } from "svelte/store"
|
||||||
import { createWebsocket } from "../../../utils"
|
import { createWebsocket } from "../../../utils"
|
||||||
import { SocketEvent, GridSocketEvent } from "@budibase/shared-core"
|
import { SocketEvent, GridSocketEvent } from "@budibase/shared-core"
|
||||||
|
import { Store } from "../stores"
|
||||||
|
import { UIDatasource, UIUser } from "@budibase/types"
|
||||||
|
|
||||||
export const createGridWebsocket = context => {
|
export const createGridWebsocket = (context: Store) => {
|
||||||
const { rows, datasource, users, focusedCellId, definition, API } = context
|
const { rows, datasource, users, focusedCellId, definition, API } = context
|
||||||
const socket = createWebsocket("/socket/grid")
|
const socket = createWebsocket("/socket/grid")
|
||||||
|
|
||||||
const connectToDatasource = datasource => {
|
const connectToDatasource = (datasource: UIDatasource) => {
|
||||||
if (!socket.connected) {
|
if (!socket.connected) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -18,7 +20,7 @@ export const createGridWebsocket = context => {
|
||||||
datasource,
|
datasource,
|
||||||
appId,
|
appId,
|
||||||
},
|
},
|
||||||
({ users: gridUsers }) => {
|
({ users: gridUsers }: { users: UIUser[] }) => {
|
||||||
users.set(gridUsers)
|
users.set(gridUsers)
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
@ -65,7 +67,7 @@ export const createGridWebsocket = context => {
|
||||||
GridSocketEvent.DatasourceChange,
|
GridSocketEvent.DatasourceChange,
|
||||||
({ datasource: newDatasource }) => {
|
({ datasource: newDatasource }) => {
|
||||||
// Listen builder renames, as these aren't handled otherwise
|
// Listen builder renames, as these aren't handled otherwise
|
||||||
if (newDatasource?.name !== get(definition).name) {
|
if (newDatasource?.name !== get(definition)?.name) {
|
||||||
definition.set(newDatasource)
|
definition.set(newDatasource)
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -179,9 +179,6 @@ export default abstract class DataFetch<
|
||||||
this.store.update($store => ({ ...$store, loaded: true }))
|
this.store.update($store => ({ ...$store, loaded: true }))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Initially fetch data but don't bother waiting for the result
|
|
||||||
this.getInitialData()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -33,7 +33,12 @@ const DataFetchMap = {
|
||||||
export const fetchData = ({ API, datasource, options }: any) => {
|
export const fetchData = ({ API, datasource, options }: any) => {
|
||||||
const Fetch =
|
const Fetch =
|
||||||
DataFetchMap[datasource?.type as keyof typeof DataFetchMap] || TableFetch
|
DataFetchMap[datasource?.type as keyof typeof DataFetchMap] || TableFetch
|
||||||
return new Fetch({ API, datasource, ...options })
|
const fetch = new Fetch({ API, datasource, ...options })
|
||||||
|
|
||||||
|
// Initially fetch data but don't bother waiting for the result
|
||||||
|
fetch.getInitialData()
|
||||||
|
|
||||||
|
return fetch
|
||||||
}
|
}
|
||||||
|
|
||||||
// Creates an empty fetch instance with no datasource configured, so no data
|
// Creates an empty fetch instance with no datasource configured, so no data
|
||||||
|
|
|
@ -209,6 +209,9 @@ export const buildFormBlockButtonConfig = props => {
|
||||||
{
|
{
|
||||||
"##eventHandlerType": "Close Side Panel",
|
"##eventHandlerType": "Close Side Panel",
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"##eventHandlerType": "Close Modal",
|
||||||
|
},
|
||||||
|
|
||||||
...(actionUrl
|
...(actionUrl
|
||||||
? [
|
? [
|
||||||
|
|
|
@ -1 +1 @@
|
||||||
Subproject commit 32d84f109d4edc526145472a7446327312151442
|
Subproject commit 193476cdfade6d3c613e6972f16ee0c527e01ff6
|
|
@ -4,15 +4,8 @@ import {
|
||||||
processAIColumns,
|
processAIColumns,
|
||||||
processFormulas,
|
processFormulas,
|
||||||
} from "../../../utilities/rowProcessor"
|
} from "../../../utilities/rowProcessor"
|
||||||
import { context, features } from "@budibase/backend-core"
|
import { context } from "@budibase/backend-core"
|
||||||
import {
|
import { Table, Row, FormulaType, FieldType, ViewV2 } from "@budibase/types"
|
||||||
Table,
|
|
||||||
Row,
|
|
||||||
FeatureFlag,
|
|
||||||
FormulaType,
|
|
||||||
FieldType,
|
|
||||||
ViewV2,
|
|
||||||
} from "@budibase/types"
|
|
||||||
import * as linkRows from "../../../db/linkedRows"
|
import * as linkRows from "../../../db/linkedRows"
|
||||||
import isEqual from "lodash/isEqual"
|
import isEqual from "lodash/isEqual"
|
||||||
import { cloneDeep, merge } from "lodash/fp"
|
import { cloneDeep, merge } from "lodash/fp"
|
||||||
|
@ -162,11 +155,10 @@ export async function finaliseRow(
|
||||||
dynamic: false,
|
dynamic: false,
|
||||||
contextRows: [enrichedRow],
|
contextRows: [enrichedRow],
|
||||||
})
|
})
|
||||||
|
|
||||||
const aiEnabled =
|
const aiEnabled =
|
||||||
((await features.isEnabled(FeatureFlag.BUDIBASE_AI)) &&
|
(await pro.features.isBudibaseAIEnabled()) ||
|
||||||
(await pro.features.isBudibaseAIEnabled())) ||
|
(await pro.features.isAICustomConfigsEnabled())
|
||||||
((await features.isEnabled(FeatureFlag.AI_CUSTOM_CONFIGS)) &&
|
|
||||||
(await pro.features.isAICustomConfigsEnabled()))
|
|
||||||
if (aiEnabled) {
|
if (aiEnabled) {
|
||||||
row = await processAIColumns(table, row, {
|
row = await processAIColumns(table, row, {
|
||||||
contextRows: [enrichedRow],
|
contextRows: [enrichedRow],
|
||||||
|
@ -184,11 +176,6 @@ export async function finaliseRow(
|
||||||
enrichedRow = await processFormulas(table, enrichedRow, {
|
enrichedRow = await processFormulas(table, enrichedRow, {
|
||||||
dynamic: false,
|
dynamic: false,
|
||||||
})
|
})
|
||||||
if (aiEnabled) {
|
|
||||||
enrichedRow = await processAIColumns(table, enrichedRow, {
|
|
||||||
contextRows: [enrichedRow],
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
// this updates the related formulas in other rows based on the relations to this row
|
// this updates the related formulas in other rows based on the relations to this row
|
||||||
if (updateFormula) {
|
if (updateFormula) {
|
||||||
|
|
|
@ -8,7 +8,13 @@ import {
|
||||||
import tk from "timekeeper"
|
import tk from "timekeeper"
|
||||||
import emitter from "../../../../src/events"
|
import emitter from "../../../../src/events"
|
||||||
import { outputProcessing } from "../../../utilities/rowProcessor"
|
import { outputProcessing } from "../../../utilities/rowProcessor"
|
||||||
import { context, InternalTable, tenancy, utils } from "@budibase/backend-core"
|
import {
|
||||||
|
context,
|
||||||
|
setEnv,
|
||||||
|
InternalTable,
|
||||||
|
tenancy,
|
||||||
|
utils,
|
||||||
|
} from "@budibase/backend-core"
|
||||||
import { quotas } from "@budibase/pro"
|
import { quotas } from "@budibase/pro"
|
||||||
import {
|
import {
|
||||||
AIOperationEnum,
|
AIOperationEnum,
|
||||||
|
@ -42,19 +48,8 @@ import { InternalTables } from "../../../db/utils"
|
||||||
import { withEnv } from "../../../environment"
|
import { withEnv } from "../../../environment"
|
||||||
import { JsTimeoutError } from "@budibase/string-templates"
|
import { JsTimeoutError } from "@budibase/string-templates"
|
||||||
import { isDate } from "../../../utilities"
|
import { isDate } from "../../../utilities"
|
||||||
|
import nock from "nock"
|
||||||
jest.mock("@budibase/pro", () => ({
|
import { mockChatGPTResponse } from "../../../tests/utilities/mocks/openai"
|
||||||
...jest.requireActual("@budibase/pro"),
|
|
||||||
ai: {
|
|
||||||
LargeLanguageModel: {
|
|
||||||
forCurrentTenant: async () => ({
|
|
||||||
llm: {},
|
|
||||||
run: jest.fn(() => `Mock LLM Response`),
|
|
||||||
buildPromptFromAIOperation: jest.fn(),
|
|
||||||
}),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}))
|
|
||||||
|
|
||||||
const timestamp = new Date("2023-01-26T11:48:57.597Z").toISOString()
|
const timestamp = new Date("2023-01-26T11:48:57.597Z").toISOString()
|
||||||
tk.freeze(timestamp)
|
tk.freeze(timestamp)
|
||||||
|
@ -99,6 +94,8 @@ if (descriptions.length) {
|
||||||
const ds = await dsProvider()
|
const ds = await dsProvider()
|
||||||
datasource = ds.datasource
|
datasource = ds.datasource
|
||||||
client = ds.client
|
client = ds.client
|
||||||
|
|
||||||
|
mocks.licenses.useCloudFree()
|
||||||
})
|
})
|
||||||
|
|
||||||
afterAll(async () => {
|
afterAll(async () => {
|
||||||
|
@ -172,10 +169,6 @@ if (descriptions.length) {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
beforeEach(async () => {
|
|
||||||
mocks.licenses.useCloudFree()
|
|
||||||
})
|
|
||||||
|
|
||||||
const getRowUsage = async () => {
|
const getRowUsage = async () => {
|
||||||
const { total } = await config.doInContext(undefined, () =>
|
const { total } = await config.doInContext(undefined, () =>
|
||||||
quotas.getCurrentUsageValues(
|
quotas.getCurrentUsageValues(
|
||||||
|
@ -3224,10 +3217,17 @@ if (descriptions.length) {
|
||||||
isInternal &&
|
isInternal &&
|
||||||
describe("AI fields", () => {
|
describe("AI fields", () => {
|
||||||
let table: Table
|
let table: Table
|
||||||
|
let envCleanup: () => void
|
||||||
|
|
||||||
beforeAll(async () => {
|
beforeAll(async () => {
|
||||||
mocks.licenses.useBudibaseAI()
|
mocks.licenses.useBudibaseAI()
|
||||||
mocks.licenses.useAICustomConfigs()
|
mocks.licenses.useAICustomConfigs()
|
||||||
|
envCleanup = setEnv({
|
||||||
|
OPENAI_API_KEY: "sk-abcdefghijklmnopqrstuvwxyz1234567890abcd",
|
||||||
|
})
|
||||||
|
|
||||||
|
mockChatGPTResponse("Mock LLM Response")
|
||||||
|
|
||||||
table = await config.api.table.save(
|
table = await config.api.table.save(
|
||||||
saveTableRequest({
|
saveTableRequest({
|
||||||
schema: {
|
schema: {
|
||||||
|
@ -3251,7 +3251,9 @@ if (descriptions.length) {
|
||||||
})
|
})
|
||||||
|
|
||||||
afterAll(() => {
|
afterAll(() => {
|
||||||
jest.unmock("@budibase/pro")
|
nock.cleanAll()
|
||||||
|
envCleanup()
|
||||||
|
mocks.licenses.useCloudFree()
|
||||||
})
|
})
|
||||||
|
|
||||||
it("should be able to save a row with an AI column", async () => {
|
it("should be able to save a row with an AI column", async () => {
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
import {
|
import {
|
||||||
|
AIOperationEnum,
|
||||||
ArrayOperator,
|
ArrayOperator,
|
||||||
BasicOperator,
|
BasicOperator,
|
||||||
BBReferenceFieldSubType,
|
BBReferenceFieldSubType,
|
||||||
|
@ -42,7 +43,9 @@ import {
|
||||||
} from "../../../integrations/tests/utils"
|
} from "../../../integrations/tests/utils"
|
||||||
import merge from "lodash/merge"
|
import merge from "lodash/merge"
|
||||||
import { quotas } from "@budibase/pro"
|
import { quotas } from "@budibase/pro"
|
||||||
import { context, db, events, roles } from "@budibase/backend-core"
|
import { context, db, events, roles, setEnv } from "@budibase/backend-core"
|
||||||
|
import { mockChatGPTResponse } from "../../../tests/utilities/mocks/openai"
|
||||||
|
import nock from "nock"
|
||||||
|
|
||||||
const descriptions = datasourceDescribe({ exclude: [DatabaseName.MONGODB] })
|
const descriptions = datasourceDescribe({ exclude: [DatabaseName.MONGODB] })
|
||||||
|
|
||||||
|
@ -100,6 +103,7 @@ if (descriptions.length) {
|
||||||
|
|
||||||
beforeAll(async () => {
|
beforeAll(async () => {
|
||||||
await config.init()
|
await config.init()
|
||||||
|
mocks.licenses.useCloudFree()
|
||||||
|
|
||||||
const ds = await dsProvider()
|
const ds = await dsProvider()
|
||||||
rawDatasource = ds.rawDatasource
|
rawDatasource = ds.rawDatasource
|
||||||
|
@ -109,7 +113,6 @@ if (descriptions.length) {
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
jest.clearAllMocks()
|
jest.clearAllMocks()
|
||||||
mocks.licenses.useCloudFree()
|
|
||||||
})
|
})
|
||||||
|
|
||||||
describe("view crud", () => {
|
describe("view crud", () => {
|
||||||
|
@ -507,7 +510,6 @@ if (descriptions.length) {
|
||||||
})
|
})
|
||||||
|
|
||||||
it("readonly fields can be used on free license", async () => {
|
it("readonly fields can be used on free license", async () => {
|
||||||
mocks.licenses.useCloudFree()
|
|
||||||
const table = await config.api.table.save(
|
const table = await config.api.table.save(
|
||||||
saveTableRequest({
|
saveTableRequest({
|
||||||
schema: {
|
schema: {
|
||||||
|
@ -933,6 +935,95 @@ if (descriptions.length) {
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
isInternal &&
|
||||||
|
describe("AI fields", () => {
|
||||||
|
let envCleanup: () => void
|
||||||
|
beforeAll(() => {
|
||||||
|
mocks.licenses.useBudibaseAI()
|
||||||
|
mocks.licenses.useAICustomConfigs()
|
||||||
|
envCleanup = setEnv({
|
||||||
|
OPENAI_API_KEY: "sk-abcdefghijklmnopqrstuvwxyz1234567890abcd",
|
||||||
|
})
|
||||||
|
|
||||||
|
mockChatGPTResponse(prompt => {
|
||||||
|
if (prompt.includes("elephant")) {
|
||||||
|
return "big"
|
||||||
|
}
|
||||||
|
if (prompt.includes("mouse")) {
|
||||||
|
return "small"
|
||||||
|
}
|
||||||
|
if (prompt.includes("whale")) {
|
||||||
|
return "big"
|
||||||
|
}
|
||||||
|
return "unknown"
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
afterAll(() => {
|
||||||
|
nock.cleanAll()
|
||||||
|
envCleanup()
|
||||||
|
mocks.licenses.useCloudFree()
|
||||||
|
})
|
||||||
|
|
||||||
|
it("can use AI fields in view calculations", async () => {
|
||||||
|
const table = await config.api.table.save(
|
||||||
|
saveTableRequest({
|
||||||
|
schema: {
|
||||||
|
animal: {
|
||||||
|
name: "animal",
|
||||||
|
type: FieldType.STRING,
|
||||||
|
},
|
||||||
|
bigOrSmall: {
|
||||||
|
name: "bigOrSmall",
|
||||||
|
type: FieldType.AI,
|
||||||
|
operation: AIOperationEnum.CATEGORISE_TEXT,
|
||||||
|
categories: "big,small",
|
||||||
|
columns: ["animal"],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
)
|
||||||
|
|
||||||
|
const view = await config.api.viewV2.create({
|
||||||
|
tableId: table._id!,
|
||||||
|
name: generator.guid(),
|
||||||
|
type: ViewV2Type.CALCULATION,
|
||||||
|
schema: {
|
||||||
|
bigOrSmall: {
|
||||||
|
visible: true,
|
||||||
|
},
|
||||||
|
count: {
|
||||||
|
visible: true,
|
||||||
|
calculationType: CalculationType.COUNT,
|
||||||
|
field: "animal",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
await config.api.row.save(table._id!, {
|
||||||
|
animal: "elephant",
|
||||||
|
})
|
||||||
|
|
||||||
|
await config.api.row.save(table._id!, {
|
||||||
|
animal: "mouse",
|
||||||
|
})
|
||||||
|
|
||||||
|
await config.api.row.save(table._id!, {
|
||||||
|
animal: "whale",
|
||||||
|
})
|
||||||
|
|
||||||
|
const { rows } = await config.api.row.search(view.id, {
|
||||||
|
sort: "bigOrSmall",
|
||||||
|
sortOrder: SortOrder.ASCENDING,
|
||||||
|
})
|
||||||
|
expect(rows).toHaveLength(2)
|
||||||
|
expect(rows[0].bigOrSmall).toEqual("big")
|
||||||
|
expect(rows[1].bigOrSmall).toEqual("small")
|
||||||
|
expect(rows[0].count).toEqual(2)
|
||||||
|
expect(rows[1].count).toEqual(1)
|
||||||
|
})
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
describe("update", () => {
|
describe("update", () => {
|
||||||
|
@ -1836,7 +1927,6 @@ if (descriptions.length) {
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
mocks.licenses.useCloudFree()
|
|
||||||
const view = await getDelegate(res)
|
const view = await getDelegate(res)
|
||||||
expect(view.schema?.one).toEqual(
|
expect(view.schema?.one).toEqual(
|
||||||
expect.objectContaining({ visible: true, readonly: true })
|
expect.objectContaining({ visible: true, readonly: true })
|
||||||
|
|
|
@ -27,11 +27,9 @@ import {
|
||||||
Hosting,
|
Hosting,
|
||||||
ActionImplementation,
|
ActionImplementation,
|
||||||
AutomationStepDefinition,
|
AutomationStepDefinition,
|
||||||
FeatureFlag,
|
|
||||||
} from "@budibase/types"
|
} from "@budibase/types"
|
||||||
import sdk from "../sdk"
|
import sdk from "../sdk"
|
||||||
import { getAutomationPlugin } from "../utilities/fileSystem"
|
import { getAutomationPlugin } from "../utilities/fileSystem"
|
||||||
import { features } from "@budibase/backend-core"
|
|
||||||
|
|
||||||
type ActionImplType = ActionImplementations<
|
type ActionImplType = ActionImplementations<
|
||||||
typeof env.SELF_HOSTED extends "true" ? Hosting.SELF : Hosting.CLOUD
|
typeof env.SELF_HOSTED extends "true" ? Hosting.SELF : Hosting.CLOUD
|
||||||
|
@ -78,6 +76,7 @@ export const BUILTIN_ACTION_DEFINITIONS: Record<
|
||||||
LOOP: loop.definition,
|
LOOP: loop.definition,
|
||||||
COLLECT: collect.definition,
|
COLLECT: collect.definition,
|
||||||
TRIGGER_AUTOMATION_RUN: triggerAutomationRun.definition,
|
TRIGGER_AUTOMATION_RUN: triggerAutomationRun.definition,
|
||||||
|
BRANCH: branch.definition,
|
||||||
// these used to be lowercase step IDs, maintain for backwards compat
|
// these used to be lowercase step IDs, maintain for backwards compat
|
||||||
discord: discord.definition,
|
discord: discord.definition,
|
||||||
slack: slack.definition,
|
slack: slack.definition,
|
||||||
|
@ -105,14 +104,7 @@ if (env.SELF_HOSTED) {
|
||||||
export async function getActionDefinitions(): Promise<
|
export async function getActionDefinitions(): Promise<
|
||||||
Record<keyof typeof AutomationActionStepId, AutomationStepDefinition>
|
Record<keyof typeof AutomationActionStepId, AutomationStepDefinition>
|
||||||
> {
|
> {
|
||||||
if (await features.isEnabled(FeatureFlag.AUTOMATION_BRANCHING)) {
|
if (env.SELF_HOSTED) {
|
||||||
BUILTIN_ACTION_DEFINITIONS["BRANCH"] = branch.definition
|
|
||||||
}
|
|
||||||
if (
|
|
||||||
env.SELF_HOSTED ||
|
|
||||||
(await features.isEnabled(FeatureFlag.BUDIBASE_AI)) ||
|
|
||||||
(await features.isEnabled(FeatureFlag.AI_CUSTOM_CONFIGS))
|
|
||||||
) {
|
|
||||||
BUILTIN_ACTION_DEFINITIONS["OPENAI"] = openai.definition
|
BUILTIN_ACTION_DEFINITIONS["OPENAI"] = openai.definition
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -7,9 +7,8 @@ import {
|
||||||
AutomationIOType,
|
AutomationIOType,
|
||||||
OpenAIStepInputs,
|
OpenAIStepInputs,
|
||||||
OpenAIStepOutputs,
|
OpenAIStepOutputs,
|
||||||
FeatureFlag,
|
|
||||||
} from "@budibase/types"
|
} from "@budibase/types"
|
||||||
import { env, features } from "@budibase/backend-core"
|
import { env } from "@budibase/backend-core"
|
||||||
import * as automationUtils from "../automationUtils"
|
import * as automationUtils from "../automationUtils"
|
||||||
import * as pro from "@budibase/pro"
|
import * as pro from "@budibase/pro"
|
||||||
|
|
||||||
|
@ -99,12 +98,8 @@ export async function run({
|
||||||
|
|
||||||
try {
|
try {
|
||||||
let response
|
let response
|
||||||
const customConfigsEnabled =
|
const customConfigsEnabled = await pro.features.isAICustomConfigsEnabled()
|
||||||
(await features.isEnabled(FeatureFlag.AI_CUSTOM_CONFIGS)) &&
|
const budibaseAIEnabled = await pro.features.isBudibaseAIEnabled()
|
||||||
(await pro.features.isAICustomConfigsEnabled())
|
|
||||||
const budibaseAIEnabled =
|
|
||||||
(await features.isEnabled(FeatureFlag.BUDIBASE_AI)) &&
|
|
||||||
(await pro.features.isBudibaseAIEnabled())
|
|
||||||
|
|
||||||
let llmWrapper
|
let llmWrapper
|
||||||
if (budibaseAIEnabled || customConfigsEnabled) {
|
if (budibaseAIEnabled || customConfigsEnabled) {
|
||||||
|
|
|
@ -432,6 +432,21 @@ export async function enrichSchema(
|
||||||
...tableSchema[key],
|
...tableSchema[key],
|
||||||
...ui,
|
...ui,
|
||||||
order: anyViewOrder ? ui?.order ?? undefined : tableSchema[key]?.order,
|
order: anyViewOrder ? ui?.order ?? undefined : tableSchema[key]?.order,
|
||||||
|
// When this was written, the only column types in FieldSchema to have columns
|
||||||
|
// field were the relationship columns. We blank this out here to make sure it's
|
||||||
|
// not set on non-relationship columns, then below we populate it by calling
|
||||||
|
// populateRelSchema.
|
||||||
|
//
|
||||||
|
// For Budibase 3.0 we introduced the FieldType.AI fields. Some of these fields
|
||||||
|
// have `columns: string[]` and it flew under the radar here because the
|
||||||
|
// AIFieldMetadata type isn't a union on its subtypes, it has a collection of
|
||||||
|
// optional fields. So columns is `columns?: string[]` which allows undefined,
|
||||||
|
// and doesn't fail this type check.
|
||||||
|
//
|
||||||
|
// What this means in practice is when FieldType.AI fields get enriched, we
|
||||||
|
// delete their `columns`. At the time of writing, I don't believe anything in
|
||||||
|
// the frontend depends on this, but it is odd and will probably bite us at
|
||||||
|
// some point.
|
||||||
columns: undefined,
|
columns: undefined,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,46 @@
|
||||||
|
import nock from "nock"
|
||||||
|
|
||||||
|
let chatID = 1
|
||||||
|
|
||||||
|
export function mockChatGPTResponse(
|
||||||
|
response: string | ((prompt: string) => string)
|
||||||
|
) {
|
||||||
|
return nock("https://api.openai.com")
|
||||||
|
.post("/v1/chat/completions")
|
||||||
|
.reply(200, (uri, requestBody) => {
|
||||||
|
let content = response
|
||||||
|
if (typeof response === "function") {
|
||||||
|
const messages = (requestBody as any).messages
|
||||||
|
content = response(messages[0].content)
|
||||||
|
}
|
||||||
|
|
||||||
|
chatID++
|
||||||
|
|
||||||
|
return {
|
||||||
|
id: `chatcmpl-${chatID}`,
|
||||||
|
object: "chat.completion",
|
||||||
|
created: Math.floor(Date.now() / 1000),
|
||||||
|
model: "gpt-4o-mini",
|
||||||
|
system_fingerprint: `fp_${chatID}`,
|
||||||
|
choices: [
|
||||||
|
{
|
||||||
|
index: 0,
|
||||||
|
message: { role: "assistant", content },
|
||||||
|
logprobs: null,
|
||||||
|
finish_reason: "stop",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
usage: {
|
||||||
|
prompt_tokens: 0,
|
||||||
|
completion_tokens: 0,
|
||||||
|
total_tokens: 0,
|
||||||
|
completion_tokens_details: {
|
||||||
|
reasoning_tokens: 0,
|
||||||
|
accepted_prediction_tokens: 0,
|
||||||
|
rejected_prediction_tokens: 0,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.persist()
|
||||||
|
}
|
|
@ -160,7 +160,7 @@ export async function processAIColumns<T extends Row | Row[]>(
|
||||||
|
|
||||||
return tracer.trace("processAIColumn", {}, async span => {
|
return tracer.trace("processAIColumn", {}, async span => {
|
||||||
span?.addTags({ table_id: table._id, column })
|
span?.addTags({ table_id: table._id, column })
|
||||||
const llmResponse = await llmWrapper.run(prompt!)
|
const llmResponse = await llmWrapper.run(prompt)
|
||||||
return {
|
return {
|
||||||
...row,
|
...row,
|
||||||
[column]: llmResponse,
|
[column]: llmResponse,
|
||||||
|
|
|
@ -154,6 +154,7 @@ export const GroupByTypes = [
|
||||||
FieldType.BOOLEAN,
|
FieldType.BOOLEAN,
|
||||||
FieldType.DATETIME,
|
FieldType.DATETIME,
|
||||||
FieldType.BIGINT,
|
FieldType.BIGINT,
|
||||||
|
FieldType.AI,
|
||||||
]
|
]
|
||||||
|
|
||||||
export function canGroupBy(type: FieldType) {
|
export function canGroupBy(type: FieldType) {
|
||||||
|
|
|
@ -123,7 +123,7 @@ export interface AIFieldMetadata extends BaseFieldSchema {
|
||||||
operation: AIOperationEnum
|
operation: AIOperationEnum
|
||||||
columns?: string[]
|
columns?: string[]
|
||||||
column?: string
|
column?: string
|
||||||
categories?: string[]
|
categories?: string
|
||||||
prompt?: string
|
prompt?: string
|
||||||
language?: string
|
language?: string
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,16 +1,8 @@
|
||||||
export enum FeatureFlag {
|
export enum FeatureFlag {
|
||||||
AUTOMATION_BRANCHING = "AUTOMATION_BRANCHING",
|
|
||||||
AI_CUSTOM_CONFIGS = "AI_CUSTOM_CONFIGS",
|
|
||||||
DEFAULT_VALUES = "DEFAULT_VALUES",
|
|
||||||
BUDIBASE_AI = "BUDIBASE_AI",
|
|
||||||
USE_ZOD_VALIDATOR = "USE_ZOD_VALIDATOR",
|
USE_ZOD_VALIDATOR = "USE_ZOD_VALIDATOR",
|
||||||
}
|
}
|
||||||
|
|
||||||
export const FeatureFlagDefaults = {
|
export const FeatureFlagDefaults = {
|
||||||
[FeatureFlag.DEFAULT_VALUES]: true,
|
|
||||||
[FeatureFlag.AUTOMATION_BRANCHING]: true,
|
|
||||||
[FeatureFlag.AI_CUSTOM_CONFIGS]: true,
|
|
||||||
[FeatureFlag.BUDIBASE_AI]: true,
|
|
||||||
[FeatureFlag.USE_ZOD_VALIDATOR]: false,
|
[FeatureFlag.USE_ZOD_VALIDATOR]: false,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -14,6 +14,7 @@ export type UIColumn = FieldSchema & {
|
||||||
type: FieldType
|
type: FieldType
|
||||||
readonly: boolean
|
readonly: boolean
|
||||||
autocolumn: boolean
|
autocolumn: boolean
|
||||||
|
cellRenderType?: FieldType | "role"
|
||||||
}
|
}
|
||||||
calculationType: CalculationType
|
calculationType: CalculationType
|
||||||
__idx: number
|
__idx: number
|
||||||
|
|
Loading…
Reference in New Issue