budibase/packages/builder/src/components/automation/SetupPanel/AutomationBlockSetup.svelte

401 lines
12 KiB
Svelte
Raw Normal View History

<script>
import TableSelector from "./TableSelector.svelte"
import RowSelector from "./RowSelector.svelte"
import FieldSelector from "./FieldSelector.svelte"
import SchemaSetup from "./SchemaSetup.svelte"
2021-09-16 22:15:09 +02:00
import {
Button,
Input,
Select,
Label,
ActionButton,
Drawer,
2021-10-11 20:38:43 +02:00
Modal,
Detail,
notifications,
2021-09-16 22:15:09 +02:00
} from "@budibase/bbui"
2021-10-11 20:38:43 +02:00
import CreateWebhookModal from "components/automation/Shared/CreateWebhookModal.svelte"
import { automationStore } from "builderStore"
2021-09-16 22:15:09 +02:00
import { tables } from "stores/backend"
import WebhookDisplay from "../Shared/WebhookDisplay.svelte"
import DrawerBindableInput from "../../common/bindings/DrawerBindableInput.svelte"
import AutomationBindingPanel from "../../common/bindings/ServerBindingPanel.svelte"
import CodeEditorModal from "./CodeEditorModal.svelte"
import QuerySelector from "./QuerySelector.svelte"
import QueryParamSelector from "./QueryParamSelector.svelte"
2021-05-18 23:20:41 +02:00
import CronBuilder from "./CronBuilder.svelte"
import Editor from "components/integration/QueryEditor.svelte"
2021-09-16 22:15:09 +02:00
import ModalBindableInput from "components/common/bindings/ModalBindableInput.svelte"
2022-04-26 15:22:32 +02:00
import FilterDrawer from "components/design/settings/controls/FilterEditor/FilterDrawer.svelte"
import { LuceneUtils } from "@budibase/frontend-core"
import { getSchemaForTable } from "builderStore/dataBinding"
import { Utils } from "@budibase/frontend-core"
import { TriggerStepID, ActionStepID } from "constants/backend/automations"
import { cloneDeep } from "lodash/fp"
export let block
2021-09-16 22:15:09 +02:00
export let testData
export let schemaProperties
export let isTestModal = false
2021-10-11 20:38:43 +02:00
let webhookModal
2021-09-16 22:15:09 +02:00
let drawer
let fillWidth = true
let codeBindingOpen = false
let inputData
2021-10-11 20:38:43 +02:00
$: filters = lookForFilters(schemaProperties) || []
$: tempFilters = filters
$: stepId = block.stepId
$: bindings = getAvailableBindings(
2021-09-16 22:15:09 +02:00
block || $automationStore.selectedBlock,
$automationStore.selectedAutomation?.automation?.definition
)
$: getInputData(testData, block.inputs)
const getInputData = (testData, blockInputs) => {
let newInputData = testData || blockInputs
if (block.event === "app:trigger" && !newInputData?.fields) {
newInputData = cloneDeep(blockInputs)
}
inputData = newInputData
}
2021-09-16 22:15:09 +02:00
$: tableId = inputData ? inputData.tableId : null
$: table = tableId
? $tables.list.find(table => table._id === inputData.tableId)
: { schema: {} }
$: schema = getSchemaForTable(tableId, { searchableSchema: true }).schema
$: schemaFields = Object.values(schema || {})
$: queryLimit = tableId?.includes("datasource") ? "∞" : "1000"
$: isTrigger = block?.type === "TRIGGER"
2021-09-16 22:15:09 +02:00
const onChange = Utils.sequential(async (e, key) => {
try {
if (isTestModal) {
// Special case for webhook, as it requires a body, but the schema already brings back the body's contents
if (stepId === TriggerStepID.WEBHOOK) {
automationStore.actions.addTestDataToAutomation({
body: {
[key]: e.detail,
2022-08-10 18:09:51 +02:00
...$automationStore.selectedAutomation.automation.testData?.body,
},
})
}
2021-10-12 12:00:49 +02:00
automationStore.actions.addTestDataToAutomation({
[key]: e.detail,
2021-10-12 12:00:49 +02:00
})
testData[key] = e.detail
} else {
block.inputs[key] = e.detail
2021-09-16 22:15:09 +02:00
}
await automationStore.actions.save(
$automationStore.selectedAutomation?.automation
)
} catch (error) {
notifications.error("Error saving automation")
2021-11-17 14:49:34 +01:00
}
})
function getAvailableBindings(block, automation) {
if (!block || !automation) {
return []
}
// Find previous steps to the selected one
let allSteps = [...automation.steps]
2022-04-11 11:26:59 +02:00
if (automation.trigger) {
allSteps = [automation.trigger, ...allSteps]
}
2022-04-11 11:26:59 +02:00
let blockIdx = allSteps.findIndex(step => step.id === block.id)
2022-04-11 11:26:59 +02:00
// Extract all outputs from all previous steps as available bindins
let bindings = []
2022-09-23 14:35:27 +02:00
let loopBlockCount = 0
for (let idx = 0; idx < blockIdx; idx++) {
2022-09-23 14:35:27 +02:00
let wasLoopBlock = allSteps[idx - 1]?.stepId === ActionStepID.LOOP
2022-04-12 00:10:29 +02:00
let isLoopBlock =
allSteps[idx]?.stepId === ActionStepID.LOOP &&
2022-04-12 00:10:29 +02:00
allSteps.find(x => x.blockToLoop === block.id)
// If the previous block was a loop block, decrement the index so the following
2022-04-12 00:10:29 +02:00
// steps are in the correct order
if (wasLoopBlock) {
2022-09-23 14:35:27 +02:00
loopBlockCount++
continue
2022-04-12 00:10:29 +02:00
}
2022-04-11 11:26:59 +02:00
let schema = allSteps[idx]?.schema?.outputs?.properties ?? {}
2022-04-12 00:10:29 +02:00
// If its a Loop Block, we need to add this custom schema
2022-04-11 11:26:59 +02:00
if (isLoopBlock) {
schema = {
currentItem: {
type: "string",
description: "the item currently being executed",
},
}
}
const outputs = Object.entries(schema)
bindings = bindings.concat(
outputs.map(([name, value]) => {
2022-04-11 11:26:59 +02:00
let runtimeName = isLoopBlock
? `loop.${name}`
: block.name.startsWith("JS")
2022-09-23 14:35:27 +02:00
? `steps[${idx - loopBlockCount}].${name}`
: `steps.${idx - loopBlockCount}.${name}`
2022-03-29 11:29:51 +02:00
const runtime = idx === 0 ? `trigger.${name}` : runtimeName
return {
label: runtime,
type: value.type,
description: value.description,
2022-04-11 11:26:59 +02:00
category:
idx === 0
? "Trigger outputs"
: isLoopBlock
? "Loop Outputs"
2022-09-23 14:35:27 +02:00
: `Step ${idx - loopBlockCount} outputs`,
path: runtime,
}
})
)
}
2022-03-29 11:29:51 +02:00
return bindings
}
2021-09-16 22:15:09 +02:00
function lookForFilters(properties) {
if (!properties) {
return []
}
let filters
const inputs = testData ? testData : block.inputs
for (let [key, field] of properties) {
// need to look for the builder definition (keyed separately, see saveFilters)
const defKey = `${key}-def`
if (field.customType === "filters" && inputs?.[defKey]) {
filters = inputs[defKey]
break
}
}
return filters || []
}
function saveFilters(key) {
const filters = LuceneUtils.buildLuceneQuery(tempFilters)
2021-09-16 22:15:09 +02:00
const defKey = `${key}-def`
inputData[key] = filters
inputData[defKey] = tempFilters
onChange({ detail: filters }, key)
// need to store the builder definition in the automation
onChange({ detail: tempFilters }, defKey)
drawer.hide()
}
</script>
<div class="fields">
2021-09-16 22:15:09 +02:00
{#each schemaProperties as [key, value]}
<div class="block-field">
{#if key !== "fields"}
<Label
tooltip={value.title === "Binding / Value"
? "If using the String input type, please use a comma or newline separated string"
: null}>{value.title || (key === "row" ? "Table" : key)}</Label
>
{/if}
2021-05-04 10:55:14 +02:00
{#if value.type === "string" && value.enum}
<Select
2021-09-16 22:15:09 +02:00
on:change={e => onChange(e, key)}
value={inputData[key]}
options={value.enum}
2021-05-04 10:55:14 +02:00
getOptionLabel={(x, idx) => (value.pretty ? value.pretty[idx] : x)}
/>
2021-09-16 22:15:09 +02:00
{:else if value.customType === "column"}
<Select
on:change={e => onChange(e, key)}
value={inputData[key]}
options={Object.keys(table?.schema || {})}
2021-09-16 22:15:09 +02:00
/>
{:else if value.customType === "filters"}
<ActionButton on:click={drawer.show}>Define filters</ActionButton>
<Drawer bind:this={drawer} {fillWidth} title="Filtering">
<Button cta slot="buttons" on:click={() => saveFilters(key)}>
Save
</Button>
2021-09-16 22:15:09 +02:00
<FilterDrawer
slot="body"
{filters}
2021-09-16 22:15:09 +02:00
{bindings}
{schemaFields}
panel={AutomationBindingPanel}
2022-09-23 14:35:27 +02:00
fillWidth
on:change={e => (tempFilters = e.detail)}
2021-09-16 22:15:09 +02:00
/>
</Drawer>
2021-05-04 10:55:14 +02:00
{:else if value.customType === "password"}
2021-09-16 22:15:09 +02:00
<Input
type="password"
on:change={e => onChange(e, key)}
value={inputData[key]}
/>
2021-09-16 22:15:09 +02:00
{:else if value.customType === "email"}
{#if isTestModal}
<ModalBindableInput
title={value.title}
value={inputData[key]}
panel={AutomationBindingPanel}
type="email"
on:change={e => onChange(e, key)}
{bindings}
fillWidth
updateOnChange={false}
2021-09-16 22:15:09 +02:00
/>
{:else}
<DrawerBindableInput
fillWidth
title={value.title}
panel={AutomationBindingPanel}
type="email"
value={inputData[key]}
on:change={e => onChange(e, key)}
{bindings}
allowJS={false}
updateOnChange={false}
drawerLeft="260px"
2021-09-16 22:15:09 +02:00
/>
{/if}
2021-05-10 15:50:37 +02:00
{:else if value.customType === "query"}
2021-09-16 22:15:09 +02:00
<QuerySelector
on:change={e => onChange(e, key)}
value={inputData[key]}
/>
2021-05-18 23:20:41 +02:00
{:else if value.customType === "cron"}
2021-09-16 22:15:09 +02:00
<CronBuilder on:change={e => onChange(e, key)} value={inputData[key]} />
2021-05-10 15:50:37 +02:00
{:else if value.customType === "queryParams"}
2021-09-16 22:15:09 +02:00
<QueryParamSelector
on:change={e => onChange(e, key)}
value={inputData[key]}
{bindings}
/>
{:else if value.customType === "table"}
2021-09-16 22:15:09 +02:00
<TableSelector
{isTrigger}
2021-09-16 22:15:09 +02:00
value={inputData[key]}
on:change={e => onChange(e, key)}
/>
2021-05-04 10:55:14 +02:00
{:else if value.customType === "row"}
2021-09-16 22:15:09 +02:00
<RowSelector
2022-02-15 14:03:24 +01:00
{block}
2021-09-16 22:15:09 +02:00
value={inputData[key]}
on:change={e => onChange(e, key)}
{bindings}
2022-04-29 12:37:40 +02:00
{isTestModal}
2021-09-16 22:15:09 +02:00
/>
2021-05-04 10:55:14 +02:00
{:else if value.customType === "webhookUrl"}
2021-10-11 20:38:43 +02:00
<WebhookDisplay
on:change={e => onChange(e, key)}
value={inputData[key]}
/>
{:else if value.customType === "fields"}
<FieldSelector
{block}
value={inputData[key]}
on:change={e => onChange(e, key)}
{bindings}
{isTestModal}
/>
2021-05-04 10:55:14 +02:00
{:else if value.customType === "triggerSchema"}
<SchemaSetup on:change={e => onChange(e, key)} value={inputData[key]} />
{:else if value.customType === "code"}
<CodeEditorModal>
<ActionButton
on:click={() => (codeBindingOpen = !codeBindingOpen)}
quiet
icon={codeBindingOpen ? "ChevronDown" : "ChevronRight"}
>
<Detail size="S">Bindings</Detail>
</ActionButton>
{#if codeBindingOpen}
<pre>{JSON.stringify(bindings, null, 2)}</pre>
{/if}
<Editor
mode="javascript"
2021-05-10 15:50:37 +02:00
on:change={e => {
// need to pass without the value inside
onChange({ detail: e.detail.value }, key)
2021-09-16 22:15:09 +02:00
inputData[key] = e.detail.value
}}
2021-09-16 22:15:09 +02:00
value={inputData[key]}
/>
</CodeEditorModal>
2022-03-25 10:26:55 +01:00
{:else if value.customType === "loopOption"}
<Select
on:change={e => onChange(e, key)}
autoWidth
value={inputData[key]}
options={["Array", "String"]}
defaultValue={"Array"}
/>
{:else if value.type === "string" || value.type === "number" || value.type === "integer"}
2021-09-16 22:15:09 +02:00
{#if isTestModal}
<ModalBindableInput
title={value.title}
value={inputData[key]}
panel={AutomationBindingPanel}
type={value.customType}
on:change={e => onChange(e, key)}
{bindings}
updateOnChange={false}
2021-09-16 22:15:09 +02:00
/>
{:else}
<div class="test">
<DrawerBindableInput
fillWidth={true}
title={value.title}
panel={AutomationBindingPanel}
type={value.customType}
value={inputData[key]}
on:change={e => onChange(e, key)}
{bindings}
updateOnChange={false}
placeholder={value.customType === "queryLimit" ? queryLimit : ""}
drawerLeft="260px"
2021-09-16 22:15:09 +02:00
/>
</div>
{/if}
{/if}
</div>
{/each}
</div>
2021-10-11 20:38:43 +02:00
<Modal bind:this={webhookModal} width="30%">
<CreateWebhookModal />
</Modal>
{#if stepId === TriggerStepID.WEBHOOK}
<Button secondary on:click={() => webhookModal.show()}>Set Up Webhook</Button>
{/if}
<style>
.fields {
display: flex;
flex-direction: column;
justify-content: flex-start;
align-items: stretch;
gap: var(--spacing-s);
}
.block-field {
display: grid;
grid-gap: 5px;
}
2020-06-04 20:27:25 +02:00
2021-09-16 22:15:09 +02:00
.test :global(.drawer) {
width: 10000px !important;
}
</style>