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 AutomationBindingPanel from "@/components/common/bindings/ServerBindingPanel.svelte"
|
||||||
import FlowItemHeader from "./FlowItemHeader.svelte"
|
import FlowItemHeader from "./FlowItemHeader.svelte"
|
||||||
import FlowItemActions from "./FlowItemActions.svelte"
|
import FlowItemActions from "./FlowItemActions.svelte"
|
||||||
import { automationStore, selectedAutomation } from "@/stores/builder"
|
import {
|
||||||
import { QueryUtils, Utils } from "@budibase/frontend-core"
|
automationStore,
|
||||||
|
selectedAutomation,
|
||||||
|
evaluationContext,
|
||||||
|
} from "@/stores/builder"
|
||||||
|
import { QueryUtils, Utils, memo } from "@budibase/frontend-core"
|
||||||
import { cloneDeep } from "lodash/fp"
|
import { cloneDeep } from "lodash/fp"
|
||||||
import { createEventDispatcher, getContext } from "svelte"
|
import { createEventDispatcher, getContext } from "svelte"
|
||||||
import DragZone from "./DragZone.svelte"
|
import DragZone from "./DragZone.svelte"
|
||||||
|
@ -34,11 +38,14 @@
|
||||||
export let automation
|
export let automation
|
||||||
|
|
||||||
const view = getContext("draggableView")
|
const view = getContext("draggableView")
|
||||||
|
const memoContext = memo({})
|
||||||
|
|
||||||
let drawer
|
let drawer
|
||||||
let open = true
|
let open = true
|
||||||
let confirmDeleteModal
|
let confirmDeleteModal
|
||||||
|
|
||||||
|
$: memoContext.set($evaluationContext)
|
||||||
|
|
||||||
$: branch = step.inputs?.branches?.[branchIdx]
|
$: branch = step.inputs?.branches?.[branchIdx]
|
||||||
$: editableConditionUI = branch.conditionUI || {}
|
$: editableConditionUI = branch.conditionUI || {}
|
||||||
|
|
||||||
|
@ -100,6 +107,7 @@
|
||||||
allowOnEmpty={false}
|
allowOnEmpty={false}
|
||||||
builderType={"condition"}
|
builderType={"condition"}
|
||||||
docsURL={null}
|
docsURL={null}
|
||||||
|
evaluationContext={$memoContext}
|
||||||
/>
|
/>
|
||||||
</DrawerContent>
|
</DrawerContent>
|
||||||
</Drawer>
|
</Drawer>
|
||||||
|
|
|
@ -21,7 +21,7 @@
|
||||||
} from "@budibase/bbui"
|
} from "@budibase/bbui"
|
||||||
|
|
||||||
import CreateWebhookModal from "@/components/automation/Shared/CreateWebhookModal.svelte"
|
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 { environment } from "@/stores/portal"
|
||||||
import WebhookDisplay from "../Shared/WebhookDisplay.svelte"
|
import WebhookDisplay from "../Shared/WebhookDisplay.svelte"
|
||||||
import {
|
import {
|
||||||
|
@ -62,6 +62,8 @@
|
||||||
} from "@budibase/types"
|
} from "@budibase/types"
|
||||||
import PropField from "./PropField.svelte"
|
import PropField from "./PropField.svelte"
|
||||||
import { utils } from "@budibase/shared-core"
|
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 automation
|
||||||
export let block
|
export let block
|
||||||
|
@ -74,6 +76,7 @@
|
||||||
|
|
||||||
// Stop unnecessary rendering
|
// Stop unnecessary rendering
|
||||||
const memoBlock = memo(block)
|
const memoBlock = memo(block)
|
||||||
|
const memoContext = memo({})
|
||||||
|
|
||||||
const rowTriggers = [
|
const rowTriggers = [
|
||||||
TriggerStepID.ROW_UPDATED,
|
TriggerStepID.ROW_UPDATED,
|
||||||
|
@ -97,6 +100,7 @@
|
||||||
let stepLayouts = {}
|
let stepLayouts = {}
|
||||||
|
|
||||||
$: memoBlock.set(block)
|
$: memoBlock.set(block)
|
||||||
|
$: memoContext.set($evaluationContext)
|
||||||
|
|
||||||
$: filters = lookForFilters(schemaProperties)
|
$: filters = lookForFilters(schemaProperties)
|
||||||
$: filterCount =
|
$: filterCount =
|
||||||
|
@ -140,6 +144,7 @@
|
||||||
? [hbAutocomplete([...bindingsToCompletions(bindings, codeMode)])]
|
? [hbAutocomplete([...bindingsToCompletions(bindings, codeMode)])]
|
||||||
: []
|
: []
|
||||||
|
|
||||||
|
// TODO: check if it inputData != newInputData (memo)
|
||||||
const getInputData = (testData, blockInputs) => {
|
const getInputData = (testData, blockInputs) => {
|
||||||
// Test data is not cloned for reactivity
|
// Test data is not cloned for reactivity
|
||||||
let newInputData = testData || cloneDeep(blockInputs)
|
let newInputData = testData || cloneDeep(blockInputs)
|
||||||
|
@ -156,6 +161,7 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
const setDefaultEnumValues = () => {
|
const setDefaultEnumValues = () => {
|
||||||
|
// TODO: Update this for memoisation
|
||||||
for (const [key, value] of schemaProperties) {
|
for (const [key, value] of schemaProperties) {
|
||||||
if (value.type === "string" && value.enum && inputData[key] == null) {
|
if (value.type === "string" && value.enum && inputData[key] == null) {
|
||||||
inputData[key] = value.enum[0]
|
inputData[key] = value.enum[0]
|
||||||
|
@ -200,7 +206,6 @@
|
||||||
onChange({ ["revision"]: e.detail })
|
onChange({ ["revision"]: e.detail })
|
||||||
},
|
},
|
||||||
updateOnChange: false,
|
updateOnChange: false,
|
||||||
forceModal: true,
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
|
@ -228,7 +233,6 @@
|
||||||
onChange({ [rowIdentifier]: e.detail })
|
onChange({ [rowIdentifier]: e.detail })
|
||||||
},
|
},
|
||||||
updateOnChange: false,
|
updateOnChange: false,
|
||||||
forceModal: true,
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
|
@ -476,6 +480,10 @@
|
||||||
...update,
|
...update,
|
||||||
})
|
})
|
||||||
|
|
||||||
|
if (!updatedAutomation) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
// Exclude default or invalid data from the test data
|
// Exclude default or invalid data from the test data
|
||||||
let updatedFields = {}
|
let updatedFields = {}
|
||||||
for (const key of Object.keys(block?.inputs?.fields || {})) {
|
for (const key of Object.keys(block?.inputs?.fields || {})) {
|
||||||
|
@ -547,7 +555,7 @@
|
||||||
...newTestData,
|
...newTestData,
|
||||||
body: {
|
body: {
|
||||||
...update,
|
...update,
|
||||||
...automation.testData?.body,
|
...(automation?.testData?.body || {}),
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -668,6 +676,7 @@
|
||||||
{...config.props}
|
{...config.props}
|
||||||
{bindings}
|
{bindings}
|
||||||
on:change={config.props.onChange}
|
on:change={config.props.onChange}
|
||||||
|
context={$memoContext}
|
||||||
/>
|
/>
|
||||||
</PropField>
|
</PropField>
|
||||||
{:else}
|
{:else}
|
||||||
|
@ -676,6 +685,7 @@
|
||||||
{...config.props}
|
{...config.props}
|
||||||
{bindings}
|
{bindings}
|
||||||
on:change={config.props.onChange}
|
on:change={config.props.onChange}
|
||||||
|
context={$memoContext}
|
||||||
/>
|
/>
|
||||||
{/if}
|
{/if}
|
||||||
{/each}
|
{/each}
|
||||||
|
@ -800,6 +810,7 @@
|
||||||
: "Add signature"}
|
: "Add signature"}
|
||||||
keyPlaceholder={"URL"}
|
keyPlaceholder={"URL"}
|
||||||
valuePlaceholder={"Filename"}
|
valuePlaceholder={"Filename"}
|
||||||
|
context={$memoContext}
|
||||||
/>
|
/>
|
||||||
{:else if isTestModal}
|
{:else if isTestModal}
|
||||||
<ModalBindableInput
|
<ModalBindableInput
|
||||||
|
@ -824,6 +835,7 @@
|
||||||
? queryLimit
|
? queryLimit
|
||||||
: ""}
|
: ""}
|
||||||
drawerLeft="260px"
|
drawerLeft="260px"
|
||||||
|
context={$memoContext}
|
||||||
/>
|
/>
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
|
@ -853,6 +865,7 @@
|
||||||
panel={AutomationBindingPanel}
|
panel={AutomationBindingPanel}
|
||||||
showFilterEmptyDropdown={!rowTriggers.includes(stepId)}
|
showFilterEmptyDropdown={!rowTriggers.includes(stepId)}
|
||||||
on:change={e => (tempFilters = e.detail)}
|
on:change={e => (tempFilters = e.detail)}
|
||||||
|
evaluationContext={$memoContext}
|
||||||
/>
|
/>
|
||||||
</DrawerContent>
|
</DrawerContent>
|
||||||
</Drawer>
|
</Drawer>
|
||||||
|
@ -895,7 +908,39 @@
|
||||||
on:change={e => onChange({ [key]: e.detail })}
|
on:change={e => onChange({ [key]: e.detail })}
|
||||||
value={inputData[key]}
|
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
|
<CodeEditorModal
|
||||||
on:hide={() => {
|
on:hide={() => {
|
||||||
// Push any pending changes when the window closes
|
// Push any pending changes when the window closes
|
||||||
|
@ -977,6 +1022,7 @@
|
||||||
? queryLimit
|
? queryLimit
|
||||||
: ""}
|
: ""}
|
||||||
drawerLeft="260px"
|
drawerLeft="260px"
|
||||||
|
context={$memoContext}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
|
@ -1044,4 +1090,23 @@
|
||||||
flex: 3;
|
flex: 3;
|
||||||
margin-top: calc((var(--spacing-xl) * -1) + 1px);
|
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>
|
</style>
|
||||||
|
|
|
@ -25,6 +25,7 @@
|
||||||
export let meta
|
export let meta
|
||||||
export let bindings
|
export let bindings
|
||||||
export let isTestModal
|
export let isTestModal
|
||||||
|
export let context = {}
|
||||||
|
|
||||||
const typeToField = Object.values(FIELDS).reduce((acc, field) => {
|
const typeToField = Object.values(FIELDS).reduce((acc, field) => {
|
||||||
acc[field.type] = field
|
acc[field.type] = field
|
||||||
|
@ -58,7 +59,7 @@
|
||||||
|
|
||||||
$: parsedBindings = bindings.map(binding => {
|
$: parsedBindings = bindings.map(binding => {
|
||||||
let clone = Object.assign({}, binding)
|
let clone = Object.assign({}, binding)
|
||||||
clone.icon = "ShareAndroid"
|
clone.icon = clone.icon ?? "ShareAndroid"
|
||||||
return clone
|
return clone
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@ -258,6 +259,7 @@
|
||||||
fields: editableFields,
|
fields: editableFields,
|
||||||
}}
|
}}
|
||||||
{onChange}
|
{onChange}
|
||||||
|
{context}
|
||||||
/>
|
/>
|
||||||
{:else}
|
{:else}
|
||||||
<DrawerBindableSlot
|
<DrawerBindableSlot
|
||||||
|
@ -276,6 +278,7 @@
|
||||||
allowJS={true}
|
allowJS={true}
|
||||||
updateOnChange={false}
|
updateOnChange={false}
|
||||||
drawerLeft="260px"
|
drawerLeft="260px"
|
||||||
|
{context}
|
||||||
>
|
>
|
||||||
<RowSelectorTypes
|
<RowSelectorTypes
|
||||||
{isTestModal}
|
{isTestModal}
|
||||||
|
@ -286,6 +289,7 @@
|
||||||
meta={{
|
meta={{
|
||||||
fields: editableFields,
|
fields: editableFields,
|
||||||
}}
|
}}
|
||||||
|
{context}
|
||||||
onChange={change => onChange(change)}
|
onChange={change => onChange(change)}
|
||||||
/>
|
/>
|
||||||
</DrawerBindableSlot>
|
</DrawerBindableSlot>
|
||||||
|
|
|
@ -25,12 +25,13 @@
|
||||||
export let meta
|
export let meta
|
||||||
export let bindings
|
export let bindings
|
||||||
export let isTestModal
|
export let isTestModal
|
||||||
|
export let context
|
||||||
|
|
||||||
$: fieldData = value[field]
|
$: fieldData = value[field]
|
||||||
|
|
||||||
$: parsedBindings = bindings.map(binding => {
|
$: parsedBindings = bindings.map(binding => {
|
||||||
let clone = Object.assign({}, binding)
|
let clone = Object.assign({}, binding)
|
||||||
clone.icon = "ShareAndroid"
|
clone.icon = clone.icon ?? "ShareAndroid"
|
||||||
return clone
|
return clone
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@ -232,6 +233,7 @@
|
||||||
actionButtonDisabled={(schema.type === FieldType.ATTACHMENT_SINGLE ||
|
actionButtonDisabled={(schema.type === FieldType.ATTACHMENT_SINGLE ||
|
||||||
schema.type === FieldType.SIGNATURE_SINGLE) &&
|
schema.type === FieldType.SIGNATURE_SINGLE) &&
|
||||||
fieldData}
|
fieldData}
|
||||||
|
{context}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
{:else}
|
{:else}
|
||||||
|
|
|
@ -1,18 +1,11 @@
|
||||||
<script>
|
<script>
|
||||||
import { Input, Select, Button } from "@budibase/bbui"
|
import { Input, Select, Button } from "@budibase/bbui"
|
||||||
import { createEventDispatcher } from "svelte"
|
import { createEventDispatcher } from "svelte"
|
||||||
|
import { memo } from "@budibase/frontend-core"
|
||||||
const dispatch = createEventDispatcher()
|
import { generate } from "shortid"
|
||||||
|
|
||||||
export let value = {}
|
export let value = {}
|
||||||
|
|
||||||
$: fieldsArray = value
|
|
||||||
? Object.entries(value).map(([name, type]) => ({
|
|
||||||
name,
|
|
||||||
type,
|
|
||||||
}))
|
|
||||||
: []
|
|
||||||
|
|
||||||
const typeOptions = [
|
const typeOptions = [
|
||||||
{
|
{
|
||||||
label: "Text",
|
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() {
|
function addField() {
|
||||||
const newValue = { ...value }
|
const newValue = { ...$memoValue.data }
|
||||||
newValue[""] = "string"
|
newValue[""] = "string"
|
||||||
dispatch("change", newValue)
|
fieldsArray = [...fieldsArray, { name: "", type: "string", id: generate() }]
|
||||||
}
|
}
|
||||||
|
|
||||||
function removeField(name) {
|
function removeField(idx) {
|
||||||
const newValues = { ...value }
|
const entries = [...fieldsArray]
|
||||||
delete newValues[name]
|
|
||||||
dispatch("change", newValues)
|
// 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 => {
|
const fieldNameChanged = originalName => e => {
|
||||||
|
@ -57,11 +76,16 @@
|
||||||
} else {
|
} else {
|
||||||
entries = entries.filter(f => f.name !== originalName)
|
entries = entries.filter(f => f.name !== originalName)
|
||||||
}
|
}
|
||||||
value = entries.reduce((newVals, current) => {
|
|
||||||
newVals[current.name.trim()] = current.type
|
const update = entries
|
||||||
return newVals
|
.filter(entry => entry.name)
|
||||||
}, {})
|
.reduce((newVals, current) => {
|
||||||
dispatch("change", value)
|
newVals[current.name.trim()] = current.type
|
||||||
|
return newVals
|
||||||
|
}, {})
|
||||||
|
if (Object.keys(update).length) {
|
||||||
|
dispatch("change", update)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
@ -69,7 +93,7 @@
|
||||||
<!-- svelte-ignore a11y-no-static-element-interactions -->
|
<!-- svelte-ignore a11y-no-static-element-interactions -->
|
||||||
<div class="root">
|
<div class="root">
|
||||||
<div class="spacer" />
|
<div class="spacer" />
|
||||||
{#each fieldsArray as field}
|
{#each fieldsArray as field, idx (field.id)}
|
||||||
<div class="field">
|
<div class="field">
|
||||||
<Input
|
<Input
|
||||||
value={field.name}
|
value={field.name}
|
||||||
|
@ -88,7 +112,9 @@
|
||||||
/>
|
/>
|
||||||
<i
|
<i
|
||||||
class="remove-field ri-delete-bin-line"
|
class="remove-field ri-delete-bin-line"
|
||||||
on:click={() => removeField(field.name)}
|
on:click={() => {
|
||||||
|
removeField(idx)
|
||||||
|
}}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
{/each}
|
{/each}
|
||||||
|
@ -115,4 +141,12 @@
|
||||||
align-items: center;
|
align-items: center;
|
||||||
gap: var(--spacing-m);
|
gap: var(--spacing-m);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.remove-field {
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.remove-field:hover {
|
||||||
|
color: var(--spectrum-global-color-gray-900);
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
|
@ -1,3 +1,10 @@
|
||||||
|
<script context="module" lang="ts">
|
||||||
|
export const DropdownPosition = {
|
||||||
|
Relative: "top",
|
||||||
|
Absolute: "right",
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { Label } from "@budibase/bbui"
|
import { Label } from "@budibase/bbui"
|
||||||
import { onMount, createEventDispatcher, onDestroy } from "svelte"
|
import { onMount, createEventDispatcher, onDestroy } from "svelte"
|
||||||
|
@ -45,6 +52,7 @@
|
||||||
import { EditorModes } from "./"
|
import { EditorModes } from "./"
|
||||||
import { themeStore } from "@/stores/portal"
|
import { themeStore } from "@/stores/portal"
|
||||||
import type { EditorMode } from "@budibase/types"
|
import type { EditorMode } from "@budibase/types"
|
||||||
|
import { tooltips } from "@codemirror/view"
|
||||||
|
|
||||||
export let label: string | undefined = undefined
|
export let label: string | undefined = undefined
|
||||||
// TODO: work out what best type fits this
|
// TODO: work out what best type fits this
|
||||||
|
@ -57,11 +65,13 @@
|
||||||
export let jsBindingWrapping = true
|
export let jsBindingWrapping = true
|
||||||
export let readonly = false
|
export let readonly = false
|
||||||
export let readonlyLineNumbers = false
|
export let readonlyLineNumbers = false
|
||||||
|
export let dropdown = DropdownPosition.Relative
|
||||||
|
|
||||||
const dispatch = createEventDispatcher()
|
const dispatch = createEventDispatcher()
|
||||||
|
|
||||||
let textarea: HTMLDivElement
|
let textarea: HTMLDivElement
|
||||||
let editor: EditorView
|
let editor: EditorView
|
||||||
|
let editorEle: HTMLDivElement
|
||||||
let mounted = false
|
let mounted = false
|
||||||
let isEditorInitialised = false
|
let isEditorInitialised = false
|
||||||
let queuedRefresh = false
|
let queuedRefresh = false
|
||||||
|
@ -112,7 +122,6 @@
|
||||||
queuedRefresh = true
|
queuedRefresh = true
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if (
|
if (
|
||||||
editor &&
|
editor &&
|
||||||
value &&
|
value &&
|
||||||
|
@ -343,14 +352,25 @@
|
||||||
const baseExtensions = buildBaseExtensions()
|
const baseExtensions = buildBaseExtensions()
|
||||||
|
|
||||||
editor = new EditorView({
|
editor = new EditorView({
|
||||||
doc: value?.toString(),
|
doc: String(value),
|
||||||
extensions: buildExtensions(baseExtensions),
|
extensions: buildExtensions([
|
||||||
|
...baseExtensions,
|
||||||
|
dropdown == DropdownPosition.Absolute
|
||||||
|
? tooltips({
|
||||||
|
position: "absolute",
|
||||||
|
})
|
||||||
|
: [],
|
||||||
|
]),
|
||||||
parent: textarea,
|
parent: textarea,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
onMount(async () => {
|
onMount(async () => {
|
||||||
mounted = true
|
mounted = true
|
||||||
|
// Capture scrolling
|
||||||
|
editorEle.addEventListener("wheel", e => {
|
||||||
|
e.stopPropagation()
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
onDestroy(() => {
|
onDestroy(() => {
|
||||||
|
@ -366,7 +386,7 @@
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
<div class={`code-editor ${mode?.name || ""}`}>
|
<div class={`code-editor ${mode?.name || ""}`} bind:this={editorEle}>
|
||||||
<div tabindex="-1" bind:this={textarea} />
|
<div tabindex="-1" bind:this={textarea} />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
@ -534,12 +554,11 @@
|
||||||
|
|
||||||
/* Live binding value / helper container */
|
/* Live binding value / helper container */
|
||||||
.code-editor :global(.cm-completionInfo) {
|
.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: 1px solid var(--spectrum-global-color-gray-300);
|
||||||
border-radius: var(--border-radius-s);
|
border-radius: var(--border-radius-s);
|
||||||
background-color: var(--spectrum-global-color-gray-50);
|
background-color: var(--spectrum-global-color-gray-50);
|
||||||
padding: var(--spacing-m);
|
padding: var(--spacing-m);
|
||||||
margin-top: -2px;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Wrapper around helpers */
|
/* Wrapper around helpers */
|
||||||
|
@ -564,6 +583,7 @@
|
||||||
white-space: pre;
|
white-space: pre;
|
||||||
text-overflow: ellipsis;
|
text-overflow: ellipsis;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
|
overflow-y: auto;
|
||||||
max-height: 480px;
|
max-height: 480px;
|
||||||
}
|
}
|
||||||
.code-editor :global(.binding__example.helper) {
|
.code-editor :global(.binding__example.helper) {
|
||||||
|
|
|
@ -61,8 +61,6 @@
|
||||||
let mode: BindingMode | null
|
let mode: BindingMode | null
|
||||||
let sidePanel: SidePanel | null
|
let sidePanel: SidePanel | null
|
||||||
let initialValueJS = value?.startsWith?.("{{ js ")
|
let initialValueJS = value?.startsWith?.("{{ js ")
|
||||||
let jsValue: string | null = initialValueJS ? value : null
|
|
||||||
let hbsValue: string | null = initialValueJS ? null : value
|
|
||||||
let getCaretPosition: CaretPositionFn | undefined
|
let getCaretPosition: CaretPositionFn | undefined
|
||||||
let insertAtPos: InsertAtPositionFn | undefined
|
let insertAtPos: InsertAtPositionFn | undefined
|
||||||
let targetMode: BindingMode | null = null
|
let targetMode: BindingMode | null = null
|
||||||
|
@ -71,6 +69,10 @@
|
||||||
let expressionError: string | undefined
|
let expressionError: string | undefined
|
||||||
let evaluating = false
|
let evaluating = false
|
||||||
|
|
||||||
|
// Ensure these values are not stale
|
||||||
|
$: jsValue = initialValueJS ? value : null
|
||||||
|
$: hbsValue = initialValueJS ? null : value
|
||||||
|
|
||||||
$: useSnippets = allowSnippets && !$licensing.isFreePlan
|
$: useSnippets = allowSnippets && !$licensing.isFreePlan
|
||||||
$: editorModeOptions = getModeOptions(allowHBS, allowJS)
|
$: editorModeOptions = getModeOptions(allowHBS, allowJS)
|
||||||
$: sidePanelOptions = getSidePanelOptions(
|
$: 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 type
|
||||||
export let schema
|
export let schema
|
||||||
|
|
||||||
|
export let allowHBS = true
|
||||||
|
export let context = {}
|
||||||
|
|
||||||
const dispatch = createEventDispatcher()
|
const dispatch = createEventDispatcher()
|
||||||
let bindingDrawer
|
let bindingDrawer
|
||||||
let currentVal = value
|
let currentVal = value
|
||||||
|
@ -147,7 +150,7 @@
|
||||||
<!-- svelte-ignore a11y-click-events-have-key-events -->
|
<!-- svelte-ignore a11y-click-events-have-key-events -->
|
||||||
<!-- svelte-ignore a11y-no-static-element-interactions -->
|
<!-- svelte-ignore a11y-no-static-element-interactions -->
|
||||||
<div class="control" class:disabled>
|
<div class="control" class:disabled>
|
||||||
{#if !isValid(value)}
|
{#if !isValid(value) && !$$slots.default}
|
||||||
<Input
|
<Input
|
||||||
{label}
|
{label}
|
||||||
{disabled}
|
{disabled}
|
||||||
|
@ -187,7 +190,6 @@
|
||||||
on:drawerShow
|
on:drawerShow
|
||||||
bind:this={bindingDrawer}
|
bind:this={bindingDrawer}
|
||||||
title={title ?? placeholder ?? "Bindings"}
|
title={title ?? placeholder ?? "Bindings"}
|
||||||
forceModal={true}
|
|
||||||
>
|
>
|
||||||
<Button cta slot="buttons" on:click={saveBinding}>Save</Button>
|
<Button cta slot="buttons" on:click={saveBinding}>Save</Button>
|
||||||
<svelte:component
|
<svelte:component
|
||||||
|
@ -197,7 +199,9 @@
|
||||||
on:change={event => (tempValue = event.detail)}
|
on:change={event => (tempValue = event.detail)}
|
||||||
{bindings}
|
{bindings}
|
||||||
{allowJS}
|
{allowJS}
|
||||||
|
{allowHBS}
|
||||||
{allowHelpers}
|
{allowHelpers}
|
||||||
|
{context}
|
||||||
/>
|
/>
|
||||||
</Drawer>
|
</Drawer>
|
||||||
|
|
||||||
|
@ -208,22 +212,22 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
.slot-icon {
|
.slot-icon {
|
||||||
right: 31px !important;
|
right: 31px;
|
||||||
border-right: 1px solid var(--spectrum-alias-border-color);
|
border-right: 1px solid var(--spectrum-alias-border-color);
|
||||||
border-top-right-radius: 0px !important;
|
border-top-right-radius: 0px;
|
||||||
border-bottom-right-radius: 0px !important;
|
border-bottom-right-radius: 0px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.text-area-slot-icon {
|
.text-area-slot-icon {
|
||||||
border-bottom: 1px solid var(--spectrum-alias-border-color);
|
border-bottom: 1px solid var(--spectrum-alias-border-color);
|
||||||
border-bottom-right-radius: 0px !important;
|
border-bottom-right-radius: 0px;
|
||||||
top: 1px !important;
|
top: 1px;
|
||||||
}
|
}
|
||||||
.json-slot-icon {
|
.json-slot-icon {
|
||||||
border-bottom: 1px solid var(--spectrum-alias-border-color);
|
border-bottom: 1px solid var(--spectrum-alias-border-color);
|
||||||
border-bottom-right-radius: 0px !important;
|
border-bottom-right-radius: 0px;
|
||||||
top: 1px !important;
|
top: 1px;
|
||||||
right: 0px !important;
|
right: 0px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.icon {
|
.icon {
|
||||||
|
|
|
@ -5,6 +5,7 @@
|
||||||
export let bindings = []
|
export let bindings = []
|
||||||
export let value = ""
|
export let value = ""
|
||||||
export let allowJS = false
|
export let allowJS = false
|
||||||
|
export let allowHBS = true
|
||||||
export let context = null
|
export let context = null
|
||||||
|
|
||||||
$: enrichedBindings = enrichBindings(bindings)
|
$: enrichedBindings = enrichBindings(bindings)
|
||||||
|
@ -22,8 +23,10 @@
|
||||||
<BindingPanel
|
<BindingPanel
|
||||||
bindings={enrichedBindings}
|
bindings={enrichedBindings}
|
||||||
snippets={$snippets}
|
snippets={$snippets}
|
||||||
|
allowHelpers
|
||||||
{value}
|
{value}
|
||||||
{allowJS}
|
{allowJS}
|
||||||
|
{allowHBS}
|
||||||
{context}
|
{context}
|
||||||
on:change
|
on:change
|
||||||
/>
|
/>
|
||||||
|
|
|
@ -16,6 +16,7 @@
|
||||||
export let datasource
|
export let datasource
|
||||||
export let builderType
|
export let builderType
|
||||||
export let docsURL
|
export let docsURL
|
||||||
|
export let evaluationContext = {}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<CoreFilterBuilder
|
<CoreFilterBuilder
|
||||||
|
@ -32,5 +33,6 @@
|
||||||
{allowOnEmpty}
|
{allowOnEmpty}
|
||||||
{builderType}
|
{builderType}
|
||||||
{docsURL}
|
{docsURL}
|
||||||
|
{evaluationContext}
|
||||||
on:change
|
on:change
|
||||||
/>
|
/>
|
||||||
|
|
|
@ -39,6 +39,7 @@
|
||||||
export let allowJS = false
|
export let allowJS = false
|
||||||
export let actionButtonDisabled = false
|
export let actionButtonDisabled = false
|
||||||
export let compare = (option, value) => option === value
|
export let compare = (option, value) => option === value
|
||||||
|
export let context = null
|
||||||
|
|
||||||
let fields = Object.entries(object || {}).map(([name, value]) => ({
|
let fields = Object.entries(object || {}).map(([name, value]) => ({
|
||||||
name,
|
name,
|
||||||
|
@ -132,6 +133,7 @@
|
||||||
{allowJS}
|
{allowJS}
|
||||||
{allowHelpers}
|
{allowHelpers}
|
||||||
drawerLeft={bindingDrawerLeft}
|
drawerLeft={bindingDrawerLeft}
|
||||||
|
{context}
|
||||||
/>
|
/>
|
||||||
{:else}
|
{:else}
|
||||||
<Input readonly={readOnly} bind:value={field.name} on:blur={changed} />
|
<Input readonly={readOnly} bind:value={field.name} on:blur={changed} />
|
||||||
|
@ -158,6 +160,7 @@
|
||||||
{allowJS}
|
{allowJS}
|
||||||
{allowHelpers}
|
{allowHelpers}
|
||||||
drawerLeft={bindingDrawerLeft}
|
drawerLeft={bindingDrawerLeft}
|
||||||
|
{context}
|
||||||
/>
|
/>
|
||||||
{:else}
|
{:else}
|
||||||
<Input
|
<Input
|
||||||
|
|
|
@ -29,7 +29,9 @@
|
||||||
let modal
|
let modal
|
||||||
let webhookModal
|
let webhookModal
|
||||||
|
|
||||||
onMount(() => {
|
onMount(async () => {
|
||||||
|
await automationStore.actions.initAppSelf()
|
||||||
|
|
||||||
$automationStore.showTestPanel = false
|
$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 { API } from "@/api"
|
||||||
import { cloneDeep } from "lodash/fp"
|
import { cloneDeep } from "lodash/fp"
|
||||||
import { generate } from "shortid"
|
import { generate } from "shortid"
|
||||||
import { createHistoryStore } from "@/stores/builder/history"
|
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 { tables, appStore } from "@/stores/builder"
|
||||||
import { notifications } from "@budibase/bbui"
|
import { notifications } from "@budibase/bbui"
|
||||||
import {
|
import {
|
||||||
|
@ -32,8 +32,10 @@ import {
|
||||||
BlockDefinitions,
|
BlockDefinitions,
|
||||||
GetAutomationTriggerDefinitionsResponse,
|
GetAutomationTriggerDefinitionsResponse,
|
||||||
GetAutomationActionDefinitionsResponse,
|
GetAutomationActionDefinitionsResponse,
|
||||||
|
AppSelfResponse,
|
||||||
|
TestAutomationResponse,
|
||||||
} from "@budibase/types"
|
} from "@budibase/types"
|
||||||
import { ActionStepID } from "@/constants/backend/automations"
|
import { ActionStepID, TriggerStepID } from "@/constants/backend/automations"
|
||||||
import { FIELDS } from "@/constants/backend"
|
import { FIELDS } from "@/constants/backend"
|
||||||
import { sdk } from "@budibase/shared-core"
|
import { sdk } from "@budibase/shared-core"
|
||||||
import { rowActions } from "./rowActions"
|
import { rowActions } from "./rowActions"
|
||||||
|
@ -43,10 +45,11 @@ import { BudiStore, DerivedBudiStore } from "@/stores/BudiStore"
|
||||||
|
|
||||||
interface AutomationState {
|
interface AutomationState {
|
||||||
automations: Automation[]
|
automations: Automation[]
|
||||||
testResults: any | null
|
testResults?: TestAutomationResponse
|
||||||
showTestPanel: boolean
|
showTestPanel: boolean
|
||||||
blockDefinitions: BlockDefinitions
|
blockDefinitions: BlockDefinitions
|
||||||
selectedAutomationId: string | null
|
selectedAutomationId: string | null
|
||||||
|
appSelf?: AppSelfResponse
|
||||||
}
|
}
|
||||||
|
|
||||||
interface DerivedAutomationState extends AutomationState {
|
interface DerivedAutomationState extends AutomationState {
|
||||||
|
@ -56,7 +59,6 @@ interface DerivedAutomationState extends AutomationState {
|
||||||
|
|
||||||
const initialAutomationState: AutomationState = {
|
const initialAutomationState: AutomationState = {
|
||||||
automations: [],
|
automations: [],
|
||||||
testResults: null,
|
|
||||||
showTestPanel: false,
|
showTestPanel: false,
|
||||||
blockDefinitions: {
|
blockDefinitions: {
|
||||||
TRIGGER: {},
|
TRIGGER: {},
|
||||||
|
@ -88,6 +90,20 @@ const getFinalDefinitions = (
|
||||||
}
|
}
|
||||||
|
|
||||||
const automationActions = (store: AutomationStore) => ({
|
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.
|
* 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
|
* Build a sequential list of all steps on the step path provided
|
||||||
*
|
*
|
||||||
* @param {Array<Object>} pathWay e.g. [{stepIdx:2},{branchIdx:0, stepIdx:2},...]
|
* @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
|
// Base Steps, including trigger
|
||||||
const steps = [
|
const steps = [
|
||||||
automation.definition.trigger,
|
automation.definition.trigger,
|
||||||
|
@ -533,18 +552,24 @@ const automationActions = (store: AutomationStore) => ({
|
||||||
icon: string,
|
icon: string,
|
||||||
idx: number,
|
idx: number,
|
||||||
isLoopBlock: boolean,
|
isLoopBlock: boolean,
|
||||||
bindingName?: string
|
pathBlock: AutomationStep | AutomationTrigger,
|
||||||
|
bindingName: string
|
||||||
) => {
|
) => {
|
||||||
if (!name) return
|
if (!name) return
|
||||||
const runtimeBinding = store.actions.determineRuntimeBinding(
|
const runtimeBinding = store.actions.determineRuntimeBinding(
|
||||||
name,
|
name,
|
||||||
idx,
|
idx,
|
||||||
isLoopBlock,
|
isLoopBlock,
|
||||||
bindingName,
|
|
||||||
automation,
|
automation,
|
||||||
currentBlock,
|
currentBlock,
|
||||||
pathSteps
|
pathSteps
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const readableBinding = store.actions.determineReadableBinding(
|
||||||
|
name,
|
||||||
|
pathBlock
|
||||||
|
)
|
||||||
|
|
||||||
const categoryName = store.actions.determineCategoryName(
|
const categoryName = store.actions.determineCategoryName(
|
||||||
idx,
|
idx,
|
||||||
isLoopBlock,
|
isLoopBlock,
|
||||||
|
@ -561,7 +586,8 @@ const automationActions = (store: AutomationStore) => ({
|
||||||
isLoopBlock,
|
isLoopBlock,
|
||||||
runtimeBinding,
|
runtimeBinding,
|
||||||
categoryName,
|
categoryName,
|
||||||
bindingName
|
bindingName,
|
||||||
|
readableBinding
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -636,7 +662,15 @@ const automationActions = (store: AutomationStore) => ({
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Object.entries(schema).forEach(([name, value]) => {
|
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
|
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: (
|
determineRuntimeBinding: (
|
||||||
name: string,
|
name: string,
|
||||||
idx: number,
|
idx: number,
|
||||||
isLoopBlock: boolean,
|
isLoopBlock: boolean,
|
||||||
bindingName: string | undefined,
|
|
||||||
automation: Automation,
|
automation: Automation,
|
||||||
currentBlock: AutomationStep | AutomationTrigger | undefined,
|
currentBlock: AutomationStep | AutomationTrigger | undefined,
|
||||||
pathSteps: (AutomationStep | AutomationTrigger)[]
|
pathSteps: (AutomationStep | AutomationTrigger)[]
|
||||||
) => {
|
) => {
|
||||||
let runtimeName: string | null
|
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 */
|
/* Begin special cases for generating custom schemas based on triggers */
|
||||||
if (
|
if (
|
||||||
idx === 0 &&
|
idx === 0 &&
|
||||||
automation.definition.trigger?.event === AutomationEventType.APP_TRIGGER
|
automation.definition.trigger?.event === AutomationEventType.APP_TRIGGER
|
||||||
) {
|
) {
|
||||||
return `trigger.fields.${name}`
|
return isJSScript
|
||||||
|
? `trigger.fields["${name}"]`
|
||||||
|
: `trigger.fields.[${name}]`
|
||||||
}
|
}
|
||||||
|
|
||||||
if (
|
if (
|
||||||
|
@ -673,18 +744,17 @@ const automationActions = (store: AutomationStore) => ({
|
||||||
automation.definition.trigger?.event === AutomationEventType.ROW_SAVE)
|
automation.definition.trigger?.event === AutomationEventType.ROW_SAVE)
|
||||||
) {
|
) {
|
||||||
let noRowKeywordBindings = ["id", "revision", "oldRow"]
|
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 */
|
/* End special cases for generating custom schemas based on triggers */
|
||||||
|
|
||||||
if (isLoopBlock) {
|
if (isLoopBlock) {
|
||||||
runtimeName = `loop.${name}`
|
runtimeName = `loop.${name}`
|
||||||
} else if (idx === 0) {
|
} else if (idx === 0) {
|
||||||
runtimeName = `trigger.${name}`
|
runtimeName = `trigger.[${name}]`
|
||||||
} else if (
|
} else if (isJSScript) {
|
||||||
currentBlock?.stepId === AutomationActionStepId.EXECUTE_SCRIPT ||
|
|
||||||
currentBlock?.stepId === AutomationActionStepId.EXECUTE_SCRIPT_V2
|
|
||||||
) {
|
|
||||||
const stepId = pathSteps[idx].id
|
const stepId = pathSteps[idx].id
|
||||||
if (!stepId) {
|
if (!stepId) {
|
||||||
notifications.error("Error generating binding: Step ID not found.")
|
notifications.error("Error generating binding: Step ID not found.")
|
||||||
|
@ -725,18 +795,22 @@ const automationActions = (store: AutomationStore) => ({
|
||||||
isLoopBlock: boolean,
|
isLoopBlock: boolean,
|
||||||
runtimeBinding: string | null,
|
runtimeBinding: string | null,
|
||||||
categoryName: string,
|
categoryName: string,
|
||||||
bindingName?: string
|
bindingName?: string,
|
||||||
|
readableBinding?: string
|
||||||
) => {
|
) => {
|
||||||
const field = Object.values(FIELDS).find(
|
const field = Object.values(FIELDS).find(
|
||||||
field =>
|
field =>
|
||||||
field.type === value.type &&
|
field.type === value.type &&
|
||||||
("subtype" in field ? field.subtype === value.subtype : true)
|
("subtype" in field ? field.subtype === value.subtype : true)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const readableBindingDefault =
|
||||||
|
bindingName && !isLoopBlock && idx !== 0
|
||||||
|
? `steps.${bindingName}.${name}`
|
||||||
|
: runtimeBinding
|
||||||
|
|
||||||
return {
|
return {
|
||||||
readableBinding:
|
readableBinding: readableBinding || readableBindingDefault,
|
||||||
bindingName && !isLoopBlock && idx !== 0
|
|
||||||
? `steps.${bindingName}.${name}`
|
|
||||||
: runtimeBinding,
|
|
||||||
runtimeBinding,
|
runtimeBinding,
|
||||||
type: value.type,
|
type: value.type,
|
||||||
description: value.description,
|
description: value.description,
|
||||||
|
@ -801,7 +875,7 @@ const automationActions = (store: AutomationStore) => ({
|
||||||
},
|
},
|
||||||
|
|
||||||
test: async (automation: Automation, testData: any) => {
|
test: async (automation: Automation, testData: any) => {
|
||||||
let result: any
|
let result: TestAutomationResponse
|
||||||
try {
|
try {
|
||||||
result = await API.testAutomation(automation._id!, testData)
|
result = await API.testAutomation(automation._id!, testData)
|
||||||
} catch (err: any) {
|
} catch (err: any) {
|
||||||
|
@ -1395,7 +1469,7 @@ const automationActions = (store: AutomationStore) => ({
|
||||||
}
|
}
|
||||||
store.update(state => {
|
store.update(state => {
|
||||||
state.selectedAutomationId = id
|
state.selectedAutomationId = id
|
||||||
state.testResults = null
|
delete state.testResults
|
||||||
state.showTestPanel = false
|
state.showTestPanel = false
|
||||||
return state
|
return state
|
||||||
})
|
})
|
||||||
|
@ -1521,3 +1595,86 @@ export class SelectedAutomationStore extends DerivedBudiStore<
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
export const selectedAutomation = new SelectedAutomationStore(automationStore)
|
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,
|
automationStore,
|
||||||
selectedAutomation,
|
selectedAutomation,
|
||||||
automationHistoryStore,
|
automationHistoryStore,
|
||||||
|
evaluationContext,
|
||||||
} from "./automations.js"
|
} from "./automations.js"
|
||||||
import { userStore, userSelectedResourceMap, isOnlyUser } from "./users.js"
|
import { userStore, userSelectedResourceMap, isOnlyUser } from "./users.js"
|
||||||
import { deploymentStore } from "./deployments.js"
|
import { deploymentStore } from "./deployments.js"
|
||||||
|
@ -67,6 +68,7 @@ export {
|
||||||
snippets,
|
snippets,
|
||||||
rowActions,
|
rowActions,
|
||||||
appPublished,
|
appPublished,
|
||||||
|
evaluationContext,
|
||||||
}
|
}
|
||||||
|
|
||||||
export const reset = () => {
|
export const reset = () => {
|
||||||
|
|
|
@ -10,6 +10,7 @@
|
||||||
export let drawerTitle
|
export let drawerTitle
|
||||||
export let toReadable
|
export let toReadable
|
||||||
export let toRuntime
|
export let toRuntime
|
||||||
|
export let evaluationContext = {}
|
||||||
|
|
||||||
const dispatch = createEventDispatcher()
|
const dispatch = createEventDispatcher()
|
||||||
|
|
||||||
|
@ -66,7 +67,6 @@
|
||||||
>
|
>
|
||||||
Confirm
|
Confirm
|
||||||
</Button>
|
</Button>
|
||||||
|
|
||||||
<svelte:component
|
<svelte:component
|
||||||
this={panel}
|
this={panel}
|
||||||
slot="body"
|
slot="body"
|
||||||
|
@ -76,6 +76,7 @@
|
||||||
allowHBS
|
allowHBS
|
||||||
on:change={drawerOnChange}
|
on:change={drawerOnChange}
|
||||||
{bindings}
|
{bindings}
|
||||||
|
context={evaluationContext}
|
||||||
/>
|
/>
|
||||||
</Drawer>
|
</Drawer>
|
||||||
|
|
||||||
|
|
|
@ -42,6 +42,7 @@
|
||||||
export let panel
|
export let panel
|
||||||
export let toReadable
|
export let toReadable
|
||||||
export let toRuntime
|
export let toRuntime
|
||||||
|
export let evaluationContext = {}
|
||||||
|
|
||||||
$: editableFilters = migrateFilters(filters)
|
$: editableFilters = migrateFilters(filters)
|
||||||
$: {
|
$: {
|
||||||
|
@ -385,6 +386,7 @@
|
||||||
{panel}
|
{panel}
|
||||||
{toReadable}
|
{toReadable}
|
||||||
{toRuntime}
|
{toRuntime}
|
||||||
|
{evaluationContext}
|
||||||
on:change={e => {
|
on:change={e => {
|
||||||
const updated = {
|
const updated = {
|
||||||
...filter,
|
...filter,
|
||||||
|
@ -423,6 +425,7 @@
|
||||||
{panel}
|
{panel}
|
||||||
{toReadable}
|
{toReadable}
|
||||||
{toRuntime}
|
{toRuntime}
|
||||||
|
{evaluationContext}
|
||||||
on:change={e => {
|
on:change={e => {
|
||||||
onFilterFieldUpdate(
|
onFilterFieldUpdate(
|
||||||
{ ...filter, ...e.detail },
|
{ ...filter, ...e.detail },
|
||||||
|
|
|
@ -24,6 +24,7 @@
|
||||||
export let drawerTitle
|
export let drawerTitle
|
||||||
export let toReadable
|
export let toReadable
|
||||||
export let toRuntime
|
export let toRuntime
|
||||||
|
export let evaluationContext = {}
|
||||||
|
|
||||||
const dispatch = createEventDispatcher()
|
const dispatch = createEventDispatcher()
|
||||||
const { OperatorOptions, FilterValueType } = Constants
|
const { OperatorOptions, FilterValueType } = Constants
|
||||||
|
@ -156,6 +157,7 @@
|
||||||
allowHBS
|
allowHBS
|
||||||
on:change={drawerOnChange}
|
on:change={drawerOnChange}
|
||||||
{bindings}
|
{bindings}
|
||||||
|
context={evaluationContext}
|
||||||
/>
|
/>
|
||||||
</Drawer>
|
</Drawer>
|
||||||
|
|
||||||
|
|
|
@ -21,6 +21,7 @@ import {
|
||||||
AutomationRowEvent,
|
AutomationRowEvent,
|
||||||
UserBindings,
|
UserBindings,
|
||||||
AutomationResults,
|
AutomationResults,
|
||||||
|
DidNotTriggerResponse,
|
||||||
} from "@budibase/types"
|
} from "@budibase/types"
|
||||||
import { executeInThread } from "../threads/automation"
|
import { executeInThread } from "../threads/automation"
|
||||||
import { dataFilters, sdk } from "@budibase/shared-core"
|
import { dataFilters, sdk } from "@budibase/shared-core"
|
||||||
|
@ -33,14 +34,6 @@ const JOB_OPTS = {
|
||||||
import * as automationUtils from "../automations/automationUtils"
|
import * as automationUtils from "../automations/automationUtils"
|
||||||
import { doesTableExist } from "../sdk/app/tables/getters"
|
import { doesTableExist } from "../sdk/app/tables/getters"
|
||||||
|
|
||||||
type DidNotTriggerResponse = {
|
|
||||||
outputs: {
|
|
||||||
success: false
|
|
||||||
status: AutomationStatus.STOPPED
|
|
||||||
}
|
|
||||||
message: AutomationStoppedReason.TRIGGER_FILTER_NOT_MET
|
|
||||||
}
|
|
||||||
|
|
||||||
async function getAllAutomations() {
|
async function getAllAutomations() {
|
||||||
const db = context.getAppDB()
|
const db = context.getAppDB()
|
||||||
let automations = await db.allDocs<Automation>(
|
let automations = await db.allDocs<Automation>(
|
||||||
|
|
|
@ -1,7 +1,9 @@
|
||||||
|
import { AutomationJob, DidNotTriggerResponse } from "../../../sdk/automations"
|
||||||
import {
|
import {
|
||||||
Automation,
|
Automation,
|
||||||
AutomationActionStepId,
|
AutomationActionStepId,
|
||||||
AutomationLogPage,
|
AutomationLogPage,
|
||||||
|
AutomationResults,
|
||||||
AutomationStatus,
|
AutomationStatus,
|
||||||
AutomationStepDefinition,
|
AutomationStepDefinition,
|
||||||
AutomationTriggerDefinition,
|
AutomationTriggerDefinition,
|
||||||
|
@ -74,4 +76,8 @@ export interface TestAutomationRequest {
|
||||||
fields: Record<string, any>
|
fields: Record<string, any>
|
||||||
row?: Row
|
row?: Row
|
||||||
}
|
}
|
||||||
export interface TestAutomationResponse {}
|
|
||||||
|
export type TestAutomationResponse =
|
||||||
|
| AutomationResults
|
||||||
|
| DidNotTriggerResponse
|
||||||
|
| AutomationJob
|
||||||
|
|
|
@ -5,6 +5,7 @@ import { ReadStream } from "fs"
|
||||||
import { Row } from "../row"
|
import { Row } from "../row"
|
||||||
import { Table } from "../table"
|
import { Table } from "../table"
|
||||||
import { AutomationStep, AutomationTrigger } from "./schema"
|
import { AutomationStep, AutomationTrigger } from "./schema"
|
||||||
|
import { StepOutputs, TriggerOutputs } from "./StepInputsOutputs"
|
||||||
|
|
||||||
export enum AutomationIOType {
|
export enum AutomationIOType {
|
||||||
OBJECT = "object",
|
OBJECT = "object",
|
||||||
|
@ -194,7 +195,7 @@ export enum AutomationStoppedReason {
|
||||||
export interface AutomationResults {
|
export interface AutomationResults {
|
||||||
automationId?: string
|
automationId?: string
|
||||||
status?: AutomationStatus
|
status?: AutomationStatus
|
||||||
trigger?: AutomationTrigger
|
trigger?: AutomationTrigger & { outputs: TriggerOutputs }
|
||||||
steps: {
|
steps: {
|
||||||
stepId: AutomationTriggerStepId | AutomationActionStepId
|
stepId: AutomationTriggerStepId | AutomationActionStepId
|
||||||
inputs: {
|
inputs: {
|
||||||
|
|
|
@ -1,6 +1,8 @@
|
||||||
import {
|
import {
|
||||||
Automation,
|
Automation,
|
||||||
AutomationMetadata,
|
AutomationMetadata,
|
||||||
|
AutomationStatus,
|
||||||
|
AutomationStoppedReason,
|
||||||
Row,
|
Row,
|
||||||
UserBindings,
|
UserBindings,
|
||||||
} from "../../documents"
|
} from "../../documents"
|
||||||
|
@ -29,3 +31,11 @@ export interface AutomationRowEvent {
|
||||||
}
|
}
|
||||||
|
|
||||||
export type AutomationJob = Job<AutomationData>
|
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