Merge remote-tracking branch 'origin/global-bindings' into fixes/global-bindings
This commit is contained in:
commit
53a2329811
|
@ -1,5 +1,5 @@
|
||||||
{
|
{
|
||||||
"version": "2.14.7",
|
"version": "2.14.8",
|
||||||
"npmClient": "yarn",
|
"npmClient": "yarn",
|
||||||
"packages": [
|
"packages": [
|
||||||
"packages/*",
|
"packages/*",
|
||||||
|
|
|
@ -66,6 +66,7 @@
|
||||||
"@fortawesome/free-solid-svg-icons": "^6.4.2",
|
"@fortawesome/free-solid-svg-icons": "^6.4.2",
|
||||||
"@spectrum-css/page": "^3.0.1",
|
"@spectrum-css/page": "^3.0.1",
|
||||||
"@spectrum-css/vars": "^3.0.1",
|
"@spectrum-css/vars": "^3.0.1",
|
||||||
|
"@zerodevx/svelte-json-view": "^1.0.7",
|
||||||
"codemirror": "^5.59.0",
|
"codemirror": "^5.59.0",
|
||||||
"dayjs": "^1.10.8",
|
"dayjs": "^1.10.8",
|
||||||
"downloadjs": "1.4.7",
|
"downloadjs": "1.4.7",
|
||||||
|
|
|
@ -19,10 +19,15 @@
|
||||||
export let lastStep
|
export let lastStep
|
||||||
|
|
||||||
let syncAutomationsEnabled = $licensing.syncAutomationsEnabled
|
let syncAutomationsEnabled = $licensing.syncAutomationsEnabled
|
||||||
|
let triggerAutomationRunEnabled = $licensing.triggerAutomationRunEnabled
|
||||||
let collectBlockAllowedSteps = [TriggerStepID.APP, TriggerStepID.WEBHOOK]
|
let collectBlockAllowedSteps = [TriggerStepID.APP, TriggerStepID.WEBHOOK]
|
||||||
let selectedAction
|
let selectedAction
|
||||||
let actionVal
|
let actionVal
|
||||||
let actions = Object.entries($automationStore.blockDefinitions.ACTION)
|
let actions = Object.entries($automationStore.blockDefinitions.ACTION)
|
||||||
|
let lockedFeatures = [
|
||||||
|
ActionStepID.COLLECT,
|
||||||
|
ActionStepID.TRIGGER_AUTOMATION_RUN,
|
||||||
|
]
|
||||||
|
|
||||||
$: collectBlockExists = checkForCollectStep($selectedAutomation)
|
$: collectBlockExists = checkForCollectStep($selectedAutomation)
|
||||||
|
|
||||||
|
@ -36,6 +41,10 @@
|
||||||
disabled: !lastStep || !syncAutomationsEnabled || collectBlockExists,
|
disabled: !lastStep || !syncAutomationsEnabled || collectBlockExists,
|
||||||
message: collectDisabledMessage(),
|
message: collectDisabledMessage(),
|
||||||
},
|
},
|
||||||
|
TRIGGER_AUTOMATION_RUN: {
|
||||||
|
disabled: !triggerAutomationRunEnabled,
|
||||||
|
message: collectDisabledMessage(),
|
||||||
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -149,7 +158,7 @@
|
||||||
<div class="item-body">
|
<div class="item-body">
|
||||||
<Icon name={action.icon} />
|
<Icon name={action.icon} />
|
||||||
<Body size="XS">{action.name}</Body>
|
<Body size="XS">{action.name}</Body>
|
||||||
{#if isDisabled && !syncAutomationsEnabled && action.stepId === ActionStepID.COLLECT}
|
{#if isDisabled && !syncAutomationsEnabled && !triggerAutomationRunEnabled && lockedFeatures.includes(action.stepId)}
|
||||||
<div class="tag-color">
|
<div class="tag-color">
|
||||||
<Tags>
|
<Tags>
|
||||||
<Tag icon="LockClosed">Premium</Tag>
|
<Tag icon="LockClosed">Premium</Tag>
|
||||||
|
|
|
@ -1,7 +1,8 @@
|
||||||
<script>
|
<script>
|
||||||
import { Icon, Divider, Tabs, Tab, TextArea, Label } from "@budibase/bbui"
|
import { Icon, Divider, Tabs, Tab, Label } from "@budibase/bbui"
|
||||||
import FlowItemHeader from "./FlowChart/FlowItemHeader.svelte"
|
import FlowItemHeader from "./FlowChart/FlowItemHeader.svelte"
|
||||||
import { ActionStepID } from "constants/backend/automations"
|
import { ActionStepID } from "constants/backend/automations"
|
||||||
|
import { JsonView } from "@zerodevx/svelte-json-view"
|
||||||
|
|
||||||
export let automation
|
export let automation
|
||||||
export let testResults
|
export let testResults
|
||||||
|
@ -14,13 +15,6 @@
|
||||||
return results?.steps.filter(x => x.stepId !== ActionStepID.LOOP || [])
|
return results?.steps.filter(x => x.stepId !== ActionStepID.LOOP || [])
|
||||||
}
|
}
|
||||||
|
|
||||||
function textArea(results, message) {
|
|
||||||
if (!results) {
|
|
||||||
return message
|
|
||||||
}
|
|
||||||
return JSON.stringify(results, null, 2)
|
|
||||||
}
|
|
||||||
|
|
||||||
$: filteredResults = prepTestResults(testResults)
|
$: filteredResults = prepTestResults(testResults)
|
||||||
|
|
||||||
$: {
|
$: {
|
||||||
|
@ -71,26 +65,34 @@
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
<div class="tabs">
|
<div class="tabs">
|
||||||
<Tabs noHorizPadding selected="Input">
|
<Tabs quiet noHorizPadding selected="Input">
|
||||||
<Tab title="Input">
|
<Tab title="Input">
|
||||||
<TextArea
|
<div class="wrap">
|
||||||
minHeight="160px"
|
{#if filteredResults?.[idx]?.inputs}
|
||||||
disabled
|
<JsonView depth={2} json={filteredResults?.[idx]?.inputs} />
|
||||||
value={textArea(filteredResults?.[idx]?.inputs, "No input")}
|
{:else}
|
||||||
/>
|
No input
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
</Tab>
|
</Tab>
|
||||||
<Tab title="Output">
|
<Tab title="Output">
|
||||||
<TextArea
|
<div class="wrap">
|
||||||
minHeight="160px"
|
{#if filteredResults?.[idx]?.inputs}
|
||||||
disabled
|
<JsonView
|
||||||
value={textArea(filteredResults?.[idx]?.outputs, "No output")}
|
depth={2}
|
||||||
|
json={filteredResults?.[idx]?.outputs}
|
||||||
/>
|
/>
|
||||||
|
{:else}
|
||||||
|
No input
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
</Tab>
|
</Tab>
|
||||||
</Tabs>
|
</Tabs>
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{#if blocks.length - 1 !== idx}
|
{#if blocks.length - 1 !== idx}
|
||||||
<div class="separator" />
|
<div class="separator" />
|
||||||
{/if}
|
{/if}
|
||||||
|
@ -104,6 +106,33 @@
|
||||||
overflow: auto;
|
overflow: auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.wrap {
|
||||||
|
font-family: monospace;
|
||||||
|
background-color: var(
|
||||||
|
--spectrum-textfield-m-background-color,
|
||||||
|
var(--spectrum-global-color-gray-50)
|
||||||
|
);
|
||||||
|
border: 1px solid var(--spectrum-global-color-gray-300);
|
||||||
|
font-size: 12px;
|
||||||
|
max-height: 160px; /* Adjusted max-height */
|
||||||
|
height: 160px;
|
||||||
|
--jsonPaddingLeft: 20px;
|
||||||
|
--jsonborderleft: 20px;
|
||||||
|
overflow: auto;
|
||||||
|
overflow: overlay;
|
||||||
|
overflow-x: hidden;
|
||||||
|
padding-left: var(--spacing-s);
|
||||||
|
padding-top: var(--spacing-xl);
|
||||||
|
border-radius: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.wrap::-webkit-scrollbar {
|
||||||
|
width: 7px; /* width of the scrollbar */
|
||||||
|
}
|
||||||
|
|
||||||
|
.wrap::-webkit-scrollbar-track {
|
||||||
|
background: transparent; /* transparent track */
|
||||||
|
}
|
||||||
.tabs {
|
.tabs {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
|
|
|
@ -1,44 +1,117 @@
|
||||||
<script>
|
<script>
|
||||||
import AutomationList from "./AutomationList.svelte"
|
|
||||||
import CreateAutomationModal from "./CreateAutomationModal.svelte"
|
import CreateAutomationModal from "./CreateAutomationModal.svelte"
|
||||||
import { Modal, Icon } from "@budibase/bbui"
|
import { Modal, notifications, Layout } from "@budibase/bbui"
|
||||||
import Panel from "components/design/Panel.svelte"
|
import NavHeader from "components/common/NavHeader.svelte"
|
||||||
|
import { onMount } from "svelte"
|
||||||
|
import {
|
||||||
|
automationStore,
|
||||||
|
selectedAutomation,
|
||||||
|
userSelectedResourceMap,
|
||||||
|
} from "builderStore"
|
||||||
|
import NavItem from "components/common/NavItem.svelte"
|
||||||
|
import EditAutomationPopover from "./EditAutomationPopover.svelte"
|
||||||
|
|
||||||
export let modal
|
export let modal
|
||||||
export let webhookModal
|
export let webhookModal
|
||||||
|
let searchString
|
||||||
|
|
||||||
|
$: selectedAutomationId = $selectedAutomation?._id
|
||||||
|
|
||||||
|
$: filteredAutomations = $automationStore.automations
|
||||||
|
.filter(automation => {
|
||||||
|
return (
|
||||||
|
!searchString ||
|
||||||
|
automation.name.toLowerCase().includes(searchString.toLowerCase())
|
||||||
|
)
|
||||||
|
})
|
||||||
|
.sort((a, b) => {
|
||||||
|
const lowerA = a.name.toLowerCase()
|
||||||
|
const lowerB = b.name.toLowerCase()
|
||||||
|
return lowerA > lowerB ? 1 : -1
|
||||||
|
})
|
||||||
|
|
||||||
|
$: showNoResults = searchString && !filteredAutomations.length
|
||||||
|
|
||||||
|
onMount(async () => {
|
||||||
|
try {
|
||||||
|
await automationStore.actions.fetch()
|
||||||
|
} catch (error) {
|
||||||
|
notifications.error("Error getting automations list")
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
function selectAutomation(id) {
|
||||||
|
automationStore.actions.select(id)
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<Panel title="Automations" borderRight noHeaderBorder titleCSS={false}>
|
<div class="side-bar">
|
||||||
<span class="panel-title-content" slot="panel-title-content">
|
<div class="side-bar-controls">
|
||||||
<div class="header">
|
<NavHeader
|
||||||
<div>Automations</div>
|
title="Automations"
|
||||||
<div on:click={modal.show} class="add-automation-button">
|
placeholder="Search for automation"
|
||||||
<Icon name="Add" />
|
bind:value={searchString}
|
||||||
|
onAdd={() => modal.show()}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="side-bar-nav">
|
||||||
|
{#each filteredAutomations as automation}
|
||||||
|
<NavItem
|
||||||
|
text={automation.name}
|
||||||
|
selected={automation._id === selectedAutomationId}
|
||||||
|
on:click={() => selectAutomation(automation._id)}
|
||||||
|
selectedBy={$userSelectedResourceMap[automation._id]}
|
||||||
|
>
|
||||||
|
<EditAutomationPopover {automation} />
|
||||||
|
</NavItem>
|
||||||
|
{/each}
|
||||||
|
|
||||||
|
{#if showNoResults}
|
||||||
|
<Layout paddingY="none" paddingX="L">
|
||||||
|
<div class="no-results">
|
||||||
|
There aren't any automations matching that name
|
||||||
</div>
|
</div>
|
||||||
</span>
|
</Layout>
|
||||||
<AutomationList />
|
{/if}
|
||||||
</Panel>
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<Modal bind:this={modal}>
|
<Modal bind:this={modal}>
|
||||||
<CreateAutomationModal {webhookModal} />
|
<CreateAutomationModal {webhookModal} />
|
||||||
</Modal>
|
</Modal>
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
.header {
|
.side-bar {
|
||||||
|
flex: 0 0 260px;
|
||||||
display: flex;
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: stretch;
|
||||||
|
border-right: var(--border-light);
|
||||||
|
background: var(--spectrum-global-color-gray-100);
|
||||||
|
overflow: hidden;
|
||||||
|
transition: margin-left 300ms ease-out;
|
||||||
|
}
|
||||||
|
@media (max-width: 640px) {
|
||||||
|
.side-bar {
|
||||||
|
margin-left: -262px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.side-bar-controls {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
justify-content: flex-start;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: space-between;
|
gap: var(--spacing-l);
|
||||||
gap: var(--spacing-m);
|
padding: 0 var(--spacing-l);
|
||||||
|
}
|
||||||
|
.side-bar-nav {
|
||||||
|
flex: 1 1 auto;
|
||||||
|
overflow: auto;
|
||||||
|
overflow-x: hidden;
|
||||||
}
|
}
|
||||||
|
|
||||||
.add-automation-button {
|
.no-results {
|
||||||
margin-left: 130px;
|
color: var(--spectrum-global-color-gray-600);
|
||||||
color: var(--grey-7);
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
|
|
||||||
.add-automation-button:hover {
|
|
||||||
color: var(--ink);
|
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
|
@ -28,6 +28,7 @@
|
||||||
import CodeEditorModal from "./CodeEditorModal.svelte"
|
import CodeEditorModal from "./CodeEditorModal.svelte"
|
||||||
import QuerySelector from "./QuerySelector.svelte"
|
import QuerySelector from "./QuerySelector.svelte"
|
||||||
import QueryParamSelector from "./QueryParamSelector.svelte"
|
import QueryParamSelector from "./QueryParamSelector.svelte"
|
||||||
|
import AutomationSelector from "./AutomationSelector.svelte"
|
||||||
import CronBuilder from "./CronBuilder.svelte"
|
import CronBuilder from "./CronBuilder.svelte"
|
||||||
import Editor from "components/integration/QueryEditor.svelte"
|
import Editor from "components/integration/QueryEditor.svelte"
|
||||||
import ModalBindableInput from "components/common/bindings/ModalBindableInput.svelte"
|
import ModalBindableInput from "components/common/bindings/ModalBindableInput.svelte"
|
||||||
|
@ -51,7 +52,6 @@
|
||||||
export let testData
|
export let testData
|
||||||
export let schemaProperties
|
export let schemaProperties
|
||||||
export let isTestModal = false
|
export let isTestModal = false
|
||||||
|
|
||||||
let webhookModal
|
let webhookModal
|
||||||
let drawer
|
let drawer
|
||||||
let fillWidth = true
|
let fillWidth = true
|
||||||
|
@ -101,7 +101,6 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const onChange = Utils.sequential(async (e, key) => {
|
const onChange = Utils.sequential(async (e, key) => {
|
||||||
// We need to cache the schema as part of the definition because it is
|
// We need to cache the schema as part of the definition because it is
|
||||||
// used in the server to detect relationships. It would be far better to
|
// used in the server to detect relationships. It would be far better to
|
||||||
|
@ -145,6 +144,7 @@
|
||||||
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]
|
||||||
|
|
||||||
|
@ -156,22 +156,96 @@
|
||||||
// Extract all outputs from all previous steps as available bindingsx§x
|
// Extract all outputs from all previous steps as available bindingsx§x
|
||||||
let bindings = []
|
let bindings = []
|
||||||
let loopBlockCount = 0
|
let loopBlockCount = 0
|
||||||
|
const addBinding = (name, value, icon, idx, isLoopBlock, bindingName) => {
|
||||||
|
const runtimeBinding = determineRuntimeBinding(name, idx, isLoopBlock)
|
||||||
|
const categoryName = determineCategoryName(idx, isLoopBlock, bindingName)
|
||||||
|
|
||||||
|
bindings.push(
|
||||||
|
createBindingObject(
|
||||||
|
name,
|
||||||
|
value,
|
||||||
|
icon,
|
||||||
|
idx,
|
||||||
|
loopBlockCount,
|
||||||
|
isLoopBlock,
|
||||||
|
runtimeBinding,
|
||||||
|
categoryName,
|
||||||
|
bindingName
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
const determineRuntimeBinding = (name, idx, isLoopBlock) => {
|
||||||
|
let runtimeName
|
||||||
|
|
||||||
|
/* Begin special cases for generating custom schemas based on triggers */
|
||||||
|
if (idx === 0 && automation.trigger?.event === "app:trigger") {
|
||||||
|
return `trigger.fields.${name}`
|
||||||
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
(idx === 0 && automation.trigger?.event === "row:update") ||
|
||||||
|
automation.trigger?.event === "row:save"
|
||||||
|
) {
|
||||||
|
if (name !== "id" && name !== "revision") return `trigger.row.${name}`
|
||||||
|
}
|
||||||
|
/* End special cases for generating custom schemas based on triggers */
|
||||||
|
|
||||||
|
if (isLoopBlock) {
|
||||||
|
runtimeName = `loop.${name}`
|
||||||
|
} else if (block.name.startsWith("JS")) {
|
||||||
|
runtimeName = `steps[${idx - loopBlockCount}].${name}`
|
||||||
|
} else {
|
||||||
|
runtimeName = `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
|
||||||
|
) => {
|
||||||
|
return {
|
||||||
|
readableBinding: bindingName
|
||||||
|
? `${bindingName}.${name}`
|
||||||
|
: runtimeBinding,
|
||||||
|
runtimeBinding,
|
||||||
|
type: value.type,
|
||||||
|
description: value.description,
|
||||||
|
icon,
|
||||||
|
category: categoryName,
|
||||||
|
display: {
|
||||||
|
type: value.type,
|
||||||
|
name,
|
||||||
|
rank: isLoopBlock ? idx + 1 : idx - loopBlockCount,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
for (let idx = 0; idx < blockIdx; idx++) {
|
for (let idx = 0; idx < blockIdx; idx++) {
|
||||||
let wasLoopBlock = allSteps[idx - 1]?.stepId === ActionStepID.LOOP
|
let wasLoopBlock = allSteps[idx - 1]?.stepId === ActionStepID.LOOP
|
||||||
let isLoopBlock =
|
let isLoopBlock =
|
||||||
allSteps[idx]?.stepId === ActionStepID.LOOP &&
|
allSteps[idx]?.stepId === ActionStepID.LOOP &&
|
||||||
allSteps.find(x => x.blockToLoop === block.id)
|
allSteps.some(x => x.blockToLoop === block.id)
|
||||||
|
let schema = cloneDeep(allSteps[idx]?.schema?.outputs?.properties) ?? {}
|
||||||
|
let bindingName =
|
||||||
|
automation.stepNames?.[allSteps[idx - loopBlockCount].id]
|
||||||
|
|
||||||
// If the previous block was a loop block, decrement the index so the following
|
|
||||||
// steps are in the correct order
|
|
||||||
if (wasLoopBlock) {
|
|
||||||
loopBlockCount++
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
let schema = allSteps[idx]?.schema?.outputs?.properties ?? {}
|
|
||||||
|
|
||||||
// If its a Loop Block, we need to add this custom schema
|
|
||||||
if (isLoopBlock) {
|
if (isLoopBlock) {
|
||||||
schema = {
|
schema = {
|
||||||
currentItem: {
|
currentItem: {
|
||||||
|
@ -180,54 +254,45 @@
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
const outputs = Object.entries(schema)
|
|
||||||
let bindingIcon = ""
|
|
||||||
let bindingRank = 0
|
|
||||||
if (idx === 0) {
|
|
||||||
bindingIcon = automation.trigger.icon
|
|
||||||
} else if (isLoopBlock) {
|
|
||||||
bindingIcon = "Reuse"
|
|
||||||
bindingRank = idx + 1
|
|
||||||
} else {
|
|
||||||
bindingIcon = allSteps[idx].icon
|
|
||||||
bindingRank = idx - loopBlockCount
|
|
||||||
}
|
|
||||||
let bindingName =
|
|
||||||
automation.stepNames?.[allSteps[idx - loopBlockCount].id]
|
|
||||||
bindings = bindings.concat(
|
|
||||||
outputs.map(([name, value]) => {
|
|
||||||
let runtimeName = isLoopBlock
|
|
||||||
? `loop.${name}`
|
|
||||||
: block.name.startsWith("JS")
|
|
||||||
? `steps[${idx - loopBlockCount}].${name}`
|
|
||||||
: `steps.${idx - loopBlockCount}.${name}`
|
|
||||||
const runtime = idx === 0 ? `trigger.${name}` : runtimeName
|
|
||||||
|
|
||||||
let categoryName
|
if (idx === 0 && automation.trigger?.event === "app:trigger") {
|
||||||
if (idx === 0) {
|
schema = Object.fromEntries(
|
||||||
categoryName = "Trigger outputs"
|
Object.keys(automation.trigger.inputs.fields || []).map(key => [
|
||||||
} else if (isLoopBlock) {
|
key,
|
||||||
categoryName = "Loop Outputs"
|
{ type: automation.trigger.inputs.fields[key] },
|
||||||
} else if (bindingName) {
|
])
|
||||||
categoryName = `${bindingName} outputs`
|
)
|
||||||
} else {
|
}
|
||||||
categoryName = `Step ${idx - loopBlockCount} outputs`
|
if (
|
||||||
|
(idx === 0 && automation.trigger.event === "row:update") ||
|
||||||
|
(idx === 0 && automation.trigger.event === "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,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// remove the original binding
|
||||||
|
delete schema.row
|
||||||
|
}
|
||||||
|
let icon =
|
||||||
|
idx === 0
|
||||||
|
? automation.trigger.icon
|
||||||
|
: isLoopBlock
|
||||||
|
? "Reuse"
|
||||||
|
: allSteps[idx].icon
|
||||||
|
|
||||||
|
if (wasLoopBlock) {
|
||||||
|
loopBlockCount++
|
||||||
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
Object.entries(schema).forEach(([name, value]) =>
|
||||||
readableBinding: bindingName ? `${bindingName}.${name}` : runtime,
|
addBinding(name, value, icon, idx, isLoopBlock, bindingName)
|
||||||
runtimeBinding: runtime,
|
|
||||||
type: value.type,
|
|
||||||
description: value.description,
|
|
||||||
icon: bindingIcon,
|
|
||||||
category: categoryName,
|
|
||||||
display: {
|
|
||||||
type: value.type,
|
|
||||||
name: name,
|
|
||||||
rank: bindingRank,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
})
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -245,10 +310,8 @@
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
return bindings
|
return bindings
|
||||||
}
|
}
|
||||||
|
|
||||||
function lookForFilters(properties) {
|
function lookForFilters(properties) {
|
||||||
if (!properties) {
|
if (!properties) {
|
||||||
return []
|
return []
|
||||||
|
@ -286,7 +349,8 @@
|
||||||
value.customType !== "code" &&
|
value.customType !== "code" &&
|
||||||
value.customType !== "queryParams" &&
|
value.customType !== "queryParams" &&
|
||||||
value.customType !== "cron" &&
|
value.customType !== "cron" &&
|
||||||
value.customType !== "triggerSchema"
|
value.customType !== "triggerSchema" &&
|
||||||
|
value.customType !== "automationFields"
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -421,6 +485,12 @@
|
||||||
on:change={e => onChange(e, key)}
|
on:change={e => onChange(e, key)}
|
||||||
value={inputData[key]}
|
value={inputData[key]}
|
||||||
/>
|
/>
|
||||||
|
{:else if value.customType === "automationFields"}
|
||||||
|
<AutomationSelector
|
||||||
|
on:change={e => onChange(e, key)}
|
||||||
|
value={inputData[key]}
|
||||||
|
{bindings}
|
||||||
|
/>
|
||||||
{:else if value.customType === "queryParams"}
|
{:else if value.customType === "queryParams"}
|
||||||
<QueryParamSelector
|
<QueryParamSelector
|
||||||
on:change={e => onChange(e, key)}
|
on:change={e => onChange(e, key)}
|
||||||
|
|
|
@ -0,0 +1,87 @@
|
||||||
|
<script>
|
||||||
|
import { Select, Label } from "@budibase/bbui"
|
||||||
|
import { createEventDispatcher } from "svelte"
|
||||||
|
import { automationStore, selectedAutomation } from "builderStore"
|
||||||
|
import { TriggerStepID } from "constants/backend/automations"
|
||||||
|
import DrawerBindableInput from "../../common/bindings/DrawerBindableInput.svelte"
|
||||||
|
import AutomationBindingPanel from "../../common/bindings/ServerBindingPanel.svelte"
|
||||||
|
|
||||||
|
const dispatch = createEventDispatcher()
|
||||||
|
|
||||||
|
export let value
|
||||||
|
export let bindings = []
|
||||||
|
const onChangeAutomation = e => {
|
||||||
|
value.automationId = e.detail
|
||||||
|
dispatch("change", value)
|
||||||
|
}
|
||||||
|
|
||||||
|
const onChange = (e, field) => {
|
||||||
|
value[field] = e.detail
|
||||||
|
dispatch("change", value)
|
||||||
|
}
|
||||||
|
$: if (value?.automationId == null) value = { automationId: "" }
|
||||||
|
|
||||||
|
$: automationFields =
|
||||||
|
$automationStore.automations.find(
|
||||||
|
automation => automation._id === value?.automationId
|
||||||
|
)?.definition?.trigger?.inputs?.fields || []
|
||||||
|
|
||||||
|
$: filteredAutomations = $automationStore.automations.filter(
|
||||||
|
automation =>
|
||||||
|
automation.definition.trigger.stepId === TriggerStepID.APP &&
|
||||||
|
automation._id !== $selectedAutomation._id
|
||||||
|
)
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div class="schema-field">
|
||||||
|
<Label>Automation</Label>
|
||||||
|
<div class="field-width">
|
||||||
|
<Select
|
||||||
|
on:change={onChangeAutomation}
|
||||||
|
value={value.automationId}
|
||||||
|
options={filteredAutomations}
|
||||||
|
getOptionValue={automation => automation._id}
|
||||||
|
getOptionLabel={automation => automation.name}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{#if Object.keys(automationFields)}
|
||||||
|
{#each Object.keys(automationFields) as field}
|
||||||
|
<div class="schema-field">
|
||||||
|
<Label>{field}</Label>
|
||||||
|
<div class="field-width">
|
||||||
|
<DrawerBindableInput
|
||||||
|
panel={AutomationBindingPanel}
|
||||||
|
extraThin
|
||||||
|
value={value[field]}
|
||||||
|
on:change={e => onChange(e, field)}
|
||||||
|
type="string"
|
||||||
|
{bindings}
|
||||||
|
fillWidth={true}
|
||||||
|
updateOnChange={false}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{/each}
|
||||||
|
{/if}
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.field-width {
|
||||||
|
width: 320px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.schema-field {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
flex-direction: row;
|
||||||
|
align-items: center;
|
||||||
|
gap: 10px;
|
||||||
|
flex: 1;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.schema-field :global(label) {
|
||||||
|
text-transform: capitalize;
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -21,6 +21,7 @@ export const ActionStepID = {
|
||||||
QUERY_ROWS: "QUERY_ROWS",
|
QUERY_ROWS: "QUERY_ROWS",
|
||||||
LOOP: "LOOP",
|
LOOP: "LOOP",
|
||||||
COLLECT: "COLLECT",
|
COLLECT: "COLLECT",
|
||||||
|
TRIGGER_AUTOMATION_RUN: "TRIGGER_AUTOMATION_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",
|
discord: "discord",
|
||||||
slack: "slack",
|
slack: "slack",
|
||||||
|
|
|
@ -125,6 +125,10 @@ export const createLicensingStore = () => {
|
||||||
const syncAutomationsEnabled = license.features.includes(
|
const syncAutomationsEnabled = license.features.includes(
|
||||||
Constants.Features.SYNC_AUTOMATIONS
|
Constants.Features.SYNC_AUTOMATIONS
|
||||||
)
|
)
|
||||||
|
const triggerAutomationRunEnabled = license.features.includes(
|
||||||
|
Constants.Features.TRIGGER_AUTOMATION_RUN
|
||||||
|
)
|
||||||
|
|
||||||
const perAppBuildersEnabled = license.features.includes(
|
const perAppBuildersEnabled = license.features.includes(
|
||||||
Constants.Features.APP_BUILDERS
|
Constants.Features.APP_BUILDERS
|
||||||
)
|
)
|
||||||
|
@ -147,6 +151,7 @@ export const createLicensingStore = () => {
|
||||||
auditLogsEnabled,
|
auditLogsEnabled,
|
||||||
enforceableSSO,
|
enforceableSSO,
|
||||||
syncAutomationsEnabled,
|
syncAutomationsEnabled,
|
||||||
|
triggerAutomationRunEnabled,
|
||||||
isViewPermissionsEnabled,
|
isViewPermissionsEnabled,
|
||||||
perAppBuildersEnabled,
|
perAppBuildersEnabled,
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,12 @@
|
||||||
|
const actual = jest.requireActual("@budibase/pro")
|
||||||
|
const pro = {
|
||||||
|
...actual,
|
||||||
|
features: {
|
||||||
|
...actual.features,
|
||||||
|
isTriggerAutomationRunEnabled: () => {
|
||||||
|
return true
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
export = pro
|
|
@ -15,6 +15,7 @@ import * as delay from "./steps/delay"
|
||||||
import * as queryRow from "./steps/queryRows"
|
import * as queryRow from "./steps/queryRows"
|
||||||
import * as loop from "./steps/loop"
|
import * as loop from "./steps/loop"
|
||||||
import * as collect from "./steps/collect"
|
import * as collect from "./steps/collect"
|
||||||
|
import * as triggerAutomationRun from "./steps/triggerAutomationRun"
|
||||||
import env from "../environment"
|
import env from "../environment"
|
||||||
import {
|
import {
|
||||||
AutomationStepSchema,
|
AutomationStepSchema,
|
||||||
|
@ -41,6 +42,7 @@ const ACTION_IMPLS: Record<
|
||||||
FILTER: filter.run,
|
FILTER: filter.run,
|
||||||
QUERY_ROWS: queryRow.run,
|
QUERY_ROWS: queryRow.run,
|
||||||
COLLECT: collect.run,
|
COLLECT: collect.run,
|
||||||
|
TRIGGER_AUTOMATION_RUN: triggerAutomationRun.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,
|
||||||
|
@ -62,6 +64,7 @@ export const BUILTIN_ACTION_DEFINITIONS: Record<string, AutomationStepSchema> =
|
||||||
QUERY_ROWS: queryRow.definition,
|
QUERY_ROWS: queryRow.definition,
|
||||||
LOOP: loop.definition,
|
LOOP: loop.definition,
|
||||||
COLLECT: collect.definition,
|
COLLECT: collect.definition,
|
||||||
|
TRIGGER_AUTOMATION_RUN: triggerAutomationRun.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,
|
||||||
|
|
|
@ -99,7 +99,7 @@ export async function run({ inputs }: AutomationStepInput) {
|
||||||
} else {
|
} else {
|
||||||
result = false
|
result = false
|
||||||
}
|
}
|
||||||
return { success: true, result }
|
return { success: true, result, refValue: field, comparisonValue: value }
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
return { success: false, result: false }
|
return { success: false, result: false }
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,90 @@
|
||||||
|
import {
|
||||||
|
AutomationActionStepId,
|
||||||
|
AutomationStepSchema,
|
||||||
|
AutomationStepInput,
|
||||||
|
AutomationStepType,
|
||||||
|
AutomationIOType,
|
||||||
|
AutomationResults,
|
||||||
|
Automation,
|
||||||
|
AutomationCustomIOType,
|
||||||
|
} from "@budibase/types"
|
||||||
|
import * as triggers from "../triggers"
|
||||||
|
import { db as dbCore, context } from "@budibase/backend-core"
|
||||||
|
import { features } from "@budibase/pro"
|
||||||
|
|
||||||
|
export const definition: AutomationStepSchema = {
|
||||||
|
name: "Trigger an automation",
|
||||||
|
tagline: "Triggers an automation synchronously",
|
||||||
|
icon: "Sync",
|
||||||
|
description: "Triggers an automation synchronously",
|
||||||
|
type: AutomationStepType.ACTION,
|
||||||
|
internal: true,
|
||||||
|
features: {},
|
||||||
|
stepId: AutomationActionStepId.TRIGGER_AUTOMATION_RUN,
|
||||||
|
inputs: {},
|
||||||
|
schema: {
|
||||||
|
inputs: {
|
||||||
|
properties: {
|
||||||
|
automation: {
|
||||||
|
type: AutomationIOType.OBJECT,
|
||||||
|
properties: {
|
||||||
|
automationId: {
|
||||||
|
type: AutomationIOType.STRING,
|
||||||
|
customType: AutomationCustomIOType.AUTOMATION,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
customType: AutomationCustomIOType.AUTOMATION_FIELDS,
|
||||||
|
title: "automatioFields",
|
||||||
|
required: ["automationId"],
|
||||||
|
},
|
||||||
|
timeout: {
|
||||||
|
type: AutomationIOType.NUMBER,
|
||||||
|
title: "Timeout (ms)",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
required: ["automationId"],
|
||||||
|
},
|
||||||
|
outputs: {
|
||||||
|
properties: {
|
||||||
|
success: {
|
||||||
|
type: AutomationIOType.BOOLEAN,
|
||||||
|
description: "Whether the automation was successful",
|
||||||
|
},
|
||||||
|
value: {
|
||||||
|
type: AutomationIOType.OBJECT,
|
||||||
|
description: "Automation Result",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
required: ["success", "value"],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function run({ inputs }: AutomationStepInput) {
|
||||||
|
const { automationId, ...fieldParams } = inputs.automation
|
||||||
|
|
||||||
|
if (await features.isTriggerAutomationRunEnabled()) {
|
||||||
|
if (!inputs.automation.automationId) {
|
||||||
|
return {
|
||||||
|
success: false,
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
const db = context.getAppDB()
|
||||||
|
let automation = await db.get<Automation>(inputs.automation.automationId)
|
||||||
|
|
||||||
|
const response: AutomationResults = await triggers.externalTrigger(
|
||||||
|
automation,
|
||||||
|
{
|
||||||
|
fields: { ...fieldParams },
|
||||||
|
timeout: inputs.timeout * 1000 || 120000,
|
||||||
|
},
|
||||||
|
{ getResponses: true }
|
||||||
|
)
|
||||||
|
|
||||||
|
return {
|
||||||
|
success: true,
|
||||||
|
value: response.steps,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,43 @@
|
||||||
|
jest.spyOn(global.console, "error")
|
||||||
|
|
||||||
|
import * as setup from "./utilities"
|
||||||
|
import * as automation from "../index"
|
||||||
|
import { serverLogAutomation } from "../../tests/utilities/structures"
|
||||||
|
|
||||||
|
describe("Test triggering an automation from another automation", () => {
|
||||||
|
let config = setup.getConfig()
|
||||||
|
|
||||||
|
beforeAll(async () => {
|
||||||
|
await automation.init()
|
||||||
|
await config.init()
|
||||||
|
})
|
||||||
|
|
||||||
|
afterAll(async () => {
|
||||||
|
await automation.shutdown()
|
||||||
|
setup.afterAll()
|
||||||
|
})
|
||||||
|
|
||||||
|
it("should trigger an other server log automation", async () => {
|
||||||
|
let automation = serverLogAutomation()
|
||||||
|
let newAutomation = await config.createAutomation(automation)
|
||||||
|
|
||||||
|
const inputs: any = {
|
||||||
|
automation: { automationId: newAutomation._id, timeout: 12000 },
|
||||||
|
}
|
||||||
|
const res = await setup.runStep(
|
||||||
|
setup.actions.TRIGGER_AUTOMATION_RUN.stepId,
|
||||||
|
inputs
|
||||||
|
)
|
||||||
|
// Check if the SERVER_LOG step was successful
|
||||||
|
expect(res.value[1].outputs.success).toBe(true)
|
||||||
|
})
|
||||||
|
|
||||||
|
it("should fail gracefully if the automation id is incorrect", async () => {
|
||||||
|
const inputs: any = { automation: { automationId: null, timeout: 12000 } }
|
||||||
|
const res = await setup.runStep(
|
||||||
|
setup.actions.TRIGGER_AUTOMATION_RUN.stepId,
|
||||||
|
inputs
|
||||||
|
)
|
||||||
|
expect(res.success).toBe(false)
|
||||||
|
})
|
||||||
|
})
|
|
@ -21,6 +21,7 @@ import {
|
||||||
Table,
|
Table,
|
||||||
INTERNAL_TABLE_SOURCE_ID,
|
INTERNAL_TABLE_SOURCE_ID,
|
||||||
TableSourceType,
|
TableSourceType,
|
||||||
|
AutomationIOType,
|
||||||
} from "@budibase/types"
|
} from "@budibase/types"
|
||||||
|
|
||||||
const { BUILTIN_ROLE_IDS } = roles
|
const { BUILTIN_ROLE_IDS } = roles
|
||||||
|
@ -153,6 +154,56 @@ export function basicAutomation(appId?: string): Automation {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function serverLogAutomation(appId?: string): Automation {
|
||||||
|
return {
|
||||||
|
name: "My Automation",
|
||||||
|
screenId: "kasdkfldsafkl",
|
||||||
|
live: true,
|
||||||
|
uiTree: {},
|
||||||
|
definition: {
|
||||||
|
trigger: {
|
||||||
|
stepId: AutomationTriggerStepId.APP,
|
||||||
|
name: "test",
|
||||||
|
tagline: "test",
|
||||||
|
icon: "test",
|
||||||
|
description: "test",
|
||||||
|
type: AutomationStepType.TRIGGER,
|
||||||
|
id: "test",
|
||||||
|
inputs: {},
|
||||||
|
schema: {
|
||||||
|
inputs: {
|
||||||
|
properties: {},
|
||||||
|
},
|
||||||
|
outputs: {
|
||||||
|
properties: {},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
steps: [
|
||||||
|
{
|
||||||
|
stepId: AutomationActionStepId.SERVER_LOG,
|
||||||
|
name: "Backend log",
|
||||||
|
tagline: "Console log a value in the backend",
|
||||||
|
icon: "Monitoring",
|
||||||
|
description: "Logs the given text to the server (using console.log)",
|
||||||
|
internal: true,
|
||||||
|
features: {
|
||||||
|
LOOPING: true,
|
||||||
|
},
|
||||||
|
inputs: {
|
||||||
|
text: "log statement",
|
||||||
|
},
|
||||||
|
schema: BUILTIN_ACTION_DEFINITIONS.SERVER_LOG.schema,
|
||||||
|
id: "y8lkZbeSe",
|
||||||
|
type: AutomationStepType.ACTION,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
type: "automation",
|
||||||
|
appId: appId!,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export function loopAutomation(tableId: string, loopOpts?: any): Automation {
|
export function loopAutomation(tableId: string, loopOpts?: any): Automation {
|
||||||
if (!loopOpts) {
|
if (!loopOpts) {
|
||||||
loopOpts = {
|
loopOpts = {
|
||||||
|
|
|
@ -28,6 +28,8 @@ export enum AutomationCustomIOType {
|
||||||
TRIGGER_SCHEMA = "triggerSchema",
|
TRIGGER_SCHEMA = "triggerSchema",
|
||||||
CRON = "cron",
|
CRON = "cron",
|
||||||
WEBHOOK_URL = "webhookUrl",
|
WEBHOOK_URL = "webhookUrl",
|
||||||
|
AUTOMATION = "automation",
|
||||||
|
AUTOMATION_FIELDS = "automationFields",
|
||||||
}
|
}
|
||||||
|
|
||||||
export enum AutomationTriggerStepId {
|
export enum AutomationTriggerStepId {
|
||||||
|
@ -61,6 +63,7 @@ export enum AutomationActionStepId {
|
||||||
LOOP = "LOOP",
|
LOOP = "LOOP",
|
||||||
COLLECT = "COLLECT",
|
COLLECT = "COLLECT",
|
||||||
OPENAI = "OPENAI",
|
OPENAI = "OPENAI",
|
||||||
|
TRIGGER_AUTOMATION_RUN = "TRIGGER_AUTOMATION_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",
|
discord = "discord",
|
||||||
slack = "slack",
|
slack = "slack",
|
||||||
|
|
|
@ -9,6 +9,7 @@ export enum Feature {
|
||||||
BRANDING = "branding",
|
BRANDING = "branding",
|
||||||
SCIM = "scim",
|
SCIM = "scim",
|
||||||
SYNC_AUTOMATIONS = "syncAutomations",
|
SYNC_AUTOMATIONS = "syncAutomations",
|
||||||
|
TRIGGER_AUTOMATION_RUN = "triggerAutomationRun",
|
||||||
APP_BUILDERS = "appBuilders",
|
APP_BUILDERS = "appBuilders",
|
||||||
OFFLINE = "offline",
|
OFFLINE = "offline",
|
||||||
EXPANDED_PUBLIC_API = "expandedPublicApi",
|
EXPANDED_PUBLIC_API = "expandedPublicApi",
|
||||||
|
|
|
@ -22,6 +22,7 @@ export enum LockName {
|
||||||
QUOTA_USAGE_EVENT = "quota_usage_event",
|
QUOTA_USAGE_EVENT = "quota_usage_event",
|
||||||
APP_MIGRATION = "app_migrations",
|
APP_MIGRATION = "app_migrations",
|
||||||
PROCESS_AUTO_COLUMNS = "process_auto_columns",
|
PROCESS_AUTO_COLUMNS = "process_auto_columns",
|
||||||
|
PROCESS_USER_INVITE = "process_user_invite",
|
||||||
}
|
}
|
||||||
|
|
||||||
export type LockOptions = {
|
export type LockOptions = {
|
||||||
|
|
|
@ -12,6 +12,8 @@ import {
|
||||||
InviteUserRequest,
|
InviteUserRequest,
|
||||||
InviteUsersRequest,
|
InviteUsersRequest,
|
||||||
InviteUsersResponse,
|
InviteUsersResponse,
|
||||||
|
LockName,
|
||||||
|
LockType,
|
||||||
MigrationType,
|
MigrationType,
|
||||||
SaveUserResponse,
|
SaveUserResponse,
|
||||||
SearchUsersRequest,
|
SearchUsersRequest,
|
||||||
|
@ -27,6 +29,7 @@ import {
|
||||||
platform,
|
platform,
|
||||||
tenancy,
|
tenancy,
|
||||||
db,
|
db,
|
||||||
|
locks,
|
||||||
} from "@budibase/backend-core"
|
} from "@budibase/backend-core"
|
||||||
import { checkAnyUserExists } from "../../../utilities/users"
|
import { checkAnyUserExists } from "../../../utilities/users"
|
||||||
import { isEmailConfigured } from "../../../utilities/email"
|
import { isEmailConfigured } from "../../../utilities/email"
|
||||||
|
@ -380,9 +383,16 @@ export const inviteAccept = async (
|
||||||
) => {
|
) => {
|
||||||
const { inviteCode, password, firstName, lastName } = ctx.request.body
|
const { inviteCode, password, firstName, lastName } = ctx.request.body
|
||||||
try {
|
try {
|
||||||
|
await locks.doWithLock(
|
||||||
|
{
|
||||||
|
type: LockType.AUTO_EXTEND,
|
||||||
|
name: LockName.PROCESS_USER_INVITE,
|
||||||
|
resource: inviteCode,
|
||||||
|
systemLock: true,
|
||||||
|
},
|
||||||
|
async () => {
|
||||||
// info is an extension of the user object that was stored by global
|
// info is an extension of the user object that was stored by global
|
||||||
const { email, info }: any = await cache.invite.getCode(inviteCode)
|
const { email, info } = await cache.invite.getCode(inviteCode)
|
||||||
await cache.invite.deleteCode(inviteCode)
|
|
||||||
const user = await tenancy.doInTenant(info.tenantId, async () => {
|
const user = await tenancy.doInTenant(info.tenantId, async () => {
|
||||||
let request: any = {
|
let request: any = {
|
||||||
firstName,
|
firstName,
|
||||||
|
@ -393,7 +403,7 @@ export const inviteAccept = async (
|
||||||
roles: info.apps,
|
roles: info.apps,
|
||||||
tenantId: info.tenantId,
|
tenantId: info.tenantId,
|
||||||
}
|
}
|
||||||
let builder: { global: boolean; apps?: string[] } = {
|
const builder: { global: boolean; apps?: string[] } = {
|
||||||
global: info?.builder?.global || false,
|
global: info?.builder?.global || false,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -408,23 +418,25 @@ export const inviteAccept = async (
|
||||||
}
|
}
|
||||||
|
|
||||||
const saved = await userSdk.db.save(request)
|
const saved = await userSdk.db.save(request)
|
||||||
const db = tenancy.getGlobalDB()
|
await events.user.inviteAccepted(saved)
|
||||||
const user = await db.get<User>(saved._id)
|
|
||||||
await events.user.inviteAccepted(user)
|
|
||||||
return saved
|
return saved
|
||||||
})
|
})
|
||||||
|
|
||||||
|
await cache.invite.deleteCode(inviteCode)
|
||||||
|
|
||||||
ctx.body = {
|
ctx.body = {
|
||||||
_id: user._id!,
|
_id: user._id!,
|
||||||
_rev: user._rev!,
|
_rev: user._rev!,
|
||||||
email: user.email,
|
email: user.email,
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
} catch (err: any) {
|
} catch (err: any) {
|
||||||
if (err.code === ErrorCode.USAGE_LIMIT_EXCEEDED) {
|
if (err.code === ErrorCode.USAGE_LIMIT_EXCEEDED) {
|
||||||
// explicitly re-throw limit exceeded errors
|
// explicitly re-throw limit exceeded errors
|
||||||
ctx.throw(400, err)
|
ctx.throw(400, err)
|
||||||
}
|
}
|
||||||
console.warn("Error inviting user", err)
|
console.warn("Error inviting user", err)
|
||||||
ctx.throw(400, "Unable to create new user, invitation invalid.")
|
ctx.throw(400, err || "Unable to create new user, invitation invalid.")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,26 +3,14 @@
|
||||||
packages_to_remove="@budibase/backend-core @budibase/bbui @budibase/builder @budibase/cli @budibase/client @budibase/frontend-core @budibase/pro @budibase/sdk @budibase/server @budibase/shared-core @budibase/string-templates @budibase/types @budibase/worker"
|
packages_to_remove="@budibase/backend-core @budibase/bbui @budibase/builder @budibase/cli @budibase/client @budibase/frontend-core @budibase/pro @budibase/sdk @budibase/server @budibase/shared-core @budibase/string-templates @budibase/types @budibase/worker"
|
||||||
|
|
||||||
package_json_path="$1"
|
package_json_path="$1"
|
||||||
package_json=$(cat "$package_json_path")
|
|
||||||
|
|
||||||
process_package() {
|
process_package() {
|
||||||
pkg_path="$1"
|
pkg_path="$1"
|
||||||
package_json=$(cat "$pkg_path")
|
|
||||||
has_changes=false
|
|
||||||
|
|
||||||
for package_name in $packages_to_remove; do
|
for package_name in $packages_to_remove; do
|
||||||
if echo "$package_json" | jq -e --arg package_name "$package_name" '.dependencies | has($package_name)' > /dev/null; then
|
jq "del(.dependencies[\"$package_name\"])" $pkg_path > tmp_file.json && mv tmp_file.json $pkg_path
|
||||||
package_json=$(echo "$package_json" | jq "del(.dependencies[\"$package_name\"])")
|
jq "del(.resolutions[\"$package_name\"])" $pkg_path > tmp_file.json && mv tmp_file.json $pkg_path
|
||||||
has_changes=true
|
|
||||||
fi
|
|
||||||
done
|
done
|
||||||
|
|
||||||
if [ "$has_changes" = true ]; then
|
|
||||||
echo "$package_json" > "$pkg_path"
|
|
||||||
fi
|
|
||||||
}
|
}
|
||||||
|
|
||||||
process_package "$package_json_path"
|
process_package "$package_json_path"
|
||||||
|
|
||||||
package_json=$(cat "$package_json_path")
|
|
||||||
echo "$package_json" | jq "del(.resolutions)" > "$1"
|
|
||||||
|
|
Loading…
Reference in New Issue