v2 JavaScript UX, automation bug fixes and general refactoring
This commit is contained in:
parent
8693cdc67f
commit
0463462b77
|
@ -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}
|
||||
/>
|
||||
</DrawerContent>
|
||||
</Drawer>
|
||||
|
|
|
@ -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}
|
||||
/>
|
||||
</PropField>
|
||||
{: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}
|
||||
<ModalBindableInput
|
||||
|
@ -824,6 +835,7 @@
|
|||
? queryLimit
|
||||
: ""}
|
||||
drawerLeft="260px"
|
||||
context={$memoContext}
|
||||
/>
|
||||
{/if}
|
||||
</div>
|
||||
|
@ -853,6 +865,7 @@
|
|||
panel={AutomationBindingPanel}
|
||||
showFilterEmptyDropdown={!rowTriggers.includes(stepId)}
|
||||
on:change={e => (tempFilters = e.detail)}
|
||||
evaluationContext={$memoContext}
|
||||
/>
|
||||
</DrawerContent>
|
||||
</Drawer>
|
||||
|
@ -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}
|
||||
<div class="scriptv2-wrapper">
|
||||
<DrawerBindableSlot
|
||||
title={"Edit Code"}
|
||||
panel={AutomationBindingPanel}
|
||||
type={"longform"}
|
||||
{schema}
|
||||
on:change={e => onChange({ [key]: e.detail })}
|
||||
value={inputData[key]}
|
||||
{bindings}
|
||||
allowJS={true}
|
||||
allowHBS={false}
|
||||
updateOnChange={false}
|
||||
context={$memoContext}
|
||||
>
|
||||
<div class="field-wrap code-editor">
|
||||
<CodeEditorField
|
||||
value={inputData[key]}
|
||||
{bindings}
|
||||
context={$memoContext}
|
||||
allowHBS={false}
|
||||
allowJS
|
||||
placeholder={codeMode === EditorModes.Handlebars
|
||||
? "Add bindings by typing {{"
|
||||
: null}
|
||||
on:blur={e =>
|
||||
onChange({ [key]: encodeJSBinding(e.detail) })}
|
||||
/>
|
||||
</div>
|
||||
</DrawerBindableSlot>
|
||||
</div>
|
||||
{:else if value.customType === "code" && stepId === ActionStepID.EXECUTE_SCRIPT}
|
||||
<!-- DEPRECATED -->
|
||||
<CodeEditorModal
|
||||
on:hide={() => {
|
||||
// Push any pending changes when the window closes
|
||||
|
@ -977,6 +1022,7 @@
|
|||
? queryLimit
|
||||
: ""}
|
||||
drawerLeft="260px"
|
||||
context={$memoContext}
|
||||
/>
|
||||
</div>
|
||||
{/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);
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -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}
|
||||
<DrawerBindableSlot
|
||||
|
@ -276,6 +278,7 @@
|
|||
allowJS={true}
|
||||
updateOnChange={false}
|
||||
drawerLeft="260px"
|
||||
{context}
|
||||
>
|
||||
<RowSelectorTypes
|
||||
{isTestModal}
|
||||
|
@ -286,6 +289,7 @@
|
|||
meta={{
|
||||
fields: editableFields,
|
||||
}}
|
||||
{context}
|
||||
onChange={change => onChange(change)}
|
||||
/>
|
||||
</DrawerBindableSlot>
|
||||
|
|
|
@ -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}
|
||||
/>
|
||||
</div>
|
||||
{:else}
|
||||
|
|
|
@ -1,18 +1,11 @@
|
|||
<script>
|
||||
import { Input, Select, Button } from "@budibase/bbui"
|
||||
import { createEventDispatcher } from "svelte"
|
||||
|
||||
const dispatch = createEventDispatcher()
|
||||
import { memo } from "@budibase/frontend-core"
|
||||
import { generate } from "shortid"
|
||||
|
||||
export let value = {}
|
||||
|
||||
$: fieldsArray = value
|
||||
? Object.entries(value).map(([name, type]) => ({
|
||||
name,
|
||||
type,
|
||||
}))
|
||||
: []
|
||||
|
||||
const typeOptions = [
|
||||
{
|
||||
label: "Text",
|
||||
|
@ -36,16 +29,42 @@
|
|||
},
|
||||
]
|
||||
|
||||
const dispatch = createEventDispatcher()
|
||||
const memoValue = memo({ data: {} })
|
||||
|
||||
$: memoValue.set({ data: value })
|
||||
|
||||
$: fieldsArray = $memoValue.data
|
||||
? Object.entries($memoValue.data).map(([name, type]) => ({
|
||||
name,
|
||||
type,
|
||||
id: generate(),
|
||||
}))
|
||||
: []
|
||||
|
||||
function addField() {
|
||||
const newValue = { ...value }
|
||||
const newValue = { ...$memoValue.data }
|
||||
newValue[""] = "string"
|
||||
dispatch("change", newValue)
|
||||
fieldsArray = [...fieldsArray, { name: "", type: "string", id: generate() }]
|
||||
}
|
||||
|
||||
function removeField(name) {
|
||||
const newValues = { ...value }
|
||||
delete newValues[name]
|
||||
dispatch("change", newValues)
|
||||
function removeField(idx) {
|
||||
const entries = [...fieldsArray]
|
||||
|
||||
// Remove empty field
|
||||
if (!entries[idx]?.name) {
|
||||
fieldsArray.splice(idx, 1)
|
||||
fieldsArray = [...fieldsArray]
|
||||
return
|
||||
}
|
||||
|
||||
entries.splice(idx, 1)
|
||||
|
||||
const update = entries.reduce((newVals, current) => {
|
||||
newVals[current.name.trim()] = current.type
|
||||
return newVals
|
||||
}, {})
|
||||
dispatch("change", update)
|
||||
}
|
||||
|
||||
const fieldNameChanged = originalName => e => {
|
||||
|
@ -57,11 +76,16 @@
|
|||
} else {
|
||||
entries = entries.filter(f => f.name !== originalName)
|
||||
}
|
||||
value = entries.reduce((newVals, current) => {
|
||||
newVals[current.name.trim()] = current.type
|
||||
return newVals
|
||||
}, {})
|
||||
dispatch("change", value)
|
||||
|
||||
const update = entries
|
||||
.filter(entry => entry.name)
|
||||
.reduce((newVals, current) => {
|
||||
newVals[current.name.trim()] = current.type
|
||||
return newVals
|
||||
}, {})
|
||||
if (Object.keys(update).length) {
|
||||
dispatch("change", update)
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
|
@ -69,7 +93,7 @@
|
|||
<!-- svelte-ignore a11y-no-static-element-interactions -->
|
||||
<div class="root">
|
||||
<div class="spacer" />
|
||||
{#each fieldsArray as field}
|
||||
{#each fieldsArray as field, idx (field.id)}
|
||||
<div class="field">
|
||||
<Input
|
||||
value={field.name}
|
||||
|
@ -88,7 +112,9 @@
|
|||
/>
|
||||
<i
|
||||
class="remove-field ri-delete-bin-line"
|
||||
on:click={() => removeField(field.name)}
|
||||
on:click={() => {
|
||||
removeField(idx)
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
{/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);
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -1,3 +1,10 @@
|
|||
<script context="module" lang="ts">
|
||||
export const DropdownPosition = {
|
||||
Relative: "top",
|
||||
Absolute: "right",
|
||||
}
|
||||
</script>
|
||||
|
||||
<script lang="ts">
|
||||
import { Label } from "@budibase/bbui"
|
||||
import { onMount, createEventDispatcher, onDestroy } from "svelte"
|
||||
|
@ -45,6 +52,7 @@
|
|||
import { EditorModes } from "./"
|
||||
import { themeStore } from "@/stores/portal"
|
||||
import type { EditorMode } from "@budibase/types"
|
||||
import { tooltips } from "@codemirror/view"
|
||||
|
||||
export let label: string | undefined = undefined
|
||||
// TODO: work out what best type fits this
|
||||
|
@ -57,11 +65,13 @@
|
|||
export let jsBindingWrapping = true
|
||||
export let readonly = false
|
||||
export let readonlyLineNumbers = false
|
||||
export let dropdown = DropdownPosition.Relative
|
||||
|
||||
const dispatch = createEventDispatcher()
|
||||
|
||||
let textarea: HTMLDivElement
|
||||
let editor: EditorView
|
||||
let editorEle: HTMLDivElement
|
||||
let mounted = false
|
||||
let isEditorInitialised = false
|
||||
let queuedRefresh = false
|
||||
|
@ -112,7 +122,6 @@
|
|||
queuedRefresh = true
|
||||
return
|
||||
}
|
||||
|
||||
if (
|
||||
editor &&
|
||||
value &&
|
||||
|
@ -343,14 +352,25 @@
|
|||
const baseExtensions = buildBaseExtensions()
|
||||
|
||||
editor = new EditorView({
|
||||
doc: value?.toString(),
|
||||
extensions: buildExtensions(baseExtensions),
|
||||
doc: String(value),
|
||||
extensions: buildExtensions([
|
||||
...baseExtensions,
|
||||
dropdown == DropdownPosition.Absolute
|
||||
? tooltips({
|
||||
position: "absolute",
|
||||
})
|
||||
: [],
|
||||
]),
|
||||
parent: textarea,
|
||||
})
|
||||
}
|
||||
|
||||
onMount(async () => {
|
||||
mounted = true
|
||||
// Capture scrolling
|
||||
editorEle.addEventListener("wheel", e => {
|
||||
e.stopPropagation()
|
||||
})
|
||||
})
|
||||
|
||||
onDestroy(() => {
|
||||
|
@ -366,7 +386,7 @@
|
|||
</div>
|
||||
{/if}
|
||||
|
||||
<div class={`code-editor ${mode?.name || ""}`}>
|
||||
<div class={`code-editor ${mode?.name || ""}`} bind:this={editorEle}>
|
||||
<div tabindex="-1" bind:this={textarea} />
|
||||
</div>
|
||||
|
||||
|
@ -534,12 +554,11 @@
|
|||
|
||||
/* Live binding value / helper container */
|
||||
.code-editor :global(.cm-completionInfo) {
|
||||
margin-left: var(--spacing-s);
|
||||
margin: 0px var(--spacing-s);
|
||||
border: 1px solid var(--spectrum-global-color-gray-300);
|
||||
border-radius: var(--border-radius-s);
|
||||
background-color: var(--spectrum-global-color-gray-50);
|
||||
padding: var(--spacing-m);
|
||||
margin-top: -2px;
|
||||
}
|
||||
|
||||
/* Wrapper around helpers */
|
||||
|
@ -564,6 +583,7 @@
|
|||
white-space: pre;
|
||||
text-overflow: ellipsis;
|
||||
overflow: hidden;
|
||||
overflow-y: auto;
|
||||
max-height: 480px;
|
||||
}
|
||||
.code-editor :global(.binding__example.helper) {
|
||||
|
|
|
@ -61,8 +61,6 @@
|
|||
let mode: BindingMode | null
|
||||
let sidePanel: SidePanel | null
|
||||
let initialValueJS = value?.startsWith?.("{{ js ")
|
||||
let jsValue: string | null = initialValueJS ? value : null
|
||||
let hbsValue: string | null = initialValueJS ? null : value
|
||||
let getCaretPosition: CaretPositionFn | undefined
|
||||
let insertAtPos: InsertAtPositionFn | undefined
|
||||
let targetMode: BindingMode | null = null
|
||||
|
@ -71,6 +69,10 @@
|
|||
let expressionError: string | undefined
|
||||
let evaluating = false
|
||||
|
||||
// Ensure these values are not stale
|
||||
$: jsValue = initialValueJS ? value : null
|
||||
$: hbsValue = initialValueJS ? null : value
|
||||
|
||||
$: useSnippets = allowSnippets && !$licensing.isFreePlan
|
||||
$: editorModeOptions = getModeOptions(allowHBS, allowJS)
|
||||
$: sidePanelOptions = getSidePanelOptions(
|
||||
|
|
|
@ -0,0 +1,203 @@
|
|||
<script lang="ts">
|
||||
import { createEventDispatcher, onMount } from "svelte"
|
||||
import {
|
||||
decodeJSBinding,
|
||||
encodeJSBinding,
|
||||
processObjectSync,
|
||||
} from "@budibase/string-templates"
|
||||
import { runtimeToReadableBinding } from "@/dataBinding"
|
||||
import CodeEditor, { DropdownPosition } from "../CodeEditor/CodeEditor.svelte"
|
||||
import {
|
||||
getHelperCompletions,
|
||||
jsAutocomplete,
|
||||
snippetAutoComplete,
|
||||
EditorModes,
|
||||
bindingsToCompletions,
|
||||
} from "../CodeEditor"
|
||||
import { JsonFormatter } from "@budibase/frontend-core"
|
||||
import { licensing } from "@/stores/portal"
|
||||
import { BindingMode } from "@budibase/types"
|
||||
import type {
|
||||
EnrichedBinding,
|
||||
BindingCompletion,
|
||||
Snippet,
|
||||
CaretPositionFn,
|
||||
InsertAtPositionFn,
|
||||
JSONValue,
|
||||
} from "@budibase/types"
|
||||
import type { CompletionContext } from "@codemirror/autocomplete"
|
||||
import { snippets } from "@/stores/builder"
|
||||
|
||||
const dispatch = createEventDispatcher()
|
||||
|
||||
export let bindings: EnrichedBinding[] = []
|
||||
export let value: string = ""
|
||||
export let allowHBS = true
|
||||
export let allowJS = false
|
||||
export let allowHelpers = true
|
||||
export let allowSnippets = true
|
||||
export let context = null
|
||||
export let autofocusEditor = false
|
||||
export let placeholder = null
|
||||
|
||||
let mode: BindingMode | null
|
||||
let initialValueJS = value?.startsWith?.("{{ js ")
|
||||
let getCaretPosition: CaretPositionFn | undefined
|
||||
let insertAtPos: InsertAtPositionFn | undefined
|
||||
|
||||
// TO Switch the runtime
|
||||
$: readable = runtimeToReadableBinding(bindings, value || "")
|
||||
|
||||
$: jsValue = decodeJSBinding(readable)
|
||||
|
||||
$: useSnippets = allowSnippets && !$licensing.isFreePlan
|
||||
$: editorModeOptions = getModeOptions(allowHBS, allowJS)
|
||||
$: enrichedBindings = enrichBindings(bindings, context, $snippets)
|
||||
$: editorMode = EditorModes.JS
|
||||
$: bindingCompletions = bindingsToCompletions(enrichedBindings, editorMode)
|
||||
$: jsCompletions = getJSCompletions(
|
||||
bindingCompletions,
|
||||
$snippets,
|
||||
useSnippets
|
||||
)
|
||||
|
||||
const getJSCompletions = (
|
||||
bindingCompletions: BindingCompletion[],
|
||||
snippets: Snippet[] | null,
|
||||
useSnippets?: boolean
|
||||
) => {
|
||||
const completions: ((_: CompletionContext) => any)[] = [
|
||||
jsAutocomplete([
|
||||
...bindingCompletions,
|
||||
...(allowHelpers ? getHelperCompletions(EditorModes.JS) : []),
|
||||
]),
|
||||
]
|
||||
if (useSnippets && snippets) {
|
||||
completions.push(snippetAutoComplete(snippets))
|
||||
}
|
||||
|
||||
return completions
|
||||
}
|
||||
|
||||
const getModeOptions = (allowHBS: boolean, allowJS: boolean) => {
|
||||
let options = []
|
||||
if (allowHBS) {
|
||||
options.push(BindingMode.Text)
|
||||
}
|
||||
if (allowJS) {
|
||||
options.push(BindingMode.JavaScript)
|
||||
}
|
||||
return options
|
||||
}
|
||||
|
||||
const highlightJSON = (json: JSONValue) => {
|
||||
return JsonFormatter.format(json, {
|
||||
keyColor: "#e06c75",
|
||||
numberColor: "#e5c07b",
|
||||
stringColor: "#98c379",
|
||||
trueColor: "#d19a66",
|
||||
falseColor: "#d19a66",
|
||||
nullColor: "#c678dd",
|
||||
})
|
||||
}
|
||||
|
||||
const enrichBindings = (
|
||||
bindings: EnrichedBinding[],
|
||||
context: any,
|
||||
snippets: Snippet[] | null
|
||||
) => {
|
||||
// Create a single big array to enrich in one go
|
||||
const bindingStrings = bindings.map(binding => {
|
||||
if (binding.runtimeBinding.startsWith('trim "')) {
|
||||
// Account for nasty hardcoded HBS bindings for roles, for legacy
|
||||
// compatibility
|
||||
return `{{ ${binding.runtimeBinding} }}`
|
||||
} else {
|
||||
return `{{ literal ${binding.runtimeBinding} }}`
|
||||
}
|
||||
})
|
||||
const bindingEvaluations = processObjectSync(bindingStrings, {
|
||||
...context,
|
||||
snippets,
|
||||
})
|
||||
|
||||
// Enrich bindings with evaluations and highlighted HTML
|
||||
return bindings.map((binding, idx) => {
|
||||
if (!context || typeof bindingEvaluations !== "object") {
|
||||
return binding
|
||||
}
|
||||
const evalObj: Record<any, any> = bindingEvaluations
|
||||
const value = JSON.stringify(evalObj[idx], null, 2)
|
||||
return {
|
||||
...binding,
|
||||
value,
|
||||
valueHTML: highlightJSON(value),
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
const updateValue = (val: any) => {
|
||||
dispatch("change", val)
|
||||
}
|
||||
|
||||
const onChangeJSValue = (e: { detail: string }) => {
|
||||
// if(typeof onChange === "function"){
|
||||
|
||||
// }
|
||||
if (!e.detail?.trim()) {
|
||||
// Don't bother saving empty values as JS
|
||||
updateValue(null)
|
||||
} else {
|
||||
updateValue(encodeJSBinding(e.detail))
|
||||
}
|
||||
}
|
||||
|
||||
onMount(() => {
|
||||
// Set the initial mode appropriately
|
||||
const initialValueMode = initialValueJS
|
||||
? BindingMode.JavaScript
|
||||
: BindingMode.Text
|
||||
if (editorModeOptions.includes(initialValueMode)) {
|
||||
mode = initialValueMode
|
||||
} else {
|
||||
mode = editorModeOptions[0]
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<div class="code-panel">
|
||||
<div class="editor">
|
||||
{#key jsCompletions}
|
||||
<CodeEditor
|
||||
value={jsValue}
|
||||
on:change={onChangeJSValue}
|
||||
on:blur
|
||||
completions={jsCompletions}
|
||||
mode={EditorModes.JS}
|
||||
bind:getCaretPosition
|
||||
bind:insertAtPos
|
||||
autofocus={autofocusEditor}
|
||||
placeholder={placeholder ||
|
||||
"Add bindings by typing $ or use the menu on the right"}
|
||||
jsBindingWrapping
|
||||
dropdown={DropdownPosition.Absolute}
|
||||
/>
|
||||
{/key}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
.code-panel {
|
||||
height: 100%;
|
||||
display: flex;
|
||||
}
|
||||
|
||||
/* Editor */
|
||||
.editor {
|
||||
flex: 1 1 auto;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: flex-start;
|
||||
align-items: stretch;
|
||||
}
|
||||
</style>
|
|
@ -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 @@
|
|||
<!-- svelte-ignore a11y-click-events-have-key-events -->
|
||||
<!-- svelte-ignore a11y-no-static-element-interactions -->
|
||||
<div class="control" class:disabled>
|
||||
{#if !isValid(value)}
|
||||
{#if !isValid(value) && !$$slots.default}
|
||||
<Input
|
||||
{label}
|
||||
{disabled}
|
||||
|
@ -187,7 +190,6 @@
|
|||
on:drawerShow
|
||||
bind:this={bindingDrawer}
|
||||
title={title ?? placeholder ?? "Bindings"}
|
||||
forceModal={true}
|
||||
>
|
||||
<Button cta slot="buttons" on:click={saveBinding}>Save</Button>
|
||||
<svelte:component
|
||||
|
@ -197,7 +199,9 @@
|
|||
on:change={event => (tempValue = event.detail)}
|
||||
{bindings}
|
||||
{allowJS}
|
||||
{allowHBS}
|
||||
{allowHelpers}
|
||||
{context}
|
||||
/>
|
||||
</Drawer>
|
||||
|
||||
|
@ -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 {
|
||||
|
|
|
@ -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 @@
|
|||
<BindingPanel
|
||||
bindings={enrichedBindings}
|
||||
snippets={$snippets}
|
||||
allowHelpers
|
||||
{value}
|
||||
{allowJS}
|
||||
{allowHBS}
|
||||
{context}
|
||||
on:change
|
||||
/>
|
||||
|
|
|
@ -16,6 +16,7 @@
|
|||
export let datasource
|
||||
export let builderType
|
||||
export let docsURL
|
||||
export let evaluationContext = {}
|
||||
</script>
|
||||
|
||||
<CoreFilterBuilder
|
||||
|
@ -32,5 +33,6 @@
|
|||
{allowOnEmpty}
|
||||
{builderType}
|
||||
{docsURL}
|
||||
{evaluationContext}
|
||||
on:change
|
||||
/>
|
||||
|
|
|
@ -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}
|
||||
<Input readonly={readOnly} bind:value={field.name} on:blur={changed} />
|
||||
|
@ -158,6 +160,7 @@
|
|||
{allowJS}
|
||||
{allowHelpers}
|
||||
drawerLeft={bindingDrawerLeft}
|
||||
{context}
|
||||
/>
|
||||
{:else}
|
||||
<Input
|
||||
|
|
|
@ -29,7 +29,9 @@
|
|||
let modal
|
||||
let webhookModal
|
||||
|
||||
onMount(() => {
|
||||
onMount(async () => {
|
||||
await automationStore.actions.initAppSelf()
|
||||
|
||||
$automationStore.showTestPanel = false
|
||||
})
|
||||
|
||||
|
|
|
@ -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<AppSelfResponse | null> => {
|
||||
// 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<Object>} pathWay e.g. [{stepIdx:2},{branchIdx:0, stepIdx:2},...]
|
||||
* @returns {Array<Object>} all steps encountered on the provided path
|
||||
* @returns {Array<AutomationStep | AutomationTrigger>} all steps encountered on the provided path
|
||||
*/
|
||||
getPathSteps: (pathWay: Array<BranchPath>, automation: Automation) => {
|
||||
getPathSteps: (
|
||||
pathWay: Array<BranchPath>,
|
||||
automation: Automation
|
||||
): Array<AutomationStep | AutomationTrigger> => {
|
||||
// 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<any, any>
|
||||
table?: Table
|
||||
body?: Record<any, any>
|
||||
row?: Record<any, any>
|
||||
oldRow?: Record<any, any>
|
||||
outputs?: Record<any, any>
|
||||
}
|
||||
|
||||
export interface AutomationContext {
|
||||
user: AppSelfResponse
|
||||
trigger: TriggerContext
|
||||
steps: Record<string, AutomationStep>
|
||||
env: Record<string, any>
|
||||
settings: Record<string, any>
|
||||
}
|
||||
|
||||
export const evaluationContext: Readable<AutomationContext> = 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<string, any>, res: Record<string, any>) => {
|
||||
acc[res.id] = res.outputs
|
||||
return acc
|
||||
},
|
||||
{}
|
||||
),
|
||||
env: ($env?.variables || []).reduce(
|
||||
(acc: Record<string, any>, variable: Record<string, any>) => {
|
||||
acc[variable.name] = ""
|
||||
return acc
|
||||
},
|
||||
{}
|
||||
),
|
||||
settings: { url, company, logo },
|
||||
}
|
||||
}
|
||||
)
|
||||
|
|
|
@ -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 = () => {
|
||||
|
|
|
@ -10,6 +10,7 @@
|
|||
export let drawerTitle
|
||||
export let toReadable
|
||||
export let toRuntime
|
||||
export let evaluationContext = {}
|
||||
|
||||
const dispatch = createEventDispatcher()
|
||||
|
||||
|
@ -66,7 +67,6 @@
|
|||
>
|
||||
Confirm
|
||||
</Button>
|
||||
|
||||
<svelte:component
|
||||
this={panel}
|
||||
slot="body"
|
||||
|
@ -76,6 +76,7 @@
|
|||
allowHBS
|
||||
on:change={drawerOnChange}
|
||||
{bindings}
|
||||
context={evaluationContext}
|
||||
/>
|
||||
</Drawer>
|
||||
|
||||
|
|
|
@ -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 },
|
||||
|
|
|
@ -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}
|
||||
/>
|
||||
</Drawer>
|
||||
|
||||
|
|
|
@ -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<Automation>(
|
||||
|
|
|
@ -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<string, any>
|
||||
row?: Row
|
||||
}
|
||||
export interface TestAutomationResponse {}
|
||||
|
||||
export type TestAutomationResponse =
|
||||
| AutomationResults
|
||||
| DidNotTriggerResponse
|
||||
| AutomationJob
|
||||
|
|
|
@ -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: {
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
import {
|
||||
Automation,
|
||||
AutomationMetadata,
|
||||
AutomationStatus,
|
||||
AutomationStoppedReason,
|
||||
Row,
|
||||
UserBindings,
|
||||
} from "../../documents"
|
||||
|
@ -29,3 +31,11 @@ export interface AutomationRowEvent {
|
|||
}
|
||||
|
||||
export type AutomationJob = Job<AutomationData>
|
||||
|
||||
export type DidNotTriggerResponse = {
|
||||
outputs: {
|
||||
success: false
|
||||
status: AutomationStatus.STOPPED
|
||||
}
|
||||
message: AutomationStoppedReason.TRIGGER_FILTER_NOT_MET
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue