Merge branch 'master' into prefill-cards

This commit is contained in:
Andrew Kingston 2024-05-21 09:10:23 +01:00 committed by GitHub
commit 0c63946df7
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
11 changed files with 97 additions and 17 deletions

View File

@ -9,7 +9,7 @@
import TestDataModal from "./TestDataModal.svelte" import TestDataModal from "./TestDataModal.svelte"
import { flip } from "svelte/animate" import { flip } from "svelte/animate"
import { fly } from "svelte/transition" 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 { ActionStepID } from "constants/backend/automations"
import UndoRedoControl from "components/common/UndoRedoControl.svelte" import UndoRedoControl from "components/common/UndoRedoControl.svelte"
@ -73,6 +73,16 @@
Test details Test details
</div> </div>
</div> </div>
<div class="setting-spacing">
<Toggle
text={automation.disabled ? "Paused" : "Activated"}
on:change={automationStore.actions.toggleDisabled(
automation._id,
automation.disabled
)}
value={!automation.disabled}
/>
</div>
</div> </div>
</div> </div>
<div class="canvas" on:scroll={handleScroll}> <div class="canvas" on:scroll={handleScroll}>

View File

@ -61,6 +61,7 @@
selected={automation._id === selectedAutomationId} selected={automation._id === selectedAutomationId}
on:click={() => selectAutomation(automation._id)} on:click={() => selectAutomation(automation._id)}
selectedBy={$userSelectedResourceMap[automation._id]} selectedBy={$userSelectedResourceMap[automation._id]}
disabled={automation.disabled}
> >
<EditAutomationPopover {automation} /> <EditAutomationPopover {automation} />
</NavItem> </NavItem>

View File

@ -39,6 +39,15 @@
>Duplicate</MenuItem >Duplicate</MenuItem
> >
<MenuItem icon="Edit" on:click={updateAutomationDialog.show}>Edit</MenuItem> <MenuItem icon="Edit" on:click={updateAutomationDialog.show}>Edit</MenuItem>
<MenuItem
icon={automation.disabled ? "CheckmarkCircle" : "Cancel"}
on:click={automationStore.actions.toggleDisabled(
automation._id,
automation.disabled
)}
>
{automation.disabled ? "Activate" : "Pause"}
</MenuItem>
<MenuItem icon="Delete" on:click={confirmDeleteDialog.show}>Delete</MenuItem> <MenuItem icon="Delete" on:click={confirmDeleteDialog.show}>Delete</MenuItem>
</ActionMenu> </ActionMenu>

View File

