budibase/packages/server/src/api/controllers/automation.ts

340 lines
9.3 KiB
TypeScript
Raw Normal View History

import * as triggers from "../../automations/triggers"
import {
getAutomationParams,
generateAutomationID,
DocumentType,
} from "../../db/utils"
import {
checkForWebhooks,
updateTestHistory,
removeDeprecated,
} from "../../automations/utils"
import { deleteEntityMetadata } from "../../utilities"
2024-02-14 18:15:42 +01:00
import { MetadataTypes } from "../../constants"
import { setTestFlag, clearTestFlag } from "../../utilities/redis"
2023-11-20 16:19:31 +01:00
import { context, cache, events, db as dbCore } from "@budibase/backend-core"
import { automations, features } from "@budibase/pro"
import {
2023-07-18 11:41:51 +02:00
App,
Automation,
AutomationActionStepId,
AutomationResults,
UserCtx,
} from "@budibase/types"
import { getActionDefinitions as actionDefs } from "../../automations/actions"
2023-05-16 17:05:37 +02:00
import sdk from "../../sdk"
import { builderSocket } from "../../websockets"
2024-02-14 18:15:42 +01:00
import env from "../../environment"
2024-02-28 13:13:13 +01:00
import { DocumentDestroyResponse } from "@budibase/nano"
2021-05-18 17:37:54 +02:00
async function getActionDefinitions() {
return removeDeprecated(await actionDefs())
}
function getTriggerDefinitions() {
return removeDeprecated(triggers.TRIGGER_DEFINITIONS)
}
/*************************
* *
* BUILDER FUNCTIONS *
* *
*************************/
2020-05-20 18:02:46 +02:00
async function cleanupAutomationMetadata(automationId: string) {
await deleteEntityMetadata(MetadataTypes.AUTOMATION_TEST_INPUT, automationId)
await deleteEntityMetadata(
MetadataTypes.AUTOMATION_TEST_HISTORY,
automationId
)
}
function cleanAutomationInputs(automation: Automation) {
if (automation == null) {
return automation
}
let steps = automation.definition.steps
let trigger = automation.definition.trigger
let allSteps = [...steps, trigger]
// live is not a property used anymore
if (automation.live != null) {
delete automation.live
}
for (let step of allSteps) {
if (step == null) {
continue
}
for (let inputName of Object.keys(step.inputs)) {
if (!step.inputs[inputName] || step.inputs[inputName] === "") {
delete step.inputs[inputName]
}
}
}
return automation
}
2024-02-28 12:46:58 +01:00
export async function create(
ctx: UserCtx<Automation, { message: string; automation: Automation }>
) {
const db = context.getAppDB()
let automation = ctx.request.body
automation.appId = ctx.appId
2020-05-20 18:02:46 +02:00
// call through to update if already exists
if (automation._id && automation._rev) {
Undo/Redo for Design and Automate sections + automations refactor (#9714) * Add full undo/redo support for screens * Add loading states to disable spamming undo/redo * Add keyboard shortcuts for undo and redo * Fix modals not closing in design section when escape is pressed * Remove log * Add smart metadata saving to undo/redo * Add error handling to undo/redo * Add active state to hoverable icons * Fix screen deletion * Always attempt to get latest doc version before deleting in case rev has changed * Move undo listener top level, hide controls when on certain tabs, and improve selection state * Add tooltips to undo/redo control * Update automation section nav to match other sections * Fix automation list padding * Fix some styles in create automation modal * Improve automation section styles and add undo/redo * Update styles in add action modal * Fix button size when creating admin user * Fix styles in add automation step modal * Fix issue selecting disabled automation steps * Reset automation history store when changing app * Reduce spammy unnecessary API calls when editing cron trigger * WIP automation refactor * Rewrite most automation state * Rewrite most of the rest of automation state * Finish refactor of automation state * Fix selection state when selecting new doc after history recreates it * Prune nullish or empty block inputs from automations and avoid sending API requests when no changes have been made * Fix animation issues with automations * Sort automations and refetch list when adding or deleting * Fix formatting * Add back in ability to swap between values and bindings for block inputs * Lint * Format * Fix potential issue in design section when selected screen is unset * Fix automation arrow directions everywhere, tidy up logic and fix crash when using invalid looping * Lint * Fix more cases of automation errors * Fix implicity any TS error * Respect _id specified when creating automations * Fix crash in history store when reverting a change on a doc whose ID has changed * Lint * Ensure cloneDeep helper doesn't crash when a nullish value is passed in * Remove deprecated frontend automation test --------- Co-authored-by: Rory Powell <rory.codes@gmail.com>
2023-02-23 14:55:18 +01:00
await update(ctx)
return
}
Undo/Redo for Design and Automate sections + automations refactor (#9714) * Add full undo/redo support for screens * Add loading states to disable spamming undo/redo * Add keyboard shortcuts for undo and redo * Fix modals not closing in design section when escape is pressed * Remove log * Add smart metadata saving to undo/redo * Add error handling to undo/redo * Add active state to hoverable icons * Fix screen deletion * Always attempt to get latest doc version before deleting in case rev has changed * Move undo listener top level, hide controls when on certain tabs, and improve selection state * Add tooltips to undo/redo control * Update automation section nav to match other sections * Fix automation list padding * Fix some styles in create automation modal * Improve automation section styles and add undo/redo * Update styles in add action modal * Fix button size when creating admin user * Fix styles in add automation step modal * Fix issue selecting disabled automation steps * Reset automation history store when changing app * Reduce spammy unnecessary API calls when editing cron trigger * WIP automation refactor * Rewrite most automation state * Rewrite most of the rest of automation state * Finish refactor of automation state * Fix selection state when selecting new doc after history recreates it * Prune nullish or empty block inputs from automations and avoid sending API requests when no changes have been made * Fix animation issues with automations * Sort automations and refetch list when adding or deleting * Fix formatting * Add back in ability to swap between values and bindings for block inputs * Lint * Format * Fix potential issue in design section when selected screen is unset * Fix automation arrow directions everywhere, tidy up logic and fix crash when using invalid looping * Lint * Fix more cases of automation errors * Fix implicity any TS error * Respect _id specified when creating automations * Fix crash in history store when reverting a change on a doc whose ID has changed * Lint * Ensure cloneDeep helper doesn't crash when a nullish value is passed in * Remove deprecated frontend automation test --------- Co-authored-by: Rory Powell <rory.codes@gmail.com>
2023-02-23 14:55:18 +01:00
// Respect existing IDs if recreating a deleted automation
if (!automation._id) {
automation._id = generateAutomationID()
}
2020-05-20 18:02:46 +02:00
automation.type = "automation"
automation = cleanAutomationInputs(automation)
automation = await checkForWebhooks({
newAuto: automation,
})
const response = await db.put(automation)
2022-05-30 22:46:08 +02:00
await events.automation.created(automation)
2022-04-06 14:54:57 +02:00
for (let step of automation.definition.steps) {
2022-05-30 22:46:08 +02:00
await events.automation.stepCreated(automation, step)
2022-04-06 14:54:57 +02:00
}
automation._rev = response.rev
2020-05-20 18:02:46 +02:00
ctx.status = 200
ctx.body = {
message: "Automation created successfully",
automation: {
...automation,
2020-05-27 13:51:19 +02:00
...response,
},
}
builderSocket?.emitAutomationUpdate(ctx, automation)
2020-05-20 18:02:46 +02:00
}
export function getNewSteps(oldAutomation: Automation, automation: Automation) {
2022-04-06 14:54:57 +02:00
const oldStepIds = oldAutomation.definition.steps.map(s => s.id)
return automation.definition.steps.filter(s => !oldStepIds.includes(s.id))
}
export function getDeletedSteps(
oldAutomation: Automation,
automation: Automation
) {
2022-04-06 14:54:57 +02:00
const stepIds = automation.definition.steps.map(s => s.id)
return oldAutomation.definition.steps.filter(s => !stepIds.includes(s.id))
}
export async function handleStepEvents(
oldAutomation: Automation,
automation: Automation
) {
2022-04-06 14:54:57 +02:00
// new steps
const newSteps = getNewSteps(oldAutomation, automation)
for (let step of newSteps) {
2022-05-31 22:04:41 +02:00
await events.automation.stepCreated(automation, step)
2022-04-06 14:54:57 +02:00
}
// old steps
const deletedSteps = getDeletedSteps(oldAutomation, automation)
for (let step of deletedSteps) {
2022-05-31 22:04:41 +02:00
await events.automation.stepDeleted(automation, step)
2022-04-06 14:54:57 +02:00
}
}
2022-05-23 23:14:44 +02:00
export async function update(ctx: UserCtx) {
const db = context.getAppDB()
let automation = ctx.request.body
automation.appId = ctx.appId
Undo/Redo for Design and Automate sections + automations refactor (#9714) * Add full undo/redo support for screens * Add loading states to disable spamming undo/redo * Add keyboard shortcuts for undo and redo * Fix modals not closing in design section when escape is pressed * Remove log * Add smart metadata saving to undo/redo * Add error handling to undo/redo * Add active state to hoverable icons * Fix screen deletion * Always attempt to get latest doc version before deleting in case rev has changed * Move undo listener top level, hide controls when on certain tabs, and improve selection state * Add tooltips to undo/redo control * Update automation section nav to match other sections * Fix automation list padding * Fix some styles in create automation modal * Improve automation section styles and add undo/redo * Update styles in add action modal * Fix button size when creating admin user * Fix styles in add automation step modal * Fix issue selecting disabled automation steps * Reset automation history store when changing app * Reduce spammy unnecessary API calls when editing cron trigger * WIP automation refactor * Rewrite most automation state * Rewrite most of the rest of automation state * Finish refactor of automation state * Fix selection state when selecting new doc after history recreates it * Prune nullish or empty block inputs from automations and avoid sending API requests when no changes have been made * Fix animation issues with automations * Sort automations and refetch list when adding or deleting * Fix formatting * Add back in ability to swap between values and bindings for block inputs * Lint * Format * Fix potential issue in design section when selected screen is unset * Fix automation arrow directions everywhere, tidy up logic and fix crash when using invalid looping * Lint * Fix more cases of automation errors * Fix implicity any TS error * Respect _id specified when creating automations * Fix crash in history store when reverting a change on a doc whose ID has changed * Lint * Ensure cloneDeep helper doesn't crash when a nullish value is passed in * Remove deprecated frontend automation test --------- Co-authored-by: Rory Powell <rory.codes@gmail.com>
2023-02-23 14:55:18 +01:00
// Call through to create if it doesn't exist
if (!automation._id || !automation._rev) {
await create(ctx)
return
}
2023-07-18 11:41:51 +02:00
const oldAutomation = await db.get<Automation>(automation._id)
automation = cleanAutomationInputs(automation)
automation = await checkForWebhooks({
oldAuto: oldAutomation,
newAuto: automation,
})
const response = await db.put(automation)
automation._rev = response.rev
2020-05-22 17:32:23 +02:00
const oldAutoTrigger =
oldAutomation && oldAutomation.definition.trigger
? oldAutomation.definition.trigger
2022-04-06 14:54:57 +02:00
: undefined
const newAutoTrigger =
automation && automation.definition.trigger
? automation.definition.trigger
: {}
// trigger has been updated, remove the test inputs
2022-04-06 14:54:57 +02:00
if (oldAutoTrigger && oldAutoTrigger.id !== newAutoTrigger.id) {
2022-05-31 22:04:41 +02:00
await events.automation.triggerUpdated(automation)
await deleteEntityMetadata(
MetadataTypes.AUTOMATION_TEST_INPUT,
automation._id!
)
}
2022-05-23 23:14:44 +02:00
await handleStepEvents(oldAutomation, automation)
2022-04-06 14:54:57 +02:00
2020-05-22 17:32:23 +02:00
ctx.status = 200
ctx.body = {
message: `Automation ${automation._id} updated successfully.`,
automation: {
...automation,
2020-05-22 17:32:23 +02:00
_rev: response.rev,
_id: response.id,
2020-05-22 17:32:23 +02:00
},
}
builderSocket?.emitAutomationUpdate(ctx, automation)
2020-05-20 18:02:46 +02:00
}
export async function fetch(ctx: UserCtx) {
const db = context.getAppDB()
const response = await db.allDocs(
getAutomationParams(null, {
include_docs: true,
})
)
2021-05-04 12:32:22 +02:00
ctx.body = response.rows.map(row => row.doc)
2020-05-20 18:02:46 +02:00
}
export async function find(ctx: UserCtx) {
const db = context.getAppDB()
ctx.body = await db.get(ctx.params.id)
2020-05-20 18:02:46 +02:00
}
2024-02-28 13:13:13 +01:00
export async function destroy(ctx: UserCtx<void, DocumentDestroyResponse>) {
const db = context.getAppDB()
const automationId = ctx.params.id
2023-07-18 11:41:51 +02:00
const oldAutomation = await db.get<Automation>(automationId)
await checkForWebhooks({
2021-05-18 22:03:26 +02:00
oldAuto: oldAutomation,
})
// delete metadata first
await cleanupAutomationMetadata(automationId)
ctx.body = await db.remove(automationId, ctx.params.rev)
2022-05-31 22:04:41 +02:00
await events.automation.deleted(oldAutomation)
builderSocket?.emitAutomationDeletion(ctx, automationId)
}
export async function logSearch(ctx: UserCtx) {
ctx.body = await automations.logs.logSearch(ctx.request.body)
}
export async function clearLogError(ctx: UserCtx) {
const { automationId, appId } = ctx.request.body
await context.doInAppContext(appId, async () => {
const db = context.getProdAppDB()
2023-07-18 11:41:51 +02:00
const metadata = await db.get<App>(DocumentType.APP_METADATA)
if (!automationId) {
delete metadata.automationErrors
} else if (
metadata.automationErrors &&
metadata.automationErrors[automationId]
) {
delete metadata.automationErrors[automationId]
}
await db.put(metadata)
await cache.app.invalidateAppMetadata(metadata.appId, metadata)
ctx.body = { message: `Error logs cleared.` }
})
}
export async function getActionList(ctx: UserCtx) {
ctx.body = await getActionDefinitions()
2020-05-26 22:34:01 +02:00
}
export async function getTriggerList(ctx: UserCtx) {
ctx.body = getTriggerDefinitions()
}
export async function getDefinitionList(ctx: UserCtx) {
ctx.body = {
trigger: getTriggerDefinitions(),
action: await getActionDefinitions(),
}
}
/*********************
* *
* API FUNCTIONS *
* *
*********************/
export async function trigger(ctx: UserCtx) {
const db = context.getAppDB()
2023-07-18 11:41:51 +02:00
let automation = await db.get<Automation>(ctx.params.id)
2021-09-07 14:58:53 +02:00
let hasCollectStep = sdk.automations.utils.checkForCollectStep(automation)
if (hasCollectStep && (await features.isSyncAutomationsEnabled())) {
const response: AutomationResults = await triggers.externalTrigger(
automation,
{
fields: ctx.request.body.fields,
2024-02-14 18:15:42 +01:00
timeout:
ctx.request.body.timeout * 1000 ||
env.getDefaults().AUTOMATION_SYNC_TIMEOUT,
},
{ getResponses: true }
)
let collectedValue = response.steps.find(
step => step.stepId === AutomationActionStepId.COLLECT
)
ctx.body = collectedValue?.outputs
} else {
2023-05-19 16:14:01 +02:00
if (ctx.appId && !dbCore.isProdAppID(ctx.appId)) {
ctx.throw(400, "Only apps in production support this endpoint")
}
await triggers.externalTrigger(automation, {
...ctx.request.body,
appId: ctx.appId,
})
ctx.body = {
message: `Automation ${automation._id} has been triggered.`,
automation,
}
}
}
function prepareTestInput(input: any) {
// prepare the test parameters
if (input.id && input.row) {
input.row._id = input.id
}
if (input.revision && input.row) {
input.row._rev = input.revision
}
return input
}
export async function test(ctx: UserCtx) {
const db = context.getAppDB()
2023-07-18 11:41:51 +02:00
let automation = await db.get<Automation>(ctx.params.id)
await setTestFlag(automation._id!)
const testInput = prepareTestInput(ctx.request.body)
const response = await triggers.externalTrigger(
automation,
{
...testInput,
appId: ctx.appId,
},
{ getResponses: true }
)
// save a test history run
await updateTestHistory(ctx.appId, automation, {
...ctx.request.body,
2021-09-14 17:54:42 +02:00
occurredAt: new Date().getTime(),
})
2023-07-18 11:41:51 +02:00
await clearTestFlag(automation._id!)
ctx.body = response
2022-05-31 22:04:41 +02:00
await events.automation.tested(automation)
2021-09-07 14:59:58 +02:00
}