diff --git a/packages/bbui/src/Actions/click_outside.js b/packages/bbui/src/Actions/click_outside.js
index 124f43ff04..526659cb7a 100644
--- a/packages/bbui/src/Actions/click_outside.js
+++ b/packages/bbui/src/Actions/click_outside.js
@@ -71,8 +71,8 @@ const handleMouseDown = e => {
// Clear any previous listeners in case of multiple down events, and register
// a single mouse up listener
- document.removeEventListener("mouseup", handleMouseUp)
- document.addEventListener("mouseup", handleMouseUp, true)
+ document.removeEventListener("click", handleMouseUp)
+ document.addEventListener("click", handleMouseUp, true)
}
// Global singleton listeners for our events
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 {