diff --git a/lerna.json b/lerna.json index 8eb8cf46a1..814102c86a 100644 --- a/lerna.json +++ b/lerna.json @@ -1,6 +1,6 @@ { "$schema": "node_modules/lerna/schemas/lerna-schema.json", - "version": "3.2.18", + "version": "3.2.19", "npmClient": "yarn", "concurrency": 20, "command": { diff --git a/packages/server/src/api/controllers/analytics.ts b/packages/server/src/api/controllers/analytics.ts index f60c7b0961..8c87af7251 100644 --- a/packages/server/src/api/controllers/analytics.ts +++ b/packages/server/src/api/controllers/analytics.ts @@ -1,16 +1,22 @@ import { events, context } from "@budibase/backend-core" -import { AnalyticsPingRequest, App, PingSource } from "@budibase/types" +import { + AnalyticsPingRequest, + App, + PingSource, + Ctx, + AnalyticsEnabledResponse, +} from "@budibase/types" import { DocumentType, isDevAppID } from "../../db/utils" -export const isEnabled = async (ctx: any) => { +export const isEnabled = async (ctx: Ctx) => { const enabled = await events.analytics.enabled() ctx.body = { enabled, } } -export const ping = async (ctx: any) => { - const body = ctx.request.body as AnalyticsPingRequest +export const ping = async (ctx: Ctx) => { + const body = ctx.request.body switch (body.source) { case PingSource.APP: { diff --git a/packages/server/src/api/controllers/apikeys.ts b/packages/server/src/api/controllers/apikeys.ts index 2a02078483..95253b09c5 100644 --- a/packages/server/src/api/controllers/apikeys.ts +++ b/packages/server/src/api/controllers/apikeys.ts @@ -1,18 +1,25 @@ import { db as dbCore, tenancy } from "@budibase/backend-core" -import { BBContext, Document } from "@budibase/types" +import { + Document, + UserCtx, + ApiKeyDoc, + ApiKeyFetchResponse, + UpdateApiKeyRequest, + UpdateApiKeyResponse, +} from "@budibase/types" const KEYS_DOC = dbCore.StaticDatabases.GLOBAL.docs.apiKeys async function getBuilderMainDoc() { const db = tenancy.getGlobalDB() - try { - return await db.get(KEYS_DOC) - } catch (err) { - // doesn't exist yet, nothing to get + const doc = await db.tryGet(KEYS_DOC) + if (!doc) { return { _id: KEYS_DOC, + apiKeys: {}, } } + return doc } async function setBuilderMainDoc(doc: Document) { @@ -22,7 +29,7 @@ async function setBuilderMainDoc(doc: Document) { return db.put(doc) } -export async function fetch(ctx: BBContext) { +export async function fetch(ctx: UserCtx) { try { const mainDoc = await getBuilderMainDoc() ctx.body = mainDoc.apiKeys ? mainDoc.apiKeys : {} @@ -32,7 +39,9 @@ export async function fetch(ctx: BBContext) { } } -export async function update(ctx: BBContext) { +export async function update( + ctx: UserCtx +) { const key = ctx.params.key const value = ctx.request.body.value diff --git a/packages/server/src/api/controllers/application.ts b/packages/server/src/api/controllers/application.ts index 101257c321..d032f14150 100644 --- a/packages/server/src/api/controllers/application.ts +++ b/packages/server/src/api/controllers/application.ts @@ -59,6 +59,15 @@ import { BBReferenceFieldSubType, Row, BBRequest, + SyncAppResponse, + CreateAppResponse, + FetchAppsResponse, + UpdateAppClientResponse, + RevertAppClientResponse, + DeleteAppResponse, + ImportToUpdateAppRequest, + ImportToUpdateAppResponse, + SetRevertableAppVersionRequest, } from "@budibase/types" import { BASE_LAYOUT_PROP_IDS } from "../../constants/layouts" import sdk from "../../sdk" @@ -166,7 +175,7 @@ async function createInstance(appId: string, template: AppTemplate) { return { _id: appId } } -export const addSampleData = async (ctx: UserCtx) => { +export const addSampleData = async (ctx: UserCtx) => { const db = context.getAppDB() try { @@ -182,7 +191,7 @@ export const addSampleData = async (ctx: UserCtx) => { ctx.status = 200 } -export async function fetch(ctx: UserCtx) { +export async function fetch(ctx: UserCtx) { ctx.body = await sdk.applications.fetch( ctx.query.status as AppStatus, ctx.user @@ -242,7 +251,9 @@ export async function fetchAppPackage( } } -async function performAppCreate(ctx: UserCtx) { +async function performAppCreate( + ctx: UserCtx +) { const apps = (await dbCore.getAllApps({ dev: true })) as App[] const { body } = ctx.request const { name, url, encryptionPassword, templateKey } = body @@ -510,7 +521,9 @@ async function appPostCreate(ctx: UserCtx, app: App) { } } -export async function create(ctx: UserCtx) { +export async function create( + ctx: UserCtx +) { const newApplication = await quotas.addApp(() => performAppCreate(ctx)) await appPostCreate(ctx, newApplication) await cache.bustCache(cache.CacheKey.CHECKLIST) @@ -553,7 +566,9 @@ export async function update( }) } -export async function updateClient(ctx: UserCtx) { +export async function updateClient( + ctx: UserCtx +) { // Get current app version const application = await sdk.applications.metadata.get() const currentVersion = application.version @@ -581,7 +596,9 @@ export async function updateClient(ctx: UserCtx) { ctx.body = app } -export async function revertClient(ctx: UserCtx) { +export async function revertClient( + ctx: UserCtx +) { // Check app can be reverted const application = await sdk.applications.metadata.get() if (!application.revertableVersion) { @@ -668,7 +685,7 @@ async function postDestroyApp(ctx: UserCtx) { } } -export async function destroy(ctx: UserCtx) { +export async function destroy(ctx: UserCtx) { await preDestroyApp(ctx) const result = await destroyApp(ctx) await postDestroyApp(ctx) @@ -676,7 +693,7 @@ export async function destroy(ctx: UserCtx) { ctx.body = result } -export async function unpublish(ctx: UserCtx) { +export async function unpublish(ctx: UserCtx) { const prodAppId = dbCore.getProdAppID(ctx.params.appId) const dbExists = await dbCore.dbExists(prodAppId) @@ -692,7 +709,7 @@ export async function unpublish(ctx: UserCtx) { builderSocket?.emitAppUnpublish(ctx) } -export async function sync(ctx: UserCtx) { +export async function sync(ctx: UserCtx) { const appId = ctx.params.appId try { ctx.body = await sdk.applications.syncApp(appId) @@ -701,10 +718,12 @@ export async function sync(ctx: UserCtx) { } } -export async function importToApp(ctx: UserCtx) { +export async function importToApp( + ctx: UserCtx +) { const { appId } = ctx.params const appExport = ctx.request.files?.appExport - const password = ctx.request.body.encryptionPassword as string + const password = ctx.request.body.encryptionPassword if (!appExport) { ctx.throw(400, "Must supply app export to import") } @@ -811,7 +830,7 @@ export async function updateAppPackage( } export async function setRevertableVersion( - ctx: UserCtx<{ revertableVersion: string }, App> + ctx: UserCtx ) { if (!env.isDev()) { ctx.status = 403 diff --git a/packages/server/src/api/controllers/auth.ts b/packages/server/src/api/controllers/auth.ts index 4ff592534d..0742583a8d 100644 --- a/packages/server/src/api/controllers/auth.ts +++ b/packages/server/src/api/controllers/auth.ts @@ -2,7 +2,7 @@ import { outputProcessing } from "../../utilities/rowProcessor" import { InternalTables } from "../../db/utils" import { getFullUser } from "../../utilities/users" import { roles, context, db as dbCore } from "@budibase/backend-core" -import { ContextUser, Row, UserCtx } from "@budibase/types" +import { AppSelfResponse, ContextUser, UserCtx } from "@budibase/types" import sdk from "../../sdk" import { processUser } from "../../utilities/global" @@ -17,7 +17,7 @@ const addSessionAttributesToUser = (ctx: any) => { } } -export async function fetchSelf(ctx: UserCtx) { +export async function fetchSelf(ctx: UserCtx) { let userId = ctx.user.userId || ctx.user._id /* istanbul ignore next */ if (!userId || !ctx.isAuthenticated) { @@ -45,9 +45,9 @@ export async function fetchSelf(ctx: UserCtx) { try { const userTable = await sdk.tables.getTable(InternalTables.USER_METADATA) // specifically needs to make sure is enriched - ctx.body = await outputProcessing(userTable, user as Row) + ctx.body = await outputProcessing(userTable, user) } catch (err: any) { - let response + let response: ContextUser | {} // user didn't exist in app, don't pretend they do if (user.roleId === PUBLIC_ROLE) { response = {} diff --git a/packages/server/src/api/controllers/automation.ts b/packages/server/src/api/controllers/automation.ts index d8bc9d6b21..c843dca89b 100644 --- a/packages/server/src/api/controllers/automation.ts +++ b/packages/server/src/api/controllers/automation.ts @@ -9,10 +9,25 @@ import { App, Automation, AutomationActionStepId, - AutomationResults, UserCtx, DeleteAutomationResponse, FetchAutomationResponse, + GetAutomationTriggerDefinitionsResponse, + GetAutomationStepDefinitionsResponse, + GetAutomationActionDefinitionsResponse, + FindAutomationResponse, + UpdateAutomationRequest, + UpdateAutomationResponse, + CreateAutomationRequest, + CreateAutomationResponse, + SearchAutomationLogsRequest, + SearchAutomationLogsResponse, + ClearAutomationLogRequest, + ClearAutomationLogResponse, + TriggerAutomationRequest, + TriggerAutomationResponse, + TestAutomationRequest, + TestAutomationResponse, } from "@budibase/types" import { getActionDefinitions as actionDefs } from "../../automations/actions" import sdk from "../../sdk" @@ -34,7 +49,7 @@ function getTriggerDefinitions() { *************************/ export async function create( - ctx: UserCtx + ctx: UserCtx ) { let automation = ctx.request.body automation.appId = ctx.appId @@ -55,7 +70,9 @@ export async function create( builderSocket?.emitAutomationUpdate(ctx, automation) } -export async function update(ctx: UserCtx) { +export async function update( + ctx: UserCtx +) { let automation = ctx.request.body automation.appId = ctx.appId @@ -80,7 +97,7 @@ export async function fetch(ctx: UserCtx) { ctx.body = { automations } } -export async function find(ctx: UserCtx) { +export async function find(ctx: UserCtx) { ctx.body = await sdk.automations.get(ctx.params.id) } @@ -96,11 +113,15 @@ export async function destroy(ctx: UserCtx) { builderSocket?.emitAutomationDeletion(ctx, automationId) } -export async function logSearch(ctx: UserCtx) { +export async function logSearch( + ctx: UserCtx +) { ctx.body = await automations.logs.logSearch(ctx.request.body) } -export async function clearLogError(ctx: UserCtx) { +export async function clearLogError( + ctx: UserCtx +) { const { automationId, appId } = ctx.request.body await context.doInAppContext(appId, async () => { const db = context.getProdAppDB() @@ -119,15 +140,21 @@ export async function clearLogError(ctx: UserCtx) { }) } -export async function getActionList(ctx: UserCtx) { +export async function getActionList( + ctx: UserCtx +) { ctx.body = await getActionDefinitions() } -export async function getTriggerList(ctx: UserCtx) { +export async function getTriggerList( + ctx: UserCtx +) { ctx.body = getTriggerDefinitions() } -export async function getDefinitionList(ctx: UserCtx) { +export async function getDefinitionList( + ctx: UserCtx +) { ctx.body = { trigger: getTriggerDefinitions(), action: await getActionDefinitions(), @@ -140,14 +167,16 @@ export async function getDefinitionList(ctx: UserCtx) { * * *********************/ -export async function trigger(ctx: UserCtx) { +export async function trigger( + ctx: UserCtx +) { const db = context.getAppDB() let automation = await db.get(ctx.params.id) let hasCollectStep = sdk.automations.utils.checkForCollectStep(automation) if (hasCollectStep && (await features.isSyncAutomationsEnabled())) { try { - const response: AutomationResults = await triggers.externalTrigger( + const response = await triggers.externalTrigger( automation, { fields: ctx.request.body.fields, @@ -158,6 +187,10 @@ export async function trigger(ctx: UserCtx) { { getResponses: true } ) + if (!("steps" in response)) { + ctx.throw(400, "Unable to collect response") + } + let collectedValue = response.steps.find( step => step.stepId === AutomationActionStepId.COLLECT ) @@ -185,7 +218,7 @@ export async function trigger(ctx: UserCtx) { } } -function prepareTestInput(input: any) { +function prepareTestInput(input: TestAutomationRequest) { // prepare the test parameters if (input.id && input.row) { input.row._id = input.id @@ -196,7 +229,9 @@ function prepareTestInput(input: any) { return input } -export async function test(ctx: UserCtx) { +export async function test( + ctx: UserCtx +) { const db = context.getAppDB() let automation = await db.get(ctx.params.id) await setTestFlag(automation._id!) diff --git a/packages/server/src/api/controllers/backup.ts b/packages/server/src/api/controllers/backup.ts index 8e1881a5fc..2104196139 100644 --- a/packages/server/src/api/controllers/backup.ts +++ b/packages/server/src/api/controllers/backup.ts @@ -1,14 +1,16 @@ import sdk from "../../sdk" import { events, context, db } from "@budibase/backend-core" import { DocumentType } from "../../db/utils" -import { App, Ctx } from "@budibase/types" +import { + App, + Ctx, + ExportAppDumpRequest, + ExportAppDumpResponse, +} from "@budibase/types" -interface ExportAppDumpRequest { - excludeRows: boolean - encryptPassword?: string -} - -export async function exportAppDump(ctx: Ctx) { +export async function exportAppDump( + ctx: Ctx +) { const { appId } = ctx.query as any const { excludeRows, encryptPassword } = ctx.request.body diff --git a/packages/server/src/api/controllers/component.ts b/packages/server/src/api/controllers/component.ts index 6d4d3e2d21..c6d8551adc 100644 --- a/packages/server/src/api/controllers/component.ts +++ b/packages/server/src/api/controllers/component.ts @@ -1,9 +1,16 @@ import { DocumentType } from "../../db/utils" -import { App, Plugin, UserCtx } from "@budibase/types" +import { + App, + FetchComponentDefinitionResponse, + Plugin, + UserCtx, +} from "@budibase/types" import { db as dbCore, context, tenancy } from "@budibase/backend-core" import { getComponentLibraryManifest } from "../../utilities/fileSystem" -export async function fetchAppComponentDefinitions(ctx: UserCtx) { +export async function fetchAppComponentDefinitions( + ctx: UserCtx +) { try { const db = context.getAppDB() const app = await db.get(DocumentType.APP_METADATA) diff --git a/packages/server/src/api/controllers/datasource.ts b/packages/server/src/api/controllers/datasource.ts index 97cf8db299..c4492f304c 100644 --- a/packages/server/src/api/controllers/datasource.ts +++ b/packages/server/src/api/controllers/datasource.ts @@ -23,13 +23,17 @@ import { Table, RowValue, DynamicVariable, + FetchDatasourcesResponse, + FindDatasourcesResponse, + DeleteDatasourceResponse, + FetchExternalSchemaResponse, } from "@budibase/types" import sdk from "../../sdk" import { builderSocket } from "../../websockets" import { isEqual } from "lodash" import { processTable } from "../../sdk/app/tables/getters" -export async function fetch(ctx: UserCtx) { +export async function fetch(ctx: UserCtx) { ctx.body = await sdk.datasources.fetch() } @@ -260,7 +264,7 @@ async function destroyInternalTablesBySourceId(datasourceId: string) { } } -export async function destroy(ctx: UserCtx) { +export async function destroy(ctx: UserCtx) { const db = context.getAppDB() const datasourceId = ctx.params.datasourceId @@ -291,12 +295,14 @@ export async function destroy(ctx: UserCtx) { builderSocket?.emitDatasourceDeletion(ctx, datasourceId) } -export async function find(ctx: UserCtx) { +export async function find(ctx: UserCtx) { const datasource = await sdk.datasources.get(ctx.params.datasourceId) ctx.body = await sdk.datasources.removeSecretSingle(datasource) } -export async function getExternalSchema(ctx: UserCtx) { +export async function getExternalSchema( + ctx: UserCtx +) { const datasource = await sdk.datasources.get(ctx.params.datasourceId) const enrichedDatasource = await sdk.datasources.getAndMergeDatasource( datasource diff --git a/packages/server/src/api/controllers/deploy/Deployment.ts b/packages/server/src/api/controllers/deploy/Deployment.ts index fe817730b6..9d5e543187 100644 --- a/packages/server/src/api/controllers/deploy/Deployment.ts +++ b/packages/server/src/api/controllers/deploy/Deployment.ts @@ -1,4 +1,5 @@ import { context, utils } from "@budibase/backend-core" +import { DeploymentStatus } from "@budibase/types" /** * This is used to pass around information about the deployment that is occurring @@ -6,7 +7,7 @@ import { context, utils } from "@budibase/backend-core" export default class Deployment { _id: string verification: any - status?: string + status?: DeploymentStatus err?: any appUrl?: string @@ -25,7 +26,7 @@ export default class Deployment { return this.verification } - setStatus(status: string, err?: any) { + setStatus(status: DeploymentStatus, err?: any) { this.status = status if (err) { this.err = err diff --git a/packages/server/src/api/controllers/deploy/index.ts b/packages/server/src/api/controllers/deploy/index.ts index 2cf3da3dda..b05b82d79a 100644 --- a/packages/server/src/api/controllers/deploy/index.ts +++ b/packages/server/src/api/controllers/deploy/index.ts @@ -7,20 +7,26 @@ import { enableCronTrigger, } from "../../../automations/utils" import { backups } from "@budibase/pro" -import { App, AppBackupTrigger } from "@budibase/types" +import { + App, + AppBackupTrigger, + DeploymentDoc, + FetchDeploymentResponse, + PublishAppResponse, + UserCtx, + DeploymentStatus, + DeploymentProgressResponse, +} from "@budibase/types" import sdk from "../../../sdk" import { builderSocket } from "../../../websockets" // the max time we can wait for an invalidation to complete before considering it failed const MAX_PENDING_TIME_MS = 30 * 60000 -const DeploymentStatus = { - SUCCESS: "SUCCESS", - PENDING: "PENDING", - FAILURE: "FAILURE", -} // checks that deployments are in a good state, any pending will be updated -async function checkAllDeployments(deployments: any) { +async function checkAllDeployments( + deployments: any +): Promise<{ updated: boolean; deployments: DeploymentDoc }> { let updated = false let deployment: any for (deployment of Object.values(deployments.history)) { @@ -96,7 +102,9 @@ async function initDeployedApp(prodAppId: any) { }) } -export async function fetchDeployments(ctx: any) { +export async function fetchDeployments( + ctx: UserCtx +) { try { const db = context.getAppDB() const deploymentDoc = await db.get(DocumentType.DEPLOYMENTS) @@ -104,17 +112,24 @@ export async function fetchDeployments(ctx: any) { if (updated) { await db.put(deployments) } - ctx.body = Object.values(deployments.history).reverse() + ctx.body = deployments.history + ? Object.values(deployments.history).reverse() + : [] } catch (err) { ctx.body = [] } } -export async function deploymentProgress(ctx: any) { +export async function deploymentProgress( + ctx: UserCtx +) { try { const db = context.getAppDB() - const deploymentDoc = await db.get(DocumentType.DEPLOYMENTS) - ctx.body = deploymentDoc[ctx.params.deploymentId] + const deploymentDoc = await db.get(DocumentType.DEPLOYMENTS) + if (!deploymentDoc.history?.[ctx.params.deploymentId]) { + ctx.throw(404, "No deployment found") + } + ctx.body = deploymentDoc.history?.[ctx.params.deploymentId] } catch (err) { ctx.throw( 500, @@ -123,7 +138,9 @@ export async function deploymentProgress(ctx: any) { } } -export const publishApp = async function (ctx: any) { +export const publishApp = async function ( + ctx: UserCtx +) { let deployment = new Deployment() console.log("Deployment object created") deployment.setStatus(DeploymentStatus.PENDING) diff --git a/packages/server/src/api/controllers/dev.ts b/packages/server/src/api/controllers/dev.ts index 497da088c6..ad0909a294 100644 --- a/packages/server/src/api/controllers/dev.ts +++ b/packages/server/src/api/controllers/dev.ts @@ -11,7 +11,13 @@ import { db as dbCore, cache, } from "@budibase/backend-core" -import { App } from "@budibase/types" +import { + App, + ClearDevLockResponse, + Ctx, + GetVersionResponse, + RevertAppResponse, +} from "@budibase/types" async function redirect( ctx: any, @@ -69,7 +75,7 @@ export function buildRedirectDelete(path: string) { } } -export async function clearLock(ctx: any) { +export async function clearLock(ctx: Ctx) { const { appId } = ctx.params try { await redisClearLock(appId, ctx.user) @@ -81,7 +87,7 @@ export async function clearLock(ctx: any) { } } -export async function revert(ctx: any) { +export async function revert(ctx: Ctx) { const { appId } = ctx.params const productionAppId = dbCore.getProdAppID(appId) @@ -131,7 +137,7 @@ export async function revert(ctx: any) { } } -export async function getBudibaseVersion(ctx: any) { +export async function getBudibaseVersion(ctx: Ctx) { const version = envCore.VERSION ctx.body = { version, diff --git a/packages/server/src/api/controllers/webhook.ts b/packages/server/src/api/controllers/webhook.ts index d667134f4d..7c648ea827 100644 --- a/packages/server/src/api/controllers/webhook.ts +++ b/packages/server/src/api/controllers/webhook.ts @@ -94,12 +94,16 @@ export async function trigger(ctx: BBContext) { { getResponses: true } ) - let collectedValue = response.steps.find( - (step: any) => step.stepId === AutomationActionStepId.COLLECT - ) + if (triggers.isAutomationResults(response)) { + let collectedValue = response.steps.find( + (step: any) => step.stepId === AutomationActionStepId.COLLECT + ) - ctx.status = 200 - ctx.body = collectedValue.outputs + ctx.status = 200 + ctx.body = collectedValue?.outputs + } else { + ctx.throw(400, "Automation did not have a collect block.") + } } else { await triggers.externalTrigger(target, { body: ctx.request.body, diff --git a/packages/server/src/automations/actions.ts b/packages/server/src/automations/actions.ts index a8b69fa7d7..e5a1c63b7d 100644 --- a/packages/server/src/automations/actions.ts +++ b/packages/server/src/automations/actions.ts @@ -96,9 +96,15 @@ if (env.SELF_HOSTED) { ACTION_IMPLS["EXECUTE_BASH"] = bash.run // @ts-ignore BUILTIN_ACTION_DEFINITIONS["EXECUTE_BASH"] = bash.definition + + if (env.isTest()) { + BUILTIN_ACTION_DEFINITIONS["OPENAI"] = openai.definition + } } -export async function getActionDefinitions() { +export async function getActionDefinitions(): Promise< + Record +> { if (await features.flags.isEnabled(FeatureFlag.AUTOMATION_BRANCHING)) { BUILTIN_ACTION_DEFINITIONS["BRANCH"] = branch.definition } diff --git a/packages/server/src/automations/steps/triggerAutomationRun.ts b/packages/server/src/automations/steps/triggerAutomationRun.ts index c43f46b6f9..f1cf65b182 100644 --- a/packages/server/src/automations/steps/triggerAutomationRun.ts +++ b/packages/server/src/automations/steps/triggerAutomationRun.ts @@ -3,7 +3,6 @@ import { AutomationStepDefinition, AutomationStepType, AutomationIOType, - AutomationResults, Automation, AutomationCustomIOType, TriggerAutomationStepInputs, @@ -78,7 +77,7 @@ export async function run({ const db = context.getAppDB() let automation = await db.get(inputs.automation.automationId) - const response: AutomationResults = await triggers.externalTrigger( + const response = await triggers.externalTrigger( automation, { fields: { ...fieldParams }, @@ -88,9 +87,13 @@ export async function run({ { getResponses: true } ) - return { - success: true, - value: response.steps, + if (triggers.isAutomationResults(response)) { + return { + success: true, + value: response.steps, + } + } else { + throw new Error("Automation did not have a collect block") } } } else { diff --git a/packages/server/src/automations/tests/bash.spec.ts b/packages/server/src/automations/tests/bash.spec.ts index 472d1092d6..12ed784268 100644 --- a/packages/server/src/automations/tests/bash.spec.ts +++ b/packages/server/src/automations/tests/bash.spec.ts @@ -1,26 +1,148 @@ -import { getConfig, afterAll as _afterAll, runStep } from "./utilities" +import { createAutomationBuilder } from "./utilities/AutomationTestBuilder" +import * as automation from "../index" +import * as setup from "./utilities" +import { Table } from "@budibase/types" -describe("test the bash action", () => { - let config = getConfig() +describe("Execute Bash Automations", () => { + let config = setup.getConfig(), + table: Table beforeAll(async () => { + await automation.init() await config.init() - }) - afterAll(_afterAll) - - it("should be able to execute a script", async () => { - let res = await runStep(config, "EXECUTE_BASH", { - code: "echo 'test'", + table = await config.createTable() + await config.createRow({ + name: "test row", + description: "test description", + tableId: table._id!, }) - expect(res.stdout).toEqual("test\n") - expect(res.success).toEqual(true) }) - it("should handle a null value", async () => { - let res = await runStep(config, "EXECUTE_BASH", { - code: null, + afterAll(setup.afterAll) + + it("should use trigger data in bash command and pass output to subsequent steps", async () => { + const result = await createAutomationBuilder({ + name: "Bash with Trigger Data", + config, }) - expect(res.stdout).toEqual( + .appAction({ fields: { command: "hello world" } }) + .bash( + { code: "echo '{{ trigger.fields.command }}'" }, + { stepName: "Echo Command" } + ) + .serverLog( + { text: "Bash output was: {{ steps.[Echo Command].stdout }}" }, + { stepName: "Log Output" } + ) + .run() + + expect(result.steps[0].outputs.stdout).toEqual("hello world\n") + expect(result.steps[1].outputs.message).toContain( + "Bash output was: hello world" + ) + }) + + it("should chain multiple bash commands using previous outputs", async () => { + const result = await createAutomationBuilder({ + name: "Chained Bash Commands", + config, + }) + .appAction({ fields: { filename: "testfile.txt" } }) + .bash( + { code: "echo 'initial content' > {{ trigger.fields.filename }}" }, + { stepName: "Create File" } + ) + .bash( + { code: "cat {{ trigger.fields.filename }} | tr '[a-z]' '[A-Z]'" }, + { stepName: "Transform Content" } + ) + .bash( + { code: "rm {{ trigger.fields.filename }}" }, + { stepName: "Cleanup" } + ) + .run() + + expect(result.steps[1].outputs.stdout).toEqual("INITIAL CONTENT\n") + expect(result.steps[1].outputs.success).toEqual(true) + }) + + it("should integrate bash output with row operations", async () => { + const result = await createAutomationBuilder({ + name: "Bash with Row Operations", + config, + }) + .appAction({ fields: {} }) + .queryRows( + { + tableId: table._id!, + filters: {}, + }, + { stepName: "Get Row" } + ) + .bash( + { + code: "echo Row data: {{ steps.[Get Row].rows.[0].name }} - {{ steps.[Get Row].rows.[0].description }}", + }, + { stepName: "Process Row Data" } + ) + .serverLog( + { text: "{{ steps.[Process Row Data].stdout }}" }, + { stepName: "Log Result" } + ) + .run() + + expect(result.steps[1].outputs.stdout).toContain( + "Row data: test row - test description" + ) + expect(result.steps[2].outputs.message).toContain( + "Row data: test row - test description" + ) + }) + + it("should handle bash output in conditional logic", async () => { + const result = await createAutomationBuilder({ + name: "Bash with Conditional", + config, + }) + .appAction({ fields: { threshold: "5" } }) + .bash( + { code: "echo $(( {{ trigger.fields.threshold }} + 5 ))" }, + { stepName: "Calculate Value" } + ) + .executeScript( + { + code: ` + const value = parseInt(steps["Calculate Value"].stdout); + return value > 8 ? "high" : "low"; + `, + }, + { stepName: "Check Value" } + ) + .serverLog( + { text: "Value was {{ steps.[Check Value].value }}" }, + { stepName: "Log Result" } + ) + .run() + + expect(result.steps[0].outputs.stdout).toEqual("10\n") + expect(result.steps[1].outputs.value).toEqual("high") + expect(result.steps[2].outputs.message).toContain("Value was high") + }) + + it("should handle null values gracefully", async () => { + const result = await createAutomationBuilder({ + name: "Null Bash Input", + config, + }) + .appAction({ fields: {} }) + .bash( + //@ts-ignore + { code: null }, + { stepName: "Null Command" } + ) + .run() + + expect(result.steps[0].outputs.stdout).toBe( "Budibase bash automation failed: Invalid inputs" ) }) diff --git a/packages/server/src/automations/tests/loop.spec.ts b/packages/server/src/automations/tests/loop.spec.ts index 372c3855b3..2199a2a3a0 100644 --- a/packages/server/src/automations/tests/loop.spec.ts +++ b/packages/server/src/automations/tests/loop.spec.ts @@ -3,7 +3,7 @@ import * as triggers from "../triggers" import { loopAutomation } from "../../tests/utilities/structures" import { context } from "@budibase/backend-core" import * as setup from "./utilities" -import { Table, LoopStepType } from "@budibase/types" +import { Table, LoopStepType, AutomationResults } from "@budibase/types" import * as loopUtils from "../loopUtils" import { LoopInput } from "../../definitions/automations" @@ -20,15 +20,19 @@ describe("Attempt to run a basic loop automation", () => { afterAll(setup.afterAll) - async function runLoop(loopOpts?: LoopInput) { + async function runLoop(loopOpts?: LoopInput): Promise { const appId = config.getAppId() return await context.doInAppContext(appId, async () => { const params = { fields: { appId } } - return await triggers.externalTrigger( + const result = await triggers.externalTrigger( loopAutomation(table._id!, loopOpts), params, { getResponses: true } ) + if ("outputs" in result && !result.outputs.success) { + throw new Error("Unable to proceed - failed to return anything.") + } + return result as AutomationResults }) } diff --git a/packages/server/src/automations/tests/openai.spec.ts b/packages/server/src/automations/tests/openai.spec.ts index 8119750f8b..9f2bc50599 100644 --- a/packages/server/src/automations/tests/openai.spec.ts +++ b/packages/server/src/automations/tests/openai.spec.ts @@ -1,7 +1,9 @@ -import { getConfig, runStep, afterAll as _afterAll } from "./utilities" +import { getConfig, afterAll as _afterAll } from "./utilities" +import { createAutomationBuilder } from "./utilities/AutomationTestBuilder" import { OpenAI } from "openai" import { setEnv as setCoreEnv } from "@budibase/backend-core" import * as pro from "@budibase/pro" +import { Model } from "@budibase/types" jest.mock("openai", () => ({ OpenAI: jest.fn().mockImplementation(() => ({ @@ -47,6 +49,7 @@ describe("test the openai action", () => { let resetEnv: () => void | undefined beforeAll(async () => { + setCoreEnv({ SELF_HOSTED: true }) await config.init() }) @@ -62,17 +65,39 @@ describe("test the openai action", () => { afterAll(_afterAll) it("should be able to receive a response from ChatGPT given a prompt", async () => { - const res = await runStep(config, "OPENAI", { prompt: OPENAI_PROMPT }) - expect(res.response).toEqual("This is a test") - expect(res.success).toBeTruthy() + setCoreEnv({ SELF_HOSTED: true }) + + const result = await createAutomationBuilder({ + name: "Test OpenAI Response", + config, + }) + .appAction({ fields: {} }) + .openai( + { prompt: OPENAI_PROMPT, model: Model.GPT_4O_MINI }, + { stepName: "Basic OpenAI Query" } + ) + .run() + + expect(result.steps[0].outputs.response).toEqual("This is a test") + expect(result.steps[0].outputs.success).toBeTruthy() }) it("should present the correct error message when a prompt is not provided", async () => { - const res = await runStep(config, "OPENAI", { prompt: null }) - expect(res.response).toEqual( + const result = await createAutomationBuilder({ + name: "Test OpenAI No Prompt", + config, + }) + .appAction({ fields: {} }) + .openai( + { prompt: "", model: Model.GPT_4O_MINI }, + { stepName: "Empty Prompt Query" } + ) + .run() + + expect(result.steps[0].outputs.response).toEqual( "Budibase OpenAI Automation Failed: No prompt supplied" ) - expect(res.success).toBeFalsy() + expect(result.steps[0].outputs.success).toBeFalsy() }) it("should present the correct error message when an error is thrown from the createChatCompletion call", async () => { @@ -91,14 +116,21 @@ describe("test the openai action", () => { } as any) ) - const res = await runStep(config, "OPENAI", { - prompt: OPENAI_PROMPT, + const result = await createAutomationBuilder({ + name: "Test OpenAI Error", + config, }) + .appAction({ fields: {} }) + .openai( + { prompt: OPENAI_PROMPT, model: Model.GPT_4O_MINI }, + { stepName: "Error Producing Query" } + ) + .run() - expect(res.response).toEqual( + expect(result.steps[0].outputs.response).toEqual( "Error: An error occurred while calling createChatCompletion" ) - expect(res.success).toBeFalsy() + expect(result.steps[0].outputs.success).toBeFalsy() }) it("should ensure that the pro AI module is called when the budibase AI features are enabled", async () => { @@ -106,10 +138,19 @@ describe("test the openai action", () => { jest.spyOn(pro.features, "isAICustomConfigsEnabled").mockResolvedValue(true) const prompt = "What is the meaning of life?" - await runStep(config, "OPENAI", { - model: "gpt-4o-mini", - prompt, + await createAutomationBuilder({ + name: "Test OpenAI Pro Features", + config, }) + .appAction({ fields: {} }) + .openai( + { + model: Model.GPT_4O_MINI, + prompt, + }, + { stepName: "Pro Features Query" } + ) + .run() expect(pro.ai.LargeLanguageModel.forCurrentTenant).toHaveBeenCalledWith( "gpt-4o-mini" diff --git a/packages/server/src/automations/tests/queryRows.spec.ts b/packages/server/src/automations/tests/queryRows.spec.ts index 12611d3f90..18d2e2d6cd 100644 --- a/packages/server/src/automations/tests/queryRows.spec.ts +++ b/packages/server/src/automations/tests/queryRows.spec.ts @@ -1,5 +1,7 @@ -import { Table } from "@budibase/types" +import { EmptyFilterOption, SortOrder, Table } from "@budibase/types" import * as setup from "./utilities" +import { createAutomationBuilder } from "./utilities/AutomationTestBuilder" +import * as automation from "../index" const NAME = "Test" @@ -8,6 +10,7 @@ describe("Test a query step automation", () => { let config = setup.getConfig() beforeAll(async () => { + await automation.init() await config.init() table = await config.createTable() const row = { @@ -22,107 +25,132 @@ describe("Test a query step automation", () => { afterAll(setup.afterAll) it("should be able to run the query step", async () => { - const inputs = { - tableId: table._id, - filters: { - equal: { - name: NAME, - }, - }, - sortColumn: "name", - sortOrder: "ascending", - limit: 10, - } - const res = await setup.runStep( + const result = await createAutomationBuilder({ + name: "Basic Query Test", config, - setup.actions.QUERY_ROWS.stepId, - inputs - ) - expect(res.success).toBe(true) - expect(res.rows).toBeDefined() - expect(res.rows.length).toBe(2) - expect(res.rows[0].name).toBe(NAME) + }) + .appAction({ fields: {} }) + .queryRows( + { + tableId: table._id!, + filters: { + equal: { + name: NAME, + }, + }, + sortColumn: "name", + sortOrder: SortOrder.ASCENDING, + limit: 10, + }, + { stepName: "Query All Rows" } + ) + .run() + + expect(result.steps[0].outputs.success).toBe(true) + expect(result.steps[0].outputs.rows).toBeDefined() + expect(result.steps[0].outputs.rows.length).toBe(2) + expect(result.steps[0].outputs.rows[0].name).toBe(NAME) }) it("Returns all rows when onEmptyFilter has no value and no filters are passed", async () => { - const inputs = { - tableId: table._id, - filters: {}, - sortColumn: "name", - sortOrder: "ascending", - limit: 10, - } - const res = await setup.runStep( + const result = await createAutomationBuilder({ + name: "Empty Filter Test", config, - setup.actions.QUERY_ROWS.stepId, - inputs - ) - expect(res.success).toBe(true) - expect(res.rows).toBeDefined() - expect(res.rows.length).toBe(2) - expect(res.rows[0].name).toBe(NAME) + }) + .appAction({ fields: {} }) + .queryRows( + { + tableId: table._id!, + filters: {}, + sortColumn: "name", + sortOrder: SortOrder.ASCENDING, + limit: 10, + }, + { stepName: "Query With Empty Filter" } + ) + .run() + + expect(result.steps[0].outputs.success).toBe(true) + expect(result.steps[0].outputs.rows).toBeDefined() + expect(result.steps[0].outputs.rows.length).toBe(2) + expect(result.steps[0].outputs.rows[0].name).toBe(NAME) }) it("Returns no rows when onEmptyFilter is RETURN_NONE and theres no filters", async () => { - const inputs = { - tableId: table._id, - filters: {}, - "filters-def": [], - sortColumn: "name", - sortOrder: "ascending", - limit: 10, - onEmptyFilter: "none", - } - const res = await setup.runStep( + const result = await createAutomationBuilder({ + name: "Return None Test", config, - setup.actions.QUERY_ROWS.stepId, - inputs - ) - expect(res.success).toBe(false) - expect(res.rows).toBeDefined() - expect(res.rows.length).toBe(0) + }) + .appAction({ fields: {} }) + .queryRows( + { + tableId: table._id!, + filters: {}, + "filters-def": [], + sortColumn: "name", + sortOrder: SortOrder.ASCENDING, + limit: 10, + onEmptyFilter: EmptyFilterOption.RETURN_NONE, + }, + { stepName: "Query With Return None" } + ) + .run() + + expect(result.steps[0].outputs.success).toBe(false) + expect(result.steps[0].outputs.rows).toBeDefined() + expect(result.steps[0].outputs.rows.length).toBe(0) }) it("Returns no rows when onEmptyFilters RETURN_NONE and a filter is passed with a null value", async () => { - const inputs = { - tableId: table._id, - onEmptyFilter: "none", - filters: {}, - "filters-def": [ - { - value: null, - }, - ], - sortColumn: "name", - sortOrder: "ascending", - limit: 10, - } - const res = await setup.runStep( + const result = await createAutomationBuilder({ + name: "Null Filter Test", config, - setup.actions.QUERY_ROWS.stepId, - inputs - ) - expect(res.success).toBe(false) - expect(res.rows).toBeDefined() - expect(res.rows.length).toBe(0) + }) + .appAction({ fields: {} }) + .queryRows( + { + tableId: table._id!, + onEmptyFilter: EmptyFilterOption.RETURN_NONE, + filters: {}, + "filters-def": [ + { + value: null, + }, + ], + sortColumn: "name", + sortOrder: SortOrder.ASCENDING, + limit: 10, + }, + { stepName: "Query With Null Filter" } + ) + .run() + + expect(result.steps[0].outputs.success).toBe(false) + expect(result.steps[0].outputs.rows).toBeDefined() + expect(result.steps[0].outputs.rows.length).toBe(0) }) it("Returns rows when onEmptyFilter is RETURN_ALL and no filter is passed", async () => { - const inputs = { - tableId: table._id, - onEmptyFilter: "all", - filters: {}, - sortColumn: "name", - sortOrder: "ascending", - limit: 10, - } - const res = await setup.runStep( + const result = await createAutomationBuilder({ + name: "Return All Test", config, - setup.actions.QUERY_ROWS.stepId, - inputs - ) - expect(res.success).toBe(true) - expect(res.rows).toBeDefined() - expect(res.rows.length).toBe(2) + }) + .appAction({ fields: {} }) + .queryRows( + { + tableId: table._id!, + onEmptyFilter: EmptyFilterOption.RETURN_ALL, + filters: {}, + sortColumn: "name", + sortOrder: SortOrder.ASCENDING, + limit: 10, + }, + { stepName: "Query With Return All" } + ) + .run() + + expect(result.steps[0].outputs.success).toBe(true) + expect(result.steps[0].outputs.rows).toBeDefined() + expect(result.steps[0].outputs.rows.length).toBe(2) }) }) diff --git a/packages/server/src/automations/tests/utilities/AutomationTestBuilder.ts b/packages/server/src/automations/tests/utilities/AutomationTestBuilder.ts index 78830adf2c..7a0d489f80 100644 --- a/packages/server/src/automations/tests/utilities/AutomationTestBuilder.ts +++ b/packages/server/src/automations/tests/utilities/AutomationTestBuilder.ts @@ -35,6 +35,8 @@ import { Branch, FilterStepInputs, ExecuteScriptStepInputs, + OpenAIStepInputs, + BashStepInputs, } from "@budibase/types" import TestConfiguration from "../../../tests/utilities/TestConfiguration" import * as setup from "../utilities" @@ -221,6 +223,30 @@ class BaseStepBuilder { input ) } + + bash( + input: BashStepInputs, + opts?: { stepName?: string; stepId?: string } + ): this { + return this.step( + AutomationActionStepId.EXECUTE_BASH, + BUILTIN_ACTION_DEFINITIONS.EXECUTE_BASH, + input, + opts + ) + } + + openai( + input: OpenAIStepInputs, + opts?: { stepName?: string; stepId?: string } + ): this { + return this.step( + AutomationActionStepId.OPENAI, + BUILTIN_ACTION_DEFINITIONS.OPENAI, + input, + opts + ) + } } class StepBuilder extends BaseStepBuilder { build(): AutomationStep[] { diff --git a/packages/server/src/automations/triggers.ts b/packages/server/src/automations/triggers.ts index 70fda1f237..ed0aaaf3ec 100644 --- a/packages/server/src/automations/triggers.ts +++ b/packages/server/src/automations/triggers.ts @@ -20,6 +20,7 @@ import { AutomationStatus, AutomationRowEvent, UserBindings, + AutomationResults, } from "@budibase/types" import { executeInThread } from "../threads/automation" import { dataFilters, sdk } from "@budibase/shared-core" @@ -32,6 +33,14 @@ const JOB_OPTS = { import * as automationUtils from "../automations/automationUtils" import { doesTableExist } from "../sdk/app/tables/getters" +type DidNotTriggerResponse = { + outputs: { + success: false + status: AutomationStatus.STOPPED + } + message: AutomationStoppedReason.TRIGGER_FILTER_NOT_MET +} + async function getAllAutomations() { const db = context.getAppDB() let automations = await db.allDocs( @@ -139,6 +148,14 @@ function rowPassesFilters(row: Row, filters: SearchFilters) { return filteredRows.length > 0 } +export function isAutomationResults( + response: AutomationResults | DidNotTriggerResponse | AutomationJob +): response is AutomationResults { + return ( + response !== null && "steps" in response && Array.isArray(response.steps) + ) +} + export async function externalTrigger( automation: Automation, params: { @@ -148,7 +165,7 @@ export async function externalTrigger( user?: UserBindings }, { getResponses }: { getResponses?: boolean } = {} -): Promise { +): Promise { if (automation.disabled) { throw new Error("Automation is disabled") } diff --git a/packages/server/src/automations/utils.ts b/packages/server/src/automations/utils.ts index 365dc36b68..3eeeae5734 100644 --- a/packages/server/src/automations/utils.ts +++ b/packages/server/src/automations/utils.ts @@ -9,9 +9,11 @@ import { cloneDeep } from "lodash/fp" import { quotas } from "@budibase/pro" import { Automation, + AutomationActionStepId, AutomationJob, AutomationStepDefinition, AutomationTriggerDefinition, + AutomationTriggerStepId, } from "@budibase/types" import { automationsEnabled } from "../features" import { helpers, REBOOT_CRON } from "@budibase/shared-core" @@ -120,19 +122,21 @@ export async function updateTestHistory( ) } -export function removeDeprecated( - definitions: Record< +export function removeDeprecated< + T extends + | Record + | Record +>(definitions: T): T { + const base: Record< string, - AutomationStepDefinition | AutomationTriggerDefinition - > -) { - const base = cloneDeep(definitions) + AutomationTriggerDefinition | AutomationStepDefinition + > = cloneDeep(definitions) for (let key of Object.keys(base)) { if (base[key].deprecated) { delete base[key] } } - return base + return base as T } // end the repetition and the job itself diff --git a/packages/server/src/definitions/automations.ts b/packages/server/src/definitions/automations.ts index e45c555201..67d6e04e9d 100644 --- a/packages/server/src/definitions/automations.ts +++ b/packages/server/src/definitions/automations.ts @@ -26,3 +26,6 @@ export interface AutomationContext extends AutomationResults { company?: string } } + +export interface AutomationResponse + extends Omit {} diff --git a/packages/server/src/sdk/app/applications/sync.ts b/packages/server/src/sdk/app/applications/sync.ts index 37450acf1d..3bbd72c12b 100644 --- a/packages/server/src/sdk/app/applications/sync.ts +++ b/packages/server/src/sdk/app/applications/sync.ts @@ -113,7 +113,7 @@ export async function syncUsersToAllApps(userIds: string[]) { export async function syncApp( appId: string, opts?: { automationOnly?: boolean } -) { +): Promise<{ message: string }> { if (env.DISABLE_AUTO_PROD_APP_SYNC) { return { message: diff --git a/packages/server/src/tests/utilities/TestConfiguration.ts b/packages/server/src/tests/utilities/TestConfiguration.ts index abecf6df44..2d36e7855b 100644 --- a/packages/server/src/tests/utilities/TestConfiguration.ts +++ b/packages/server/src/tests/utilities/TestConfiguration.ts @@ -621,7 +621,7 @@ export default class TestConfiguration { } async unpublish() { - const response = await this._req(appController.unpublish, { + const response = await this._req(appController.unpublish, undefined, { appId: this.appId, }) this.prodAppId = undefined diff --git a/packages/server/src/threads/automation.ts b/packages/server/src/threads/automation.ts index 7e8f947580..2d10f5d1fb 100644 --- a/packages/server/src/threads/automation.ts +++ b/packages/server/src/threads/automation.ts @@ -30,7 +30,11 @@ import { UserBindings, isBasicSearchOperator, } from "@budibase/types" -import { AutomationContext, TriggerOutput } from "../definitions/automations" +import { + AutomationContext, + AutomationResponse, + TriggerOutput, +} from "../definitions/automations" import { WorkerCallback } from "./definitions" import { context, logging, configs } from "@budibase/backend-core" import { @@ -81,7 +85,7 @@ class Orchestrator { private job: Job private loopStepOutputs: LoopStep[] private stopped: boolean - private executionOutput: Omit + private executionOutput: AutomationResponse private currentUser: UserBindings | undefined constructor(job: AutomationJob) { @@ -257,7 +261,7 @@ class Orchestrator { }) } - async execute(): Promise { + async execute(): Promise { return tracer.trace( "Orchestrator.execute", { resource: "automation" }, @@ -723,7 +727,9 @@ export function execute(job: Job, callback: WorkerCallback) { }) } -export async function executeInThread(job: Job) { +export async function executeInThread( + job: Job +): Promise { const appId = job.data.event.appId if (!appId) { throw new Error("Unable to execute, event doesn't contain app ID.") @@ -735,7 +741,7 @@ export async function executeInThread(job: Job) { }, job.data.event.timeout || env.AUTOMATION_THREAD_TIMEOUT) }) - return await context.doInAppContext(appId, async () => { + return (await context.doInAppContext(appId, async () => { await context.ensureSnippetContext() const envVars = await sdkUtils.getEnvironmentVariables() // put into automation thread for whole context @@ -746,7 +752,7 @@ export async function executeInThread(job: Job) { timeoutPromise, ]) }) - }) + })) as AutomationResponse } export const removeStalled = async (job: Job) => { diff --git a/packages/types/src/api/web/analytics.ts b/packages/types/src/api/web/analytics.ts index 172aeb8dd4..2585964f94 100644 --- a/packages/types/src/api/web/analytics.ts +++ b/packages/types/src/api/web/analytics.ts @@ -3,6 +3,10 @@ export enum PingSource { APP = "app", } +export interface AnalyticsEnabledResponse { + enabled: boolean +} + export interface AnalyticsPingRequest { source: PingSource timezone: string diff --git a/packages/types/src/api/web/apikeys.ts b/packages/types/src/api/web/apikeys.ts new file mode 100644 index 0000000000..1d089cd0ac --- /dev/null +++ b/packages/types/src/api/web/apikeys.ts @@ -0,0 +1,10 @@ +export type ApiKeyFetchResponse = Record + +export interface UpdateApiKeyRequest { + value: string +} + +export interface UpdateApiKeyResponse { + _id: string + _rev: string +} diff --git a/packages/types/src/api/web/app/automation.ts b/packages/types/src/api/web/app/automation.ts new file mode 100644 index 0000000000..c10f19c88d --- /dev/null +++ b/packages/types/src/api/web/app/automation.ts @@ -0,0 +1,21 @@ +import { + AutomationActionStepId, + AutomationStepDefinition, + AutomationTriggerDefinition, + AutomationTriggerStepId, +} from "../../../documents" + +export type GetAutomationTriggerDefinitionsResponse = Record< + keyof typeof AutomationTriggerStepId, + AutomationTriggerDefinition +> + +export type GetAutomationActionDefinitionsResponse = Record< + keyof typeof AutomationActionStepId, + AutomationStepDefinition +> + +export interface GetAutomationStepDefinitionsResponse { + trigger: GetAutomationTriggerDefinitionsResponse + action: GetAutomationActionDefinitionsResponse +} diff --git a/packages/types/src/api/web/app/component.ts b/packages/types/src/api/web/app/component.ts new file mode 100644 index 0000000000..486809a6b1 --- /dev/null +++ b/packages/types/src/api/web/app/component.ts @@ -0,0 +1,4 @@ +export type FetchComponentDefinitionResponse = Record< + string, + Record +> diff --git a/packages/types/src/api/web/app/datasource.ts b/packages/types/src/api/web/app/datasource.ts index f931665917..6f982d7060 100644 --- a/packages/types/src/api/web/app/datasource.ts +++ b/packages/types/src/api/web/app/datasource.ts @@ -42,3 +42,14 @@ export interface BuildSchemaFromSourceResponse { datasource: Datasource errors: Record } + +export type FetchDatasourcesResponse = Datasource[] +export type FindDatasourcesResponse = Datasource + +export interface DeleteDatasourceResponse { + message: string +} + +export interface FetchExternalSchemaResponse { + schema: string +} diff --git a/packages/types/src/api/web/app/index.ts b/packages/types/src/api/web/app/index.ts index 55e1428fb9..9cc0bf36b6 100644 --- a/packages/types/src/api/web/app/index.ts +++ b/packages/types/src/api/web/app/index.ts @@ -8,3 +8,5 @@ export * from "./permission" export * from "./attachment" export * from "./user" export * from "./rowAction" +export * from "./automation" +export * from "./component" diff --git a/packages/types/src/api/web/app/user.ts b/packages/types/src/api/web/app/user.ts index 7faec83e9c..f5f2049724 100644 --- a/packages/types/src/api/web/app/user.ts +++ b/packages/types/src/api/web/app/user.ts @@ -7,3 +7,5 @@ export interface SetFlagRequest { flag: string value: any } + +export type AppSelfResponse = ContextUserMetadata | {} diff --git a/packages/types/src/api/web/application.ts b/packages/types/src/api/web/application.ts index 57422ceabc..ed999ee574 100644 --- a/packages/types/src/api/web/application.ts +++ b/packages/types/src/api/web/application.ts @@ -1,5 +1,10 @@ import type { PlanType } from "../../sdk" import type { Layout, App, Screen } from "../../documents" +import { ReadStream } from "fs" + +export interface SyncAppResponse { + message: string +} export interface CreateAppRequest { name: string @@ -12,6 +17,8 @@ export interface CreateAppRequest { file?: { path: string } } +export interface CreateAppResponse extends App {} + export interface DuplicateAppRequest { name: string url?: string @@ -37,6 +44,8 @@ export interface FetchAppPackageResponse { hasLock: boolean } +export type FetchAppsResponse = App[] + export interface PublishResponse { _id: string status: string @@ -45,3 +54,27 @@ export interface PublishResponse { export interface UpdateAppRequest extends Partial {} export interface UpdateAppResponse extends App {} +export interface UpdateAppClientResponse extends App {} +export interface RevertAppClientResponse extends App {} + +export interface DeleteAppResponse { + ok: boolean +} + +export interface ImportToUpdateAppRequest { + encryptionPassword?: string +} +export interface ImportToUpdateAppResponse { + message: string +} + +export interface SetRevertableAppVersionRequest { + revertableVersion: string +} + +export interface ExportAppDumpRequest { + excludeRows: boolean + encryptPassword?: string +} + +export type ExportAppDumpResponse = ReadStream diff --git a/packages/types/src/api/web/automation.ts b/packages/types/src/api/web/automation.ts index 06080fc667..0f0699939e 100644 --- a/packages/types/src/api/web/automation.ts +++ b/packages/types/src/api/web/automation.ts @@ -1,8 +1,58 @@ import { DocumentDestroyResponse } from "@budibase/nano" -import { Automation } from "../../documents" +import { + Automation, + AutomationLogPage, + AutomationStatus, + Row, +} from "../../documents" export interface DeleteAutomationResponse extends DocumentDestroyResponse {} export interface FetchAutomationResponse { automations: Automation[] } + +export interface FindAutomationResponse extends Automation {} + +export interface UpdateAutomationRequest extends Automation {} +export interface UpdateAutomationResponse { + message: string + automation: Automation +} + +export interface CreateAutomationRequest extends Automation {} +export interface CreateAutomationResponse { + message: string + automation: Automation +} + +export interface SearchAutomationLogsRequest { + startDate?: string + status?: AutomationStatus + automationId?: string + page?: string +} +export interface SearchAutomationLogsResponse extends AutomationLogPage {} + +export interface ClearAutomationLogRequest { + automationId: string + appId: string +} +export interface ClearAutomationLogResponse { + message: string +} + +export interface TriggerAutomationRequest { + fields: Record + // time in seconds + timeout: number +} +export type TriggerAutomationResponse = Record | undefined + +export interface TestAutomationRequest { + id?: string + revision?: string + fields: Record + row?: Row +} +export interface TestAutomationResponse {} diff --git a/packages/types/src/api/web/deployment.ts b/packages/types/src/api/web/deployment.ts new file mode 100644 index 0000000000..f5ed9242b1 --- /dev/null +++ b/packages/types/src/api/web/deployment.ts @@ -0,0 +1,12 @@ +import { DeploymentDoc, DeploymentStatus } from "../../documents" + +export interface PublishAppResponse extends DeploymentDoc {} + +export interface DeploymentProgressResponse { + _id: string + appId: string + status?: DeploymentStatus + updatedAt: number +} + +export type FetchDeploymentResponse = DeploymentProgressResponse[] diff --git a/packages/types/src/api/web/dev.ts b/packages/types/src/api/web/dev.ts new file mode 100644 index 0000000000..461676a480 --- /dev/null +++ b/packages/types/src/api/web/dev.ts @@ -0,0 +1,11 @@ +export interface GetVersionResponse { + version: string +} + +export interface ClearDevLockResponse { + message: string +} + +export interface RevertAppResponse { + message: string +} diff --git a/packages/types/src/api/web/index.ts b/packages/types/src/api/web/index.ts index 27d51ce1b7..4021eafee8 100644 --- a/packages/types/src/api/web/index.ts +++ b/packages/types/src/api/web/index.ts @@ -16,3 +16,6 @@ export * from "./layout" export * from "./query" export * from "./role" export * from "./plugins" +export * from "./apikeys" +export * from "./deployment" +export * from "./dev" diff --git a/packages/types/src/documents/app/automation/StepInputsOutputs.ts b/packages/types/src/documents/app/automation/StepInputsOutputs.ts index 3aadb77108..b2f679edee 100644 --- a/packages/types/src/documents/app/automation/StepInputsOutputs.ts +++ b/packages/types/src/documents/app/automation/StepInputsOutputs.ts @@ -150,7 +150,7 @@ export type OpenAIStepInputs = { prompt: string model: Model } -enum Model { +export enum Model { GPT_35_TURBO = "gpt-3.5-turbo", // will only work with api keys that have access to the GPT4 API GPT_4 = "gpt-4", diff --git a/packages/types/src/documents/app/automation/schema.ts b/packages/types/src/documents/app/automation/schema.ts index b8a19b7b45..efdf60a4e2 100644 --- a/packages/types/src/documents/app/automation/schema.ts +++ b/packages/types/src/documents/app/automation/schema.ts @@ -311,6 +311,7 @@ export type AutomationStep = type EmptyInputs = {} export type AutomationStepDefinition = Omit & { inputs: EmptyInputs + deprecated?: boolean } export type AutomationTriggerDefinition = Omit< @@ -318,6 +319,7 @@ export type AutomationTriggerDefinition = Omit< "id" | "inputs" > & { inputs: EmptyInputs + deprecated?: boolean } export type AutomationTriggerInputs = diff --git a/packages/types/src/documents/app/deployment.ts b/packages/types/src/documents/app/deployment.ts new file mode 100644 index 0000000000..3216ebae3d --- /dev/null +++ b/packages/types/src/documents/app/deployment.ts @@ -0,0 +1,22 @@ +export enum DeploymentStatus { + SUCCESS = "SUCCESS", + PENDING = "PENDING", + FAILURE = "FAILURE", +} + +export interface DeploymentDoc { + _id: string + verification: any + status?: DeploymentStatus + history?: Record< + string, + { + _id: string + appId: string + status?: DeploymentStatus + updatedAt: number + } + > + err?: any + appUrl?: string +} diff --git a/packages/types/src/documents/app/index.ts b/packages/types/src/documents/app/index.ts index bb94c3b4da..51c6889f14 100644 --- a/packages/types/src/documents/app/index.ts +++ b/packages/types/src/documents/app/index.ts @@ -18,3 +18,4 @@ export * from "./sqlite" export * from "./snippet" export * from "./rowAction" export * from "./theme" +export * from "./deployment" diff --git a/packages/types/src/documents/global/apikeys.ts b/packages/types/src/documents/global/apikeys.ts new file mode 100644 index 0000000000..4b46f1d341 --- /dev/null +++ b/packages/types/src/documents/global/apikeys.ts @@ -0,0 +1,5 @@ +import { Document } from "../../" + +export interface ApiKeyDoc extends Document { + apiKeys: Record +} diff --git a/packages/types/src/documents/global/index.ts b/packages/types/src/documents/global/index.ts index b728439dd6..7d2f5a767c 100644 --- a/packages/types/src/documents/global/index.ts +++ b/packages/types/src/documents/global/index.ts @@ -7,3 +7,4 @@ export * from "./schedule" export * from "./templates" export * from "./environmentVariables" export * from "./auditLogs" +export * from "./apikeys"