diff --git a/packages/builder/src/components/automation/AutomationBuilder/FlowChart/BranchNode.svelte b/packages/builder/src/components/automation/AutomationBuilder/FlowChart/BranchNode.svelte index 779eaf415a..cdf0f82225 100644 --- a/packages/builder/src/components/automation/AutomationBuilder/FlowChart/BranchNode.svelte +++ b/packages/builder/src/components/automation/AutomationBuilder/FlowChart/BranchNode.svelte @@ -18,8 +18,12 @@ import AutomationBindingPanel from "@/components/common/bindings/ServerBindingPanel.svelte" import FlowItemHeader from "./FlowItemHeader.svelte" import FlowItemActions from "./FlowItemActions.svelte" - import { automationStore, selectedAutomation } from "@/stores/builder" - import { QueryUtils, Utils } from "@budibase/frontend-core" + import { + automationStore, + selectedAutomation, + evaluationContext, + } from "@/stores/builder" + import { QueryUtils, Utils, memo } from "@budibase/frontend-core" import { cloneDeep } from "lodash/fp" import { createEventDispatcher, getContext } from "svelte" import DragZone from "./DragZone.svelte" @@ -34,11 +38,14 @@ export let automation const view = getContext("draggableView") + const memoContext = memo({}) let drawer let open = true let confirmDeleteModal + $: memoContext.set($evaluationContext) + $: branch = step.inputs?.branches?.[branchIdx] $: editableConditionUI = branch.conditionUI || {} @@ -100,6 +107,7 @@ allowOnEmpty={false} builderType={"condition"} docsURL={null} + evaluationContext={$memoContext} /> diff --git a/packages/builder/src/components/automation/SetupPanel/AutomationBlockSetup.svelte b/packages/builder/src/components/automation/SetupPanel/AutomationBlockSetup.svelte index 16183ea59a..9470afdce6 100644 --- a/packages/builder/src/components/automation/SetupPanel/AutomationBlockSetup.svelte +++ b/packages/builder/src/components/automation/SetupPanel/AutomationBlockSetup.svelte @@ -21,7 +21,7 @@ } from "@budibase/bbui" import CreateWebhookModal from "@/components/automation/Shared/CreateWebhookModal.svelte" - import { automationStore, tables } from "@/stores/builder" + import { automationStore, tables, evaluationContext } from "@/stores/builder" import { environment } from "@/stores/portal" import WebhookDisplay from "../Shared/WebhookDisplay.svelte" import { @@ -62,6 +62,8 @@ } from "@budibase/types" import PropField from "./PropField.svelte" import { utils } from "@budibase/shared-core" + import { encodeJSBinding } from "@budibase/string-templates" + import CodeEditorField from "@/components/common/bindings/CodeEditorField.svelte" export let automation export let block @@ -74,6 +76,7 @@ // Stop unnecessary rendering const memoBlock = memo(block) + const memoContext = memo({}) const rowTriggers = [ TriggerStepID.ROW_UPDATED, @@ -97,6 +100,7 @@ let stepLayouts = {} $: memoBlock.set(block) + $: memoContext.set($evaluationContext) $: filters = lookForFilters(schemaProperties) $: filterCount = @@ -140,6 +144,7 @@ ? [hbAutocomplete([...bindingsToCompletions(bindings, codeMode)])] : [] + // TODO: check if it inputData != newInputData (memo) const getInputData = (testData, blockInputs) => { // Test data is not cloned for reactivity let newInputData = testData || cloneDeep(blockInputs) @@ -156,6 +161,7 @@ } const setDefaultEnumValues = () => { + // TODO: Update this for memoisation for (const [key, value] of schemaProperties) { if (value.type === "string" && value.enum && inputData[key] == null) { inputData[key] = value.enum[0] @@ -200,7 +206,6 @@ onChange({ ["revision"]: e.detail }) }, updateOnChange: false, - forceModal: true, }, }, ] @@ -228,7 +233,6 @@ onChange({ [rowIdentifier]: e.detail }) }, updateOnChange: false, - forceModal: true, }, }, ] @@ -476,6 +480,10 @@ ...update, }) + if (!updatedAutomation) { + return + } + // Exclude default or invalid data from the test data let updatedFields = {} for (const key of Object.keys(block?.inputs?.fields || {})) { @@ -547,7 +555,7 @@ ...newTestData, body: { ...update, - ...automation.testData?.body, + ...(automation?.testData?.body || {}), }, } } @@ -668,6 +676,7 @@ {...config.props} {bindings} on:change={config.props.onChange} + context={$memoContext} /> {:else} @@ -676,6 +685,7 @@ {...config.props} {bindings} on:change={config.props.onChange} + context={$memoContext} /> {/if} {/each} @@ -800,6 +810,7 @@ : "Add signature"} keyPlaceholder={"URL"} valuePlaceholder={"Filename"} + context={$memoContext} /> {:else if isTestModal} {/if} @@ -853,6 +865,7 @@ panel={AutomationBindingPanel} showFilterEmptyDropdown={!rowTriggers.includes(stepId)} on:change={e => (tempFilters = e.detail)} + evaluationContext={$memoContext} /> @@ -895,7 +908,39 @@ on:change={e => onChange({ [key]: e.detail })} value={inputData[key]} /> - {:else if value.customType === "code"} + {:else if value.customType === "code" && stepId === ActionStepID.EXECUTE_SCRIPT_V2} +
+ onChange({ [key]: e.detail })} + value={inputData[key]} + {bindings} + allowJS={true} + allowHBS={false} + updateOnChange={false} + context={$memoContext} + > +
+ + onChange({ [key]: encodeJSBinding(e.detail) })} + /> +
+
+
+ {:else if value.customType === "code" && stepId === ActionStepID.EXECUTE_SCRIPT} + { // Push any pending changes when the window closes @@ -977,6 +1022,7 @@ ? queryLimit : ""} drawerLeft="260px" + context={$memoContext} /> {/if} @@ -1044,4 +1090,23 @@ flex: 3; margin-top: calc((var(--spacing-xl) * -1) + 1px); } + + .field-wrap :global(.cm-editor), + .field-wrap :global(.cm-scroller) { + border-radius: 4px; + } + .field-wrap { + box-sizing: border-box; + border: 1px solid var(--spectrum-global-color-gray-400); + border-radius: 4px; + } + .field-wrap.code-editor { + height: 180px; + } + .scriptv2-wrapper :global(.icon.slot-icon) { + top: 1px; + border-bottom-left-radius: var(--spectrum-alias-border-radius-regular); + border-right: 0px; + border-bottom: 1px solid var(--spectrum-alias-border-color); + } diff --git a/packages/builder/src/components/automation/SetupPanel/RowSelector.svelte b/packages/builder/src/components/automation/SetupPanel/RowSelector.svelte index cc3315a4ab..d859f86f9c 100644 --- a/packages/builder/src/components/automation/SetupPanel/RowSelector.svelte +++ b/packages/builder/src/components/automation/SetupPanel/RowSelector.svelte @@ -25,6 +25,7 @@ export let meta export let bindings export let isTestModal + export let context = {} const typeToField = Object.values(FIELDS).reduce((acc, field) => { acc[field.type] = field @@ -58,7 +59,7 @@ $: parsedBindings = bindings.map(binding => { let clone = Object.assign({}, binding) - clone.icon = "ShareAndroid" + clone.icon = clone.icon ?? "ShareAndroid" return clone }) @@ -258,6 +259,7 @@ fields: editableFields, }} {onChange} + {context} /> {:else} onChange(change)} /> diff --git a/packages/builder/src/components/automation/SetupPanel/RowSelectorTypes.svelte b/packages/builder/src/components/automation/SetupPanel/RowSelectorTypes.svelte index 86666660af..aefbf02303 100644 --- a/packages/builder/src/components/automation/SetupPanel/RowSelectorTypes.svelte +++ b/packages/builder/src/components/automation/SetupPanel/RowSelectorTypes.svelte @@ -25,12 +25,13 @@ export let meta export let bindings export let isTestModal + export let context $: fieldData = value[field] $: parsedBindings = bindings.map(binding => { let clone = Object.assign({}, binding) - clone.icon = "ShareAndroid" + clone.icon = clone.icon ?? "ShareAndroid" return clone }) @@ -232,6 +233,7 @@ actionButtonDisabled={(schema.type === FieldType.ATTACHMENT_SINGLE || schema.type === FieldType.SIGNATURE_SINGLE) && fieldData} + {context} /> {:else} diff --git a/packages/builder/src/components/automation/SetupPanel/SchemaSetup.svelte b/packages/builder/src/components/automation/SetupPanel/SchemaSetup.svelte index 24b04abf31..d829bbe65a 100644 --- a/packages/builder/src/components/automation/SetupPanel/SchemaSetup.svelte +++ b/packages/builder/src/components/automation/SetupPanel/SchemaSetup.svelte @@ -1,18 +1,11 @@ @@ -69,7 +93,7 @@
- {#each fieldsArray as field} + {#each fieldsArray as field, idx (field.id)}
removeField(field.name)} + on:click={() => { + removeField(idx) + }} />
{/each} @@ -115,4 +141,12 @@ align-items: center; gap: var(--spacing-m); } + + .remove-field { + cursor: pointer; + } + + .remove-field:hover { + color: var(--spectrum-global-color-gray-900); + } diff --git a/packages/builder/src/components/common/CodeEditor/CodeEditor.svelte b/packages/builder/src/components/common/CodeEditor/CodeEditor.svelte index bc88f0f981..46907cd71c 100644 --- a/packages/builder/src/components/common/CodeEditor/CodeEditor.svelte +++ b/packages/builder/src/components/common/CodeEditor/CodeEditor.svelte @@ -1,3 +1,10 @@ + + + +
+
+ {#key jsCompletions} + + {/key} +
+
+ + diff --git a/packages/builder/src/components/common/bindings/DrawerBindableSlot.svelte b/packages/builder/src/components/common/bindings/DrawerBindableSlot.svelte index 6fda04480c..8c10239b49 100644 --- a/packages/builder/src/components/common/bindings/DrawerBindableSlot.svelte +++ b/packages/builder/src/components/common/bindings/DrawerBindableSlot.svelte @@ -23,6 +23,9 @@ export let type export let schema + export let allowHBS = true + export let context = {} + const dispatch = createEventDispatcher() let bindingDrawer let currentVal = value @@ -147,7 +150,7 @@
- {#if !isValid(value)} + {#if !isValid(value) && !$$slots.default} (tempValue = event.detail)} {bindings} {allowJS} + {allowHBS} {allowHelpers} + {context} /> @@ -208,22 +212,22 @@ } .slot-icon { - right: 31px !important; + right: 31px; border-right: 1px solid var(--spectrum-alias-border-color); - border-top-right-radius: 0px !important; - border-bottom-right-radius: 0px !important; + border-top-right-radius: 0px; + border-bottom-right-radius: 0px; } .text-area-slot-icon { border-bottom: 1px solid var(--spectrum-alias-border-color); - border-bottom-right-radius: 0px !important; - top: 1px !important; + border-bottom-right-radius: 0px; + top: 1px; } .json-slot-icon { border-bottom: 1px solid var(--spectrum-alias-border-color); - border-bottom-right-radius: 0px !important; - top: 1px !important; - right: 0px !important; + border-bottom-right-radius: 0px; + top: 1px; + right: 0px; } .icon { diff --git a/packages/builder/src/components/common/bindings/ServerBindingPanel.svelte b/packages/builder/src/components/common/bindings/ServerBindingPanel.svelte index 19a466db6b..14c8de89a3 100644 --- a/packages/builder/src/components/common/bindings/ServerBindingPanel.svelte +++ b/packages/builder/src/components/common/bindings/ServerBindingPanel.svelte @@ -5,6 +5,7 @@ export let bindings = [] export let value = "" export let allowJS = false + export let allowHBS = true export let context = null $: enrichedBindings = enrichBindings(bindings) @@ -22,8 +23,10 @@ diff --git a/packages/builder/src/components/design/settings/controls/FilterEditor/FilterBuilder.svelte b/packages/builder/src/components/design/settings/controls/FilterEditor/FilterBuilder.svelte index 010216cb4d..cf45fad78a 100644 --- a/packages/builder/src/components/design/settings/controls/FilterEditor/FilterBuilder.svelte +++ b/packages/builder/src/components/design/settings/controls/FilterEditor/FilterBuilder.svelte @@ -16,6 +16,7 @@ export let datasource export let builderType export let docsURL + export let evaluationContext = {} diff --git a/packages/builder/src/components/integration/KeyValueBuilder.svelte b/packages/builder/src/components/integration/KeyValueBuilder.svelte index e42703f9c6..7ea7a935c8 100644 --- a/packages/builder/src/components/integration/KeyValueBuilder.svelte +++ b/packages/builder/src/components/integration/KeyValueBuilder.svelte @@ -39,6 +39,7 @@ export let allowJS = false export let actionButtonDisabled = false export let compare = (option, value) => option === value + export let context = null let fields = Object.entries(object || {}).map(([name, value]) => ({ name, @@ -132,6 +133,7 @@ {allowJS} {allowHelpers} drawerLeft={bindingDrawerLeft} + {context} /> {:else} @@ -158,6 +160,7 @@ {allowJS} {allowHelpers} drawerLeft={bindingDrawerLeft} + {context} /> {:else} { + onMount(async () => { + await automationStore.actions.initAppSelf() + $automationStore.showTestPanel = false }) diff --git a/packages/builder/src/stores/builder/automations.ts b/packages/builder/src/stores/builder/automations.ts index 25930e953b..d977625edf 100644 --- a/packages/builder/src/stores/builder/automations.ts +++ b/packages/builder/src/stores/builder/automations.ts @@ -1,9 +1,9 @@ -import { derived, get } from "svelte/store" +import { derived, get, Readable, Writable } from "svelte/store" import { API } from "@/api" import { cloneDeep } from "lodash/fp" import { generate } from "shortid" import { createHistoryStore } from "@/stores/builder/history" -import { licensing } from "@/stores/portal" +import { licensing, organisation, environment, auth } from "@/stores/portal" import { tables, appStore } from "@/stores/builder" import { notifications } from "@budibase/bbui" import { @@ -32,8 +32,10 @@ import { BlockDefinitions, GetAutomationTriggerDefinitionsResponse, GetAutomationActionDefinitionsResponse, + AppSelfResponse, + TestAutomationResponse, } from "@budibase/types" -import { ActionStepID } from "@/constants/backend/automations" +import { ActionStepID, TriggerStepID } from "@/constants/backend/automations" import { FIELDS } from "@/constants/backend" import { sdk } from "@budibase/shared-core" import { rowActions } from "./rowActions" @@ -43,10 +45,11 @@ import { BudiStore, DerivedBudiStore } from "@/stores/BudiStore" interface AutomationState { automations: Automation[] - testResults: any | null + testResults?: TestAutomationResponse showTestPanel: boolean blockDefinitions: BlockDefinitions selectedAutomationId: string | null + appSelf?: AppSelfResponse } interface DerivedAutomationState extends AutomationState { @@ -56,7 +59,6 @@ interface DerivedAutomationState extends AutomationState { const initialAutomationState: AutomationState = { automations: [], - testResults: null, showTestPanel: false, blockDefinitions: { TRIGGER: {}, @@ -88,6 +90,20 @@ const getFinalDefinitions = ( } const automationActions = (store: AutomationStore) => ({ + /** + * Fetches the app user context used for live evaluation + * This matches the context used on the server + * @returns {AppSelfResponse | null} + */ + initAppSelf: async (): Promise => { + // Fetch and update the app self if it hasn't been set + const appSelfResponse = await API.fetchSelf() + store.update(state => ({ + ...state, + appSelf: appSelfResponse, + })) + return appSelfResponse + }, /** * Move a given block from one location on the tree to another. * @@ -284,9 +300,12 @@ const automationActions = (store: AutomationStore) => ({ * Build a sequential list of all steps on the step path provided * * @param {Array} pathWay e.g. [{stepIdx:2},{branchIdx:0, stepIdx:2},...] - * @returns {Array} all steps encountered on the provided path + * @returns {Array} all steps encountered on the provided path */ - getPathSteps: (pathWay: Array, automation: Automation) => { + getPathSteps: ( + pathWay: Array, + automation: Automation + ): Array => { // Base Steps, including trigger const steps = [ automation.definition.trigger, @@ -533,18 +552,24 @@ const automationActions = (store: AutomationStore) => ({ icon: string, idx: number, isLoopBlock: boolean, - bindingName?: string + pathBlock: AutomationStep | AutomationTrigger, + bindingName: string ) => { if (!name) return const runtimeBinding = store.actions.determineRuntimeBinding( name, idx, isLoopBlock, - bindingName, automation, currentBlock, pathSteps ) + + const readableBinding = store.actions.determineReadableBinding( + name, + pathBlock + ) + const categoryName = store.actions.determineCategoryName( idx, isLoopBlock, @@ -561,7 +586,8 @@ const automationActions = (store: AutomationStore) => ({ isLoopBlock, runtimeBinding, categoryName, - bindingName + bindingName, + readableBinding ) ) } @@ -636,7 +662,15 @@ const automationActions = (store: AutomationStore) => ({ } } Object.entries(schema).forEach(([name, value]) => { - addBinding(name, value, icon, blockIdx, isLoopBlock, bindingName) + addBinding( + name, + value, + icon, + blockIdx, + isLoopBlock, + pathBlock, + bindingName + ) }) } @@ -647,23 +681,60 @@ const automationActions = (store: AutomationStore) => ({ return bindings }, + determineReadableBinding: ( + name: string, + block: AutomationStep | AutomationTrigger + ) => { + const rowTriggers = [ + TriggerStepID.ROW_UPDATED, + TriggerStepID.ROW_SAVED, + TriggerStepID.ROW_DELETED, + TriggerStepID.ROW_ACTION, + ] + + const isTrigger = block.type === AutomationStepType.TRIGGER + const isAppTrigger = block.stepId === AutomationTriggerStepId.APP + const isRowTrigger = rowTriggers.includes(block.stepId) + + let readableBinding: string = "" + if (isTrigger) { + if (isAppTrigger) { + readableBinding = `trigger.fields.${name}` + } else if (isRowTrigger) { + let noRowKeywordBindings = ["id", "revision", "oldRow"] + readableBinding = noRowKeywordBindings.includes(name) + ? `trigger.${name}` + : `trigger.row.${name}` + } else { + readableBinding = `trigger.${name}` + } + } + + return readableBinding + }, + determineRuntimeBinding: ( name: string, idx: number, isLoopBlock: boolean, - bindingName: string | undefined, automation: Automation, currentBlock: AutomationStep | AutomationTrigger | undefined, pathSteps: (AutomationStep | AutomationTrigger)[] ) => { let runtimeName: string | null + // Legacy support for EXECUTE_SCRIPT steps + const isJSScript = + currentBlock?.stepId === AutomationActionStepId.EXECUTE_SCRIPT + /* Begin special cases for generating custom schemas based on triggers */ if ( idx === 0 && automation.definition.trigger?.event === AutomationEventType.APP_TRIGGER ) { - return `trigger.fields.${name}` + return isJSScript + ? `trigger.fields["${name}"]` + : `trigger.fields.[${name}]` } if ( @@ -673,18 +744,17 @@ const automationActions = (store: AutomationStore) => ({ automation.definition.trigger?.event === AutomationEventType.ROW_SAVE) ) { let noRowKeywordBindings = ["id", "revision", "oldRow"] - if (!noRowKeywordBindings.includes(name)) return `trigger.row.${name}` + if (!noRowKeywordBindings.includes(name)) { + return isJSScript ? `trigger.row["${name}"]` : `trigger.row.[${name}]` + } } /* End special cases for generating custom schemas based on triggers */ if (isLoopBlock) { runtimeName = `loop.${name}` } else if (idx === 0) { - runtimeName = `trigger.${name}` - } else if ( - currentBlock?.stepId === AutomationActionStepId.EXECUTE_SCRIPT || - currentBlock?.stepId === AutomationActionStepId.EXECUTE_SCRIPT_V2 - ) { + runtimeName = `trigger.[${name}]` + } else if (isJSScript) { const stepId = pathSteps[idx].id if (!stepId) { notifications.error("Error generating binding: Step ID not found.") @@ -725,18 +795,22 @@ const automationActions = (store: AutomationStore) => ({ isLoopBlock: boolean, runtimeBinding: string | null, categoryName: string, - bindingName?: string + bindingName?: string, + readableBinding?: string ) => { const field = Object.values(FIELDS).find( field => field.type === value.type && ("subtype" in field ? field.subtype === value.subtype : true) ) + + const readableBindingDefault = + bindingName && !isLoopBlock && idx !== 0 + ? `steps.${bindingName}.${name}` + : runtimeBinding + return { - readableBinding: - bindingName && !isLoopBlock && idx !== 0 - ? `steps.${bindingName}.${name}` - : runtimeBinding, + readableBinding: readableBinding || readableBindingDefault, runtimeBinding, type: value.type, description: value.description, @@ -801,7 +875,7 @@ const automationActions = (store: AutomationStore) => ({ }, test: async (automation: Automation, testData: any) => { - let result: any + let result: TestAutomationResponse try { result = await API.testAutomation(automation._id!, testData) } catch (err: any) { @@ -1395,7 +1469,7 @@ const automationActions = (store: AutomationStore) => ({ } store.update(state => { state.selectedAutomationId = id - state.testResults = null + delete state.testResults state.showTestPanel = false return state }) @@ -1521,3 +1595,86 @@ export class SelectedAutomationStore extends DerivedBudiStore< } } export const selectedAutomation = new SelectedAutomationStore(automationStore) + +type TriggerContext = AutomationTrigger & { + meta?: Record + table?: Table + body?: Record + row?: Record + oldRow?: Record + outputs?: Record +} + +export interface AutomationContext { + user: AppSelfResponse + trigger: TriggerContext + steps: Record + env: Record + settings: Record +} + +export const evaluationContext: Readable = derived( + [organisation, selectedAutomation, environment, tables], + ([$organisation, $selectedAutomation, $env, $tables]) => { + const { platformUrl: url, company, logoUrl: logo } = $organisation + + const results: TestAutomationResponse | undefined = + $selectedAutomation?.testResults + + const testData = $selectedAutomation.data?.testData || {} + const triggerDef = $selectedAutomation.data?.definition?.trigger + + const isWebhook = triggerDef?.stepId! === TriggerStepID.WEBHOOK + const isRowAction = triggerDef?.stepId! === TriggerStepID.ROW_ACTION + const rowActionTableId = triggerDef?.inputs?.tableId + const rowActionTable = rowActionTableId + ? $tables.list.find(table => table._id === rowActionTableId) + : undefined + + // Needs a clone to avoid state mutation. + const triggerData: TriggerContext = cloneDeep( + results?.trigger?.outputs || testData + ) + + if (isRowAction && rowActionTable) { + // Row action table must always be retrieved as it is never + // returned in the test results + triggerData.table = rowActionTable + } else if (isWebhook) { + // Ensure it displays in the event that the configuration have been skipped + triggerData.body = triggerData.body ?? {} + } + + // Clean up unnecessary data from the context + // Meta contains UI/UX config data. Non-bindable + delete triggerData?.meta + + // AppSelf context required to mirror server user context + const userContext = $selectedAutomation.appSelf || {} + + return { + user: userContext, + trigger: { + ...triggerData, + }, + + // This will initially be empty for each step but will populate + // upon running the test. + steps: (results?.steps || []).reduce( + (acc: Record, res: Record) => { + acc[res.id] = res.outputs + return acc + }, + {} + ), + env: ($env?.variables || []).reduce( + (acc: Record, variable: Record) => { + acc[variable.name] = "" + return acc + }, + {} + ), + settings: { url, company, logo }, + } + } +) diff --git a/packages/builder/src/stores/builder/index.js b/packages/builder/src/stores/builder/index.js index 08d87bebf5..44e7f979d2 100644 --- a/packages/builder/src/stores/builder/index.js +++ b/packages/builder/src/stores/builder/index.js @@ -11,6 +11,7 @@ import { automationStore, selectedAutomation, automationHistoryStore, + evaluationContext, } from "./automations.js" import { userStore, userSelectedResourceMap, isOnlyUser } from "./users.js" import { deploymentStore } from "./deployments.js" @@ -67,6 +68,7 @@ export { snippets, rowActions, appPublished, + evaluationContext, } export const reset = () => { diff --git a/packages/frontend-core/src/components/ConditionField.svelte b/packages/frontend-core/src/components/ConditionField.svelte index 58dffc8dea..310be4acea 100644 --- a/packages/frontend-core/src/components/ConditionField.svelte +++ b/packages/frontend-core/src/components/ConditionField.svelte @@ -10,6 +10,7 @@ export let drawerTitle export let toReadable export let toRuntime + export let evaluationContext = {} const dispatch = createEventDispatcher() @@ -66,7 +67,6 @@ > Confirm - diff --git a/packages/frontend-core/src/components/CoreFilterBuilder.svelte b/packages/frontend-core/src/components/CoreFilterBuilder.svelte index 2bf40bda37..932298fc7d 100644 --- a/packages/frontend-core/src/components/CoreFilterBuilder.svelte +++ b/packages/frontend-core/src/components/CoreFilterBuilder.svelte @@ -42,6 +42,7 @@ export let panel export let toReadable export let toRuntime + export let evaluationContext = {} $: editableFilters = migrateFilters(filters) $: { @@ -385,6 +386,7 @@ {panel} {toReadable} {toRuntime} + {evaluationContext} on:change={e => { const updated = { ...filter, @@ -423,6 +425,7 @@ {panel} {toReadable} {toRuntime} + {evaluationContext} on:change={e => { onFilterFieldUpdate( { ...filter, ...e.detail }, diff --git a/packages/frontend-core/src/components/FilterField.svelte b/packages/frontend-core/src/components/FilterField.svelte index 861df9c2fc..7c201a7e64 100644 --- a/packages/frontend-core/src/components/FilterField.svelte +++ b/packages/frontend-core/src/components/FilterField.svelte @@ -24,6 +24,7 @@ export let drawerTitle export let toReadable export let toRuntime + export let evaluationContext = {} const dispatch = createEventDispatcher() const { OperatorOptions, FilterValueType } = Constants @@ -156,6 +157,7 @@ allowHBS on:change={drawerOnChange} {bindings} + context={evaluationContext} /> diff --git a/packages/server/src/automations/triggers.ts b/packages/server/src/automations/triggers.ts index 67d2dcb911..1353628513 100644 --- a/packages/server/src/automations/triggers.ts +++ b/packages/server/src/automations/triggers.ts @@ -21,6 +21,7 @@ import { AutomationRowEvent, UserBindings, AutomationResults, + DidNotTriggerResponse, } from "@budibase/types" import { executeInThread } from "../threads/automation" import { dataFilters, sdk } from "@budibase/shared-core" @@ -33,14 +34,6 @@ const JOB_OPTS = { import * as automationUtils from "../automations/automationUtils" import { doesTableExist } from "../sdk/app/tables/getters" -type DidNotTriggerResponse = { - outputs: { - success: false - status: AutomationStatus.STOPPED - } - message: AutomationStoppedReason.TRIGGER_FILTER_NOT_MET -} - async function getAllAutomations() { const db = context.getAppDB() let automations = await db.allDocs( diff --git a/packages/types/src/api/web/app/automation.ts b/packages/types/src/api/web/app/automation.ts index 40f69fc467..551defbff5 100644 --- a/packages/types/src/api/web/app/automation.ts +++ b/packages/types/src/api/web/app/automation.ts @@ -1,7 +1,9 @@ +import { AutomationJob, DidNotTriggerResponse } from "../../../sdk/automations" import { Automation, AutomationActionStepId, AutomationLogPage, + AutomationResults, AutomationStatus, AutomationStepDefinition, AutomationTriggerDefinition, @@ -74,4 +76,8 @@ export interface TestAutomationRequest { fields: Record row?: Row } -export interface TestAutomationResponse {} + +export type TestAutomationResponse = + | AutomationResults + | DidNotTriggerResponse + | AutomationJob diff --git a/packages/types/src/documents/app/automation/automation.ts b/packages/types/src/documents/app/automation/automation.ts index 0bb3750c5c..2e05d23ffe 100644 --- a/packages/types/src/documents/app/automation/automation.ts +++ b/packages/types/src/documents/app/automation/automation.ts @@ -5,6 +5,7 @@ import { ReadStream } from "fs" import { Row } from "../row" import { Table } from "../table" import { AutomationStep, AutomationTrigger } from "./schema" +import { StepOutputs, TriggerOutputs } from "./StepInputsOutputs" export enum AutomationIOType { OBJECT = "object", @@ -194,7 +195,7 @@ export enum AutomationStoppedReason { export interface AutomationResults { automationId?: string status?: AutomationStatus - trigger?: AutomationTrigger + trigger?: AutomationTrigger & { outputs: TriggerOutputs } steps: { stepId: AutomationTriggerStepId | AutomationActionStepId inputs: { diff --git a/packages/types/src/sdk/automations/index.ts b/packages/types/src/sdk/automations/index.ts index ba79d47021..605bbd5069 100644 --- a/packages/types/src/sdk/automations/index.ts +++ b/packages/types/src/sdk/automations/index.ts @@ -1,6 +1,8 @@ import { Automation, AutomationMetadata, + AutomationStatus, + AutomationStoppedReason, Row, UserBindings, } from "../../documents" @@ -29,3 +31,11 @@ export interface AutomationRowEvent { } export type AutomationJob = Job + +export type DidNotTriggerResponse = { + outputs: { + success: false + status: AutomationStatus.STOPPED + } + message: AutomationStoppedReason.TRIGGER_FILTER_NOT_MET +}