From 3c74d29cf6ff371cc206dfc56f55244ae430a7d2 Mon Sep 17 00:00:00 2001
From: Conor Webb <126772285+ConorWebb96@users.noreply.github.com>
Date: Mon, 20 May 2024 15:13:08 +0100
Subject: [PATCH] Added the ability to disable automations (#13667)
* Added disabling functionality for automations
* Removed external trigger automations that are disabled from selectable bindings
* Added new popover option for disabling automations
* Added toggle UI Inside automation screen
* Added subtle styling to automation list for disabled functionality.
* Fixed linting error
* Removed duplicate bbui import
* Fixed store function spacing
* Fixed linting issues.
* Added the requested changes to how disable is handled.
* Fixed linting issues.
* Minor UI tweaks based on feedback.
* Added logic to prevent crons type automations from running when disabled.
* Removing webhook disable, causes trigger url to be re-generated.
* Add unit test to ensure disabled automations are filtered out of the active queue
* Fixed lint issues
* Reverted disabled unit test
* Added error throw for disabled automations
* Add test for when a disabled automation gets triggered
* Added try, catch for trigger function - error handling
* Fixed linting issues
---
.../FlowChart/FlowChart.svelte | 12 ++++++-
.../AutomationPanel/AutomationPanel.svelte | 1 +
.../EditAutomationPopover.svelte | 9 +++++
.../src/components/common/NavItem.svelte | 5 +++
.../actions/TriggerAutomation.svelte | 4 ++-
.../builder/src/stores/builder/automations.js | 23 +++++++++++++
.../server/src/api/controllers/automation.ts | 34 ++++++++++++-------
.../src/api/routes/tests/automation.spec.ts | 17 ++++++++++
packages/server/src/automations/triggers.ts | 7 ++--
packages/server/src/automations/utils.ts | 1 +
.../types/src/documents/app/automation.ts | 1 +
11 files changed, 97 insertions(+), 17 deletions(-)
diff --git a/packages/builder/src/components/automation/AutomationBuilder/FlowChart/FlowChart.svelte b/packages/builder/src/components/automation/AutomationBuilder/FlowChart/FlowChart.svelte
index 22303dec1f..d68e57ca36 100644
--- a/packages/builder/src/components/automation/AutomationBuilder/FlowChart/FlowChart.svelte
+++ b/packages/builder/src/components/automation/AutomationBuilder/FlowChart/FlowChart.svelte
@@ -9,7 +9,7 @@
import TestDataModal from "./TestDataModal.svelte"
import { flip } from "svelte/animate"
import { fly } from "svelte/transition"
- import { Icon, notifications, Modal } from "@budibase/bbui"
+ import { Icon, notifications, Modal, Toggle } from "@budibase/bbui"
import { ActionStepID } from "constants/backend/automations"
import UndoRedoControl from "components/common/UndoRedoControl.svelte"
@@ -73,6 +73,16 @@
Test details
+
diff --git a/packages/builder/src/components/automation/AutomationPanel/AutomationPanel.svelte b/packages/builder/src/components/automation/AutomationPanel/AutomationPanel.svelte
index ac1c4f91cb..7898e13ec8 100644
--- a/packages/builder/src/components/automation/AutomationPanel/AutomationPanel.svelte
+++ b/packages/builder/src/components/automation/AutomationPanel/AutomationPanel.svelte
@@ -61,6 +61,7 @@
selected={automation._id === selectedAutomationId}
on:click={() => selectAutomation(automation._id)}
selectedBy={$userSelectedResourceMap[automation._id]}
+ disabled={automation.disabled}
>
diff --git a/packages/builder/src/components/automation/AutomationPanel/EditAutomationPopover.svelte b/packages/builder/src/components/automation/AutomationPanel/EditAutomationPopover.svelte
index 1bc4b0f18e..9465374ae2 100644
--- a/packages/builder/src/components/automation/AutomationPanel/EditAutomationPopover.svelte
+++ b/packages/builder/src/components/automation/AutomationPanel/EditAutomationPopover.svelte
@@ -39,6 +39,15 @@
>Duplicate
+
diff --git a/packages/builder/src/components/common/NavItem.svelte b/packages/builder/src/components/common/NavItem.svelte
index 66b21e95a1..5cc6db65a0 100644
--- a/packages/builder/src/components/common/NavItem.svelte
+++ b/packages/builder/src/components/common/NavItem.svelte
@@ -25,6 +25,7 @@
export let selectedBy = null
export let compact = false
export let hovering = false
+ export let disabled = false
const scrollApi = getContext("scroll")
const dispatch = createEventDispatcher()
@@ -74,6 +75,7 @@
class:scrollable
class:highlighted
class:selectedBy
+ class:disabled
on:dragend
on:dragstart
on:dragover
@@ -165,6 +167,9 @@
--avatars-background: var(--spectrum-global-color-gray-300);
color: var(--ink);
}
+ .nav-item.disabled span {
+ color: var(--spectrum-global-color-gray-700);
+ }
.nav-item:hover,
.hovering {
background-color: var(--spectrum-global-color-gray-200);
diff --git a/packages/builder/src/components/design/settings/controls/ButtonActionEditor/actions/TriggerAutomation.svelte b/packages/builder/src/components/design/settings/controls/ButtonActionEditor/actions/TriggerAutomation.svelte
index 0a52a693c3..5cd5658063 100644
--- a/packages/builder/src/components/design/settings/controls/ButtonActionEditor/actions/TriggerAutomation.svelte
+++ b/packages/builder/src/components/design/settings/controls/ButtonActionEditor/actions/TriggerAutomation.svelte
@@ -24,7 +24,9 @@
parameters
}
$: automations = $automationStore.automations
- .filter(a => a.definition.trigger?.stepId === TriggerStepID.APP)
+ .filter(
+ a => a.definition.trigger?.stepId === TriggerStepID.APP && !a.disabled
+ )
.map(automation => {
const schema = Object.entries(
automation.definition.trigger.inputs.fields || {}
diff --git a/packages/builder/src/stores/builder/automations.js b/packages/builder/src/stores/builder/automations.js
index cbe48cef33..ff53dda86b 100644
--- a/packages/builder/src/stores/builder/automations.js
+++ b/packages/builder/src/stores/builder/automations.js
@@ -82,6 +82,7 @@ const automationActions = store => ({
steps: [],
trigger,
},
+ disabled: false,
}
const response = await store.actions.save(automation)
await store.actions.fetch()
@@ -134,6 +135,28 @@ const automationActions = store => ({
})
await store.actions.fetch()
},
+ toggleDisabled: async automationId => {
+ let automation
+ try {
+ automation = store.actions.getDefinition(automationId)
+ if (!automation) {
+ return
+ }
+ automation.disabled = !automation.disabled
+ await store.actions.save(automation)
+ notifications.success(
+ `Automation ${
+ automation.disabled ? "enabled" : "disabled"
+ } successfully`
+ )
+ } catch (error) {
+ notifications.error(
+ `Error ${
+ automation && automation.disabled ? "enabling" : "disabling"
+ } automation`
+ )
+ }
+ },
updateBlockInputs: async (block, data) => {
// Create new modified block
let newBlock = {
diff --git a/packages/server/src/api/controllers/automation.ts b/packages/server/src/api/controllers/automation.ts
index 5b199341b1..a49fe834d3 100644
--- a/packages/server/src/api/controllers/automation.ts
+++ b/packages/server/src/api/controllers/automation.ts
@@ -274,20 +274,28 @@ export async function trigger(ctx: UserCtx) {
let hasCollectStep = sdk.automations.utils.checkForCollectStep(automation)
if (hasCollectStep && (await features.isSyncAutomationsEnabled())) {
- const response: AutomationResults = await triggers.externalTrigger(
- automation,
- {
- fields: ctx.request.body.fields,
- timeout:
- ctx.request.body.timeout * 1000 || env.AUTOMATION_THREAD_TIMEOUT,
- },
- { getResponses: true }
- )
+ try {
+ const response: AutomationResults = await triggers.externalTrigger(
+ automation,
+ {
+ fields: ctx.request.body.fields,
+ timeout:
+ ctx.request.body.timeout * 1000 || env.AUTOMATION_THREAD_TIMEOUT,
+ },
+ { getResponses: true }
+ )
- let collectedValue = response.steps.find(
- step => step.stepId === AutomationActionStepId.COLLECT
- )
- ctx.body = collectedValue?.outputs
+ let collectedValue = response.steps.find(
+ step => step.stepId === AutomationActionStepId.COLLECT
+ )
+ ctx.body = collectedValue?.outputs
+ } catch (err: any) {
+ if (err.message) {
+ ctx.throw(400, err.message)
+ } else {
+ throw err
+ }
+ }
} else {
if (ctx.appId && !dbCore.isProdAppID(ctx.appId)) {
ctx.throw(400, "Only apps in production support this endpoint")
diff --git a/packages/server/src/api/routes/tests/automation.spec.ts b/packages/server/src/api/routes/tests/automation.spec.ts
index 7885e97fbf..711cfb8d4f 100644
--- a/packages/server/src/api/routes/tests/automation.spec.ts
+++ b/packages/server/src/api/routes/tests/automation.spec.ts
@@ -206,6 +206,23 @@ describe("/automations", () => {
expect(res.body.value).toEqual([1, 2, 3])
})
+ it("should throw an error when attempting to trigger a disabled automation", async () => {
+ mocks.licenses.useSyncAutomations()
+ let automation = collectAutomation()
+ automation = await config.createAutomation({
+ ...automation,
+ disabled: true,
+ })
+
+ const res = await request
+ .post(`/api/automations/${automation._id}/trigger`)
+ .set(config.defaultHeaders())
+ .expect("Content-Type", /json/)
+ .expect(400)
+
+ expect(res.body.message).toEqual("Automation is disabled")
+ })
+
it("triggers an asynchronous automation", async () => {
let automation = newAutomation()
automation = await config.createAutomation(automation)
diff --git a/packages/server/src/automations/triggers.ts b/packages/server/src/automations/triggers.ts
index 08e3199a11..223b8d2eb6 100644
--- a/packages/server/src/automations/triggers.ts
+++ b/packages/server/src/automations/triggers.ts
@@ -36,10 +36,10 @@ async function queueRelevantRowAutomations(
await context.doInAppContext(event.appId, async () => {
let automations = await getAllAutomations()
- // filter down to the correct event type
+ // filter down to the correct event type and enabled automations
automations = automations.filter(automation => {
const trigger = automation.definition.trigger
- return trigger && trigger.event === eventType
+ return trigger && trigger.event === eventType && !automation.disabled
})
for (let automation of automations) {
@@ -94,6 +94,9 @@ export async function externalTrigger(
params: { fields: Record
; timeout?: number },
{ getResponses }: { getResponses?: boolean } = {}
): Promise {
+ if (automation.disabled) {
+ throw new Error("Automation is disabled")
+ }
if (
automation.definition != null &&
automation.definition.trigger != null &&
diff --git a/packages/server/src/automations/utils.ts b/packages/server/src/automations/utils.ts
index 360802df6a..4d7e169f52 100644
--- a/packages/server/src/automations/utils.ts
+++ b/packages/server/src/automations/utils.ts
@@ -196,6 +196,7 @@ export async function enableCronTrigger(appId: any, automation: Automation) {
if (
isCronTrigger(automation) &&
!isRebootTrigger(automation) &&
+ !automation.disabled &&
trigger?.inputs.cron
) {
const cronExp = trigger.inputs.cron
diff --git a/packages/types/src/documents/app/automation.ts b/packages/types/src/documents/app/automation.ts
index 481a051e1c..6d1753dc28 100644
--- a/packages/types/src/documents/app/automation.ts
+++ b/packages/types/src/documents/app/automation.ts
@@ -125,6 +125,7 @@ export interface Automation extends Document {
name: string
internal?: boolean
type?: string
+ disabled?: boolean
}
interface BaseIOStructure {