Merge pull request #12699 from Budibase/feat/automation-features
Triggering another automation from within an automation
This commit is contained in:
commit
8d55dc0691
|
@ -19,10 +19,15 @@
|
|||
export let lastStep
|
||||
|
||||
let syncAutomationsEnabled = $licensing.syncAutomationsEnabled
|
||||
let triggerAutomationRunEnabled = $licensing.triggerAutomationRunEnabled
|
||||
let collectBlockAllowedSteps = [TriggerStepID.APP, TriggerStepID.WEBHOOK]
|
||||
let selectedAction
|
||||
let actionVal
|
||||
let actions = Object.entries($automationStore.blockDefinitions.ACTION)
|
||||
let lockedFeatures = [
|
||||
ActionStepID.COLLECT,
|
||||
ActionStepID.TRIGGER_AUTOMATION_RUN,
|
||||
]
|
||||
|
||||
$: collectBlockExists = checkForCollectStep($selectedAutomation)
|
||||
|
||||
|
@ -36,6 +41,10 @@
|
|||
disabled: !lastStep || !syncAutomationsEnabled || collectBlockExists,
|
||||
message: collectDisabledMessage(),
|
||||
},
|
||||
TRIGGER_AUTOMATION_RUN: {
|
||||
disabled: !triggerAutomationRunEnabled,
|
||||
message: collectDisabledMessage(),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -149,7 +158,7 @@
|
|||
<div class="item-body">
|
||||
<Icon name={action.icon} />
|
||||
<Body size="XS">{action.name}</Body>
|
||||
{#if isDisabled && !syncAutomationsEnabled && action.stepId === ActionStepID.COLLECT}
|
||||
{#if isDisabled && !syncAutomationsEnabled && !triggerAutomationRunEnabled && lockedFeatures.includes(action.stepId)}
|
||||
<div class="tag-color">
|
||||
<Tags>
|
||||
<Tag icon="LockClosed">Premium</Tag>
|
||||
|
|
|
@ -28,6 +28,7 @@
|
|||
import CodeEditorModal from "./CodeEditorModal.svelte"
|
||||
import QuerySelector from "./QuerySelector.svelte"
|
||||
import QueryParamSelector from "./QueryParamSelector.svelte"
|
||||
import AutomationSelector from "./AutomationSelector.svelte"
|
||||
import CronBuilder from "./CronBuilder.svelte"
|
||||
import Editor from "components/integration/QueryEditor.svelte"
|
||||
import ModalBindableInput from "components/common/bindings/ModalBindableInput.svelte"
|
||||
|
@ -286,7 +287,8 @@
|
|||
value.customType !== "code" &&
|
||||
value.customType !== "queryParams" &&
|
||||
value.customType !== "cron" &&
|
||||
value.customType !== "triggerSchema"
|
||||
value.customType !== "triggerSchema" &&
|
||||
value.customType !== "automationFields"
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -421,6 +423,12 @@
|
|||
on:change={e => onChange(e, key)}
|
||||
value={inputData[key]}
|
||||
/>
|
||||
{:else if value.customType === "automationFields"}
|
||||
<AutomationSelector
|
||||
on:change={e => onChange(e, key)}
|
||||
value={inputData[key]}
|
||||
{bindings}
|
||||
/>
|
||||
{:else if value.customType === "queryParams"}
|
||||
<QueryParamSelector
|
||||
on:change={e => onChange(e, key)}
|
||||
|
|
|
@ -0,0 +1,87 @@
|
|||
<script>
|
||||
import { Select, Label } from "@budibase/bbui"
|
||||
import { createEventDispatcher } from "svelte"
|
||||
import { automationStore, selectedAutomation } from "builderStore"
|
||||
import { TriggerStepID } from "constants/backend/automations"
|
||||
import DrawerBindableInput from "../../common/bindings/DrawerBindableInput.svelte"
|
||||
import AutomationBindingPanel from "../../common/bindings/ServerBindingPanel.svelte"
|
||||
|
||||
const dispatch = createEventDispatcher()
|
||||
|
||||
export let value
|
||||
export let bindings = []
|
||||
const onChangeAutomation = e => {
|
||||
value.automationId = e.detail
|
||||
dispatch("change", value)
|
||||
}
|
||||
|
||||
const onChange = (e, field) => {
|
||||
value[field] = e.detail
|
||||
dispatch("change", value)
|
||||
}
|
||||
$: if (value?.automationId == null) value = { automationId: "" }
|
||||
|
||||
$: automationFields =
|
||||
$automationStore.automations.find(
|
||||
automation => automation._id === value?.automationId
|
||||
)?.definition?.trigger?.inputs?.fields || []
|
||||
|
||||
$: filteredAutomations = $automationStore.automations.filter(
|
||||
automation =>
|
||||
automation.definition.trigger.stepId === TriggerStepID.APP &&
|
||||
automation._id !== $selectedAutomation._id
|
||||
)
|
||||
</script>
|
||||
|
||||
<div class="schema-field">
|
||||
<Label>Automation</Label>
|
||||
<div class="field-width">
|
||||
<Select
|
||||
on:change={onChangeAutomation}
|
||||
value={value.automationId}
|
||||
options={filteredAutomations}
|
||||
getOptionValue={automation => automation._id}
|
||||
getOptionLabel={automation => automation.name}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
{#if Object.keys(automationFields)}
|
||||
{#each Object.keys(automationFields) as field}
|
||||
<div class="schema-field">
|
||||
<Label>{field}</Label>
|
||||
<div class="field-width">
|
||||
<DrawerBindableInput
|
||||
panel={AutomationBindingPanel}
|
||||
extraThin
|
||||
value={value[field]}
|
||||
on:change={e => onChange(e, field)}
|
||||
type="string"
|
||||
{bindings}
|
||||
fillWidth={true}
|
||||
updateOnChange={false}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
{/each}
|
||||
{/if}
|
||||
|
||||
<style>
|
||||
.field-width {
|
||||
width: 320px;
|
||||
}
|
||||
|
||||
.schema-field {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
flex: 1;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.schema-field :global(label) {
|
||||
text-transform: capitalize;
|
||||
}
|
||||
</style>
|
|
@ -21,6 +21,7 @@ export const ActionStepID = {
|
|||
QUERY_ROWS: "QUERY_ROWS",
|
||||
LOOP: "LOOP",
|
||||
COLLECT: "COLLECT",
|
||||
TRIGGER_AUTOMATION_RUN: "TRIGGER_AUTOMATION_RUN",
|
||||
// these used to be lowercase step IDs, maintain for backwards compat
|
||||
discord: "discord",
|
||||
slack: "slack",
|
||||
|
|
|
@ -125,6 +125,10 @@ export const createLicensingStore = () => {
|
|||
const syncAutomationsEnabled = license.features.includes(
|
||||
Constants.Features.SYNC_AUTOMATIONS
|
||||
)
|
||||
const triggerAutomationRunEnabled = license.features.includes(
|
||||
Constants.Features.TRIGGER_AUTOMATION_RUN
|
||||
)
|
||||
|
||||
const perAppBuildersEnabled = license.features.includes(
|
||||
Constants.Features.APP_BUILDERS
|
||||
)
|
||||
|
@ -147,6 +151,7 @@ export const createLicensingStore = () => {
|
|||
auditLogsEnabled,
|
||||
enforceableSSO,
|
||||
syncAutomationsEnabled,
|
||||
triggerAutomationRunEnabled,
|
||||
isViewPermissionsEnabled,
|
||||
perAppBuildersEnabled,
|
||||
}
|
||||
|
|
|
@ -1 +1 @@
|
|||
Subproject commit 8c466d6ef2a0c09b843ef63276793ab5af2e96f7
|
||||
Subproject commit 6f5e2d9a6ee671c5d987462ceb4156e2c3276a2f
|
|
@ -0,0 +1,12 @@
|
|||
const actual = jest.requireActual("@budibase/pro")
|
||||
const pro = {
|
||||
...actual,
|
||||
features: {
|
||||
...actual.features,
|
||||
isTriggerAutomationRunEnabled: () => {
|
||||
return true
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
export = pro
|
|
@ -15,6 +15,7 @@ import * as delay from "./steps/delay"
|
|||
import * as queryRow from "./steps/queryRows"
|
||||
import * as loop from "./steps/loop"
|
||||
import * as collect from "./steps/collect"
|
||||
import * as triggerAutomationRun from "./steps/triggerAutomationRun"
|
||||
import env from "../environment"
|
||||
import {
|
||||
AutomationStepSchema,
|
||||
|
@ -41,6 +42,7 @@ const ACTION_IMPLS: Record<
|
|||
FILTER: filter.run,
|
||||
QUERY_ROWS: queryRow.run,
|
||||
COLLECT: collect.run,
|
||||
TRIGGER_AUTOMATION_RUN: triggerAutomationRun.run,
|
||||
// these used to be lowercase step IDs, maintain for backwards compat
|
||||
discord: discord.run,
|
||||
slack: slack.run,
|
||||
|
@ -62,6 +64,7 @@ export const BUILTIN_ACTION_DEFINITIONS: Record<string, AutomationStepSchema> =
|
|||
QUERY_ROWS: queryRow.definition,
|
||||
LOOP: loop.definition,
|
||||
COLLECT: collect.definition,
|
||||
TRIGGER_AUTOMATION_RUN: triggerAutomationRun.definition,
|
||||
// these used to be lowercase step IDs, maintain for backwards compat
|
||||
discord: discord.definition,
|
||||
slack: slack.definition,
|
||||
|
|
|
@ -0,0 +1,90 @@
|
|||
import {
|
||||
AutomationActionStepId,
|
||||
AutomationStepSchema,
|
||||
AutomationStepInput,
|
||||
AutomationStepType,
|
||||
AutomationIOType,
|
||||
AutomationResults,
|
||||
Automation,
|
||||
AutomationCustomIOType,
|
||||
} from "@budibase/types"
|
||||
import * as triggers from "../triggers"
|
||||
import { db as dbCore, context } from "@budibase/backend-core"
|
||||
import { features } from "@budibase/pro"
|
||||
|
||||
export const definition: AutomationStepSchema = {
|
||||
name: "Trigger an automation",
|
||||
tagline: "Triggers an automation synchronously",
|
||||
icon: "Sync",
|
||||
description: "Triggers an automation synchronously",
|
||||
type: AutomationStepType.ACTION,
|
||||
internal: true,
|
||||
features: {},
|
||||
stepId: AutomationActionStepId.TRIGGER_AUTOMATION_RUN,
|
||||
inputs: {},
|
||||
schema: {
|
||||
inputs: {
|
||||
properties: {
|
||||
automation: {
|
||||
type: AutomationIOType.OBJECT,
|
||||
properties: {
|
||||
automationId: {
|
||||
type: AutomationIOType.STRING,
|
||||
customType: AutomationCustomIOType.AUTOMATION,
|
||||
},
|
||||
},
|
||||
customType: AutomationCustomIOType.AUTOMATION_FIELDS,
|
||||
title: "automatioFields",
|
||||
required: ["automationId"],
|
||||
},
|
||||
timeout: {
|
||||
type: AutomationIOType.NUMBER,
|
||||
title: "Timeout (ms)",
|
||||
},
|
||||
},
|
||||
required: ["automationId"],
|
||||
},
|
||||
outputs: {
|
||||
properties: {
|
||||
success: {
|
||||
type: AutomationIOType.BOOLEAN,
|
||||
description: "Whether the automation was successful",
|
||||
},
|
||||
value: {
|
||||
type: AutomationIOType.OBJECT,
|
||||
description: "Automation Result",
|
||||
},
|
||||
},
|
||||
required: ["success", "value"],
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
export async function run({ inputs }: AutomationStepInput) {
|
||||
const { automationId, ...fieldParams } = inputs.automation
|
||||
|
||||
if (await features.isTriggerAutomationRunEnabled()) {
|
||||
if (!inputs.automation.automationId) {
|
||||
return {
|
||||
success: false,
|
||||
}
|
||||
} else {
|
||||
const db = context.getAppDB()
|
||||
let automation = await db.get<Automation>(inputs.automation.automationId)
|
||||
|
||||
const response: AutomationResults = await triggers.externalTrigger(
|
||||
automation,
|
||||
{
|
||||
fields: { ...fieldParams },
|
||||
timeout: inputs.timeout * 1000 || 120000,
|
||||
},
|
||||
{ getResponses: true }
|
||||
)
|
||||
|
||||
return {
|
||||
success: true,
|
||||
value: response.steps,
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,43 @@
|
|||
jest.spyOn(global.console, "error")
|
||||
|
||||
import * as setup from "./utilities"
|
||||
import * as automation from "../index"
|
||||
import { serverLogAutomation } from "../../tests/utilities/structures"
|
||||
|
||||
describe("Test triggering an automation from another automation", () => {
|
||||
let config = setup.getConfig()
|
||||
|
||||
beforeAll(async () => {
|
||||
await automation.init()
|
||||
await config.init()
|
||||
})
|
||||
|
||||
afterAll(async () => {
|
||||
await automation.shutdown()
|
||||
setup.afterAll()
|
||||
})
|
||||
|
||||
it("should trigger an other server log automation", async () => {
|
||||
let automation = serverLogAutomation()
|
||||
let newAutomation = await config.createAutomation(automation)
|
||||
|
||||
const inputs: any = {
|
||||
automation: { automationId: newAutomation._id, timeout: 12000 },
|
||||
}
|
||||
const res = await setup.runStep(
|
||||
setup.actions.TRIGGER_AUTOMATION_RUN.stepId,
|
||||
inputs
|
||||
)
|
||||
// Check if the SERVER_LOG step was successful
|
||||
expect(res.value[1].outputs.success).toBe(true)
|
||||
})
|
||||
|
||||
it("should fail gracefully if the automation id is incorrect", async () => {
|
||||
const inputs: any = { automation: { automationId: null, timeout: 12000 } }
|
||||
const res = await setup.runStep(
|
||||
setup.actions.TRIGGER_AUTOMATION_RUN.stepId,
|
||||
inputs
|
||||
)
|
||||
expect(res.success).toBe(false)
|
||||
})
|
||||
})
|
|
@ -21,6 +21,7 @@ import {
|
|||
Table,
|
||||
INTERNAL_TABLE_SOURCE_ID,
|
||||
TableSourceType,
|
||||
AutomationIOType,
|
||||
} from "@budibase/types"
|
||||
|
||||
const { BUILTIN_ROLE_IDS } = roles
|
||||
|
@ -153,6 +154,56 @@ export function basicAutomation(appId?: string): Automation {
|
|||
}
|
||||
}
|
||||
|
||||
export function serverLogAutomation(appId?: string): Automation {
|
||||
return {
|
||||
name: "My Automation",
|
||||
screenId: "kasdkfldsafkl",
|
||||
live: true,
|
||||
uiTree: {},
|
||||
definition: {
|
||||
trigger: {
|
||||
stepId: AutomationTriggerStepId.APP,
|
||||
name: "test",
|
||||
tagline: "test",
|
||||
icon: "test",
|
||||
description: "test",
|
||||
type: AutomationStepType.TRIGGER,
|
||||
id: "test",
|
||||
inputs: {},
|
||||
schema: {
|
||||
inputs: {
|
||||
properties: {},
|
||||
},
|
||||
outputs: {
|
||||
properties: {},
|
||||
},
|
||||
},
|
||||
},
|
||||
steps: [
|
||||
{
|
||||
stepId: AutomationActionStepId.SERVER_LOG,
|
||||
name: "Backend log",
|
||||
tagline: "Console log a value in the backend",
|
||||
icon: "Monitoring",
|
||||
description: "Logs the given text to the server (using console.log)",
|
||||
internal: true,
|
||||
features: {
|
||||
LOOPING: true,
|
||||
},
|
||||
inputs: {
|
||||
text: "log statement",
|
||||
},
|
||||
schema: BUILTIN_ACTION_DEFINITIONS.SERVER_LOG.schema,
|
||||
id: "y8lkZbeSe",
|
||||
type: AutomationStepType.ACTION,
|
||||
},
|
||||
],
|
||||
},
|
||||
type: "automation",
|
||||
appId: appId!,
|
||||
}
|
||||
}
|
||||
|
||||
export function loopAutomation(tableId: string, loopOpts?: any): Automation {
|
||||
if (!loopOpts) {
|
||||
loopOpts = {
|
||||
|
|
|
@ -28,6 +28,8 @@ export enum AutomationCustomIOType {
|
|||
TRIGGER_SCHEMA = "triggerSchema",
|
||||
CRON = "cron",
|
||||
WEBHOOK_URL = "webhookUrl",
|
||||
AUTOMATION = "automation",
|
||||
AUTOMATION_FIELDS = "automationFields",
|
||||
}
|
||||
|
||||
export enum AutomationTriggerStepId {
|
||||
|
@ -61,6 +63,7 @@ export enum AutomationActionStepId {
|
|||
LOOP = "LOOP",
|
||||
COLLECT = "COLLECT",
|
||||
OPENAI = "OPENAI",
|
||||
TRIGGER_AUTOMATION_RUN = "TRIGGER_AUTOMATION_RUN",
|
||||
// these used to be lowercase step IDs, maintain for backwards compat
|
||||
discord = "discord",
|
||||
slack = "slack",
|
||||
|
|
|
@ -9,6 +9,7 @@ export enum Feature {
|
|||
BRANDING = "branding",
|
||||
SCIM = "scim",
|
||||
SYNC_AUTOMATIONS = "syncAutomations",
|
||||
TRIGGER_AUTOMATION_RUN = "triggerAutomationRun",
|
||||
APP_BUILDERS = "appBuilders",
|
||||
OFFLINE = "offline",
|
||||
EXPANDED_PUBLIC_API = "expandedPublicApi",
|
||||
|
|
Loading…
Reference in New Issue