Merge branch 'master' into prefill-cards
This commit is contained in:
commit
0c63946df7
11 changed files with 97 additions and 17 deletions
|
@ -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}>
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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>
|
||||||
|
|
||||||
|
|
|
@ -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);
|
||||||
|
|
|
@ -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 || {}
|
||||||
|
|
|
@ -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 = {
|
||||||
|
|
|
@ -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")
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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 &&
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
Loading…
Reference in a new issue