Merge remote-tracking branch 'origin/develop' into feature/new-app-publish-workflow
This commit is contained in:
commit
c7b6453862
|
@ -7,6 +7,15 @@ assignees: ''
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
**Hosting**
|
||||||
|
<!-- Delete as appropriate -->
|
||||||
|
- Self
|
||||||
|
- Method: <method> <!-- One of: k8s, docker single image, docker compose, digital ocean: -->
|
||||||
|
- Budibase Version: <version> <!-- e.g. 1.0.105 -->
|
||||||
|
- App Version: <version> <!-- Indicate app version if bug is related to an application -->
|
||||||
|
- Cloud
|
||||||
|
- Tenant ID: <tenantId> <!-- shown in URL as <tenantID>.budibase.app -->
|
||||||
|
|
||||||
**Describe the bug**
|
**Describe the bug**
|
||||||
A clear and concise description of what the bug is.
|
A clear and concise description of what the bug is.
|
||||||
|
|
||||||
|
|
|
@ -114,6 +114,9 @@ spec:
|
||||||
value: {{ .Values.globals.google.clientId | quote }}
|
value: {{ .Values.globals.google.clientId | quote }}
|
||||||
- name: GOOGLE_CLIENT_SECRET
|
- name: GOOGLE_CLIENT_SECRET
|
||||||
value: {{ .Values.globals.google.secret | quote }}
|
value: {{ .Values.globals.google.secret | quote }}
|
||||||
|
- name: AUTOMATION_MAX_ITERATIONS
|
||||||
|
value: {{ .Values.globals.automationMaxIterations | quote }}
|
||||||
|
|
||||||
image: budibase/apps:{{ .Values.globals.appVersion }}
|
image: budibase/apps:{{ .Values.globals.appVersion }}
|
||||||
imagePullPolicy: Always
|
imagePullPolicy: Always
|
||||||
name: bbapps
|
name: bbapps
|
||||||
|
|
|
@ -103,6 +103,7 @@ globals:
|
||||||
google:
|
google:
|
||||||
clientId: ""
|
clientId: ""
|
||||||
secret: ""
|
secret: ""
|
||||||
|
automationMaxIterations: "500"
|
||||||
|
|
||||||
createSecrets: true # creates an internal API key, JWT secrets and redis password for you
|
createSecrets: true # creates an internal API key, JWT secrets and redis password for you
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
{
|
{
|
||||||
"version": "1.0.105-alpha.19",
|
"version": "1.0.105-alpha.21",
|
||||||
"npmClient": "yarn",
|
"npmClient": "yarn",
|
||||||
"packages": [
|
"packages": [
|
||||||
"packages/*"
|
"packages/*"
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "@budibase/backend-core",
|
"name": "@budibase/backend-core",
|
||||||
"version": "1.0.105-alpha.19",
|
"version": "1.0.105-alpha.21",
|
||||||
"description": "Budibase backend core libraries used in server and worker",
|
"description": "Budibase backend core libraries used in server and worker",
|
||||||
"main": "src/index.js",
|
"main": "src/index.js",
|
||||||
"author": "Budibase",
|
"author": "Budibase",
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
{
|
{
|
||||||
"name": "@budibase/bbui",
|
"name": "@budibase/bbui",
|
||||||
"description": "A UI solution used in the different Budibase projects.",
|
"description": "A UI solution used in the different Budibase projects.",
|
||||||
"version": "1.0.105-alpha.19",
|
"version": "1.0.105-alpha.21",
|
||||||
"license": "MPL-2.0",
|
"license": "MPL-2.0",
|
||||||
"svelte": "src/index.js",
|
"svelte": "src/index.js",
|
||||||
"module": "dist/bbui.es.js",
|
"module": "dist/bbui.es.js",
|
||||||
|
@ -38,7 +38,7 @@
|
||||||
],
|
],
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@adobe/spectrum-css-workflow-icons": "^1.2.1",
|
"@adobe/spectrum-css-workflow-icons": "^1.2.1",
|
||||||
"@budibase/string-templates": "^1.0.105-alpha.19",
|
"@budibase/string-templates": "^1.0.105-alpha.21",
|
||||||
"@spectrum-css/actionbutton": "^1.0.1",
|
"@spectrum-css/actionbutton": "^1.0.1",
|
||||||
"@spectrum-css/actiongroup": "^1.0.1",
|
"@spectrum-css/actiongroup": "^1.0.1",
|
||||||
"@spectrum-css/avatar": "^3.0.2",
|
"@spectrum-css/avatar": "^3.0.2",
|
||||||
|
|
|
@ -20,7 +20,6 @@ filterTests(['smoke', 'all'], () => {
|
||||||
})
|
})
|
||||||
|
|
||||||
// Setup trigger
|
// Setup trigger
|
||||||
cy.contains("Setup").click()
|
|
||||||
cy.get(".spectrum-Picker-label").click()
|
cy.get(".spectrum-Picker-label").click()
|
||||||
cy.wait(500)
|
cy.wait(500)
|
||||||
cy.contains("dog").click()
|
cy.contains("dog").click()
|
||||||
|
@ -32,12 +31,11 @@ filterTests(['smoke', 'all'], () => {
|
||||||
cy.contains("Create Row").trigger('mouseover').click().click()
|
cy.contains("Create Row").trigger('mouseover').click().click()
|
||||||
cy.get(".spectrum-Button--cta").click()
|
cy.get(".spectrum-Button--cta").click()
|
||||||
})
|
})
|
||||||
cy.contains("Setup").click()
|
|
||||||
cy.get(".spectrum-Picker-label").eq(1).click()
|
cy.get(".spectrum-Picker-label").eq(1).click()
|
||||||
cy.contains("dog").click()
|
cy.contains("dog").click()
|
||||||
cy.get(".spectrum-Textfield-input")
|
cy.get(".spectrum-Textfield-input")
|
||||||
.first()
|
.first()
|
||||||
.type("{{ trigger.row.name }}", { parseSpecialCharSequences: false })
|
.type("{{ trigger.row.name }}", { parseSpecialCharSequences: false })
|
||||||
cy.get(".spectrum-Textfield-input")
|
cy.get(".spectrum-Textfield-input")
|
||||||
.eq(1)
|
.eq(1)
|
||||||
.type("11")
|
.type("11")
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "@budibase/builder",
|
"name": "@budibase/builder",
|
||||||
"version": "1.0.105-alpha.19",
|
"version": "1.0.105-alpha.21",
|
||||||
"license": "GPL-3.0",
|
"license": "GPL-3.0",
|
||||||
"private": true,
|
"private": true,
|
||||||
"scripts": {
|
"scripts": {
|
||||||
|
@ -65,10 +65,10 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@budibase/bbui": "^1.0.105-alpha.19",
|
"@budibase/bbui": "^1.0.105-alpha.21",
|
||||||
"@budibase/client": "^1.0.105-alpha.19",
|
"@budibase/client": "^1.0.105-alpha.21",
|
||||||
"@budibase/frontend-core": "^1.0.105-alpha.19",
|
"@budibase/frontend-core": "^1.0.105-alpha.21",
|
||||||
"@budibase/string-templates": "^1.0.105-alpha.19",
|
"@budibase/string-templates": "^1.0.105-alpha.21",
|
||||||
"@sentry/browser": "5.19.1",
|
"@sentry/browser": "5.19.1",
|
||||||
"@spectrum-css/page": "^3.0.1",
|
"@spectrum-css/page": "^3.0.1",
|
||||||
"@spectrum-css/vars": "^3.0.1",
|
"@spectrum-css/vars": "^3.0.1",
|
||||||
|
|
|
@ -39,6 +39,7 @@
|
||||||
if (v.internal) {
|
if (v.internal) {
|
||||||
acc[k] = v
|
acc[k] = v
|
||||||
}
|
}
|
||||||
|
delete acc.LOOP
|
||||||
return acc
|
return acc
|
||||||
}, {})
|
}, {})
|
||||||
|
|
||||||
|
|
|
@ -72,7 +72,9 @@
|
||||||
animate:flip={{ duration: 500 }}
|
animate:flip={{ duration: 500 }}
|
||||||
in:fly|local={{ x: 500, duration: 1500 }}
|
in:fly|local={{ x: 500, duration: 1500 }}
|
||||||
>
|
>
|
||||||
<FlowItem {testDataModal} {block} />
|
{#if block.stepId !== "LOOP"}
|
||||||
|
<FlowItem {testDataModal} {block} />
|
||||||
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
{/each}
|
{/each}
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -9,8 +9,8 @@
|
||||||
Modal,
|
Modal,
|
||||||
Button,
|
Button,
|
||||||
StatusLight,
|
StatusLight,
|
||||||
ActionButton,
|
|
||||||
Select,
|
Select,
|
||||||
|
ActionButton,
|
||||||
notifications,
|
notifications,
|
||||||
} from "@budibase/bbui"
|
} from "@budibase/bbui"
|
||||||
import AutomationBlockSetup from "../../SetupPanel/AutomationBlockSetup.svelte"
|
import AutomationBlockSetup from "../../SetupPanel/AutomationBlockSetup.svelte"
|
||||||
|
@ -25,8 +25,8 @@
|
||||||
let webhookModal
|
let webhookModal
|
||||||
let actionModal
|
let actionModal
|
||||||
let resultsModal
|
let resultsModal
|
||||||
let setupToggled
|
|
||||||
let blockComplete
|
let blockComplete
|
||||||
|
let showLooping = false
|
||||||
|
|
||||||
$: rowControl = $automationStore.selectedAutomation.automation.rowControl
|
$: rowControl = $automationStore.selectedAutomation.automation.rowControl
|
||||||
$: showBindingPicker =
|
$: showBindingPicker =
|
||||||
|
@ -52,8 +52,21 @@
|
||||||
block.schema?.inputs?.properties || {}
|
block.schema?.inputs?.properties || {}
|
||||||
).every(x => block?.inputs[x])
|
).every(x => block?.inputs[x])
|
||||||
|
|
||||||
|
$: loopingSelected =
|
||||||
|
$automationStore.selectedAutomation?.automation.definition.steps.find(
|
||||||
|
x => x.blockToLoop === block.id
|
||||||
|
)
|
||||||
|
|
||||||
async function deleteStep() {
|
async function deleteStep() {
|
||||||
|
let loopBlock =
|
||||||
|
$automationStore.selectedAutomation?.automation.definition.steps.find(
|
||||||
|
x => x.blockToLoop === block.id
|
||||||
|
)
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
if (loopBlock) {
|
||||||
|
automationStore.actions.deleteAutomationBlock(loopBlock)
|
||||||
|
}
|
||||||
automationStore.actions.deleteAutomationBlock(block)
|
automationStore.actions.deleteAutomationBlock(block)
|
||||||
await automationStore.actions.save(
|
await automationStore.actions.save(
|
||||||
$automationStore.selectedAutomation?.automation
|
$automationStore.selectedAutomation?.automation
|
||||||
|
@ -76,6 +89,23 @@
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function addLooping() {
|
||||||
|
loopingSelected = true
|
||||||
|
const loopDefinition = $automationStore.blockDefinitions.ACTION.LOOP
|
||||||
|
|
||||||
|
const loopBlock = $automationStore.selectedAutomation.constructBlock(
|
||||||
|
"ACTION",
|
||||||
|
"LOOP",
|
||||||
|
loopDefinition
|
||||||
|
)
|
||||||
|
loopBlock.blockToLoop = block.id
|
||||||
|
block.loopBlock = loopBlock.id
|
||||||
|
automationStore.actions.addBlockToAutomation(loopBlock, blockIdx)
|
||||||
|
await automationStore.actions.save(
|
||||||
|
$automationStore.selectedAutomation?.automation
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
async function onSelect(block) {
|
async function onSelect(block) {
|
||||||
await automationStore.update(state => {
|
await automationStore.update(state => {
|
||||||
state.selectedBlock = block
|
state.selectedBlock = block
|
||||||
|
@ -84,13 +114,68 @@
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div
|
<div class={`block ${block.type} hoverable`} class:selected on:click={() => {}}>
|
||||||
class={`block ${block.type} hoverable`}
|
{#if loopingSelected}
|
||||||
class:selected
|
<div class="blockSection">
|
||||||
on:click={() => {
|
<div
|
||||||
onSelect(block)
|
on:click={() => {
|
||||||
}}
|
showLooping = !showLooping
|
||||||
>
|
}}
|
||||||
|
class="splitHeader"
|
||||||
|
>
|
||||||
|
<div class="center-items">
|
||||||
|
<svg
|
||||||
|
width="28px"
|
||||||
|
height="28px"
|
||||||
|
class="spectrum-Icon"
|
||||||
|
style="color:grey;"
|
||||||
|
focusable="false"
|
||||||
|
>
|
||||||
|
<use xlink:href="#spectrum-icon-18-Reuse" />
|
||||||
|
</svg>
|
||||||
|
<div class="iconAlign">
|
||||||
|
<Detail size="S">Looping</Detail>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="blockTitle">
|
||||||
|
<div
|
||||||
|
style="margin-left: 10px;"
|
||||||
|
on:click={() => {
|
||||||
|
onSelect(block)
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Icon name={showLooping ? "ChevronDown" : "ChevronUp"} />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<Divider noMargin />
|
||||||
|
{#if !showLooping}
|
||||||
|
<div class="blockSection">
|
||||||
|
<div class="block-options">
|
||||||
|
<div class="delete-padding" on:click={() => deleteStep()}>
|
||||||
|
<Icon name="DeleteOutline" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<Layout noPadding gap="S">
|
||||||
|
<AutomationBlockSetup
|
||||||
|
schemaProperties={Object.entries(
|
||||||
|
$automationStore.blockDefinitions.ACTION.LOOP.schema.inputs
|
||||||
|
.properties
|
||||||
|
)}
|
||||||
|
block={$automationStore.selectedAutomation?.automation.definition.steps.find(
|
||||||
|
x => x.blockToLoop === block.id
|
||||||
|
)}
|
||||||
|
{webhookModal}
|
||||||
|
/>
|
||||||
|
</Layout>
|
||||||
|
</div>
|
||||||
|
<Divider noMargin />
|
||||||
|
{/if}
|
||||||
|
{/if}
|
||||||
|
|
||||||
<div class="blockSection">
|
<div class="blockSection">
|
||||||
<div
|
<div
|
||||||
on:click={() => {
|
on:click={() => {
|
||||||
|
@ -127,65 +212,66 @@
|
||||||
<Detail size="S">{block?.name?.toUpperCase() || ""}</Detail>
|
<Detail size="S">{block?.name?.toUpperCase() || ""}</Detail>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{#if testResult && testResult[0]}
|
<div class="blockTitle">
|
||||||
<span on:click={() => resultsModal.show()}>
|
{#if testResult && testResult[0]}
|
||||||
<StatusLight
|
<div style="float: right;" on:click={() => resultsModal.show()}>
|
||||||
positive={isTrigger || testResult[0].outputs?.success}
|
<StatusLight
|
||||||
negative={!testResult[0].outputs?.success}
|
positive={isTrigger || testResult[0].outputs?.success}
|
||||||
><Body size="XS">View response</Body></StatusLight
|
negative={!testResult[0].outputs?.success}
|
||||||
>
|
><Body size="XS">View response</Body></StatusLight
|
||||||
</span>
|
>
|
||||||
{/if}
|
</div>
|
||||||
|
{/if}
|
||||||
|
<div
|
||||||
|
style="margin-left: 10px;"
|
||||||
|
on:click={() => {
|
||||||
|
onSelect(block)
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Icon name={blockComplete ? "ChevronDown" : "ChevronUp"} />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{#if !blockComplete}
|
{#if !blockComplete}
|
||||||
<Divider noMargin />
|
<Divider noMargin />
|
||||||
<div class="blockSection">
|
<div class="blockSection">
|
||||||
<Layout noPadding gap="S">
|
<Layout noPadding gap="S">
|
||||||
<div class="splitHeader">
|
{#if !isTrigger}
|
||||||
<ActionButton
|
<div>
|
||||||
on:click={() => {
|
|
||||||
onSelect(block)
|
|
||||||
setupToggled = !setupToggled
|
|
||||||
}}
|
|
||||||
quiet
|
|
||||||
icon={setupToggled ? "ChevronDown" : "ChevronRight"}
|
|
||||||
>
|
|
||||||
<Detail size="S">Setup</Detail>
|
|
||||||
</ActionButton>
|
|
||||||
{#if !isTrigger}
|
|
||||||
<div class="block-options">
|
<div class="block-options">
|
||||||
{#if showBindingPicker}
|
{#if !loopingSelected}
|
||||||
<div>
|
<ActionButton on:click={() => addLooping()} icon="Reuse"
|
||||||
<Select
|
>Add Looping</ActionButton
|
||||||
on:change={toggleFieldControl}
|
>
|
||||||
quiet
|
|
||||||
defaultValue="Use values"
|
|
||||||
autoWidth
|
|
||||||
value={rowControl ? "Use bindings" : "Use values"}
|
|
||||||
options={["Use values", "Use bindings"]}
|
|
||||||
placeholder={null}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
{/if}
|
{/if}
|
||||||
<div class="delete-padding" on:click={() => deleteStep()}>
|
{#if showBindingPicker}
|
||||||
<Icon name="DeleteOutline" />
|
<Select
|
||||||
</div>
|
on:change={toggleFieldControl}
|
||||||
|
defaultValue="Use values"
|
||||||
|
autoWidth
|
||||||
|
value={rowControl ? "Use bindings" : "Use values"}
|
||||||
|
options={["Use values", "Use bindings"]}
|
||||||
|
placeholder={null}
|
||||||
|
/>
|
||||||
|
{/if}
|
||||||
|
<ActionButton
|
||||||
|
on:click={() => deleteStep()}
|
||||||
|
icon="DeleteOutline"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
</div>
|
||||||
</div>
|
{/if}
|
||||||
|
|
||||||
{#if setupToggled}
|
<AutomationBlockSetup
|
||||||
<AutomationBlockSetup
|
schemaProperties={Object.entries(block.schema.inputs.properties)}
|
||||||
schemaProperties={Object.entries(block.schema.inputs.properties)}
|
{block}
|
||||||
{block}
|
{webhookModal}
|
||||||
{webhookModal}
|
/>
|
||||||
/>
|
{#if lastStep}
|
||||||
{#if lastStep}
|
<Button on:click={() => testDataModal.show()} cta
|
||||||
<Button on:click={() => testDataModal.show()} cta
|
>Finish and test automation</Button
|
||||||
>Finish and test automation</Button
|
>
|
||||||
>
|
|
||||||
{/if}
|
|
||||||
{/if}
|
{/if}
|
||||||
</Layout>
|
</Layout>
|
||||||
</div>
|
</div>
|
||||||
|
@ -220,8 +306,10 @@
|
||||||
padding-left: 30px;
|
padding-left: 30px;
|
||||||
}
|
}
|
||||||
.block-options {
|
.block-options {
|
||||||
display: flex;
|
justify-content: flex-end;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
display: flex;
|
||||||
|
gap: var(--spacing-m);
|
||||||
}
|
}
|
||||||
.center-items {
|
.center-items {
|
||||||
display: flex;
|
display: flex;
|
||||||
|
@ -256,4 +344,9 @@
|
||||||
/* center horizontally */
|
/* center horizontally */
|
||||||
align-self: center;
|
align-self: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.blockTitle {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
<script>
|
<script>
|
||||||
import { ModalContent, Icon, Detail, TextArea } from "@budibase/bbui"
|
import { ModalContent, Icon, Detail, TextArea, Label } from "@budibase/bbui"
|
||||||
|
|
||||||
export let testResult
|
export let testResult
|
||||||
export let isTrigger
|
export let isTrigger
|
||||||
|
@ -13,7 +13,7 @@
|
||||||
cancelText="Close"
|
cancelText="Close"
|
||||||
>
|
>
|
||||||
<div slot="header" class="result-modal-header">
|
<div slot="header" class="result-modal-header">
|
||||||
<span>Test Automation</span>
|
<span>Test Results</span>
|
||||||
<div>
|
<div>
|
||||||
{#if isTrigger || testResult[0].outputs.success}
|
{#if isTrigger || testResult[0].outputs.success}
|
||||||
<div class="iconSuccess">
|
<div class="iconSuccess">
|
||||||
|
@ -26,7 +26,18 @@
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<span>
|
||||||
|
{#if testResult[0].outputs.iterations}
|
||||||
|
<div style="display: flex;">
|
||||||
|
<Icon name="Reuse" />
|
||||||
|
<div style="margin-left: 10px;">
|
||||||
|
<Label>
|
||||||
|
This loop ran {testResult[0].outputs.iterations} times.</Label
|
||||||
|
>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
</span>
|
||||||
<div
|
<div
|
||||||
on:click={() => {
|
on:click={() => {
|
||||||
inputToggled = !inputToggled
|
inputToggled = !inputToggled
|
||||||
|
|
|
@ -88,36 +88,65 @@
|
||||||
if (!block || !automation) {
|
if (!block || !automation) {
|
||||||
return []
|
return []
|
||||||
}
|
}
|
||||||
|
|
||||||
// Find previous steps to the selected one
|
// Find previous steps to the selected one
|
||||||
let allSteps = [...automation.steps]
|
let allSteps = [...automation.steps]
|
||||||
|
|
||||||
if (automation.trigger) {
|
if (automation.trigger) {
|
||||||
allSteps = [automation.trigger, ...allSteps]
|
allSteps = [automation.trigger, ...allSteps]
|
||||||
}
|
}
|
||||||
const blockIdx = allSteps.findIndex(step => step.id === block.id)
|
let blockIdx = allSteps.findIndex(step => step.id === block.id)
|
||||||
|
|
||||||
// Extract all outputs from all previous steps as available bindings
|
// Extract all outputs from all previous steps as available bindins
|
||||||
let bindings = []
|
let bindings = []
|
||||||
for (let idx = 0; idx < blockIdx; idx++) {
|
for (let idx = 0; idx < blockIdx; idx++) {
|
||||||
const outputs = Object.entries(
|
let wasLoopBlock = allSteps[idx]?.stepId === "LOOP"
|
||||||
allSteps[idx].schema?.outputs?.properties ?? {}
|
let isLoopBlock =
|
||||||
)
|
allSteps[idx]?.stepId === "LOOP" &&
|
||||||
|
allSteps.find(x => x.blockToLoop === block.id)
|
||||||
|
|
||||||
|
// If the previous block was a loop block, decerement the index so the following
|
||||||
|
// steps are in the correct order
|
||||||
|
if (wasLoopBlock) {
|
||||||
|
blockIdx--
|
||||||
|
}
|
||||||
|
|
||||||
|
let schema = allSteps[idx]?.schema?.outputs?.properties ?? {}
|
||||||
|
|
||||||
|
// If its a Loop Block, we need to add this custom schema
|
||||||
|
if (isLoopBlock) {
|
||||||
|
schema = {
|
||||||
|
currentItem: {
|
||||||
|
type: "string",
|
||||||
|
description: "the item currently being executed",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const outputs = Object.entries(schema)
|
||||||
|
|
||||||
bindings = bindings.concat(
|
bindings = bindings.concat(
|
||||||
outputs.map(([name, value]) => {
|
outputs.map(([name, value]) => {
|
||||||
const stepsLabel = block.name.startsWith("JS")
|
let runtimeName = isLoopBlock
|
||||||
|
? `loop.${name}`
|
||||||
|
: block.name.startsWith("JS")
|
||||||
? `steps[${idx}].${name}`
|
? `steps[${idx}].${name}`
|
||||||
: `steps.${idx}.${name}`
|
: `steps.${idx}.${name}`
|
||||||
const runtime = idx === 0 ? `trigger.${name}` : stepsLabel
|
const runtime = idx === 0 ? `trigger.${name}` : runtimeName
|
||||||
return {
|
return {
|
||||||
label: runtime,
|
label: runtime,
|
||||||
type: value.type,
|
type: value.type,
|
||||||
description: value.description,
|
description: value.description,
|
||||||
category: idx === 0 ? "Trigger outputs" : `Step ${idx} outputs`,
|
category:
|
||||||
|
idx === 0
|
||||||
|
? "Trigger outputs"
|
||||||
|
: isLoopBlock
|
||||||
|
? "Loop Outputs"
|
||||||
|
: `Step ${idx} outputs`,
|
||||||
path: runtime,
|
path: runtime,
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
return bindings
|
return bindings
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -264,6 +293,14 @@
|
||||||
value={inputData[key]}
|
value={inputData[key]}
|
||||||
/>
|
/>
|
||||||
</CodeEditorModal>
|
</CodeEditorModal>
|
||||||
|
{: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"}
|
{:else if value.type === "string" || value.type === "number" || value.type === "integer"}
|
||||||
{#if isTestModal}
|
{#if isTestModal}
|
||||||
<ModalBindableInput
|
<ModalBindableInput
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "@budibase/cli",
|
"name": "@budibase/cli",
|
||||||
"version": "1.0.105-alpha.19",
|
"version": "1.0.105-alpha.21",
|
||||||
"description": "Budibase CLI, for developers, self hosting and migrations.",
|
"description": "Budibase CLI, for developers, self hosting and migrations.",
|
||||||
"main": "src/index.js",
|
"main": "src/index.js",
|
||||||
"bin": {
|
"bin": {
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "@budibase/client",
|
"name": "@budibase/client",
|
||||||
"version": "1.0.105-alpha.19",
|
"version": "1.0.105-alpha.21",
|
||||||
"license": "MPL-2.0",
|
"license": "MPL-2.0",
|
||||||
"module": "dist/budibase-client.js",
|
"module": "dist/budibase-client.js",
|
||||||
"main": "dist/budibase-client.js",
|
"main": "dist/budibase-client.js",
|
||||||
|
@ -19,9 +19,9 @@
|
||||||
"dev:builder": "rollup -cw"
|
"dev:builder": "rollup -cw"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@budibase/bbui": "^1.0.105-alpha.19",
|
"@budibase/bbui": "^1.0.105-alpha.21",
|
||||||
"@budibase/frontend-core": "^1.0.105-alpha.19",
|
"@budibase/frontend-core": "^1.0.105-alpha.21",
|
||||||
"@budibase/string-templates": "^1.0.105-alpha.19",
|
"@budibase/string-templates": "^1.0.105-alpha.21",
|
||||||
"@spectrum-css/button": "^3.0.3",
|
"@spectrum-css/button": "^3.0.3",
|
||||||
"@spectrum-css/card": "^3.0.3",
|
"@spectrum-css/card": "^3.0.3",
|
||||||
"@spectrum-css/divider": "^1.0.3",
|
"@spectrum-css/divider": "^1.0.3",
|
||||||
|
|
|
@ -1,12 +1,12 @@
|
||||||
{
|
{
|
||||||
"name": "@budibase/frontend-core",
|
"name": "@budibase/frontend-core",
|
||||||
"version": "1.0.105-alpha.19",
|
"version": "1.0.105-alpha.21",
|
||||||
"description": "Budibase frontend core libraries used in builder and client",
|
"description": "Budibase frontend core libraries used in builder and client",
|
||||||
"author": "Budibase",
|
"author": "Budibase",
|
||||||
"license": "MPL-2.0",
|
"license": "MPL-2.0",
|
||||||
"svelte": "src/index.js",
|
"svelte": "src/index.js",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@budibase/bbui": "^1.0.105-alpha.19",
|
"@budibase/bbui": "^1.0.105-alpha.21",
|
||||||
"lodash": "^4.17.21",
|
"lodash": "^4.17.21",
|
||||||
"svelte": "^3.46.2"
|
"svelte": "^3.46.2"
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
{
|
{
|
||||||
"name": "@budibase/server",
|
"name": "@budibase/server",
|
||||||
"email": "hi@budibase.com",
|
"email": "hi@budibase.com",
|
||||||
"version": "1.0.105-alpha.19",
|
"version": "1.0.105-alpha.21",
|
||||||
"description": "Budibase Web Server",
|
"description": "Budibase Web Server",
|
||||||
"main": "src/index.ts",
|
"main": "src/index.ts",
|
||||||
"repository": {
|
"repository": {
|
||||||
|
@ -68,9 +68,9 @@
|
||||||
"license": "GPL-3.0",
|
"license": "GPL-3.0",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@apidevtools/swagger-parser": "^10.0.3",
|
"@apidevtools/swagger-parser": "^10.0.3",
|
||||||
"@budibase/backend-core": "^1.0.105-alpha.19",
|
"@budibase/backend-core": "^1.0.105-alpha.21",
|
||||||
"@budibase/client": "^1.0.105-alpha.19",
|
"@budibase/client": "^1.0.105-alpha.21",
|
||||||
"@budibase/string-templates": "^1.0.105-alpha.19",
|
"@budibase/string-templates": "^1.0.105-alpha.21",
|
||||||
"@bull-board/api": "^3.7.0",
|
"@bull-board/api": "^3.7.0",
|
||||||
"@bull-board/koa": "^3.7.0",
|
"@bull-board/koa": "^3.7.0",
|
||||||
"@elastic/elasticsearch": "7.10.0",
|
"@elastic/elasticsearch": "7.10.0",
|
||||||
|
|
|
@ -13,6 +13,7 @@ const integromat = require("./steps/integromat")
|
||||||
let filter = require("./steps/filter")
|
let filter = require("./steps/filter")
|
||||||
let delay = require("./steps/delay")
|
let delay = require("./steps/delay")
|
||||||
let queryRow = require("./steps/queryRows")
|
let queryRow = require("./steps/queryRows")
|
||||||
|
let loop = require("./steps/loop")
|
||||||
const env = require("../environment")
|
const env = require("../environment")
|
||||||
|
|
||||||
const ACTION_IMPLS = {
|
const ACTION_IMPLS = {
|
||||||
|
@ -27,6 +28,7 @@ const ACTION_IMPLS = {
|
||||||
DELAY: delay.run,
|
DELAY: delay.run,
|
||||||
FILTER: filter.run,
|
FILTER: filter.run,
|
||||||
QUERY_ROWS: queryRow.run,
|
QUERY_ROWS: queryRow.run,
|
||||||
|
LOOP: loop.run,
|
||||||
// these used to be lowercase step IDs, maintain for backwards compat
|
// these used to be lowercase step IDs, maintain for backwards compat
|
||||||
discord: discord.run,
|
discord: discord.run,
|
||||||
slack: slack.run,
|
slack: slack.run,
|
||||||
|
@ -45,6 +47,7 @@ const ACTION_DEFINITIONS = {
|
||||||
DELAY: delay.definition,
|
DELAY: delay.definition,
|
||||||
FILTER: filter.definition,
|
FILTER: filter.definition,
|
||||||
QUERY_ROWS: queryRow.definition,
|
QUERY_ROWS: queryRow.definition,
|
||||||
|
LOOP: loop.definition,
|
||||||
// these used to be lowercase step IDs, maintain for backwards compat
|
// these used to be lowercase step IDs, maintain for backwards compat
|
||||||
discord: discord.definition,
|
discord: discord.definition,
|
||||||
slack: slack.definition,
|
slack: slack.definition,
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
const { getTable } = require("../api/controllers/table/utils")
|
const { getTable } = require("../api/controllers/table/utils")
|
||||||
|
const { findHBSBlocks } = require("@budibase/string-templates")
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* When values are input to the system generally they will be of type string as this is required for template strings.
|
* When values are input to the system generally they will be of type string as this is required for template strings.
|
||||||
|
@ -74,3 +75,14 @@ exports.getError = err => {
|
||||||
}
|
}
|
||||||
return typeof err !== "string" ? err.toString() : err
|
return typeof err !== "string" ? err.toString() : err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
exports.substituteLoopStep = (hbsString, substitute) => {
|
||||||
|
let blocks = findHBSBlocks(hbsString)
|
||||||
|
for (let block of blocks) {
|
||||||
|
let oldBlock = block
|
||||||
|
block = block.replace(/loop/, substitute)
|
||||||
|
hbsString = hbsString.replace(new RegExp(oldBlock, "g"), block)
|
||||||
|
}
|
||||||
|
|
||||||
|
return hbsString
|
||||||
|
}
|
||||||
|
|
|
@ -0,0 +1,50 @@
|
||||||
|
exports.definition = {
|
||||||
|
name: "Looping",
|
||||||
|
icon: "Reuse",
|
||||||
|
tagline: "Loop the block",
|
||||||
|
description: "Loop",
|
||||||
|
stepId: "LOOP",
|
||||||
|
internal: true,
|
||||||
|
inputs: {},
|
||||||
|
schema: {
|
||||||
|
inputs: {
|
||||||
|
properties: {
|
||||||
|
option: {
|
||||||
|
customType: "loopOption",
|
||||||
|
title: "Input type",
|
||||||
|
},
|
||||||
|
binding: {
|
||||||
|
type: "string",
|
||||||
|
title: "Binding / Value",
|
||||||
|
},
|
||||||
|
iterations: {
|
||||||
|
type: "number",
|
||||||
|
title: "Max loop iterations",
|
||||||
|
},
|
||||||
|
failure: {
|
||||||
|
type: "string",
|
||||||
|
title: "Failure Condition",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
required: ["type", "value", "iterations", "failure"],
|
||||||
|
},
|
||||||
|
outputs: {
|
||||||
|
properties: {
|
||||||
|
items: {
|
||||||
|
customType: "item",
|
||||||
|
description: "The item currently being executed",
|
||||||
|
},
|
||||||
|
success: {
|
||||||
|
type: "boolean",
|
||||||
|
description: "Whether the message loop was successfully",
|
||||||
|
},
|
||||||
|
iterations: {
|
||||||
|
type: "number",
|
||||||
|
descriptions: "The amount of times the block ran",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
required: ["success", "items", "iterations"],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
type: "LOGIC",
|
||||||
|
}
|
|
@ -190,5 +190,11 @@ exports.WebhookType = {
|
||||||
AUTOMATION: "automation",
|
AUTOMATION: "automation",
|
||||||
}
|
}
|
||||||
|
|
||||||
|
exports.AutomationErrors = {
|
||||||
|
INCORRECT_TYPE: "INCORRECT_TYPE",
|
||||||
|
MAX_ITERATIONS: "MAX_ITERATIONS_REACHED",
|
||||||
|
FAILURE_CONDITION: "FAILURE_CONDITION_MET",
|
||||||
|
}
|
||||||
|
|
||||||
// pass through the list from the auth/core lib
|
// pass through the list from the auth/core lib
|
||||||
exports.ObjectStoreBuckets = ObjectStoreBuckets
|
exports.ObjectStoreBuckets = ObjectStoreBuckets
|
||||||
|
|
|
@ -59,6 +59,7 @@ module.exports = {
|
||||||
LOG_LEVEL: process.env.LOG_LEVEL,
|
LOG_LEVEL: process.env.LOG_LEVEL,
|
||||||
AUTOMATION_DIRECTORY: process.env.AUTOMATION_DIRECTORY,
|
AUTOMATION_DIRECTORY: process.env.AUTOMATION_DIRECTORY,
|
||||||
AUTOMATION_BUCKET: process.env.AUTOMATION_BUCKET,
|
AUTOMATION_BUCKET: process.env.AUTOMATION_BUCKET,
|
||||||
|
AUTOMATION_MAX_ITERATIONS: process.env.AUTOMATION_MAX_ITERATIONS,
|
||||||
SENDGRID_API_KEY: process.env.SENDGRID_API_KEY,
|
SENDGRID_API_KEY: process.env.SENDGRID_API_KEY,
|
||||||
DYNAMO_ENDPOINT: process.env.DYNAMO_ENDPOINT,
|
DYNAMO_ENDPOINT: process.env.DYNAMO_ENDPOINT,
|
||||||
POSTHOG_TOKEN: process.env.POSTHOG_TOKEN,
|
POSTHOG_TOKEN: process.env.POSTHOG_TOKEN,
|
||||||
|
|
|
@ -8,10 +8,14 @@ const { DocumentTypes } = require("../db/utils")
|
||||||
const { doInTenant } = require("@budibase/backend-core/tenancy")
|
const { doInTenant } = require("@budibase/backend-core/tenancy")
|
||||||
const { definitions: triggerDefs } = require("../automations/triggerInfo")
|
const { definitions: triggerDefs } = require("../automations/triggerInfo")
|
||||||
const { doInAppContext, getAppDB } = require("@budibase/backend-core/context")
|
const { doInAppContext, getAppDB } = require("@budibase/backend-core/context")
|
||||||
|
const { AutomationErrors } = require("../constants")
|
||||||
const FILTER_STEP_ID = actions.ACTION_DEFINITIONS.FILTER.stepId
|
const FILTER_STEP_ID = actions.ACTION_DEFINITIONS.FILTER.stepId
|
||||||
|
const LOOP_STEP_ID = actions.ACTION_DEFINITIONS.LOOP.stepId
|
||||||
|
|
||||||
const CRON_STEP_ID = triggerDefs.CRON.stepId
|
const CRON_STEP_ID = triggerDefs.CRON.stepId
|
||||||
const STOPPED_STATUS = { success: false, status: "STOPPED" }
|
const STOPPED_STATUS = { success: false, status: "STOPPED" }
|
||||||
|
const { cloneDeep } = require("lodash/fp")
|
||||||
|
const env = require("../environment")
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The automation orchestrator is a class responsible for executing automations.
|
* The automation orchestrator is a class responsible for executing automations.
|
||||||
|
@ -74,50 +78,208 @@ class Orchestrator {
|
||||||
this.executionOutput.steps.push(stepObj)
|
this.executionOutput.steps.push(stepObj)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
updateContextAndOutput(loopStepNumber, step, output, result) {
|
||||||
|
this.executionOutput.steps.splice(loopStepNumber, 0, {
|
||||||
|
id: step.id,
|
||||||
|
stepId: step.stepId,
|
||||||
|
outputs: {
|
||||||
|
...output,
|
||||||
|
success: result.success,
|
||||||
|
status: result.status,
|
||||||
|
},
|
||||||
|
inputs: step.inputs,
|
||||||
|
})
|
||||||
|
this._context.steps.splice(loopStepNumber, 0, {
|
||||||
|
...output,
|
||||||
|
success: result.success,
|
||||||
|
status: result.status,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
async execute() {
|
async execute() {
|
||||||
let automation = this._automation
|
let automation = this._automation
|
||||||
const app = await this.getApp()
|
const app = await this.getApp()
|
||||||
let stopped = false
|
let stopped = false
|
||||||
|
let loopStep
|
||||||
|
|
||||||
|
let stepCount = 0
|
||||||
|
let loopStepNumber
|
||||||
|
let loopSteps = []
|
||||||
for (let step of automation.definition.steps) {
|
for (let step of automation.definition.steps) {
|
||||||
// execution stopped, record state for that
|
stepCount++
|
||||||
if (stopped) {
|
let input
|
||||||
this.updateExecutionOutput(step.id, step.stepId, {}, STOPPED_STATUS)
|
if (step.stepId === LOOP_STEP_ID) {
|
||||||
|
loopStep = step
|
||||||
|
loopStepNumber = stepCount
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
let stepFn = await this.getStepFunctionality(step.stepId)
|
|
||||||
step.inputs = await processObject(step.inputs, this._context)
|
if (loopStep) {
|
||||||
step.inputs = automationUtils.cleanInputValues(
|
input = await processObject(loopStep.inputs, this._context)
|
||||||
step.inputs,
|
}
|
||||||
step.schema.inputs
|
let iterations = loopStep ? input.binding.length : 1
|
||||||
)
|
let iterationCount = 0
|
||||||
// appId is always passed
|
for (let index = 0; index < iterations; index++) {
|
||||||
try {
|
let originalStepInput = cloneDeep(step.inputs)
|
||||||
let tenantId = app.tenantId || DEFAULT_TENANT_ID
|
|
||||||
const outputs = await doInTenant(tenantId, () => {
|
// Handle if the user has set a max iteration count or if it reaches the max limit set by us
|
||||||
return stepFn({
|
if (loopStep) {
|
||||||
inputs: step.inputs,
|
// lets first of all handle the input
|
||||||
appId: this._appId,
|
// if the input is array then use it, if it is a string then split it on every new line
|
||||||
emitter: this._emitter,
|
let newInput = await processObject(
|
||||||
context: this._context,
|
loopStep.inputs,
|
||||||
})
|
cloneDeep(this._context)
|
||||||
})
|
)
|
||||||
this._context.steps.push(outputs)
|
newInput = automationUtils.cleanInputValues(
|
||||||
// if filter causes us to stop execution don't break the loop, set a var
|
newInput,
|
||||||
// so that we can finish iterating through the steps and record that it stopped
|
loopStep.schema.inputs
|
||||||
if (step.stepId === FILTER_STEP_ID && !outputs.success) {
|
)
|
||||||
stopped = true
|
this._context.steps[loopStepNumber] = {
|
||||||
this.updateExecutionOutput(step.id, step.stepId, step.inputs, {
|
currentItem: newInput.binding[index],
|
||||||
...outputs,
|
}
|
||||||
...STOPPED_STATUS,
|
|
||||||
})
|
let tempOutput = { items: loopSteps, iterations: iterationCount }
|
||||||
|
if (
|
||||||
|
(loopStep.inputs.option === "Array" &&
|
||||||
|
!Array.isArray(newInput.binding)) ||
|
||||||
|
(loopStep.inputs.option === "String" &&
|
||||||
|
typeof newInput.binding !== "string")
|
||||||
|
) {
|
||||||
|
this.updateContextAndOutput(loopStepNumber, step, tempOutput, {
|
||||||
|
status: AutomationErrors.INCORRECT_TYPE,
|
||||||
|
success: false,
|
||||||
|
})
|
||||||
|
loopSteps = null
|
||||||
|
loopStep = null
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
// The "Loop" binding in the front end is "fake", so replace it here so the context can understand it
|
||||||
|
// Pretty hacky because we need to account for the row object
|
||||||
|
for (let [key, value] of Object.entries(originalStepInput)) {
|
||||||
|
if (typeof value === "object") {
|
||||||
|
for (let [innerKey, innerValue] of Object.entries(
|
||||||
|
originalStepInput[key]
|
||||||
|
)) {
|
||||||
|
if (typeof innerValue === "string") {
|
||||||
|
originalStepInput[key][innerKey] =
|
||||||
|
automationUtils.substituteLoopStep(
|
||||||
|
innerValue,
|
||||||
|
`steps.${loopStepNumber}`
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (typeof value === "string") {
|
||||||
|
originalStepInput[key] = automationUtils.substituteLoopStep(
|
||||||
|
value,
|
||||||
|
`steps.${loopStepNumber}`
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
index === parseInt(env.AUTOMATION_MAX_ITERATIONS) ||
|
||||||
|
index === loopStep.inputs.iterations
|
||||||
|
) {
|
||||||
|
this.updateContextAndOutput(loopStepNumber, step, tempOutput, {
|
||||||
|
status: AutomationErrors.MAX_ITERATIONS,
|
||||||
|
success: true,
|
||||||
|
})
|
||||||
|
loopSteps = null
|
||||||
|
loopStep = null
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
this._context.steps[loopStepNumber]?.currentItem ===
|
||||||
|
loopStep.inputs.failure
|
||||||
|
) {
|
||||||
|
this.updateContextAndOutput(loopStepNumber, step, tempOutput, {
|
||||||
|
status: AutomationErrors.FAILURE_CONDITION,
|
||||||
|
success: false,
|
||||||
|
})
|
||||||
|
loopSteps = null
|
||||||
|
loopStep = null
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// execution stopped, record state for that
|
||||||
|
if (stopped) {
|
||||||
|
this.updateExecutionOutput(step.id, step.stepId, {}, STOPPED_STATUS)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
this.updateExecutionOutput(step.id, step.stepId, step.inputs, outputs)
|
|
||||||
} catch (err) {
|
// If it's a loop step, we need to manually add the bindings to the context
|
||||||
console.error(`Automation error - ${step.stepId} - ${err}`)
|
let stepFn = await this.getStepFunctionality(step.stepId)
|
||||||
return err
|
let inputs = await processObject(originalStepInput, this._context)
|
||||||
|
inputs = automationUtils.cleanInputValues(inputs, step.schema.inputs)
|
||||||
|
try {
|
||||||
|
// appId is always passed
|
||||||
|
let tenantId = app.tenantId || DEFAULT_TENANT_ID
|
||||||
|
const outputs = await doInTenant(tenantId, () => {
|
||||||
|
return stepFn({
|
||||||
|
inputs: inputs,
|
||||||
|
appId: this._appId,
|
||||||
|
emitter: this._emitter,
|
||||||
|
context: this._context,
|
||||||
|
})
|
||||||
|
})
|
||||||
|
this._context.steps[stepCount] = outputs
|
||||||
|
// if filter causes us to stop execution don't break the loop, set a var
|
||||||
|
// so that we can finish iterating through the steps and record that it stopped
|
||||||
|
if (step.stepId === FILTER_STEP_ID && !outputs.success) {
|
||||||
|
stopped = true
|
||||||
|
this.updateExecutionOutput(step.id, step.stepId, step.inputs, {
|
||||||
|
...outputs,
|
||||||
|
...STOPPED_STATUS,
|
||||||
|
})
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if (loopStep && loopSteps) {
|
||||||
|
loopSteps.push(outputs)
|
||||||
|
} else {
|
||||||
|
this.updateExecutionOutput(
|
||||||
|
step.id,
|
||||||
|
step.stepId,
|
||||||
|
step.inputs,
|
||||||
|
outputs
|
||||||
|
)
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
console.error(`Automation error - ${step.stepId} - ${err}`)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if (loopStep) {
|
||||||
|
iterationCount++
|
||||||
|
if (index === iterations - 1) {
|
||||||
|
loopStep = null
|
||||||
|
this._context.steps.splice(loopStepNumber, 1)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (loopSteps && loopSteps.length) {
|
||||||
|
let tempOutput = {
|
||||||
|
success: true,
|
||||||
|
items: loopSteps,
|
||||||
|
iterations: iterationCount,
|
||||||
|
}
|
||||||
|
this.executionOutput.steps.splice(loopStepNumber + 1, 0, {
|
||||||
|
id: step.id,
|
||||||
|
stepId: step.stepId,
|
||||||
|
outputs: tempOutput,
|
||||||
|
inputs: step.inputs,
|
||||||
|
})
|
||||||
|
|
||||||
|
this._context.steps.splice(loopStepNumber, 0, tempOutput)
|
||||||
|
loopSteps = null
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return this.executionOutput
|
return this.executionOutput
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "@budibase/string-templates",
|
"name": "@budibase/string-templates",
|
||||||
"version": "1.0.105-alpha.19",
|
"version": "1.0.105-alpha.21",
|
||||||
"description": "Handlebars wrapper for Budibase templating.",
|
"description": "Handlebars wrapper for Budibase templating.",
|
||||||
"main": "src/index.cjs",
|
"main": "src/index.cjs",
|
||||||
"module": "dist/bundle.mjs",
|
"module": "dist/bundle.mjs",
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
{
|
{
|
||||||
"name": "@budibase/worker",
|
"name": "@budibase/worker",
|
||||||
"email": "hi@budibase.com",
|
"email": "hi@budibase.com",
|
||||||
"version": "1.0.105-alpha.19",
|
"version": "1.0.105-alpha.21",
|
||||||
"description": "Budibase background service",
|
"description": "Budibase background service",
|
||||||
"main": "src/index.ts",
|
"main": "src/index.ts",
|
||||||
"repository": {
|
"repository": {
|
||||||
|
@ -31,8 +31,8 @@
|
||||||
"author": "Budibase",
|
"author": "Budibase",
|
||||||
"license": "GPL-3.0",
|
"license": "GPL-3.0",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@budibase/backend-core": "^1.0.105-alpha.19",
|
"@budibase/backend-core": "^1.0.105-alpha.21",
|
||||||
"@budibase/string-templates": "^1.0.105-alpha.19",
|
"@budibase/string-templates": "^1.0.105-alpha.21",
|
||||||
"@koa/router": "^8.0.0",
|
"@koa/router": "^8.0.0",
|
||||||
"@sentry/node": "^6.0.0",
|
"@sentry/node": "^6.0.0",
|
||||||
"@techpass/passport-openidconnect": "^0.3.0",
|
"@techpass/passport-openidconnect": "^0.3.0",
|
||||||
|
|
Loading…
Reference in New Issue