Merge commit

This commit is contained in:
Dean 2024-09-19 17:21:45 +01:00
parent 61bce0163c
commit 8837cb932f
4 changed files with 439 additions and 11 deletions

View File

@ -12,6 +12,20 @@
import { Icon, notifications, Modal, Toggle } from "@budibase/bbui"
import { ActionStepID } from "constants/backend/automations"
import UndoRedoControl from "components/common/UndoRedoControl.svelte"
import StepNode from "./StepNode.svelte"
// Test test test
import { FIELDS } from "constants/backend"
import { tables } from "stores/builder"
import { AutomationEventType } from "@budibase/types"
import { writable } from "svelte/store"
import { setContext } from "svelte"
const test = writable({
someupdate: () => {
console.log("updated")
},
})
export let automation
@ -19,6 +33,7 @@
let confirmDeleteDialog
let scrolling = false
$: blocks = getBlocks(automation).filter(x => x.stepId !== ActionStepID.LOOP)
const getBlocks = automation => {
let blocks = []
if (automation.definition.trigger) {
@ -49,6 +64,14 @@
<!-- svelte-ignore a11y-click-events-have-key-events -->
<!-- svelte-ignore a11y-no-static-element-interactions -->
<div class="header" class:scrolling>
<button
on:click={() => {
automationStore.actions.traverse($selectedAutomation)
console.log($automationStore)
}}
>
TEST
</button>
<div class="header-left">
<UndoRedoControl store={automationHistoryStore} />
</div>
@ -89,7 +112,21 @@
</div>
<div class="canvas" on:scroll={handleScroll}>
<div class="content">
{#each blocks as block, idx (block.id)}
<!--
Separate out the Trigger node?
-->
<div class="root">
{#each blocks as block, idx (block.id)}
<StepNode
step={blocks[idx]}
stepIdx={idx}
isLast={blocks?.length - 1 === idx}
{automation}
/>
{/each}
</div>
<!-- {#each blocks as block, idx (block.id)}
<div
class="block"
animate:flip={{ duration: 500 }}
@ -100,7 +137,7 @@
<FlowItem {testDataModal} {block} {idx} />
{/if}
</div>
{/each}
{/each} -->
</div>
</div>
<ConfirmDialog
@ -133,18 +170,36 @@
padding-bottom: 40px;
}
.block {
.root {
display: inline-flex;
flex-direction: column;
align-items: center;
}
.root :global(.block) {
display: flex;
flex-direction: column;
justify-content: flex-start;
align-items: center;
}
.root :global(.blockSection) {
width: 100%;
box-sizing: border-box;
}
/* .block {
display: flex;
flex-direction: column;
justify-content: flex-start;
align-items: center;
} */
.content {
flex-grow: 1;
padding: 23px 23px 80px;
box-sizing: border-box;
overflow-x: hidden;
/* overflow-x: hidden; */
}
.header.scrolling {
@ -163,10 +218,12 @@
flex: 0 0 48px;
padding-right: var(--spacing-xl);
}
.controls {
display: flex;
gap: var(--spacing-xl);
}
.buttons {
display: flex;
justify-content: flex-end;

View File

@ -23,10 +23,12 @@
import FlowItemHeader from "./FlowItemHeader.svelte"
import RoleSelect from "components/design/settings/controls/RoleSelect.svelte"
import { ActionStepID, TriggerStepID } from "constants/backend/automations"
import FlowItemActions from "./FlowItemActions.svelte"
export let block
export let testDataModal
export let idx
export let isLast
let selected
let webhookModal
@ -40,6 +42,7 @@
)
$: automationId = $selectedAutomation?._id
$: isTrigger = block.type === "TRIGGER"
$: steps = $selectedAutomation?.definition?.steps ?? []
$: blockIdx = steps.findIndex(step => step.id === block.id)
$: lastStep = !isTrigger && blockIdx + 1 === steps.length
@ -183,7 +186,9 @@
</div>
{/if}
<AutomationBlockSetup
schemaProperties={Object.entries(block.schema.inputs.properties)}
schemaProperties={Object.entries(
block?.schema?.inputs?.properties || {}
)}
{block}
{webhookModal}
/>
@ -204,13 +209,17 @@
</div>
{#if !collectBlockExists || !lastStep}
<div class="separator" />
<Icon
<!-- Need to break out the separators -->
<FlowItemActions on:addStep={actionModal.show()} />
<!-- <Icon
on:click={() => actionModal.show()}
hoverable
name="AddCircle"
size="S"
/>
{#if isTrigger ? totalBlocks > 1 : blockIdx !== totalBlocks - 2}
/> -->
{#if isTrigger ? !isLast || totalBlocks > 1 : blockIdx !== totalBlocks - 2}
<div class="separator" />
{/if}
{/if}

View File

@ -106,6 +106,9 @@
$: environmentBindings = buildEnvironmentBindings($memoEnvVariables)
$: bindings = [...automationBindings, ...environmentBindings]
$: bindingsx = automationStore.actions.getPathBindings($memoBlock)
// $: console.log(bindingsx)
$: getInputData(testData, $memoBlock.inputs)
$: tableId = inputData ? inputData.tableId : null
$: table = tableId
@ -522,6 +525,9 @@
})
*/
const onChange = Utils.sequential(async update => {
if (1 == 1) {
console.error("ABORT UPDATE")
}
const request = cloneDeep(update)
// Process app trigger updates
if (isTrigger && !isTestModal) {

View File

@ -3,9 +3,11 @@ import { API } from "api"
import { cloneDeep } from "lodash/fp"
import { generate } from "shortid"
import { createHistoryStore } from "stores/builder/history"
import { environment, licensing } from "stores/portal"
import { notifications } from "@budibase/bbui"
import { updateReferencesInObject } from "dataBinding"
import { AutomationTriggerStepId } from "@budibase/types"
import { updateReferencesInObject, getEnvironmentBindings } from "dataBinding"
import { AutomationTriggerStepId, AutomationEventType } from "@budibase/types"
import { FIELDS } from "constants/backend"
import {
updateBindingsInSteps,
getNewStepName,
@ -22,6 +24,10 @@ const initialAutomationState = {
},
selectedAutomationId: null,
automationDisplayData: {},
// registered on screen.
// may not be the right store
blocks: {},
}
// If this functions, remove the actions elements
@ -68,6 +74,356 @@ const getFinalDefinitions = (triggers, actions) => {
}
const automationActions = store => ({
// Should this be in the store?
// or just a context item.
registerBlock: (block, pathTo) => {
// console.log("Register ", block)
/*
Traverse before even rendering
Push all data into a core location i.e here
Derived store?
blocks:
{
[step.id]:{
path: [{step:2, id: abc123 },{branch:0, step:1, id: xyz789}]
bindings: []
// byName
// byStepIdx
}
}
*/
store.update(state => {
state.blocks = {
...state.blocks,
[block.id]: {
bindings: block.inputs.text,
},
}
return state
})
},
// build and store ONCE
// make it derived?
buildEnvironmentBindings: () => {
if (get(licensing).environmentVariablesEnabled) {
return getEnvironmentBindings().map(binding => {
return {
...binding,
display: {
...binding.display,
rank: 98,
},
}
})
}
return []
},
//TESTING retrieve all preceding
getAvailableBindings: (block, automation) => {
if (!block || !automation) {
return []
}
// Find previous steps to the selected one
let allSteps = [...automation.steps]
if (automation.trigger) {
allSteps = [automation.trigger, ...allSteps]
}
if (1 == 1) {
return
}
let blockIdx = allSteps.findIndex(step => step.id === block.id)
// Extract all outputs from all previous steps as available bindingsx§x
let bindings = []
let loopBlockCount = 0
const addBinding = (name, value, icon, idx, isLoopBlock, bindingName) => {
if (!name) return
const runtimeBinding = determineRuntimeBinding(
name,
idx,
isLoopBlock,
bindingName
)
const categoryName = determineCategoryName(idx, isLoopBlock, bindingName)
bindings.push(
createBindingObject(
name,
value,
icon,
idx,
loopBlockCount,
isLoopBlock,
runtimeBinding,
categoryName,
bindingName
)
)
}
const determineRuntimeBinding = (name, idx, isLoopBlock, bindingName) => {
let runtimeName
/* Begin special cases for generating custom schemas based on triggers */
if (
idx === 0 &&
automation.trigger?.event === AutomationEventType.APP_TRIGGER
) {
return `trigger.fields.${name}`
}
if (
idx === 0 &&
(automation.trigger?.event === AutomationEventType.ROW_UPDATE ||
automation.trigger?.event === AutomationEventType.ROW_SAVE)
) {
let noRowKeywordBindings = ["id", "revision", "oldRow"]
if (!noRowKeywordBindings.includes(name)) return `trigger.row.${name}`
}
/* End special cases for generating custom schemas based on triggers */
let hasUserDefinedName = automation.stepNames?.[allSteps[idx]?.id]
if (isLoopBlock) {
runtimeName = `loop.${name}`
} else if (block.name.startsWith("JS")) {
runtimeName = hasUserDefinedName
? `stepsByName[${bindingName}].${name}`
: `steps[${idx - loopBlockCount}].${name}`
} else {
runtimeName = hasUserDefinedName
? `stepsByName.${bindingName}.${name}`
: `steps.${idx - loopBlockCount}.${name}`
}
return idx === 0 ? `trigger.${name}` : runtimeName
}
const determineCategoryName = (idx, isLoopBlock, bindingName) => {
if (idx === 0) return "Trigger outputs"
if (isLoopBlock) return "Loop Outputs"
return bindingName
? `${bindingName} outputs`
: `Step ${idx - loopBlockCount} outputs`
}
const createBindingObject = (
name,
value,
icon,
idx,
loopBlockCount,
isLoopBlock,
runtimeBinding,
categoryName,
bindingName
) => {
const field = Object.values(FIELDS).find(
field => field.type === value.type && field.subtype === value.subtype
)
return {
readableBinding:
bindingName && !isLoopBlock
? `steps.${bindingName}.${name}`
: runtimeBinding,
runtimeBinding,
type: value.type,
description: value.description,
icon,
category: categoryName,
display: {
type: field?.name || value.type,
name,
rank: isLoopBlock ? idx + 1 : idx - loopBlockCount,
},
}
}
for (let idx = 0; idx < blockIdx; idx++) {
let wasLoopBlock = allSteps[idx - 1]?.stepId === ActionStepID.LOOP
let isLoopBlock =
allSteps[idx]?.stepId === ActionStepID.LOOP &&
allSteps.some(x => x.blockToLoop === block.id)
let schema = cloneDeep(allSteps[idx]?.schema?.outputs?.properties) ?? {}
if (allSteps[idx]?.name.includes("Looping")) {
isLoopBlock = true
loopBlockCount++
}
let bindingName =
automation.stepNames?.[allSteps[idx].id] || allSteps[idx].name
if (isLoopBlock) {
schema = {
currentItem: {
type: "string",
description: "the item currently being executed",
},
}
}
if (
idx === 0 &&
automation.trigger?.event === AutomationEventType.APP_TRIGGER
) {
schema = Object.fromEntries(
Object.keys(automation.trigger.inputs.fields || []).map(key => [
key,
{ type: automation.trigger.inputs.fields[key] },
])
)
}
if (
(idx === 0 &&
automation.trigger.event === AutomationEventType.ROW_UPDATE) ||
(idx === 0 && automation.trigger.event === AutomationEventType.ROW_SAVE)
) {
let table = $tables.list.find(
table => table._id === automation.trigger.inputs.tableId
)
// We want to generate our own schema for the bindings from the table schema itself
for (const key in table?.schema) {
schema[key] = {
type: table.schema[key].type,
subtype: table.schema[key].subtype,
}
}
// remove the original binding
delete schema.row
}
let icon =
idx === 0
? automation.trigger.icon
: isLoopBlock
? "Reuse"
: allSteps[idx].icon
if (wasLoopBlock) {
loopBlockCount++
schema = cloneDeep(allSteps[idx - 1]?.schema?.outputs?.properties)
}
Object.entries(schema).forEach(([name, value]) => {
addBinding(name, value, icon, idx, isLoopBlock, bindingName)
})
}
// for (let idx = 0; idx < blockIdx; idx++) {
// let wasLoopBlock = allSteps[idx - 1]?.stepId === ActionStepID.LOOP
// let isLoopBlock =
// allSteps[idx]?.stepId === ActionStepID.LOOP &&
// allSteps.some(x => x.blockToLoop === block.id)
// let schema = cloneDeep(allSteps[idx]?.schema?.outputs?.properties) ?? {}
// if (allSteps[idx]?.name.includes("Looping")) {
// isLoopBlock = true
// loopBlockCount++
// }
// let bindingName =
// automation.stepNames?.[allSteps[idx].id] || allSteps[idx].name
// if (isLoopBlock) {
// schema = {
// currentItem: {
// type: "string",
// description: "the item currently being executed",
// },
// }
// }
// if (
// idx === 0 &&
// automation.trigger?.event === AutomationEventType.APP_TRIGGER
// ) {
// schema = Object.fromEntries(
// Object.keys(automation.trigger.inputs.fields || []).map(key => [
// key,
// { type: automation.trigger.inputs.fields[key] },
// ])
// )
// }
// if (
// (idx === 0 &&
// automation.trigger.event === AutomationEventType.ROW_UPDATE) ||
// (idx === 0 && automation.trigger.event === AutomationEventType.ROW_SAVE)
// ) {
// let table = $tables.list.find(
// table => table._id === automation.trigger.inputs.tableId
// )
// // We want to generate our own schema for the bindings from the table schema itself
// for (const key in table?.schema) {
// schema[key] = {
// type: table.schema[key].type,
// subtype: table.schema[key].subtype,
// }
// }
// // remove the original binding
// delete schema.row
// }
// let icon =
// idx === 0
// ? automation.trigger.icon
// : isLoopBlock
// ? "Reuse"
// : allSteps[idx].icon
// if (wasLoopBlock) {
// loopBlockCount++
// schema = cloneDeep(allSteps[idx - 1]?.schema?.outputs?.properties)
// }
// Object.entries(schema).forEach(([name, value]) => {
// addBinding(name, value, icon, idx, isLoopBlock, bindingName)
// })
// }
return bindings
},
// $: automationStore.actions.traverse($selectedAutomation)
getPathBindings: step => {
console.log("Hello world", step)
// get(store)
// build the full path worth of bindings
/*
[..globals],
blocks[...step.id].binding,
ppath.
*/
return []
},
traverse: automation => {
let blocks = []
if (automation.definition.trigger) {
blocks.push(automation.definition.trigger)
}
blocks = blocks.concat(automation.definition.steps || [])
const treeTraverse = (block, pathTo, stepIdx, branchIdx) => {
const pathToCurrentNode = [
...(pathTo || []),
{
...(Number.isInteger(branchIdx) ? { branchIdx } : {}),
stepIdx,
id: block.id,
...(block.stepId === "TRIGGER" ? { isTrigger: true } : {}),
},
]
const branches = block.inputs?.branches || []
branches.forEach((branch, bIdx) => {
block.inputs?.children[branch.name].forEach((bBlock, sIdx) => {
treeTraverse(bBlock, pathToCurrentNode, sIdx, bIdx)
})
})
if (block.stepId !== "BRANCH") {
//dev, log.text
console.log(pathToCurrentNode, block.inputs.text)
//store.actions.registerBlock(block)
}
store.actions.registerBlock(block)
}
//maybe a cfg block for cleanliness
blocks.forEach((block, idx) => treeTraverse(block, null, idx))
},
definitions: async () => {
const response = await API.getAutomationDefinitions()
store.update(state => {
@ -285,7 +641,7 @@ const automationActions = store => ({
inputs: blockDefinition.inputs || {},
stepId,
type,
id: generate(),
id: generate(), // this can this be relied on
}
newName = getNewStepName(get(selectedAutomation), newStep)
newStep.name = newName