move workflow to array data structure
This commit is contained in:
parent
dc90e141f5
commit
a220822e3a
|
@ -152,7 +152,7 @@ export default {
|
||||||
{
|
{
|
||||||
find: "builderStore",
|
find: "builderStore",
|
||||||
replacement: path.resolve(projectRootDir, "src/builderStore"),
|
replacement: path.resolve(projectRootDir, "src/builderStore"),
|
||||||
}
|
},
|
||||||
],
|
],
|
||||||
customResolver,
|
customResolver,
|
||||||
}),
|
}),
|
||||||
|
|
|
@ -7,8 +7,7 @@
|
||||||
import AppNotification, {
|
import AppNotification, {
|
||||||
showAppNotification,
|
showAppNotification,
|
||||||
} from "components/common/AppNotification.svelte"
|
} from "components/common/AppNotification.svelte"
|
||||||
import { NotificationDisplay } from '@beyonk/svelte-notifications'
|
import { NotificationDisplay } from "@beyonk/svelte-notifications"
|
||||||
|
|
||||||
|
|
||||||
function showErrorBanner() {
|
function showErrorBanner() {
|
||||||
showAppNotification({
|
showAppNotification({
|
||||||
|
|
|
@ -13,82 +13,109 @@ export default class Workflow {
|
||||||
}
|
}
|
||||||
|
|
||||||
isEmpty() {
|
isEmpty() {
|
||||||
return !this.workflow.definition.next
|
// return this.workflow.definition.next
|
||||||
|
return this.workflow.length > 0
|
||||||
}
|
}
|
||||||
|
|
||||||
addBlock(block) {
|
addBlock(block) {
|
||||||
let node = this.workflow.definition
|
// Make sure to add trigger if doesn't exist
|
||||||
while (node.next) node = node.next
|
this.workflow.definition.steps.push({
|
||||||
node.next = {
|
|
||||||
id: generate(),
|
id: generate(),
|
||||||
...block
|
...block,
|
||||||
}
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
updateBlock(updatedBlock, id) {
|
updateBlock(updatedBlock, id) {
|
||||||
let block = this.workflow.definition
|
const { steps, trigger } = this.workflow.definition
|
||||||
|
|
||||||
while (block.id !== id) block = block.next
|
// if the block is a trigger do X
|
||||||
if (!block) throw new Error("Block not found.")
|
|
||||||
|
|
||||||
block = updatedBlock
|
// if step
|
||||||
|
const stepIdx = steps.findIndex(step => step.id === id)
|
||||||
|
|
||||||
|
// while (block.id !== id) block = block.next
|
||||||
|
if (stepIdx < 0) throw new Error("Block not found.")
|
||||||
|
|
||||||
|
steps.splice(stepIdx, 1, updatedBlock)
|
||||||
}
|
}
|
||||||
|
|
||||||
deleteBlock(id) {
|
deleteBlock(id) {
|
||||||
let previous = null
|
const { steps, trigger } = this.workflow.definition
|
||||||
let block = this.workflow.definition
|
|
||||||
|
|
||||||
// iterate through the blocks
|
const stepIdx = steps.findIndex(step => step.id === id)
|
||||||
while (block.id !== id) {
|
|
||||||
previous = block
|
|
||||||
block = block.next
|
|
||||||
}
|
|
||||||
|
|
||||||
// delete the block matching your id
|
if (stepIdx < 0) throw new Error("Block not found.")
|
||||||
if (!block.next) {
|
|
||||||
delete previous.next
|
|
||||||
} else {
|
|
||||||
previous.next = block.next
|
|
||||||
}
|
|
||||||
|
|
||||||
|
steps.splice(stepIdx, 1)
|
||||||
}
|
}
|
||||||
|
|
||||||
createUiTree() {
|
createUiTree() {
|
||||||
if (!this.workflow.definition.next) return []
|
if (!this.workflow.definition) return []
|
||||||
return Workflow.buildUiTree(this.workflow.definition.next)
|
return Workflow.buildUiTree(this.workflow.definition)
|
||||||
}
|
}
|
||||||
|
|
||||||
static buildUiTree(block, tree = []) {
|
static buildUiTree(definition) {
|
||||||
if (!block) return tree
|
return definition.steps.map(step => {
|
||||||
|
// The client side display definition for the block
|
||||||
|
const definition = blockDefinitions[step.type][step.actionId]
|
||||||
|
if (!definition) {
|
||||||
|
throw new Error(
|
||||||
|
`No block definition exists for the chosen block. Check there's an entry in the block definitions for ${step.actionId}`
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
// The client side display definition for the block
|
if (!definition.params) {
|
||||||
const definition = blockDefinitions[block.type][block.actionId]
|
throw new Error(
|
||||||
if (!definition) {
|
`Blocks should always have parameters. Ensure that the block definition is correct for ${step.actionId}`
|
||||||
throw new Error(
|
)
|
||||||
`No block definition exists for the chosen block. Check there's an entry in the block definitions for ${block.actionId}`
|
}
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!definition.params) {
|
const tagline = definition.tagline || ""
|
||||||
throw new Error(
|
const args = step.args || {}
|
||||||
`Blocks should always have parameters. Ensure that the block definition is correct for ${block.actionId}`
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
const tagline = definition.tagline || ""
|
return {
|
||||||
const args = block.args || {}
|
id: step.id,
|
||||||
|
type: step.type,
|
||||||
// all the fields the workflow block needs to render in the UI
|
params: step.params,
|
||||||
tree.push({
|
args,
|
||||||
id: block.id,
|
heading: step.actionId,
|
||||||
type: block.type,
|
body: mustache.render(tagline, args),
|
||||||
params: block.params,
|
name: definition.name,
|
||||||
args,
|
}
|
||||||
heading: block.actionId,
|
|
||||||
body: mustache.render(tagline, args),
|
|
||||||
name: definition.name
|
|
||||||
})
|
})
|
||||||
|
|
||||||
return this.buildUiTree(block.next, tree)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// static buildUiTree(block, tree = []) {
|
||||||
|
// if (!block) return tree
|
||||||
|
|
||||||
|
// // The client side display definition for the block
|
||||||
|
// const definition = blockDefinitions[block.type][block.actionId]
|
||||||
|
// if (!definition) {
|
||||||
|
// throw new Error(
|
||||||
|
// `No block definition exists for the chosen block. Check there's an entry in the block definitions for ${block.actionId}`
|
||||||
|
// )
|
||||||
|
// }
|
||||||
|
|
||||||
|
// if (!definition.params) {
|
||||||
|
// throw new Error(
|
||||||
|
// `Blocks should always have parameters. Ensure that the block definition is correct for ${block.actionId}`
|
||||||
|
// )
|
||||||
|
// }
|
||||||
|
|
||||||
|
// const tagline = definition.tagline || ""
|
||||||
|
// const args = block.args || {}
|
||||||
|
|
||||||
|
// // all the fields the workflow block needs to render in the UI
|
||||||
|
// tree.push({
|
||||||
|
// id: block.id,
|
||||||
|
// type: block.type,
|
||||||
|
// params: block.params,
|
||||||
|
// args,
|
||||||
|
// heading: block.actionId,
|
||||||
|
// body: mustache.render(tagline, args),
|
||||||
|
// name: definition.name
|
||||||
|
// })
|
||||||
|
|
||||||
|
// return this.buildUiTree(block.next, tree)
|
||||||
|
// }
|
||||||
}
|
}
|
||||||
|
|
|
@ -13,7 +13,8 @@ const workflowActions = store => ({
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
create: async ({ instanceId, name }) => {
|
create: async ({ instanceId, name }) => {
|
||||||
const workflow = { name, definition: {} }
|
// TODO: set these defaults in the backend
|
||||||
|
const workflow = { name, definition: { trigger: {}, steps: [] } }
|
||||||
const CREATE_WORKFLOW_URL = `/api/${instanceId}/workflows`
|
const CREATE_WORKFLOW_URL = `/api/${instanceId}/workflows`
|
||||||
const response = await api.post(CREATE_WORKFLOW_URL, workflow)
|
const response = await api.post(CREATE_WORKFLOW_URL, workflow)
|
||||||
const json = await response.json()
|
const json = await response.json()
|
||||||
|
|
|
@ -116,7 +116,7 @@
|
||||||
stylesheetLinks,
|
stylesheetLinks,
|
||||||
selectedComponentType,
|
selectedComponentType,
|
||||||
selectedComponentId,
|
selectedComponentId,
|
||||||
frontendDefinition: JSON.stringify(frontendDefinition)
|
frontendDefinition: JSON.stringify(frontendDefinition),
|
||||||
})} />
|
})} />
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -17,14 +17,16 @@
|
||||||
export let onChange
|
export let onChange
|
||||||
|
|
||||||
let isOpen = false
|
let isOpen = false
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="handler-option">
|
<div class="handler-option">
|
||||||
<span>{parameter.name}</span>
|
<span>{parameter.name}</span>
|
||||||
<div class="handler-input">
|
<div class="handler-input">
|
||||||
{#if parameter.name === 'workflow'}
|
{#if parameter.name === 'workflow'}
|
||||||
<select class="budibase__input" on:change={onChange} bind:value={parameter.value}>
|
<select
|
||||||
|
class="budibase__input"
|
||||||
|
on:change={onChange}
|
||||||
|
bind:value={parameter.value}>
|
||||||
{#each $workflowStore.workflows as workflow}
|
{#each $workflowStore.workflows as workflow}
|
||||||
<option value={workflow._id}>{workflow.name}</option>
|
<option value={workflow._id}>{workflow.name}</option>
|
||||||
{/each}
|
{/each}
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
<script>
|
<script>
|
||||||
import { store, backendUiStore, workflowStore } from "builderStore"
|
import { store, backendUiStore, workflowStore } from "builderStore"
|
||||||
import { notifier } from '@beyonk/svelte-notifications'
|
import { notifier } from "@beyonk/svelte-notifications"
|
||||||
import api from "builderStore/api"
|
import api from "builderStore/api"
|
||||||
import ActionButton from "components/common/ActionButton.svelte"
|
import ActionButton from "components/common/ActionButton.svelte"
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
<script>
|
<script>
|
||||||
import { store } from "builderStore"
|
import { store } from "builderStore"
|
||||||
import deepmerge from "deepmerge";
|
import deepmerge from "deepmerge"
|
||||||
|
|
||||||
export let value
|
export let value
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,3 @@
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import { backendUiStore } from "builderStore"
|
import { backendUiStore } from "builderStore"
|
||||||
|
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
<script>
|
<script>
|
||||||
import { backendUiStore, store } from "builderStore"
|
import { backendUiStore, store } from "builderStore"
|
||||||
import ComponentSelector from "./ParamInputs/ComponentSelector.svelte";
|
import ComponentSelector from "./ParamInputs/ComponentSelector.svelte"
|
||||||
import ModelSelector from "./ParamInputs/ModelSelector.svelte";
|
import ModelSelector from "./ParamInputs/ModelSelector.svelte"
|
||||||
|
|
||||||
export let workflowBlock
|
export let workflowBlock
|
||||||
|
|
||||||
|
@ -12,9 +12,7 @@
|
||||||
: []
|
: []
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<label class="uk-form-label">
|
<label class="uk-form-label">{workflowBlock.type}: {workflowBlock.name}</label>
|
||||||
{workflowBlock.type}: {workflowBlock.name}
|
|
||||||
</label>
|
|
||||||
{#each workflowParams as [parameter, type]}
|
{#each workflowParams as [parameter, type]}
|
||||||
<div class="uk-margin block-field">
|
<div class="uk-margin block-field">
|
||||||
<label class="uk-form-label">{parameter}</label>
|
<label class="uk-form-label">{parameter}</label>
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
<script>
|
<script>
|
||||||
import FlowItem from "./FlowItem.svelte"
|
import FlowItem from "./FlowItem.svelte"
|
||||||
import Arrow from "./Arrow.svelte";
|
import Arrow from "./Arrow.svelte"
|
||||||
|
|
||||||
export let blocks = []
|
export let blocks = []
|
||||||
export let onSelect
|
export let onSelect
|
||||||
|
|
|
@ -12,7 +12,10 @@
|
||||||
$: definitions = Object.entries(blockDefinitions[selectedTab])
|
$: definitions = Object.entries(blockDefinitions[selectedTab])
|
||||||
|
|
||||||
$: {
|
$: {
|
||||||
if (!$workflowStore.currentWorkflow.isEmpty() && selectedTab === "TRIGGER") {
|
if (
|
||||||
|
!$workflowStore.currentWorkflow.isEmpty() &&
|
||||||
|
selectedTab === "TRIGGER"
|
||||||
|
) {
|
||||||
selectedTab = "ACTION"
|
selectedTab = "ACTION"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
<script>
|
<script>
|
||||||
import { store, backendUiStore, workflowStore } from "builderStore"
|
import { store, backendUiStore, workflowStore } from "builderStore"
|
||||||
import { notifier } from '@beyonk/svelte-notifications'
|
import { notifier } from "@beyonk/svelte-notifications"
|
||||||
import api from "builderStore/api"
|
import api from "builderStore/api"
|
||||||
import ActionButton from "components/common/ActionButton.svelte"
|
import ActionButton from "components/common/ActionButton.svelte"
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
<script>
|
<script>
|
||||||
import Modal from "svelte-simple-modal"
|
import Modal from "svelte-simple-modal"
|
||||||
import { notifier } from "@beyonk/svelte-notifications";
|
import { notifier } from "@beyonk/svelte-notifications"
|
||||||
import { onMount, getContext } from "svelte"
|
import { onMount, getContext } from "svelte"
|
||||||
import { backendUiStore, workflowStore } from "builderStore"
|
import { backendUiStore, workflowStore } from "builderStore"
|
||||||
import api from "builderStore/api"
|
import api from "builderStore/api"
|
||||||
|
@ -9,7 +9,8 @@
|
||||||
const { open, close } = getContext("simple-modal")
|
const { open, close } = getContext("simple-modal")
|
||||||
|
|
||||||
$: currentWorkflowId =
|
$: currentWorkflowId =
|
||||||
$workflowStore.currentWorkflow && $workflowStore.currentWorkflow.workflow._id
|
$workflowStore.currentWorkflow &&
|
||||||
|
$workflowStore.currentWorkflow.workflow._id
|
||||||
|
|
||||||
function newWorkflow() {
|
function newWorkflow() {
|
||||||
open(
|
open(
|
||||||
|
@ -30,9 +31,9 @@
|
||||||
// TODO: Clean up args
|
// TODO: Clean up args
|
||||||
await workflowStore.actions.save({
|
await workflowStore.actions.save({
|
||||||
instanceId: $backendUiStore.selectedDatabase._id,
|
instanceId: $backendUiStore.selectedDatabase._id,
|
||||||
workflow
|
workflow,
|
||||||
})
|
})
|
||||||
notifier.success(`Workflow ${workflow.name} saved.`);
|
notifier.success(`Workflow ${workflow.name} saved.`)
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|
|
@ -94,7 +94,7 @@ const TRIGGER = {
|
||||||
description: "Fired when a record is deleted from your database.",
|
description: "Fired when a record is deleted from your database.",
|
||||||
environment: "SERVER",
|
environment: "SERVER",
|
||||||
params: {
|
params: {
|
||||||
model: "model"
|
model: "model",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
// CLICK: {
|
// CLICK: {
|
||||||
|
@ -138,10 +138,8 @@ const LOGIC = {
|
||||||
environment: "CLIENT",
|
environment: "CLIENT",
|
||||||
params: {
|
params: {
|
||||||
filter: "string",
|
filter: "string",
|
||||||
condition: [
|
condition: ["equals"],
|
||||||
"equals",
|
value: "string",
|
||||||
],
|
|
||||||
value: "string"
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
DELAY: {
|
DELAY: {
|
||||||
|
|
|
@ -1,3 +1,3 @@
|
||||||
export { default as WorkflowBuilder } from "./WorkflowBuilder/WorkflowBuilder.svelte";
|
export { default as WorkflowBuilder } from "./WorkflowBuilder/WorkflowBuilder.svelte"
|
||||||
export { default as SetupPanel } from "./SetupPanel/SetupPanel.svelte";
|
export { default as SetupPanel } from "./SetupPanel/SetupPanel.svelte"
|
||||||
export { default as WorkflowPanel } from "./WorkflowPanel/WorkflowPanel.svelte";
|
export { default as WorkflowPanel } from "./WorkflowPanel/WorkflowPanel.svelte"
|
||||||
|
|
|
@ -0,0 +1,29 @@
|
||||||
|
import { get } from "svelte/store"
|
||||||
|
import { setState } from "../../state/setState"
|
||||||
|
import { appStore } from "../../state/store"
|
||||||
|
|
||||||
|
const delay = ms => new Promise(resolve => setTimeout(resolve, ms))
|
||||||
|
|
||||||
|
export default {
|
||||||
|
SET_STATE: ({ context, args, id }) => {
|
||||||
|
// get props from the workflow context if required
|
||||||
|
setState(...Object.values(args))
|
||||||
|
// update the context with the data
|
||||||
|
context = {
|
||||||
|
...context,
|
||||||
|
[id]: args,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
NAVIGATE: ({ context, args, id }) => {},
|
||||||
|
DELAY: async ({ context, args }) => await delay(args.time),
|
||||||
|
FILTER: (context, args) => {
|
||||||
|
const { field, condition, value } = args
|
||||||
|
switch (condition) {
|
||||||
|
case "equals":
|
||||||
|
if (field !== value) return
|
||||||
|
break
|
||||||
|
default:
|
||||||
|
return
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
|
@ -3,7 +3,7 @@ import Orchestrator, { clientStrategy } from "./orchestrator"
|
||||||
export const triggerWorkflow = api => ({ workflow }) => {
|
export const triggerWorkflow = api => ({ workflow }) => {
|
||||||
const workflowOrchestrator = new Orchestrator(
|
const workflowOrchestrator = new Orchestrator(
|
||||||
api,
|
api,
|
||||||
"inst_60dd510_700f7dc06735403e81d5af91072d7241"
|
"inst_ad75c7f_4f3e7d5d80a74b17a5187a18e2aba85e"
|
||||||
)
|
)
|
||||||
workflowOrchestrator.strategy = clientStrategy
|
workflowOrchestrator.strategy = clientStrategy
|
||||||
|
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import { get } from "svelte/store";
|
import { get } from "svelte/store"
|
||||||
import { setState } from "../../state/setState";
|
import mustache from "mustache"
|
||||||
import mustache from "mustache";
|
import { appStore } from "../../state/store"
|
||||||
import { appStore } from "../../state/store";
|
import clientActions from "./actions"
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The workflow orchestrator is a class responsible for executing workflows.
|
* The workflow orchestrator is a class responsible for executing workflows.
|
||||||
|
@ -17,7 +17,7 @@ export default class Orchestrator {
|
||||||
}
|
}
|
||||||
|
|
||||||
set strategy(strategy) {
|
set strategy(strategy) {
|
||||||
this._strategy = strategy({ api: this.api, instanceId: this.instanceId });
|
this._strategy = strategy({ api: this.api, instanceId: this.instanceId })
|
||||||
}
|
}
|
||||||
|
|
||||||
async execute(workflowId) {
|
async execute(workflowId) {
|
||||||
|
@ -32,85 +32,58 @@ export default class Orchestrator {
|
||||||
|
|
||||||
// Execute a workflow from a running budibase app
|
// Execute a workflow from a running budibase app
|
||||||
export const clientStrategy = ({ api, instanceId }) => ({
|
export const clientStrategy = ({ api, instanceId }) => ({
|
||||||
delay: ms => new Promise(resolve => setTimeout(resolve, ms)),
|
|
||||||
context: {},
|
context: {},
|
||||||
bindContextArgs: function(args) {
|
bindContextArgs: function(args) {
|
||||||
const mappedArgs = { ...args }
|
const mappedArgs = { ...args }
|
||||||
|
|
||||||
console.log("original args", args)
|
|
||||||
|
|
||||||
// bind the workflow action args to the workflow context, if required
|
// bind the workflow action args to the workflow context, if required
|
||||||
for (let arg in args) {
|
for (let arg in args) {
|
||||||
const argValue = args[arg]
|
const argValue = args[arg]
|
||||||
|
|
||||||
|
// We don't want to render mustache templates on non-strings
|
||||||
|
if (typeof argValue !== "string") continue
|
||||||
|
|
||||||
// Means that it's bound to state or workflow context
|
// Means that it's bound to state or workflow context
|
||||||
console.log(argValue, get(appStore));
|
|
||||||
mappedArgs[arg] = mustache.render(argValue, {
|
mappedArgs[arg] = mustache.render(argValue, {
|
||||||
context: this.context,
|
context: this.context,
|
||||||
state: get(appStore)
|
state: get(appStore),
|
||||||
});
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log(mappedArgs)
|
|
||||||
|
|
||||||
return mappedArgs
|
return mappedArgs
|
||||||
},
|
},
|
||||||
run: async function(workflow) {
|
run: async function(workflow) {
|
||||||
const block = workflow.next
|
for (let block of workflow.steps) {
|
||||||
|
console.log("Executing workflow block", block)
|
||||||
|
|
||||||
console.log("Executing workflow block", block)
|
// This code gets run in the browser
|
||||||
|
if (block.environment === "CLIENT") {
|
||||||
|
const action = clientActions[block.actionId]
|
||||||
|
await action({
|
||||||
|
context: this.context,
|
||||||
|
args: this.bindContextArgs(block.args),
|
||||||
|
id: block.id,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
if (!block) return
|
// this workflow block gets executed on the server
|
||||||
|
if (block.environment === "SERVER") {
|
||||||
|
const EXECUTE_WORKFLOW_URL = `/api/${instanceId}/workflows/action`
|
||||||
|
const response = await api.post({
|
||||||
|
url: EXECUTE_WORKFLOW_URL,
|
||||||
|
body: {
|
||||||
|
action: block.actionId,
|
||||||
|
args: this.bindContextArgs(block.args, api),
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
// This code gets run in the browser
|
|
||||||
if (block.environment === "CLIENT") {
|
|
||||||
if (block.actionId === "SET_STATE") {
|
|
||||||
// get props from the workflow context if required
|
|
||||||
setState(...Object.values(this.bindContextArgs(block.args)))
|
|
||||||
// update the context with the data
|
|
||||||
this.context = {
|
this.context = {
|
||||||
...this.context,
|
...this.context,
|
||||||
SET_STATE: block.args,
|
[block.actionId]: response,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (block.actionId === "NAVIGATE") {
|
console.log("workflowContext", this.context)
|
||||||
}
|
|
||||||
|
|
||||||
if (block.actionId === "DELAY") {
|
|
||||||
await this.delay(block.args.time)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (block.actionId === "FILTER") {
|
|
||||||
const { field, condition, value } = block.args;
|
|
||||||
switch (condition) {
|
|
||||||
case "equals":
|
|
||||||
if (field !== value) return;
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// this workflow block gets executed on the server
|
|
||||||
if (block.environment === "SERVER") {
|
|
||||||
const EXECUTE_WORKFLOW_URL = `/api/${instanceId}/workflows/action`
|
|
||||||
const response = await api.post({
|
|
||||||
url: EXECUTE_WORKFLOW_URL,
|
|
||||||
body: {
|
|
||||||
action: block.actionId,
|
|
||||||
args: this.bindContextArgs(block.args, api),
|
|
||||||
},
|
|
||||||
})
|
|
||||||
|
|
||||||
this.context = {
|
|
||||||
...this.context,
|
|
||||||
[block.actionId]: response,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
console.log("workflowContext", this.context)
|
|
||||||
|
|
||||||
await this.run(workflow.next)
|
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
|
@ -8,7 +8,7 @@ export const createApp = ({
|
||||||
componentLibraries,
|
componentLibraries,
|
||||||
frontendDefinition,
|
frontendDefinition,
|
||||||
user,
|
user,
|
||||||
window
|
window,
|
||||||
}) => {
|
}) => {
|
||||||
let routeTo
|
let routeTo
|
||||||
let currentUrl
|
let currentUrl
|
||||||
|
@ -38,7 +38,7 @@ export const createApp = ({
|
||||||
routeTo = screenRouter({
|
routeTo = screenRouter({
|
||||||
screens: frontendDefinition.screens,
|
screens: frontendDefinition.screens,
|
||||||
onScreenSelected,
|
onScreenSelected,
|
||||||
appRootPath: frontendDefinition.appRootPath
|
appRootPath: frontendDefinition.appRootPath,
|
||||||
})
|
})
|
||||||
const fallbackPath = window.location.pathname.replace(
|
const fallbackPath = window.location.pathname.replace(
|
||||||
frontendDefinition.appRootPath,
|
frontendDefinition.appRootPath,
|
||||||
|
|
|
@ -39,7 +39,7 @@ export const loadBudibase = async opts => {
|
||||||
componentLibraries: componentLibraryModules,
|
componentLibraries: componentLibraryModules,
|
||||||
frontendDefinition,
|
frontendDefinition,
|
||||||
user,
|
user,
|
||||||
window
|
window,
|
||||||
})
|
})
|
||||||
|
|
||||||
const route = _window.location
|
const route = _window.location
|
||||||
|
|
|
@ -42,7 +42,7 @@ export const attachChildren = initialiseOpts => (htmlElement, options) => {
|
||||||
parentNode: treeNode,
|
parentNode: treeNode,
|
||||||
ComponentConstructor,
|
ComponentConstructor,
|
||||||
htmlElement,
|
htmlElement,
|
||||||
anchor
|
anchor,
|
||||||
})
|
})
|
||||||
|
|
||||||
for (let childNode of childNodesThisIteration) {
|
for (let childNode of childNodesThisIteration) {
|
||||||
|
|
|
@ -1,12 +1,12 @@
|
||||||
import { appStore } from "../state/store"
|
import { appStore } from "../state/store"
|
||||||
import mustache from "mustache";
|
import mustache from "mustache"
|
||||||
|
|
||||||
export const prepareRenderComponent = ({
|
export const prepareRenderComponent = ({
|
||||||
ComponentConstructor,
|
ComponentConstructor,
|
||||||
htmlElement,
|
htmlElement,
|
||||||
anchor,
|
anchor,
|
||||||
props,
|
props,
|
||||||
parentNode
|
parentNode,
|
||||||
}) => {
|
}) => {
|
||||||
const parentContext = (parentNode && parentNode.context) || {}
|
const parentContext = (parentNode && parentNode.context) || {}
|
||||||
|
|
||||||
|
@ -42,14 +42,16 @@ export const prepareRenderComponent = ({
|
||||||
// make this node listen to the store
|
// make this node listen to the store
|
||||||
if (thisNode.stateBound) {
|
if (thisNode.stateBound) {
|
||||||
const unsubscribe = appStore.subscribe(state => {
|
const unsubscribe = appStore.subscribe(state => {
|
||||||
const storeBoundProps = { ...initialProps._bb.props };
|
const storeBoundProps = { ...initialProps._bb.props }
|
||||||
for (let prop in storeBoundProps) {
|
for (let prop in storeBoundProps) {
|
||||||
if (typeof storeBoundProps[prop] === "string") {
|
if (typeof storeBoundProps[prop] === "string") {
|
||||||
storeBoundProps[prop] = mustache.render(storeBoundProps[prop], { state });
|
storeBoundProps[prop] = mustache.render(storeBoundProps[prop], {
|
||||||
|
state,
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
thisNode.component.$set(storeBoundProps);
|
thisNode.component.$set(storeBoundProps)
|
||||||
});
|
})
|
||||||
thisNode.unsubscribe = unsubscribe
|
thisNode.unsubscribe = unsubscribe
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import regexparam from "regexparam"
|
import regexparam from "regexparam"
|
||||||
import { routerStore } from "../state/store";
|
import { routerStore } from "../state/store"
|
||||||
import { initRouteStore } from "../state/store"
|
import { initRouteStore } from "../state/store"
|
||||||
|
|
||||||
// TODO: refactor
|
// TODO: refactor
|
||||||
|
@ -43,8 +43,8 @@ export const screenRouter = ({ screens, onScreenSelected, appRootPath }) => {
|
||||||
}
|
}
|
||||||
|
|
||||||
routerStore.update(state => {
|
routerStore.update(state => {
|
||||||
state["##routeParams"] = params;
|
state["##routeParams"] = params
|
||||||
return state;
|
return state
|
||||||
})
|
})
|
||||||
|
|
||||||
const screenIndex = current !== -1 ? current : fallback
|
const screenIndex = current !== -1 ? current : fallback
|
||||||
|
|
|
@ -51,7 +51,7 @@ export const bbFactory = ({
|
||||||
componentLibraries,
|
componentLibraries,
|
||||||
treeNode,
|
treeNode,
|
||||||
onScreenSlotRendered,
|
onScreenSlotRendered,
|
||||||
setupState
|
setupState,
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import { setState } from "./setState"
|
import { setState } from "./setState"
|
||||||
import { getState } from "./getState"
|
import { getState } from "./getState"
|
||||||
import { isArray, isUndefined } from "lodash/fp"
|
import { isArray, isUndefined } from "lodash/fp"
|
||||||
import { appStore } from "./store";
|
import { appStore } from "./store"
|
||||||
|
|
||||||
import { createApi } from "../api"
|
import { createApi } from "../api"
|
||||||
|
|
||||||
|
@ -21,7 +21,7 @@ export const eventHandlers = (store, rootPath, routeTo) => {
|
||||||
const api = createApi({
|
const api = createApi({
|
||||||
rootPath,
|
rootPath,
|
||||||
setState,
|
setState,
|
||||||
getState: (path, fallback) => getState(path, fallback)
|
getState: (path, fallback) => getState(path, fallback),
|
||||||
})
|
})
|
||||||
|
|
||||||
const setStateHandler = ({ path, value }) => setState(path, value)
|
const setStateHandler = ({ path, value }) => setState(path, value)
|
||||||
|
@ -29,7 +29,7 @@ export const eventHandlers = (store, rootPath, routeTo) => {
|
||||||
return {
|
return {
|
||||||
"Set State": handler(["path", "value"], setStateHandler),
|
"Set State": handler(["path", "value"], setStateHandler),
|
||||||
"Navigate To": handler(["url"], param => routeTo(param && param.url)),
|
"Navigate To": handler(["url"], param => routeTo(param && param.url)),
|
||||||
"Trigger Workflow": handler(["workflow"], api.triggerWorkflow)
|
"Trigger Workflow": handler(["workflow"], api.triggerWorkflow),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,10 +1,10 @@
|
||||||
// import { isUndefined, isObject } from "lodash/fp"
|
// import { isUndefined, isObject } from "lodash/fp"
|
||||||
import { get } from "svelte/store";
|
import { get } from "svelte/store"
|
||||||
import getOr from "lodash/fp/getOr";
|
import getOr from "lodash/fp/getOr"
|
||||||
import { appStore } from "./store";
|
import { appStore } from "./store"
|
||||||
|
|
||||||
export const getState = (path, fallback) => {
|
export const getState = (path, fallback) => {
|
||||||
if (!path || path.length === 0) return fallback
|
if (!path || path.length === 0) return fallback
|
||||||
|
|
||||||
return getOr(fallback, path, get(appStore));
|
return getOr(fallback, path, get(appStore))
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,11 +1,11 @@
|
||||||
import set from "lodash/fp/set";
|
import set from "lodash/fp/set"
|
||||||
import { appStore } from "./store";
|
import { appStore } from "./store"
|
||||||
|
|
||||||
export const setState = (path, value) => {
|
export const setState = (path, value) => {
|
||||||
if (!path || path.length === 0) return
|
if (!path || path.length === 0) return
|
||||||
|
|
||||||
appStore.update(state => {
|
appStore.update(state => {
|
||||||
state = set(path, value, state);
|
state = set(path, value, state)
|
||||||
return state
|
return state
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,8 +5,8 @@ import {
|
||||||
} from "./eventHandlers"
|
} from "./eventHandlers"
|
||||||
import { bbFactory } from "./bbComponentApi"
|
import { bbFactory } from "./bbComponentApi"
|
||||||
import mustache from "mustache"
|
import mustache from "mustache"
|
||||||
import { get } from "svelte/store";
|
import { get } from "svelte/store"
|
||||||
import { appStore } from "./store";
|
import { appStore } from "./store"
|
||||||
|
|
||||||
const doNothing = () => {}
|
const doNothing = () => {}
|
||||||
doNothing.isPlaceholder = true
|
doNothing.isPlaceholder = true
|
||||||
|
@ -55,9 +55,9 @@ export const createStateManager = ({
|
||||||
|
|
||||||
// TODO: remove
|
// TODO: remove
|
||||||
const unsubscribe = appStore.subscribe(state => {
|
const unsubscribe = appStore.subscribe(state => {
|
||||||
console.log("store updated", state);
|
console.log("store updated", state)
|
||||||
return state;
|
return state
|
||||||
});
|
})
|
||||||
|
|
||||||
// const unsubscribe = store.subscribe(
|
// const unsubscribe = store.subscribe(
|
||||||
// onStoreStateUpdated({
|
// onStoreStateUpdated({
|
||||||
|
@ -84,20 +84,18 @@ const onStoreStateUpdated = ({
|
||||||
getCurrentState,
|
getCurrentState,
|
||||||
componentLibraries,
|
componentLibraries,
|
||||||
onScreenSlotRendered,
|
onScreenSlotRendered,
|
||||||
setupState
|
setupState,
|
||||||
}) => state => {
|
}) => state => {
|
||||||
// fire the state update event to re-render anything bound to this
|
// fire the state update event to re-render anything bound to this
|
||||||
// setCurrentState(state)
|
// setCurrentState(state)
|
||||||
|
// setCurrentState(state)
|
||||||
// setCurrentState(state)
|
// attachChildren({
|
||||||
// attachChildren({
|
// componentLibraries,
|
||||||
// componentLibraries,
|
// treeNode: createTreeNode(),
|
||||||
// treeNode: createTreeNode(),
|
// onScreenSlotRendered,
|
||||||
// onScreenSlotRendered,
|
// setupState,
|
||||||
// setupState,
|
// getCurrentState,
|
||||||
// getCurrentState,
|
// })(document.querySelector("#app"), { hydrate: true, force: true })
|
||||||
// })(document.querySelector("#app"), { hydrate: true, force: true })
|
|
||||||
|
|
||||||
// // the original array gets changed by components' destroy()
|
// // the original array gets changed by components' destroy()
|
||||||
// // so we make a clone and check if they are still in the original
|
// // so we make a clone and check if they are still in the original
|
||||||
// const nodesWithBoundChildren_clone = [...nodesWithCodeBoundChildren]
|
// const nodesWithBoundChildren_clone = [...nodesWithCodeBoundChildren]
|
||||||
|
@ -161,19 +159,14 @@ const onStoreStateUpdated = ({
|
||||||
// node.component.$set(newProps)
|
// node.component.$set(newProps)
|
||||||
// }
|
// }
|
||||||
|
|
||||||
const _setup = ({
|
const _setup = ({ handlerTypes, getCurrentState, bb, store }) => node => {
|
||||||
handlerTypes,
|
|
||||||
getCurrentState,
|
|
||||||
bb,
|
|
||||||
store
|
|
||||||
}) => node => {
|
|
||||||
const props = node.props
|
const props = node.props
|
||||||
const context = node.context || {}
|
const context = node.context || {}
|
||||||
const initialProps = { ...props }
|
const initialProps = { ...props }
|
||||||
// const storeBoundProps = []
|
// const storeBoundProps = []
|
||||||
const currentStoreState = get(appStore)
|
const currentStoreState = get(appStore)
|
||||||
|
|
||||||
console.log("node", node);
|
console.log("node", node)
|
||||||
|
|
||||||
// console.log("node", node);
|
// console.log("node", node);
|
||||||
// console.log("nodeComponent", node.component);
|
// console.log("nodeComponent", node.component);
|
||||||
|
@ -185,12 +178,12 @@ const _setup = ({
|
||||||
|
|
||||||
// const binding = parseBinding(propValue)
|
// const binding = parseBinding(propValue)
|
||||||
// TODO: better binding stuff
|
// TODO: better binding stuff
|
||||||
const isBound = typeof propValue === "string" && propValue.startsWith("{{");
|
const isBound = typeof propValue === "string" && propValue.startsWith("{{")
|
||||||
|
|
||||||
if (isBound) {
|
if (isBound) {
|
||||||
initialProps[propName] = mustache.render(propValue, {
|
initialProps[propName] = mustache.render(propValue, {
|
||||||
state: currentStoreState,
|
state: currentStoreState,
|
||||||
context
|
context,
|
||||||
})
|
})
|
||||||
|
|
||||||
if (!node.stateBound) {
|
if (!node.stateBound) {
|
||||||
|
@ -230,10 +223,11 @@ const _setup = ({
|
||||||
const resolvedParams = {}
|
const resolvedParams = {}
|
||||||
for (let paramName in handlerInfo.parameters) {
|
for (let paramName in handlerInfo.parameters) {
|
||||||
const paramValue = handlerInfo.parameters[paramName]
|
const paramValue = handlerInfo.parameters[paramName]
|
||||||
resolvedParams[paramName] = () => mustache.render(paramValue, {
|
resolvedParams[paramName] = () =>
|
||||||
state: getCurrentState(),
|
mustache.render(paramValue, {
|
||||||
context,
|
state: getCurrentState(),
|
||||||
})
|
context,
|
||||||
|
})
|
||||||
// const paramBinding = parseBinding(paramValue)
|
// const paramBinding = parseBinding(paramValue)
|
||||||
// if (!paramBinding) {
|
// if (!paramBinding) {
|
||||||
// resolvedParams[paramName] = () => paramValue
|
// resolvedParams[paramName] = () => paramValue
|
||||||
|
|
|
@ -1,16 +1,9 @@
|
||||||
import { writable } from "svelte/store";
|
import { writable } from "svelte/store"
|
||||||
|
|
||||||
const appStore = writable({});
|
const appStore = writable({})
|
||||||
appStore.actions = {
|
appStore.actions = {}
|
||||||
|
|
||||||
};
|
const routerStore = writable({})
|
||||||
|
routerStore.actions = {}
|
||||||
|
|
||||||
const routerStore = writable({});
|
export { appStore, routerStore }
|
||||||
routerStore.actions = {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
export {
|
|
||||||
appStore,
|
|
||||||
routerStore
|
|
||||||
}
|
|
||||||
|
|
|
@ -182,4 +182,4 @@ const maketestlib = window => ({
|
||||||
set(opts.props)
|
set(opts.props)
|
||||||
opts.target.appendChild(node)
|
opts.target.appendChild(node)
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
|
@ -37,8 +37,8 @@ exports.create = async function(ctx) {
|
||||||
emit([trigger.event], trigger)
|
emit([trigger.event], trigger)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}.toString()
|
}.toString(),
|
||||||
}
|
},
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
|
@ -47,7 +47,7 @@ exports.save = async function(ctx) {
|
||||||
|
|
||||||
ctx.eventEmitter.emit(`record:save`, {
|
ctx.eventEmitter.emit(`record:save`, {
|
||||||
record,
|
record,
|
||||||
instanceId: ctx.params.instanceId
|
instanceId: ctx.params.instanceId,
|
||||||
})
|
})
|
||||||
ctx.body = record
|
ctx.body = record
|
||||||
ctx.status = 200
|
ctx.status = 200
|
||||||
|
|
|
@ -8,19 +8,19 @@ module.exports = async function createUser(user) {
|
||||||
instanceId: "inst_60dd510_700f7dc06735403e81d5af91072d7241",
|
instanceId: "inst_60dd510_700f7dc06735403e81d5af91072d7241",
|
||||||
},
|
},
|
||||||
request: {
|
request: {
|
||||||
body: user
|
body: user,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const response = await userController.create(ctx)
|
const response = await userController.create(ctx)
|
||||||
return {
|
return {
|
||||||
user: response
|
user: response,
|
||||||
}
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error(err);
|
console.error(err)
|
||||||
return {
|
return {
|
||||||
user: null
|
user: null,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,27 +1,33 @@
|
||||||
const EventEmitter = require("events").EventEmitter
|
const EventEmitter = require("events").EventEmitter
|
||||||
const CouchDB = require("../db");
|
const CouchDB = require("../db")
|
||||||
|
|
||||||
const emitter = new EventEmitter()
|
const emitter = new EventEmitter()
|
||||||
|
|
||||||
function determineWorkflowsToTrigger(instanceId, event) {
|
async function determineWorkflowsToTrigger(instanceId, event) {
|
||||||
const db = new CouchDB(instanceId);
|
const db = new CouchDB(instanceId)
|
||||||
const workflowsToTrigger = await db.query("database/by_workflow_trigger", {
|
const workflowsToTrigger = await db.query("database/by_workflow_trigger", {
|
||||||
key: [event]
|
key: [event],
|
||||||
})
|
})
|
||||||
|
|
||||||
return workflowsToTrigger.rows;
|
return workflowsToTrigger.rows
|
||||||
}
|
}
|
||||||
|
|
||||||
emitter.on("record:save", async function(event) {
|
emitter.on("record:save", async function(event) {
|
||||||
const workflowsToTrigger = await determineWorkflowsToTrigger(instanceId, "record:save")
|
const workflowsToTrigger = await determineWorkflowsToTrigger(
|
||||||
|
instanceId,
|
||||||
|
"record:save"
|
||||||
|
)
|
||||||
|
|
||||||
for (let workflow of workflowsToTrigger) {
|
for (let workflow of workflowsToTrigger) {
|
||||||
// SERVER SIDE STUFF!!
|
// SERVER SIDE STUFF!!
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
emitter.on("record:delete", function(event) {
|
emitter.on("record:delete", async function(event) {
|
||||||
const workflowsToTrigger = await determineWorkflowsToTrigger(instanceId, "record:delete")
|
const workflowsToTrigger = await determineWorkflowsToTrigger(
|
||||||
|
instanceId,
|
||||||
|
"record:delete"
|
||||||
|
)
|
||||||
|
|
||||||
for (let workflow of workflowsToTrigger) {
|
for (let workflow of workflowsToTrigger) {
|
||||||
// SERVER SIDE STUFF!!
|
// SERVER SIDE STUFF!!
|
||||||
|
|
|
@ -17,7 +17,7 @@ const WORKFLOW_SCHEMA = {
|
||||||
type: "object",
|
type: "object",
|
||||||
properties: {
|
properties: {
|
||||||
triggers: { type: "array" },
|
triggers: { type: "array" },
|
||||||
steps: { type: "array" }
|
steps: { type: "array" },
|
||||||
// next: {
|
// next: {
|
||||||
// type: "object",
|
// type: "object",
|
||||||
// properties: {
|
// properties: {
|
||||||
|
|
Loading…
Reference in New Issue