@ -25,6 +25,7 @@
export let selectedBy = null export let selectedBy = null
export let compact = false export let compact = false
export let hovering = false export let hovering = false
export let disabled = false
const scrollApi = getContext("scroll") const scrollApi = getContext("scroll")
const dispatch = createEventDispatcher() const dispatch = createEventDispatcher()
@ -74,6 +75,7 @@
class:scrollable class:scrollable
class:highlighted class:highlighted
class:selectedBy class:selectedBy
class:disabled
on:dragend on:dragend
on:dragstart on:dragstart
on:dragover on:dragover
@ -165,6 +167,9 @@
--avatars-background: var(--spectrum-global-color-gray-300); --avatars-background: var(--spectrum-global-color-gray-300);
color: var(--ink); color: var(--ink);
} }
.nav-item.disabled span {
color: var(--spectrum-global-color-gray-700);
}
.nav-item:hover, .nav-item:hover,
.hovering { .hovering {
background-color: var(--spectrum-global-color-gray-200); background-color: var(--spectrum-global-color-gray-200);

View File

@ -24,7 +24,9 @@
parameters parameters
} }
$: automations = $automationStore.automations $: automations = $automationStore.automations
.filter(a => a.definition.trigger?.stepId === TriggerStepID.APP) .filter(
a => a.definition.trigger?.stepId === TriggerStepID.APP && !a.disabled
)
.map(automation => { .map(automation => {
const schema = Object.entries( const schema = Object.entries(
automation.definition.trigger.inputs.fields || {} automation.definition.trigger.inputs.fields || {}

View File

@ -82,6 +82,7 @@ const automationActions = store => ({
steps: [], steps: [],
trigger, trigger,
}, },
disabled: false,
} }
const response = await store.actions.save(automation) const response = await store.actions.save(automation)
await store.actions.fetch() await store.actions.fetch()
@ -134,6 +135,28 @@ const automationActions = store => ({
}) })
await store.actions.fetch() 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) => { updateBlockInputs: async (block, data) => {
// Create new modified block // Create new modified block
let newBlock = { let newBlock = {

View File

@ -274,20 +274,28 @@ export async function trigger(ctx: UserCtx) {
let hasCollectStep = sdk.automations.utils.checkForCollectStep(automation) let hasCollectStep = sdk.automations.utils.checkForCollectStep(automation)
if (hasCollectStep && (await features.isSyncAutomationsEnabled())) { if (hasCollectStep && (await features.isSyncAutomationsEnabled())) {
const response: AutomationResults = await triggers.externalTrigger( try {
automation, const response: AutomationResults = await triggers.externalTrigger(
{ automation,
fields: ctx.request.body.fields, {
timeout: fields: ctx.request.body.fields,
ctx.request.body.timeout * 1000 || env.AUTOMATION_THREAD_TIMEOUT, timeout:
}, ctx.request.body.timeout * 1000 || env.AUTOMATION_THREAD_TIMEOUT,
{ getResponses: true } },
) { getResponses: true }
)
let collectedValue = response.steps.find( let collectedValue = response.steps.find(
step => step.stepId === AutomationActionStepId.COLLECT step => step.stepId === AutomationActionStepId.COLLECT
) )
ctx.body = collectedValue?.outputs ctx.body = collectedValue?.outputs
} catch (err: any) {
if (err.message) {
ctx.throw(400, err.message)
} else {
throw err
}
}
} else { } else {
if (ctx.appId && !dbCore.isProdAppID(ctx.appId)) { if (ctx.appId && !dbCore.isProdAppID(ctx.appId)) {
ctx.throw(400, "Only apps in production support this endpoint") ctx.throw(400, "Only apps in production support this endpoint")

View File

@ -206,6 +206,23 @@ describe("/automations", () => {
expect(res.body.value).toEqual([1, 2, 3]) 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 () => { it("triggers an asynchronous automation", async () => {
let automation = newAutomation() let automation = newAutomation()
automation = await config.createAutomation(automation) automation = await config.createAutomation(automation)

View File

@ -36,10 +36,10 @@ async function queueRelevantRowAutomations(
await context.doInAppContext(event.appId, async () => { await context.doInAppContext(event.appId, async () => {
let automations = await getAllAutomations() 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 => { automations = automations.filter(automation => {
const trigger = automation.definition.trigger const trigger = automation.definition.trigger
return trigger && trigger.event === eventType return trigger && trigger.event === eventType && !automation.disabled
}) })
for (let automation of automations) { for (let automation of automations) {
@ -94,6 +94,9 @@ export async function externalTrigger(
params: { fields: Record<string, any>; timeout?: number }, params: { fields: Record<string, any>; timeout?: number },
{ getResponses }: { getResponses?: boolean } = {} { getResponses }: { getResponses?: boolean } = {}
): Promise<any> { ): Promise<any> {
if (automation.disabled) {
throw new Error("Automation is disabled")
}
if ( if (
automation.definition != null && automation.definition != null &&
automation.definition.trigger != null && automation.definition.trigger != null &&

View File

@ -196,6 +196,7 @@ export async function enableCronTrigger(appId: any, automation: Automation) {
if ( if (
isCronTrigger(automation) && isCronTrigger(automation) &&
!isRebootTrigger(automation) && !isRebootTrigger(automation) &&
!automation.disabled &&
trigger?.inputs.cron trigger?.inputs.cron
) { ) {
const cronExp = trigger.inputs.cron const cronExp = trigger.inputs.cron

View File

@ -125,6 +125,7 @@ export interface Automation extends Document {
name: string name: string
internal?: boolean internal?: boolean
type?: string type?: string
disabled?: boolean
} }
interface BaseIOStructure { interface BaseIOStructure {