Merge pull request #626 from Budibase/rename-workflow-automation

Rename workflow -> automation
This commit is contained in:
Michael Drury 2020-09-22 15:27:33 +01:00 committed by GitHub
commit 91edce8e9d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
72 changed files with 527 additions and 522 deletions

View File

@ -4,6 +4,6 @@ node_modules_win
package-lock.json package-lock.json
release/ release/
dist/ dist/
cypress/screenshots
cypress/videos
routify routify
cypress/videos
cypress/screenshots

View File

@ -1,27 +1,27 @@
context("Create a workflow", () => { context("Create a automation", () => {
before(() => { before(() => {
cy.server() cy.server()
cy.visit("localhost:4001/_builder") cy.visit("localhost:4001/_builder")
cy.createApp( cy.createApp(
"Workflow Test App", "Automation Test App",
"This app is used to test that workflows do in fact work!" "This app is used to test that automations do in fact work!"
) )
}) })
// https://on.cypress.io/interacting-with-elements // https://on.cypress.io/interacting-with-elements
it("should create a workflow", () => { it("should create a automation", () => {
cy.createTestTableWithData() cy.createTestTableWithData()
cy.contains("workflow").click() cy.contains("automate").click()
cy.contains("Create New Workflow").click() cy.contains("Create New Automation").click()
cy.get("input").type("Add Record") cy.get("input").type("Add Record")
cy.contains("Save").click() cy.contains("Save").click()
// Add trigger // Add trigger
cy.get("[data-cy=add-workflow-component]").click() cy.get("[data-cy=add-automation-component]").click()
cy.get("[data-cy=RECORD_SAVED]").click() cy.get("[data-cy=RECORD_SAVED]").click()
cy.get("[data-cy=workflow-block-setup]").within(() => { cy.get("[data-cy=automation-block-setup]").within(() => {
cy.get("select") cy.get("select")
.first() .first()
.select("dog") .select("dog")
@ -29,7 +29,7 @@ context("Create a workflow", () => {
// Create action // Create action
cy.get("[data-cy=SAVE_RECORD]").click() cy.get("[data-cy=SAVE_RECORD]").click()
cy.get("[data-cy=workflow-block-setup]").within(() => { cy.get("[data-cy=automation-block-setup]").within(() => {
cy.get("select") cy.get("select")
.first() .first()
.select("dog") .select("dog")
@ -42,10 +42,10 @@ context("Create a workflow", () => {
}) })
// Save // Save
cy.contains("Save Workflow").click() cy.contains("Save Automation").click()
// Activate Workflow // Activate Automation
cy.get("[data-cy=activate-workflow]").click() cy.get("[data-cy=activate-automation]").click()
cy.contains("Add Record").should("be.visible") cy.contains("Add Record").should("be.visible")
cy.get(".stop-button.highlighted").should("be.visible") cy.get(".stop-button.highlighted").should("be.visible")
}) })

View File

@ -1,11 +1,11 @@
import { getStore } from "./store" import { getStore } from "./store"
import { getBackendUiStore } from "./store/backend" import { getBackendUiStore } from "./store/backend"
import { getWorkflowStore } from "./store/workflow/" import { getAutomationStore } from "./store/automation/"
import analytics from "../analytics" import analytics from "../analytics"
export const store = getStore() export const store = getStore()
export const backendUiStore = getBackendUiStore() export const backendUiStore = getBackendUiStore()
export const workflowStore = getWorkflowStore() export const automationStore = getAutomationStore()
export const initialise = async () => { export const initialise = async () => {
try { try {

View File

@ -1,59 +1,59 @@
import { generate } from "shortid" import { generate } from "shortid"
/** /**
* Class responsible for the traversing of the workflow definition. * Class responsible for the traversing of the automation definition.
* Workflow definitions are stored in linked lists. * Automation definitions are stored in linked lists.
*/ */
export default class Workflow { export default class Automation {
constructor(workflow) { constructor(automation) {
this.workflow = workflow this.automation = automation
} }
hasTrigger() { hasTrigger() {
return this.workflow.definition.trigger return this.automation.definition.trigger
} }
addBlock(block) { addBlock(block) {
// Make sure to add trigger if doesn't exist // Make sure to add trigger if doesn't exist
if (!this.hasTrigger() && block.type === "TRIGGER") { if (!this.hasTrigger() && block.type === "TRIGGER") {
const trigger = { id: generate(), ...block } const trigger = { id: generate(), ...block }
this.workflow.definition.trigger = trigger this.automation.definition.trigger = trigger
return trigger return trigger
} }
const newBlock = { id: generate(), ...block } const newBlock = { id: generate(), ...block }
this.workflow.definition.steps = [ this.automation.definition.steps = [
...this.workflow.definition.steps, ...this.automation.definition.steps,
newBlock, newBlock,
] ]
return newBlock return newBlock
} }
updateBlock(updatedBlock, id) { updateBlock(updatedBlock, id) {
const { steps, trigger } = this.workflow.definition const { steps, trigger } = this.automation.definition
if (trigger && trigger.id === id) { if (trigger && trigger.id === id) {
this.workflow.definition.trigger = updatedBlock this.automation.definition.trigger = updatedBlock
return return
} }
const stepIdx = steps.findIndex(step => step.id === id) const stepIdx = steps.findIndex(step => step.id === id)
if (stepIdx < 0) throw new Error("Block not found.") if (stepIdx < 0) throw new Error("Block not found.")
steps.splice(stepIdx, 1, updatedBlock) steps.splice(stepIdx, 1, updatedBlock)
this.workflow.definition.steps = steps this.automation.definition.steps = steps
} }
deleteBlock(id) { deleteBlock(id) {
const { steps, trigger } = this.workflow.definition const { steps, trigger } = this.automation.definition
if (trigger && trigger.id === id) { if (trigger && trigger.id === id) {
this.workflow.definition.trigger = null this.automation.definition.trigger = null
return return
} }
const stepIdx = steps.findIndex(step => step.id === id) const stepIdx = steps.findIndex(step => step.id === id)
if (stepIdx < 0) throw new Error("Block not found.") if (stepIdx < 0) throw new Error("Block not found.")
steps.splice(stepIdx, 1) steps.splice(stepIdx, 1)
this.workflow.definition.steps = steps this.automation.definition.steps = steps
} }
} }

View File

@ -0,0 +1,126 @@
import { writable } from "svelte/store"
import api from "../../api"
import Automation from "./Automation"
import { cloneDeep } from "lodash/fp"
const automationActions = store => ({
fetch: async () => {
const responses = await Promise.all([
api.get(`/api/automations`),
api.get(`/api/automations/definitions/list`),
])
const jsonResponses = await Promise.all(responses.map(x => x.json()))
store.update(state => {
state.automations = jsonResponses[0]
state.blockDefinitions = {
TRIGGER: jsonResponses[1].trigger,
ACTION: jsonResponses[1].action,
LOGIC: jsonResponses[1].logic,
}
return state
})
},
create: async ({ name }) => {
const automation = {
name,
type: "automation",
definition: {
steps: [],
},
}
const CREATE_AUTOMATION_URL = `/api/automations`
const response = await api.post(CREATE_AUTOMATION_URL, automation)
const json = await response.json()
store.update(state => {
state.automations = [...state.automations, json.automation]
store.actions.select(json.automation)
return state
})
},
save: async ({ automation }) => {
const UPDATE_AUTOMATION_URL = `/api/automations`
const response = await api.put(UPDATE_AUTOMATION_URL, automation)
const json = await response.json()
store.update(state => {
const existingIdx = state.automations.findIndex(
existing => existing._id === automation._id
)
state.automations.splice(existingIdx, 1, json.automation)
state.automations = state.automations
store.actions.select(json.automation)
return state
})
},
delete: async ({ automation }) => {
const { _id, _rev } = automation
const DELETE_AUTOMATION_URL = `/api/automations/${_id}/${_rev}`
await api.delete(DELETE_AUTOMATION_URL)
store.update(state => {
const existingIdx = state.automations.findIndex(
existing => existing._id === _id
)
state.automations.splice(existingIdx, 1)
state.automations = state.automations
state.selectedAutomation = null
state.selectedBlock = null
return state
})
},
trigger: async ({ automation }) => {
const { _id } = automation
const TRIGGER_AUTOMATION_URL = `/api/automations/${_id}/trigger`
return await api.post(TRIGGER_AUTOMATION_URL)
},
select: automation => {
store.update(state => {
state.selectedAutomation = new Automation(cloneDeep(automation))
state.selectedBlock = null
return state
})
},
addBlockToAutomation: block => {
store.update(state => {
const newBlock = state.selectedAutomation.addBlock(cloneDeep(block))
state.selectedBlock = newBlock
return state
})
},
deleteAutomationBlock: block => {
store.update(state => {
const idx = state.selectedAutomation.automation.definition.steps.findIndex(
x => x.id === block.id
)
state.selectedAutomation.deleteBlock(block.id)
// Select next closest step
const steps = state.selectedAutomation.automation.definition.steps
let nextSelectedBlock
if (steps[idx] != null) {
nextSelectedBlock = steps[idx]
} else if (steps[idx - 1] != null) {
nextSelectedBlock = steps[idx - 1]
} else {
nextSelectedBlock =
state.selectedAutomation.automation.definition.trigger || null
}
state.selectedBlock = nextSelectedBlock
return state
})
},
})
export const getAutomationStore = () => {
const INITIAL_AUTOMATION_STATE = {
automations: [],
blockDefinitions: {
TRIGGER: [],
ACTION: [],
LOGIC: [],
},
selectedAutomation: null,
}
const store = writable(INITIAL_AUTOMATION_STATE)
store.actions = automationActions(store)
return store
}

View File

@ -0,0 +1,48 @@
import Automation from "../Automation"
import TEST_AUTOMATION from "./testAutomation"
const TEST_BLOCK = {
id: "AUXJQGZY7",
name: "Delay",
icon: "ri-time-fill",
tagline: "Delay for <b>{{time}}</b> milliseconds",
description: "Delay the automation until an amount of time has passed.",
params: { time: "number" },
type: "LOGIC",
args: { time: "5000" },
stepId: "DELAY",
}
describe("Automation Data Object", () => {
let automation
beforeEach(() => {
automation = new Automation({ ...TEST_AUTOMATION })
})
it("adds a automation block to the automation", () => {
automation.addBlock(TEST_BLOCK)
expect(automation.automation.definition)
})
it("updates a automation block with new attributes", () => {
const firstBlock = automation.automation.definition.steps[0]
const updatedBlock = {
...firstBlock,
name: "UPDATED",
}
automation.updateBlock(updatedBlock, firstBlock.id)
expect(automation.automation.definition.steps[0]).toEqual(updatedBlock)
})
it("deletes a automation block successfully", () => {
const { steps } = automation.automation.definition
const originalLength = steps.length
const lastBlock = steps[steps.length - 1]
automation.deleteBlock(lastBlock.id)
expect(automation.automation.definition.steps.length).toBeLessThan(
originalLength
)
})
})

View File

@ -1,5 +1,5 @@
export default { export default {
name: "Test workflow", name: "Test automation",
definition: { definition: {
steps: [ steps: [
{ {
@ -68,7 +68,7 @@ export default {
stepId: "RECORD_SAVED", stepId: "RECORD_SAVED",
}, },
}, },
type: "workflow", type: "automation",
ok: true, ok: true,
id: "b384f861f4754e1693835324a7fcca62", id: "b384f861f4754e1693835324a7fcca62",
rev: "1-aa1c2cbd868ef02e26f8fad531dd7e37", rev: "1-aa1c2cbd868ef02e26f8fad531dd7e37",

View File

@ -1,126 +0,0 @@
import { writable } from "svelte/store"
import api from "../../api"
import Workflow from "./Workflow"
import { cloneDeep } from "lodash/fp"
const workflowActions = store => ({
fetch: async () => {
const responses = await Promise.all([
api.get(`/api/workflows`),
api.get(`/api/workflows/definitions/list`),
])
const jsonResponses = await Promise.all(responses.map(x => x.json()))
store.update(state => {
state.workflows = jsonResponses[0]
state.blockDefinitions = {
TRIGGER: jsonResponses[1].trigger,
ACTION: jsonResponses[1].action,
LOGIC: jsonResponses[1].logic,
}
return state
})
},
create: async ({ name }) => {
const workflow = {
name,
type: "workflow",
definition: {
steps: [],
},
}
const CREATE_WORKFLOW_URL = `/api/workflows`
const response = await api.post(CREATE_WORKFLOW_URL, workflow)
const json = await response.json()
store.update(state => {
state.workflows = [...state.workflows, json.workflow]
store.actions.select(json.workflow)
return state
})
},
save: async ({ workflow }) => {
const UPDATE_WORKFLOW_URL = `/api/workflows`
const response = await api.put(UPDATE_WORKFLOW_URL, workflow)
const json = await response.json()
store.update(state => {
const existingIdx = state.workflows.findIndex(
existing => existing._id === workflow._id
)
state.workflows.splice(existingIdx, 1, json.workflow)
state.workflows = state.workflows
store.actions.select(json.workflow)
return state
})
},
delete: async ({ workflow }) => {
const { _id, _rev } = workflow
const DELETE_WORKFLOW_URL = `/api/workflows/${_id}/${_rev}`
await api.delete(DELETE_WORKFLOW_URL)
store.update(state => {
const existingIdx = state.workflows.findIndex(
existing => existing._id === _id
)
state.workflows.splice(existingIdx, 1)
state.workflows = state.workflows
state.selectedWorkflow = null
state.selectedBlock = null
return state
})
},
trigger: async ({ workflow }) => {
const { _id } = workflow
const TRIGGER_WORKFLOW_URL = `/api/workflows/${_id}/trigger`
return await api.post(TRIGGER_WORKFLOW_URL)
},
select: workflow => {
store.update(state => {
state.selectedWorkflow = new Workflow(cloneDeep(workflow))
state.selectedBlock = null
return state
})
},
addBlockToWorkflow: block => {
store.update(state => {
const newBlock = state.selectedWorkflow.addBlock(cloneDeep(block))
state.selectedBlock = newBlock
return state
})
},
deleteWorkflowBlock: block => {
store.update(state => {
const idx = state.selectedWorkflow.workflow.definition.steps.findIndex(
x => x.id === block.id
)
state.selectedWorkflow.deleteBlock(block.id)
// Select next closest step
const steps = state.selectedWorkflow.workflow.definition.steps
let nextSelectedBlock
if (steps[idx] != null) {
nextSelectedBlock = steps[idx]
} else if (steps[idx - 1] != null) {
nextSelectedBlock = steps[idx - 1]
} else {
nextSelectedBlock =
state.selectedWorkflow.workflow.definition.trigger || null
}
state.selectedBlock = nextSelectedBlock
return state
})
},
})
export const getWorkflowStore = () => {
const INITIAL_WORKFLOW_STATE = {
workflows: [],
blockDefinitions: {
TRIGGER: [],
ACTION: [],
LOGIC: [],
},
selectedWorkflow: null,
}
const store = writable(INITIAL_WORKFLOW_STATE)
store.actions = workflowActions(store)
return store
}

View File

@ -1,48 +0,0 @@
import Workflow from "../Workflow"
import TEST_WORKFLOW from "./testWorkflow"
const TEST_BLOCK = {
id: "AUXJQGZY7",
name: "Delay",
icon: "ri-time-fill",
tagline: "Delay for <b>{{time}}</b> milliseconds",
description: "Delay the workflow until an amount of time has passed.",
params: { time: "number" },
type: "LOGIC",
args: { time: "5000" },
stepId: "DELAY",
}
describe("Workflow Data Object", () => {
let workflow
beforeEach(() => {
workflow = new Workflow({ ...TEST_WORKFLOW })
})
it("adds a workflow block to the workflow", () => {
workflow.addBlock(TEST_BLOCK)
expect(workflow.workflow.definition)
})
it("updates a workflow block with new attributes", () => {
const firstBlock = workflow.workflow.definition.steps[0]
const updatedBlock = {
...firstBlock,
name: "UPDATED",
}
workflow.updateBlock(updatedBlock, firstBlock.id)
expect(workflow.workflow.definition.steps[0]).toEqual(updatedBlock)
})
it("deletes a workflow block successfully", () => {
const { steps } = workflow.workflow.definition
const originalLength = steps.length
const lastBlock = steps[steps.length - 1]
workflow.deleteBlock(lastBlock.id)
expect(workflow.workflow.definition.steps.length).toBeLessThan(
originalLength
)
})
})

View File

@ -1,48 +1,48 @@
<script> <script>
import { afterUpdate } from "svelte" import { afterUpdate } from "svelte"
import { workflowStore, backendUiStore } from "builderStore" import { automationStore, backendUiStore } from "builderStore"
import { notifier } from "builderStore/store/notifications" import { notifier } from "builderStore/store/notifications"
import Flowchart from "./flowchart/FlowChart.svelte" import Flowchart from "./flowchart/FlowChart.svelte"
$: workflow = $workflowStore.selectedWorkflow?.workflow $: automation = $automationStore.selectedAutomation?.automation
$: workflowLive = workflow?.live $: automationLive = automation?.live
$: instanceId = $backendUiStore.selectedDatabase._id $: instanceId = $backendUiStore.selectedDatabase._id
function onSelect(block) { function onSelect(block) {
workflowStore.update(state => { automationStore.update(state => {
state.selectedBlock = block state.selectedBlock = block
return state return state
}) })
} }
function setWorkflowLive(live) { function setAutomationLive(live) {
workflow.live = live automation.live = live
workflowStore.actions.save({ instanceId, workflow }) automationStore.actions.save({ instanceId, automation })
if (live) { if (live) {
notifier.info(`Workflow ${workflow.name} enabled.`) notifier.info(`Automation ${automation.name} enabled.`)
} else { } else {
notifier.danger(`Workflow ${workflow.name} disabled.`) notifier.danger(`Automation ${automation.name} disabled.`)
} }
} }
</script> </script>
<section> <section>
<Flowchart {workflow} {onSelect} /> <Flowchart {automation} {onSelect} />
</section> </section>
<footer> <footer>
{#if workflow} {#if automation}
<button <button
class:highlighted={workflowLive} class:highlighted={automationLive}
class:hoverable={workflowLive} class:hoverable={automationLive}
class="stop-button hoverable"> class="stop-button hoverable">
<i class="ri-stop-fill" on:click={() => setWorkflowLive(false)} /> <i class="ri-stop-fill" on:click={() => setAutomationLive(false)} />
</button> </button>
<button <button
class:highlighted={!workflowLive} class:highlighted={!automationLive}
class:hoverable={!workflowLive} class:hoverable={!automationLive}
class="play-button hoverable" class="play-button hoverable"
data-cy="activate-workflow" data-cy="activate-automation"
on:click={() => setWorkflowLive(true)}> on:click={() => setAutomationLive(true)}>
<i class="ri-play-fill" /> <i class="ri-play-fill" />
</button> </button>
{/if} {/if}

View File

@ -4,17 +4,17 @@
import { flip } from "svelte/animate" import { flip } from "svelte/animate"
import { fade, fly } from "svelte/transition" import { fade, fly } from "svelte/transition"
export let workflow export let automation
export let onSelect export let onSelect
let blocks let blocks
$: { $: {
blocks = [] blocks = []
if (workflow) { if (automation) {
if (workflow.definition.trigger) { if (automation.definition.trigger) {
blocks.push(workflow.definition.trigger) blocks.push(automation.definition.trigger)
} }
blocks = blocks.concat(workflow.definition.steps || []) blocks = blocks.concat(automation.definition.steps || [])
} }
} }
</script> </script>

View File

@ -1,13 +1,13 @@
<script> <script>
import { workflowStore } from "builderStore" import { automationStore } from "builderStore"
import WorkflowBlockTagline from "./WorkflowBlockTagline.svelte" import AutomationBlockTagline from "./AutomationBlockTagline.svelte"
export let onSelect export let onSelect
export let block export let block
let selected let selected
$: selected = $workflowStore.selectedBlock?.id === block.id $: selected = $automationStore.selectedBlock?.id === block.id
$: steps = $workflowStore.selectedWorkflow?.workflow?.definition?.steps ?? [] $: steps = $automationStore.selectedAutomation?.automation?.definition?.steps ?? []
$: blockIdx = steps.findIndex(step => step.id === block.id) $: blockIdx = steps.findIndex(step => step.id === block.id)
</script> </script>
@ -32,7 +32,7 @@
</header> </header>
<hr /> <hr />
<p> <p>
<WorkflowBlockTagline {block} /> <AutomationBlockTagline {block} />
</p> </p>
</div> </div>

View File

@ -2,17 +2,17 @@
import Modal from "svelte-simple-modal" import Modal from "svelte-simple-modal"
import { notifier } from "builderStore/store/notifications" import { notifier } from "builderStore/store/notifications"
import { onMount, getContext } from "svelte" import { onMount, getContext } from "svelte"
import { backendUiStore, workflowStore } from "builderStore" import { backendUiStore, automationStore } from "builderStore"
import CreateWorkflowModal from "./CreateWorkflowModal.svelte" import CreateAutomationModal from "./CreateAutomationModal.svelte"
import { Button } from "@budibase/bbui" import { Button } from "@budibase/bbui"
const { open, close } = getContext("simple-modal") const { open, close } = getContext("simple-modal")
$: selectedWorkflowId = $workflowStore.selectedWorkflow?.workflow?._id $: selectedAutomationId = $automationStore.selectedAutomation?.automation?._id
function newWorkflow() { function newAutomation() {
open( open(
CreateWorkflowModal, CreateAutomationModal,
{ {
onClosed: close, onClosed: close,
}, },
@ -21,20 +21,20 @@
} }
onMount(() => { onMount(() => {
workflowStore.actions.fetch() automationStore.actions.fetch()
}) })
</script> </script>
<section> <section>
<Button purple wide on:click={newWorkflow}>Create New Workflow</Button> <Button purple wide on:click={newAutomation}>Create New Automation</Button>
<ul> <ul>
{#each $workflowStore.workflows as workflow} {#each $automationStore.automations as automation}
<li <li
class="workflow-item" class="automation-item"
class:selected={workflow._id === selectedWorkflowId} class:selected={automation._id === selectedAutomationId}
on:click={() => workflowStore.actions.select(workflow)}> on:click={() => automationStore.actions.select(automation)}>
<i class="ri-stackshare-line" class:live={workflow.live} /> <i class="ri-stackshare-line" class:live={automation.live} />
{workflow.name} {automation.name}
</li> </li>
{/each} {/each}
</ul> </ul>
@ -68,7 +68,7 @@
font-size: 14px; font-size: 14px;
} }
.workflow-item { .automation-item {
display: flex; display: flex;
border-radius: 5px; border-radius: 5px;
padding-left: 12px; padding-left: 12px;
@ -78,21 +78,21 @@
color: var(--ink); color: var(--ink);
} }
.workflow-item i { .automation-item i {
font-size: 24px; font-size: 24px;
margin-right: 10px; margin-right: 10px;
} }
.workflow-item:hover { .automation-item:hover {
cursor: pointer; cursor: pointer;
background: var(--grey-1); background: var(--grey-1);
} }
.workflow-item.selected { .automation-item.selected {
background: var(--grey-2); background: var(--grey-2);
} }
.new-workflow-button { .new-automation-button {
cursor: pointer; cursor: pointer;
border: 1px solid var(--grey-4); border: 1px solid var(--grey-4);
border-radius: 3px; border-radius: 3px;
@ -108,7 +108,7 @@
transition: all 2ms; transition: all 2ms;
} }
.new-workflow-button:hover { .new-automation-button:hover {
background: var(--grey-1); background: var(--grey-1);
} }

View File

@ -1,5 +1,5 @@
<script> <script>
import { store, backendUiStore, workflowStore } from "builderStore" import { store, backendUiStore, automationStore } from "builderStore"
import { notifier } from "builderStore/store/notifications" import { notifier } from "builderStore/store/notifications"
import ActionButton from "components/common/ActionButton.svelte" import ActionButton from "components/common/ActionButton.svelte"
import { Input } from "@budibase/bbui" import { Input } from "@budibase/bbui"
@ -12,19 +12,19 @@
$: instanceId = $backendUiStore.selectedDatabase._id $: instanceId = $backendUiStore.selectedDatabase._id
$: appId = $store.appId $: appId = $store.appId
async function createWorkflow() { async function createAutomation() {
await workflowStore.actions.create({ await automationStore.actions.create({
name, name,
instanceId, instanceId,
}) })
onClosed() onClosed()
notifier.success(`Workflow ${name} created.`) notifier.success(`Automation ${name} created.`)
} }
</script> </script>
<header> <header>
<i class="ri-stackshare-line" /> <i class="ri-stackshare-line" />
Create Workflow Create Automation
</header> </header>
<div> <div>
<Input bind:value={name} label="Name" /> <Input bind:value={name} label="Name" />
@ -32,10 +32,10 @@
<footer> <footer>
<a href="https://docs.budibase.com"> <a href="https://docs.budibase.com">
<i class="ri-information-line" /> <i class="ri-information-line" />
Learn about workflows Learn about automations
</a> </a>
<ActionButton secondary on:click={onClosed}>Cancel</ActionButton> <ActionButton secondary on:click={onClosed}>Cancel</ActionButton>
<ActionButton disabled={!valid} on:click={createWorkflow}>Save</ActionButton> <ActionButton disabled={!valid} on:click={createAutomation}>Save</ActionButton>
</footer> </footer>
<style> <style>

View File

@ -1,22 +1,22 @@
<script> <script>
import { workflowStore } from "builderStore" import { automationStore } from "builderStore"
import WorkflowList from "./WorkflowList/WorkflowList.svelte" import AutomationList from "./AutomationList/AutomationList.svelte"
import BlockList from "./BlockList/BlockList.svelte" import BlockList from "./BlockList/BlockList.svelte"
let selectedTab = "WORKFLOWS" let selectedTab = "AUTOMATIONS"
</script> </script>
<header> <header>
<span <span
data-cy="workflow-list" data-cy="automation-list"
class="hoverable workflow-header" class="hoverable automation-header"
class:selected={selectedTab === 'WORKFLOWS'} class:selected={selectedTab === 'AUTOMATIONS'}
on:click={() => (selectedTab = 'WORKFLOWS')}> on:click={() => (selectedTab = 'AUTOMATIONS')}>
Workflows Automations
</span> </span>
{#if $workflowStore.selectedWorkflow} {#if $automationStore.selectedAutomation}
<span <span
data-cy="add-workflow-component" data-cy="add-automation-component"
class="hoverable" class="hoverable"
class:selected={selectedTab === 'ADD'} class:selected={selectedTab === 'ADD'}
on:click={() => (selectedTab = 'ADD')}> on:click={() => (selectedTab = 'ADD')}>
@ -24,8 +24,8 @@
</span> </span>
{/if} {/if}
</header> </header>
{#if selectedTab === 'WORKFLOWS'} {#if selectedTab === 'AUTOMATIONS'}
<WorkflowList /> <AutomationList />
{:else if selectedTab === 'ADD'} {:else if selectedTab === 'ADD'}
<BlockList /> <BlockList />
{/if} {/if}
@ -40,7 +40,7 @@
margin-bottom: 20px; margin-bottom: 20px;
} }
.workflow-header { .automation-header {
margin-right: 20px; margin-right: 20px;
} }

View File

@ -1,12 +1,12 @@
<script> <script>
import { workflowStore } from "builderStore" import { automationStore } from "builderStore"
export let blockDefinition export let blockDefinition
export let stepId export let stepId
export let blockType export let blockType
function addBlockToWorkflow() { function addBlockToAutomation() {
workflowStore.actions.addBlockToWorkflow({ automationStore.actions.addBlockToAutomation({
...blockDefinition, ...blockDefinition,
args: blockDefinition.args || {}, args: blockDefinition.args || {},
stepId, stepId,
@ -16,20 +16,20 @@
</script> </script>
<div <div
class="workflow-block hoverable" class="automation-block hoverable"
on:click={addBlockToWorkflow} on:click={addBlockToAutomation}
data-cy={stepId}> data-cy={stepId}>
<div> <div>
<i class={blockDefinition.icon} /> <i class={blockDefinition.icon} />
</div> </div>
<div class="workflow-text"> <div class="automation-text">
<h4>{blockDefinition.name}</h4> <h4>{blockDefinition.name}</h4>
<p>{blockDefinition.description}</p> <p>{blockDefinition.description}</p>
</div> </div>
</div> </div>
<style> <style>
.workflow-block { .automation-block {
display: grid; display: grid;
grid-template-columns: 20px auto; grid-template-columns: 20px auto;
align-items: center; align-items: center;
@ -38,11 +38,11 @@
border-radius: var(--border-radius-m); border-radius: var(--border-radius-m);
} }
.workflow-block:hover { .automation-block:hover {
background-color: var(--grey-1); background-color: var(--grey-1);
} }
.workflow-text { .automation-text {
margin-left: 16px; margin-left: 16px;
} }

View File

@ -1,14 +1,14 @@
<script> <script>
import { workflowStore } from "builderStore" import { automationStore } from "builderStore"
import WorkflowBlock from "./WorkflowBlock.svelte" import AutomationBlock from "./AutomationBlock.svelte"
import FlatButtonGroup from "components/userInterface/FlatButtonGroup.svelte" import FlatButtonGroup from "components/userInterface/FlatButtonGroup.svelte"
let selectedTab = "TRIGGER" let selectedTab = "TRIGGER"
let buttonProps = [] let buttonProps = []
$: blocks = Object.entries($workflowStore.blockDefinitions[selectedTab]) $: blocks = Object.entries($automationStore.blockDefinitions[selectedTab])
$: { $: {
if ($workflowStore.selectedWorkflow.hasTrigger()) { if ($automationStore.selectedAutomation.hasTrigger()) {
buttonProps = [ buttonProps = [
{ value: "ACTION", text: "Action" }, { value: "ACTION", text: "Action" },
{ value: "LOGIC", text: "Logic" }, { value: "LOGIC", text: "Logic" },
@ -33,7 +33,7 @@
<FlatButtonGroup value={selectedTab} {buttonProps} onChange={onChangeTab} /> <FlatButtonGroup value={selectedTab} {buttonProps} onChange={onChangeTab} />
<div id="blocklist"> <div id="blocklist">
{#each blocks as [stepId, blockDefinition]} {#each blocks as [stepId, blockDefinition]}
<WorkflowBlock {blockDefinition} {stepId} blockType={selectedTab} /> <AutomationBlock {blockDefinition} {stepId} blockType={selectedTab} />
{/each} {/each}
</div> </div>
</section> </section>

View File

@ -0,0 +1,3 @@
export { default as AutomationPanel } from "./AutomationPanel.svelte"
export { default as BlockList } from "./BlockList/BlockList.svelte"
export { default as AutomationList } from "./AutomationList/AutomationList.svelte"

View File

@ -2,25 +2,25 @@
import ModelSelector from "./ParamInputs/ModelSelector.svelte" import ModelSelector from "./ParamInputs/ModelSelector.svelte"
import RecordSelector from "./ParamInputs/RecordSelector.svelte" import RecordSelector from "./ParamInputs/RecordSelector.svelte"
import { Input, TextArea, Select, Label } from "@budibase/bbui" import { Input, TextArea, Select, Label } from "@budibase/bbui"
import { workflowStore } from "builderStore" import { automationStore } from "builderStore"
import BindableInput from "../../userInterface/BindableInput.svelte" import BindableInput from "../../userInterface/BindableInput.svelte"
export let block export let block
$: inputs = Object.entries(block.schema?.inputs?.properties || {}) $: inputs = Object.entries(block.schema?.inputs?.properties || {})
$: bindings = getAvailableBindings( $: bindings = getAvailableBindings(
block, block,
$workflowStore.selectedWorkflow?.workflow?.definition $automationStore.selectedAutomation?.automation?.definition
) )
function getAvailableBindings(block, workflow) { function getAvailableBindings(block, automation) {
if (!block || !workflow) { if (!block || !automation) {
return [] return []
} }
// Find previous steps to the selected one // Find previous steps to the selected one
let allSteps = [...workflow.steps] let allSteps = [...automation.steps]
if (workflow.trigger) { if (automation.trigger) {
allSteps = [workflow.trigger, ...allSteps] allSteps = [automation.trigger, ...allSteps]
} }
const blockIdx = allSteps.findIndex(step => step.id === block.id) const blockIdx = allSteps.findIndex(step => step.id === block.id)
@ -44,7 +44,7 @@
} }
</script> </script>
<div class="container" data-cy="workflow-block-setup"> <div class="container" data-cy="automation-block-setup">
<div class="block-label">{block.name}</div> <div class="block-label">{block.name}</div>
{#each inputs as [key, value]} {#each inputs as [key, value]}
<div class="bb-margin-xl block-field"> <div class="bb-margin-xl block-field">

View File

@ -1,5 +1,5 @@
<script> <script>
import { store, backendUiStore, workflowStore } from "builderStore" import { store, backendUiStore, automationStore } from "builderStore"
import { notifier } from "builderStore/store/notifications" import { notifier } from "builderStore/store/notifications"
import ActionButton from "components/common/ActionButton.svelte" import ActionButton from "components/common/ActionButton.svelte"
@ -10,32 +10,32 @@
$: valid = !!name $: valid = !!name
$: instanceId = $backendUiStore.selectedDatabase._id $: instanceId = $backendUiStore.selectedDatabase._id
async function deleteWorkflow() { async function deleteAutomation() {
await workflowStore.actions.delete({ await automationStore.actions.delete({
instanceId, instanceId,
workflow: $workflowStore.selectedWorkflow.workflow, automation: $automationStore.selectedAutomation.automation,
}) })
onClosed() onClosed()
notifier.danger("Workflow deleted.") notifier.danger("Automation deleted.")
} }
</script> </script>
<header> <header>
<i class="ri-stackshare-line" /> <i class="ri-stackshare-line" />
Delete Workflow Delete Automation
</header> </header>
<div> <div>
<p> <p>
Are you sure you want to delete this workflow? This action can't be undone. Are you sure you want to delete this automation? This action can't be undone.
</p> </p>
</div> </div>
<footer> <footer>
<a href="https://docs.budibase.com"> <a href="https://docs.budibase.com">
<i class="ri-information-line" /> <i class="ri-information-line" />
Learn about workflows Learn about automations
</a> </a>
<ActionButton on:click={onClosed}>Cancel</ActionButton> <ActionButton on:click={onClosed}>Cancel</ActionButton>
<ActionButton alert on:click={deleteWorkflow}>Delete</ActionButton> <ActionButton alert on:click={deleteAutomation}>Delete</ActionButton>
</footer> </footer>
<style> <style>

View File

@ -1,49 +1,49 @@
<script> <script>
import { getContext } from "svelte" import { getContext } from "svelte"
import { backendUiStore, workflowStore } from "builderStore" import { backendUiStore, automationStore } from "builderStore"
import { notifier } from "builderStore/store/notifications" import { notifier } from "builderStore/store/notifications"
import WorkflowBlockSetup from "./WorkflowBlockSetup.svelte" import AutomationBlockSetup from "./AutomationBlockSetup.svelte"
import DeleteWorkflowModal from "./DeleteWorkflowModal.svelte" import DeleteAutomationModal from "./DeleteAutomationModal.svelte"
import { Button, Input, Label } from "@budibase/bbui" import { Button, Input, Label } from "@budibase/bbui"
const { open, close } = getContext("simple-modal") const { open, close } = getContext("simple-modal")
let selectedTab = "SETUP" let selectedTab = "SETUP"
$: workflow = $workflowStore.selectedWorkflow?.workflow $: automation = $automationStore.selectedAutomation?.automation
$: allowDeleteBlock = $: allowDeleteBlock =
$workflowStore.selectedBlock?.type !== "TRIGGER" || $automationStore.selectedBlock?.type !== "TRIGGER" ||
!workflow?.definition?.steps?.length !automation?.definition?.steps?.length
function deleteWorkflow() { function deleteAutomation() {
open( open(
DeleteWorkflowModal, DeleteAutomationModal,
{ onClosed: close }, { onClosed: close },
{ styleContent: { padding: "0" } } { styleContent: { padding: "0" } }
) )
} }
function deleteWorkflowBlock() { function deleteAutomationBlock() {
workflowStore.actions.deleteWorkflowBlock($workflowStore.selectedBlock) automationStore.actions.deleteAutomationBlock($automationStore.selectedBlock)
} }
async function testWorkflow() { async function testAutomation() {
const result = await workflowStore.actions.trigger({ const result = await automationStore.actions.trigger({
workflow: $workflowStore.selectedWorkflow.workflow, automation: $automationStore.selectedAutomation.automation,
}) })
if (result.status === 200) { if (result.status === 200) {
notifier.success(`Workflow ${workflow.name} triggered successfully.`) notifier.success(`Automation ${automation.name} triggered successfully.`)
} else { } else {
notifier.danger(`Failed to trigger workflow ${workflow.name}.`) notifier.danger(`Failed to trigger automation ${automation.name}.`)
} }
} }
async function saveWorkflow() { async function saveAutomation() {
await workflowStore.actions.save({ await automationStore.actions.save({
instanceId: $backendUiStore.selectedDatabase._id, instanceId: $backendUiStore.selectedDatabase._id,
workflow, automation,
}) })
notifier.success(`Workflow ${workflow.name} saved.`) notifier.success(`Automation ${automation.name} saved.`)
} }
</script> </script>
@ -56,38 +56,38 @@
Setup Setup
</span> </span>
</header> </header>
{#if $workflowStore.selectedBlock} {#if $automationStore.selectedBlock}
<WorkflowBlockSetup bind:block={$workflowStore.selectedBlock} /> <AutomationBlockSetup bind:block={$automationStore.selectedBlock} />
<div class="buttons"> <div class="buttons">
<Button green wide data-cy="save-workflow-setup" on:click={saveWorkflow}> <Button green wide data-cy="save-automation-setup" on:click={saveAutomation}>
Save Workflow Save Automation
</Button> </Button>
<Button <Button
disabled={!allowDeleteBlock} disabled={!allowDeleteBlock}
red red
wide wide
on:click={deleteWorkflowBlock}> on:click={deleteAutomationBlock}>
Delete Block Delete Block
</Button> </Button>
</div> </div>
{:else if $workflowStore.selectedWorkflow} {:else if $automationStore.selectedAutomation}
<div class="panel"> <div class="panel">
<div class="panel-body"> <div class="panel-body">
<div class="block-label"> <div class="block-label">
Workflow Automation
<b>{workflow.name}</b> <b>{automation.name}</b>
</div> </div>
</div> </div>
<Button secondary wide on:click={testWorkflow}>Test Workflow</Button> <Button secondary wide on:click={testAutomation}>Test Automation</Button>
<div class="buttons"> <div class="buttons">
<Button <Button
green green
wide wide
data-cy="save-workflow-setup" data-cy="save-automation-setup"
on:click={saveWorkflow}> on:click={saveAutomation}>
Save Workflow Save Automation
</Button> </Button>
<Button red wide on:click={deleteWorkflow}>Delete Workflow</Button> <Button red wide on:click={deleteAutomation}>Delete Automation</Button>
</div> </div>
</div> </div>
{/if} {/if}

View File

@ -0,0 +1,3 @@
export { default as AutomationBuilder } from "./AutomationBuilder/AutomationBuilder.svelte"
export { default as SetupPanel } from "./SetupPanel/SetupPanel.svelte"
export { default as AutomationPanel } from "./AutomationPanel/AutomationPanel.svelte"

View File

@ -1,7 +1,7 @@
<script> <script>
import { writable } from "svelte/store" import { writable } from "svelte/store"
import { store, workflowStore, backendUiStore } from "builderStore" import { store, automationStore, backendUiStore } from "builderStore"
import { string, object } from "yup" import { string, object } from "yup"
import api, { get } from "builderStore/api" import api, { get } from "builderStore/api"
import Form from "@svelteschool/svelte-forms" import Form from "@svelteschool/svelte-forms"
@ -133,7 +133,7 @@
if (applicationPkg.ok) { if (applicationPkg.ok) {
backendUiStore.actions.reset() backendUiStore.actions.reset()
await store.setPackage(pkg) await store.setPackage(pkg)
workflowStore.actions.fetch() automationStore.actions.fetch()
} else { } else {
throw new Error(pkg) throw new Error(pkg)
} }

View File

@ -3,7 +3,7 @@
import { find, map, keys, reduce, keyBy } from "lodash/fp" import { find, map, keys, reduce, keyBy } from "lodash/fp"
import { pipe } from "components/common/core" import { pipe } from "components/common/core"
import { EVENT_TYPE_MEMBER_NAME } from "components/common/eventHandlers" import { EVENT_TYPE_MEMBER_NAME } from "components/common/eventHandlers"
import { store, workflowStore } from "builderStore" import { store, automationStore } from "builderStore"
import { ArrowDownIcon } from "components/common/Icons/" import { ArrowDownIcon } from "components/common/Icons/"
import { createEventDispatcher } from "svelte" import { createEventDispatcher } from "svelte"
@ -18,14 +18,14 @@
</script> </script>
<div class="handler-option"> <div class="handler-option">
{#if parameter.name === 'workflow'} {#if parameter.name === 'automation'}
<span>{parameter.name}</span> <span>{parameter.name}</span>
{/if} {/if}
{#if parameter.name === 'workflow'} {#if parameter.name === 'automation'}
<Select on:change bind:value={parameter.value}> <Select on:change bind:value={parameter.value}>
<option value="" /> <option value="" />
{#each $workflowStore.workflows.filter(wf => wf.live) as workflow} {#each $automationStore.automations.filter(wf => wf.live) as automation}
<option value={workflow._id}>{workflow.name}</option> <option value={automation._id}>{automation.name}</option>
{/each} {/each}
</Select> </Select>
{:else if parameter.name === 'url'} {:else if parameter.name === 'url'}

View File

@ -1,3 +0,0 @@
export { default as WorkflowPanel } from "./WorkflowPanel.svelte"
export { default as BlockList } from "./BlockList/BlockList.svelte"
export { default as WorkflowList } from "./WorkflowList/WorkflowList.svelte"

View File

@ -1,3 +0,0 @@
export { default as WorkflowBuilder } from "./WorkflowBuilder/WorkflowBuilder.svelte"
export { default as SetupPanel } from "./SetupPanel/SetupPanel.svelte"
export { default as WorkflowPanel } from "./WorkflowPanel/WorkflowPanel.svelte"

View File

@ -1,6 +1,6 @@
<script> <script>
import Modal from "svelte-simple-modal" import Modal from "svelte-simple-modal"
import { store, workflowStore, backendUiStore } from "builderStore" import { store, automationStore, backendUiStore } from "builderStore"
import SettingsLink from "components/settings/Link.svelte" import SettingsLink from "components/settings/Link.svelte"
import { get } from "builderStore/api" import { get } from "builderStore/api"
@ -21,17 +21,17 @@
if (res.ok) { if (res.ok) {
backendUiStore.actions.reset() backendUiStore.actions.reset()
await store.setPackage(pkg) await store.setPackage(pkg)
workflowStore.actions.fetch() await automationStore.actions.fetch()
return pkg return pkg
} else { } else {
throw new Error(pkg) throw new Error(pkg)
} }
} }
// handles navigation between frontend, backend, workflow. // handles navigation between frontend, backend, automation.
// this remembers your last place on each of the sections // this remembers your last place on each of the sections
// e.g. if one of your screens is selected on front end, then // e.g. if one of your screens is selected on front end, then
// you browse to backend, when you click fronend, you will be // you browse to backend, when you click frontend, you will be
// brought back to the same screen // brought back to the same screen
const topItemNavigate = path => () => { const topItemNavigate = path => () => {
const activeTopNav = $layout.children.find(c => $isActive(c.path)) const activeTopNav = $layout.children.find(c => $isActive(c.path))

View File

@ -1,18 +1,19 @@
<!-- routify:options index=3 -->
<script> <script>
import { workflowStore } from "builderStore" import { automationStore } from "builderStore"
import { WorkflowPanel, SetupPanel } from "components/workflow" import { AutomationPanel, SetupPanel } from "components/automation"
</script> </script>
<div class="root"> <div class="root">
<div class="nav"> <div class="nav">
<div class="inner"> <div class="inner">
<WorkflowPanel /> <AutomationPanel />
</div> </div>
</div> </div>
<div class="content"> <div class="content">
<slot /> <slot />
</div> </div>
{#if $workflowStore.selectedWorkflow} {#if $automationStore.selectedAutomation}
<div class="nav"> <div class="nav">
<div class="inner"> <div class="inner">
<SetupPanel /> <SetupPanel />

View File

@ -0,0 +1,5 @@
<script>
import { AutomationBuilder } from "components/automation"
</script>
<AutomationBuilder />

View File

@ -1,3 +1,4 @@
<!-- routify:options index=1 -->
<script> <script>
import { getContext } from "svelte" import { getContext } from "svelte"
import { store, backendUiStore } from "builderStore" import { store, backendUiStore } from "builderStore"

View File

@ -1,3 +1,4 @@
<!-- routify:options index=1 -->
<script> <script>
import { store, backendUiStore } from "builderStore" import { store, backendUiStore } from "builderStore"
import { goto } from "@sveltech/routify" import { goto } from "@sveltech/routify"

View File

@ -1,5 +0,0 @@
<script>
import { WorkflowBuilder } from "components/workflow"
</script>
<WorkflowBuilder />

View File

@ -1,8 +1,8 @@
const CouchDB = require("../../db") const CouchDB = require("../../db")
const newid = require("../../db/newid") const newid = require("../../db/newid")
const actions = require("../../workflows/actions") const actions = require("../../automations/actions")
const logic = require("../../workflows/logic") const logic = require("../../automations/logic")
const triggers = require("../../workflows/triggers") const triggers = require("../../automations/triggers")
/************************* /*************************
* * * *
@ -10,12 +10,12 @@ const triggers = require("../../workflows/triggers")
* * * *
*************************/ *************************/
function cleanWorkflowInputs(workflow) { function cleanAutomationInputs(automation) {
if (workflow == null) { if (automation == null) {
return workflow return automation
} }
let steps = workflow.definition.steps let steps = automation.definition.steps
let trigger = workflow.definition.trigger let trigger = automation.definition.trigger
let allSteps = [...steps, trigger] let allSteps = [...steps, trigger]
for (let step of allSteps) { for (let step of allSteps) {
if (step == null) { if (step == null) {
@ -27,25 +27,25 @@ function cleanWorkflowInputs(workflow) {
} }
} }
} }
return workflow return automation
} }
exports.create = async function(ctx) { exports.create = async function(ctx) {
const db = new CouchDB(ctx.user.instanceId) const db = new CouchDB(ctx.user.instanceId)
let workflow = ctx.request.body let automation = ctx.request.body
workflow._id = newid() automation._id = newid()
workflow.type = "workflow" automation.type = "automation"
workflow = cleanWorkflowInputs(workflow) automation = cleanAutomationInputs(automation)
const response = await db.post(workflow) const response = await db.post(automation)
workflow._rev = response.rev automation._rev = response.rev
ctx.status = 200 ctx.status = 200
ctx.body = { ctx.body = {
message: "Workflow created successfully", message: "Automation created successfully",
workflow: { automation: {
...workflow, ...automation,
...response, ...response,
}, },
} }
@ -53,17 +53,17 @@ exports.create = async function(ctx) {
exports.update = async function(ctx) { exports.update = async function(ctx) {
const db = new CouchDB(ctx.user.instanceId) const db = new CouchDB(ctx.user.instanceId)
let workflow = ctx.request.body let automation = ctx.request.body
workflow = cleanWorkflowInputs(workflow) automation = cleanAutomationInputs(automation)
const response = await db.put(workflow) const response = await db.put(automation)
workflow._rev = response.rev automation._rev = response.rev
ctx.status = 200 ctx.status = 200
ctx.body = { ctx.body = {
message: `Workflow ${workflow._id} updated successfully.`, message: `Automation ${automation._id} updated successfully.`,
workflow: { automation: {
...workflow, ...automation,
_rev: response.rev, _rev: response.rev,
_id: response.id, _id: response.id,
}, },
@ -73,7 +73,7 @@ exports.update = async function(ctx) {
exports.fetch = async function(ctx) { exports.fetch = async function(ctx) {
const db = new CouchDB(ctx.user.instanceId) const db = new CouchDB(ctx.user.instanceId)
const response = await db.query(`database/by_type`, { const response = await db.query(`database/by_type`, {
key: ["workflow"], key: ["automation"],
include_docs: true, include_docs: true,
}) })
ctx.body = response.rows.map(row => row.doc) ctx.body = response.rows.map(row => row.doc)
@ -117,14 +117,14 @@ module.exports.getDefinitionList = async function(ctx) {
exports.trigger = async function(ctx) { exports.trigger = async function(ctx) {
const db = new CouchDB(ctx.user.instanceId) const db = new CouchDB(ctx.user.instanceId)
let workflow = await db.get(ctx.params.id) let automation = await db.get(ctx.params.id)
await triggers.externalTrigger(workflow, { await triggers.externalTrigger(automation, {
...ctx.request.body, ...ctx.request.body,
instanceId: ctx.user.instanceId, instanceId: ctx.user.instanceId,
}) })
ctx.status = 200 ctx.status = 200
ctx.body = { ctx.body = {
message: `Workflow ${workflow._id} has been triggered.`, message: `Automation ${automation._id} has been triggered.`,
workflow, automation,
} }
} }

View File

@ -31,9 +31,9 @@ exports.create = async function(ctx) {
emit([doc.type], doc._id) emit([doc.type], doc._id)
}.toString(), }.toString(),
}, },
by_workflow_trigger: { by_automation_trigger: {
map: function(doc) { map: function(doc) {
if (doc.type === "workflow") { if (doc.type === "automation") {
const trigger = doc.definition.trigger const trigger = doc.definition.trigger
if (trigger) { if (trigger) {
emit([trigger.event], trigger) emit([trigger.event], trigger)

View File

@ -188,7 +188,7 @@ exports.destroy = async function(ctx) {
} }
ctx.body = await db.remove(ctx.params.recordId, ctx.params.revId) ctx.body = await db.remove(ctx.params.recordId, ctx.params.revId)
ctx.status = 200 ctx.status = 200
// for workflows // for automations
ctx.record = record ctx.record = record
emitEvent(`record:delete`, ctx, record) emitEvent(`record:delete`, ctx, record)
} }

View File

@ -12,7 +12,7 @@ const controller = {
!name.startsWith("all") && !name.startsWith("all") &&
name !== "by_type" && name !== "by_type" &&
name !== "by_username" && name !== "by_username" &&
name !== "by_workflow_trigger" name !== "by_automation_trigger"
) { ) {
response.push({ response.push({
name, name,

View File

@ -16,7 +16,7 @@ const {
viewRoutes, viewRoutes,
staticRoutes, staticRoutes,
componentRoutes, componentRoutes,
workflowRoutes, automationRoutes,
accesslevelRoutes, accesslevelRoutes,
apiKeysRoutes, apiKeysRoutes,
} = require("./routes") } = require("./routes")
@ -84,8 +84,8 @@ router.use(userRoutes.allowedMethods())
router.use(instanceRoutes.routes()) router.use(instanceRoutes.routes())
router.use(instanceRoutes.allowedMethods()) router.use(instanceRoutes.allowedMethods())
router.use(workflowRoutes.routes()) router.use(automationRoutes.routes())
router.use(workflowRoutes.allowedMethods()) router.use(automationRoutes.allowedMethods())
router.use(deployRoutes.routes()) router.use(deployRoutes.routes())
router.use(deployRoutes.allowedMethods()) router.use(deployRoutes.allowedMethods())

View File

@ -1,5 +1,5 @@
const Router = require("@koa/router") const Router = require("@koa/router")
const controller = require("../controllers/workflow") const controller = require("../controllers/automation")
const authorized = require("../../middleware/authorized") const authorized = require("../../middleware/authorized")
const joiValidator = require("../../middleware/joi-validator") const joiValidator = require("../../middleware/joi-validator")
const { BUILDER } = require("../../utilities/accessLevels") const { BUILDER } = require("../../utilities/accessLevels")
@ -30,7 +30,7 @@ function generateValidator(existing = false) {
_id: existing ? Joi.string().required() : Joi.string(), _id: existing ? Joi.string().required() : Joi.string(),
_rev: existing ? Joi.string().required() : Joi.string(), _rev: existing ? Joi.string().required() : Joi.string(),
name: Joi.string().required(), name: Joi.string().required(),
type: Joi.string().valid("workflow").required(), type: Joi.string().valid("automation").required(),
definition: Joi.object({ definition: Joi.object({
steps: Joi.array().required().items(generateStepSchema(["ACTION", "LOGIC"])), steps: Joi.array().required().items(generateStepSchema(["ACTION", "LOGIC"])),
trigger: generateStepSchema(["TRIGGER"]), trigger: generateStepSchema(["TRIGGER"]),
@ -40,40 +40,40 @@ function generateValidator(existing = false) {
router router
.get( .get(
"/api/workflows/trigger/list", "/api/automations/trigger/list",
authorized(BUILDER), authorized(BUILDER),
controller.getTriggerList controller.getTriggerList
) )
.get( .get(
"/api/workflows/action/list", "/api/automations/action/list",
authorized(BUILDER), authorized(BUILDER),
controller.getActionList controller.getActionList
) )
.get( .get(
"/api/workflows/logic/list", "/api/automations/logic/list",
authorized(BUILDER), authorized(BUILDER),
controller.getLogicList controller.getLogicList
) )
.get( .get(
"/api/workflows/definitions/list", "/api/automations/definitions/list",
authorized(BUILDER), authorized(BUILDER),
controller.getDefinitionList controller.getDefinitionList
) )
.get("/api/workflows", authorized(BUILDER), controller.fetch) .get("/api/automations", authorized(BUILDER), controller.fetch)
.get("/api/workflows/:id", authorized(BUILDER), controller.find) .get("/api/automations/:id", authorized(BUILDER), controller.find)
.put( .put(
"/api/workflows", "/api/automations",
authorized(BUILDER), authorized(BUILDER),
generateValidator(true), generateValidator(true),
controller.update controller.update
) )
.post( .post(
"/api/workflows", "/api/automations",
authorized(BUILDER), authorized(BUILDER),
generateValidator(false), generateValidator(false),
controller.create controller.create
) )
.post("/api/workflows/:id/trigger", controller.trigger) .post("/api/automations/:id/trigger", controller.trigger)
.delete("/api/workflows/:id/:rev", authorized(BUILDER), controller.destroy) .delete("/api/automations/:id/:rev", authorized(BUILDER), controller.destroy)
module.exports = router module.exports = router

View File

@ -9,7 +9,7 @@ const recordRoutes = require("./record")
const viewRoutes = require("./view") const viewRoutes = require("./view")
const staticRoutes = require("./static") const staticRoutes = require("./static")
const componentRoutes = require("./component") const componentRoutes = require("./component")
const workflowRoutes = require("./workflow") const automationRoutes = require("./automation")
const accesslevelRoutes = require("./accesslevel") const accesslevelRoutes = require("./accesslevel")
const deployRoutes = require("./deploy") const deployRoutes = require("./deploy")
const apiKeysRoutes = require("./apikeys") const apiKeysRoutes = require("./apikeys")
@ -27,7 +27,7 @@ module.exports = {
viewRoutes, viewRoutes,
staticRoutes, staticRoutes,
componentRoutes, componentRoutes,
workflowRoutes, automationRoutes,
accesslevelRoutes, accesslevelRoutes,
apiKeysRoutes, apiKeysRoutes,
} }

View File

@ -14,9 +14,9 @@ const {
const { delay } = require("./testUtils") const { delay } = require("./testUtils")
const MAX_RETRIES = 4 const MAX_RETRIES = 4
const TEST_WORKFLOW = { const TEST_AUTOMATION = {
_id: "Test Workflow", _id: "Test Automation",
name: "My Workflow", name: "My Automation",
pageId: "123123123", pageId: "123123123",
screenId: "kasdkfldsafkl", screenId: "kasdkfldsafkl",
live: true, live: true,
@ -28,20 +28,20 @@ const TEST_WORKFLOW = {
steps: [ steps: [
], ],
}, },
type: "workflow", type: "automation",
} }
let ACTION_DEFINITIONS = {} let ACTION_DEFINITIONS = {}
let TRIGGER_DEFINITIONS = {} let TRIGGER_DEFINITIONS = {}
let LOGIC_DEFINITIONS = {} let LOGIC_DEFINITIONS = {}
describe("/workflows", () => { describe("/automations", () => {
let request let request
let server let server
let app let app
let instance let instance
let workflow let automation
let workflowId let automationId
beforeAll(async () => { beforeAll(async () => {
({ request, server } = await supertest()) ({ request, server } = await supertest())
@ -50,7 +50,7 @@ describe("/workflows", () => {
}) })
beforeEach(async () => { beforeEach(async () => {
if (workflow) await destroyDocument(workflow.id) if (automation) await destroyDocument(automation.id)
instance = await createInstance(request, app._id) instance = await createInstance(request, app._id)
}) })
@ -58,18 +58,18 @@ describe("/workflows", () => {
server.close() server.close()
}) })
const createWorkflow = async () => { const createAutomation = async () => {
workflow = await insertDocument(instance._id, { automation = await insertDocument(instance._id, {
type: "workflow", type: "automation",
...TEST_WORKFLOW ...TEST_AUTOMATION
}) })
workflow = { ...workflow, ...TEST_WORKFLOW } automation = { ...automation, ...TEST_AUTOMATION }
} }
describe("get definitions", () => { describe("get definitions", () => {
it("returns a list of definitions for actions", async () => { it("returns a list of definitions for actions", async () => {
const res = await request const res = await request
.get(`/api/workflows/action/list`) .get(`/api/automations/action/list`)
.set(defaultHeaders(app._id, instance._id)) .set(defaultHeaders(app._id, instance._id))
.expect('Content-Type', /json/) .expect('Content-Type', /json/)
.expect(200) .expect(200)
@ -80,7 +80,7 @@ describe("/workflows", () => {
it("returns a list of definitions for triggers", async () => { it("returns a list of definitions for triggers", async () => {
const res = await request const res = await request
.get(`/api/workflows/trigger/list`) .get(`/api/automations/trigger/list`)
.set(defaultHeaders(app._id, instance._id)) .set(defaultHeaders(app._id, instance._id))
.expect('Content-Type', /json/) .expect('Content-Type', /json/)
.expect(200) .expect(200)
@ -91,7 +91,7 @@ describe("/workflows", () => {
it("returns a list of definitions for actions", async () => { it("returns a list of definitions for actions", async () => {
const res = await request const res = await request
.get(`/api/workflows/logic/list`) .get(`/api/automations/logic/list`)
.set(defaultHeaders(app._id, instance._id)) .set(defaultHeaders(app._id, instance._id))
.expect('Content-Type', /json/) .expect('Content-Type', /json/)
.expect(200) .expect(200)
@ -102,7 +102,7 @@ describe("/workflows", () => {
it("returns all of the definitions in one", async () => { it("returns all of the definitions in one", async () => {
const res = await request const res = await request
.get(`/api/workflows/definitions/list`) .get(`/api/automations/definitions/list`)
.set(defaultHeaders(app._id, instance._id)) .set(defaultHeaders(app._id, instance._id))
.expect('Content-Type', /json/) .expect('Content-Type', /json/)
.expect(200) .expect(200)
@ -114,7 +114,7 @@ describe("/workflows", () => {
}) })
describe("create", () => { describe("create", () => {
it("should setup the workflow fully", () => { it("should setup the automation fully", () => {
let trigger = TRIGGER_DEFINITIONS["RECORD_SAVED"] let trigger = TRIGGER_DEFINITIONS["RECORD_SAVED"]
trigger.id = "wadiawdo34" trigger.id = "wadiawdo34"
let saveAction = ACTION_DEFINITIONS["SAVE_RECORD"] let saveAction = ACTION_DEFINITIONS["SAVE_RECORD"]
@ -124,51 +124,51 @@ describe("/workflows", () => {
} }
saveAction.id = "awde444wk" saveAction.id = "awde444wk"
TEST_WORKFLOW.definition.steps.push(saveAction) TEST_AUTOMATION.definition.steps.push(saveAction)
TEST_WORKFLOW.definition.trigger = trigger TEST_AUTOMATION.definition.trigger = trigger
}) })
it("returns a success message when the workflow is successfully created", async () => { it("returns a success message when the automation is successfully created", async () => {
const res = await request const res = await request
.post(`/api/workflows`) .post(`/api/automations`)
.set(defaultHeaders(app._id, instance._id)) .set(defaultHeaders(app._id, instance._id))
.send(TEST_WORKFLOW) .send(TEST_AUTOMATION)
.expect('Content-Type', /json/) .expect('Content-Type', /json/)
.expect(200) .expect(200)
expect(res.body.message).toEqual("Workflow created successfully") expect(res.body.message).toEqual("Automation created successfully")
expect(res.body.workflow.name).toEqual("My Workflow") expect(res.body.automation.name).toEqual("My Automation")
expect(res.body.workflow._id).not.toEqual(null) expect(res.body.automation._id).not.toEqual(null)
workflowId = res.body.workflow._id automationId = res.body.automation._id
}) })
it("should apply authorization to endpoint", async () => { it("should apply authorization to endpoint", async () => {
await builderEndpointShouldBlockNormalUsers({ await builderEndpointShouldBlockNormalUsers({
request, request,
method: "POST", method: "POST",
url: `/api/workflows`, url: `/api/automations`,
instanceId: instance._id, instanceId: instance._id,
appId: app._id, appId: app._id,
body: TEST_WORKFLOW body: TEST_AUTOMATION
}) })
}) })
}) })
describe("trigger", () => { describe("trigger", () => {
it("trigger the workflow successfully", async () => { it("trigger the automation successfully", async () => {
let model = await createModel(request, app._id, instance._id) let model = await createModel(request, app._id, instance._id)
TEST_WORKFLOW.definition.trigger.inputs.modelId = model._id TEST_AUTOMATION.definition.trigger.inputs.modelId = model._id
TEST_WORKFLOW.definition.steps[0].inputs.record.modelId = model._id TEST_AUTOMATION.definition.steps[0].inputs.record.modelId = model._id
await createWorkflow() await createAutomation()
const res = await request const res = await request
.post(`/api/workflows/${workflow._id}/trigger`) .post(`/api/automations/${automation._id}/trigger`)
.send({ name: "Test" }) .send({ name: "Test" })
.set(defaultHeaders(app._id, instance._id)) .set(defaultHeaders(app._id, instance._id))
.expect('Content-Type', /json/) .expect('Content-Type', /json/)
.expect(200) .expect(200)
expect(res.body.message).toEqual(`Workflow ${workflow._id} has been triggered.`) expect(res.body.message).toEqual(`Automation ${automation._id} has been triggered.`)
expect(res.body.workflow.name).toEqual(TEST_WORKFLOW.name) expect(res.body.automation.name).toEqual(TEST_AUTOMATION.name)
// wait for workflow to complete in background // wait for automation to complete in background
for (let tries = 0; tries < MAX_RETRIES; tries++) { for (let tries = 0; tries < MAX_RETRIES; tries++) {
let elements = await getAllFromModel(request, app._id, instance._id, model._id) let elements = await getAllFromModel(request, app._id, instance._id, model._id)
// don't test it unless there are values to test // don't test it unless there are values to test
@ -185,42 +185,42 @@ describe("/workflows", () => {
}) })
describe("update", () => { describe("update", () => {
it("updates a workflows data", async () => { it("updates a automations data", async () => {
await createWorkflow() await createAutomation()
workflow._id = workflow.id automation._id = automation.id
workflow._rev = workflow.rev automation._rev = automation.rev
workflow.name = "Updated Name" automation.name = "Updated Name"
workflow.type = "workflow" automation.type = "automation"
const res = await request const res = await request
.put(`/api/workflows`) .put(`/api/automations`)
.set(defaultHeaders(app._id, instance._id)) .set(defaultHeaders(app._id, instance._id))
.send(workflow) .send(automation)
.expect('Content-Type', /json/) .expect('Content-Type', /json/)
.expect(200) .expect(200)
expect(res.body.message).toEqual("Workflow Test Workflow updated successfully.") expect(res.body.message).toEqual("Automation Test Automation updated successfully.")
expect(res.body.workflow.name).toEqual("Updated Name") expect(res.body.automation.name).toEqual("Updated Name")
}) })
}) })
describe("fetch", () => { describe("fetch", () => {
it("return all the workflows for an instance", async () => { it("return all the automations for an instance", async () => {
await createWorkflow() await createAutomation()
const res = await request const res = await request
.get(`/api/workflows`) .get(`/api/automations`)
.set(defaultHeaders(app._id, instance._id)) .set(defaultHeaders(app._id, instance._id))
.expect('Content-Type', /json/) .expect('Content-Type', /json/)
.expect(200) .expect(200)
expect(res.body[0]).toEqual(expect.objectContaining(TEST_WORKFLOW)) expect(res.body[0]).toEqual(expect.objectContaining(TEST_AUTOMATION))
}) })
it("should apply authorization to endpoint", async () => { it("should apply authorization to endpoint", async () => {
await builderEndpointShouldBlockNormalUsers({ await builderEndpointShouldBlockNormalUsers({
request, request,
method: "GET", method: "GET",
url: `/api/workflows`, url: `/api/automations`,
instanceId: instance._id, instanceId: instance._id,
appId: app._id, appId: app._id,
}) })
@ -228,23 +228,23 @@ describe("/workflows", () => {
}) })
describe("destroy", () => { describe("destroy", () => {
it("deletes a workflow by its ID", async () => { it("deletes a automation by its ID", async () => {
await createWorkflow() await createAutomation()
const res = await request const res = await request
.delete(`/api/workflows/${workflow.id}/${workflow.rev}`) .delete(`/api/automations/${automation.id}/${automation.rev}`)
.set(defaultHeaders(app._id, instance._id)) .set(defaultHeaders(app._id, instance._id))
.expect('Content-Type', /json/) .expect('Content-Type', /json/)
.expect(200) .expect(200)
expect(res.body.id).toEqual(TEST_WORKFLOW._id) expect(res.body.id).toEqual(TEST_AUTOMATION._id)
}) })
it("should apply authorization to endpoint", async () => { it("should apply authorization to endpoint", async () => {
await createWorkflow() await createAutomation()
await builderEndpointShouldBlockNormalUsers({ await builderEndpointShouldBlockNormalUsers({
request, request,
method: "DELETE", method: "DELETE",
url: `/api/workflows/${workflow.id}/${workflow._rev}`, url: `/api/automations/${automation.id}/${automation._rev}`,
instanceId: instance._id, instanceId: instance._id,
appId: app._id, appId: app._id,
}) })

View File

@ -6,7 +6,7 @@ const http = require("http")
const api = require("./api") const api = require("./api")
const env = require("./environment") const env = require("./environment")
const eventEmitter = require("./events") const eventEmitter = require("./events")
const workflows = require("./workflows/index") const automations = require("./automations/index")
const Sentry = require("@sentry/node") const Sentry = require("@sentry/node")
const app = new Koa() const app = new Koa()
@ -50,5 +50,5 @@ process.on("SIGINT", () => process.exit(1))
module.exports = server.listen(env.PORT || 4001, () => { module.exports = server.listen(env.PORT || 4001, () => {
console.log(`Budibase running on ${JSON.stringify(server.address())}`) console.log(`Budibase running on ${JSON.stringify(server.address())}`)
workflows.init() automations.init()
}) })

View File

@ -21,11 +21,10 @@ function runWorker(job) {
* This module is built purely to kick off the worker farm and manage the inputs/outputs * This module is built purely to kick off the worker farm and manage the inputs/outputs
*/ */
module.exports.init = function() { module.exports.init = function() {
triggers.workflowQueue.process(async job => { triggers.automationQueue.process(async job => {
if (environment.BUDIBASE_ENVIRONMENT === "PRODUCTION") { if (environment.BUDIBASE_ENVIRONMENT === "PRODUCTION") {
await runWorker(job) await runWorker(job)
} else { } else {
console.log("Testing standard thread")
await singleThread(job) await singleThread(job)
} }
}) })

View File

@ -4,7 +4,7 @@ module.exports.definition = {
name: "Delay", name: "Delay",
icon: "ri-time-fill", icon: "ri-time-fill",
tagline: "Delay for {{inputs.time}} milliseconds", tagline: "Delay for {{inputs.time}} milliseconds",
description: "Delay the workflow until an amount of time has passed", description: "Delay the automation until an amount of time has passed",
stepId: "DELAY", stepId: "DELAY",
inputs: {}, inputs: {},
schema: { schema: {

View File

@ -16,7 +16,7 @@ module.exports.definition = {
name: "Filter", name: "Filter",
tagline: "{{inputs.field}} {{inputs.condition}} {{inputs.value}}", tagline: "{{inputs.field}} {{inputs.condition}} {{inputs.value}}",
icon: "ri-git-branch-line", icon: "ri-git-branch-line",
description: "Filter any workflows which do not meet certain conditions", description: "Filter any automations which do not meet certain conditions",
type: "LOGIC", type: "LOGIC",
stepId: "FILTER", stepId: "FILTER",
inputs: { inputs: {

View File

@ -45,18 +45,18 @@ function recurseMustache(inputs, context) {
} }
/** /**
* The workflow orchestrator is a class responsible for executing workflows. * The automation orchestrator is a class responsible for executing automations.
* It handles the context of the workflow and makes sure each step gets the correct * It handles the context of the automation and makes sure each step gets the correct
* inputs and handles any outputs. * inputs and handles any outputs.
*/ */
class Orchestrator { class Orchestrator {
constructor(workflow, triggerOutput) { constructor(automation, triggerOutput) {
this._instanceId = triggerOutput.instanceId this._instanceId = triggerOutput.instanceId
// remove from context // remove from context
delete triggerOutput.instanceId delete triggerOutput.instanceId
// step zero is never used as the mustache is zero indexed for customer facing // step zero is never used as the mustache is zero indexed for customer facing
this._context = { steps: [{}], trigger: triggerOutput } this._context = { steps: [{}], trigger: triggerOutput }
this._workflow = workflow this._automation = automation
} }
async getStepFunctionality(type, stepId) { async getStepFunctionality(type, stepId) {
@ -67,14 +67,14 @@ class Orchestrator {
step = logic.getLogic(stepId) step = logic.getLogic(stepId)
} }
if (step == null) { if (step == null) {
throw `Cannot find workflow step by name ${stepId}` throw `Cannot find automation step by name ${stepId}`
} }
return step return step
} }
async execute() { async execute() {
let workflow = this._workflow let automation = this._automation
for (let step of workflow.definition.steps) { for (let step of automation.definition.steps) {
let stepFn = await this.getStepFunctionality(step.type, step.stepId) let stepFn = await this.getStepFunctionality(step.type, step.stepId)
step.inputs = recurseMustache(step.inputs, this._context) step.inputs = recurseMustache(step.inputs, this._context)
// instanceId is always passed // instanceId is always passed
@ -93,11 +93,11 @@ class Orchestrator {
// callback is required for worker-farm to state that the worker thread has completed // callback is required for worker-farm to state that the worker thread has completed
module.exports = async (job, cb = null) => { module.exports = async (job, cb = null) => {
try { try {
const workflowOrchestrator = new Orchestrator( const automationOrchestrator = new Orchestrator(
job.data.workflow, job.data.automation,
job.data.event job.data.event
) )
await workflowOrchestrator.execute() await automationOrchestrator.execute()
if (cb) { if (cb) {
cb() cb()
} }

View File

@ -2,7 +2,7 @@ const CouchDB = require("../db")
const emitter = require("../events/index") const emitter = require("../events/index")
const InMemoryQueue = require("./queue/inMemoryQueue") const InMemoryQueue = require("./queue/inMemoryQueue")
let workflowQueue = new InMemoryQueue() let automationQueue = new InMemoryQueue()
const FAKE_STRING = "TEST" const FAKE_STRING = "TEST"
const FAKE_BOOL = false const FAKE_BOOL = false
@ -76,28 +76,31 @@ const BUILTIN_DEFINITIONS = {
}, },
} }
async function queueRelevantRecordWorkflows(event, eventType) { async function queueRelevantRecordAutomations(event, eventType) {
if (event.instanceId == null) { if (event.instanceId == null) {
throw `No instanceId specified for ${eventType} - check event emitters.` throw `No instanceId specified for ${eventType} - check event emitters.`
} }
const db = new CouchDB(event.instanceId) const db = new CouchDB(event.instanceId)
const workflowsToTrigger = await db.query("database/by_workflow_trigger", { const automationsToTrigger = await db.query(
key: [eventType], "database/by_automation_trigger",
include_docs: true, {
}) key: [eventType],
include_docs: true,
}
)
const workflows = workflowsToTrigger.rows.map(wf => wf.doc) const automations = automationsToTrigger.rows.map(wf => wf.doc)
for (let workflow of workflows) { for (let automation of automations) {
let workflowDef = workflow.definition let automationDef = automation.definition
let workflowTrigger = workflowDef ? workflowDef.trigger : {} let automationTrigger = automationDef ? automationDef.trigger : {}
if ( if (
!workflow.live || !automation.live ||
!workflowTrigger.inputs || !automationTrigger.inputs ||
workflowTrigger.inputs.modelId !== event.record.modelId automationTrigger.inputs.modelId !== event.record.modelId
) { ) {
continue continue
} }
workflowQueue.add({ workflow, event }) automationQueue.add({ automation, event })
} }
} }
@ -105,18 +108,18 @@ emitter.on("record:save", async function(event) {
if (!event || !event.record || !event.record.modelId) { if (!event || !event.record || !event.record.modelId) {
return return
} }
await queueRelevantRecordWorkflows(event, "record:save") await queueRelevantRecordAutomations(event, "record:save")
}) })
emitter.on("record:delete", async function(event) { emitter.on("record:delete", async function(event) {
if (!event || !event.record || !event.record.modelId) { if (!event || !event.record || !event.record.modelId) {
return return
} }
await queueRelevantRecordWorkflows(event, "record:delete") await queueRelevantRecordAutomations(event, "record:delete")
}) })
async function fillRecordOutput(workflow, params) { async function fillRecordOutput(automation, params) {
let triggerSchema = workflow.definition.trigger let triggerSchema = automation.definition.trigger
let modelId = triggerSchema.inputs.modelId let modelId = triggerSchema.inputs.modelId
const db = new CouchDB(params.instanceId) const db = new CouchDB(params.instanceId)
try { try {
@ -147,19 +150,19 @@ async function fillRecordOutput(workflow, params) {
return params return params
} }
module.exports.externalTrigger = async function(workflow, params) { module.exports.externalTrigger = async function(automation, params) {
// TODO: replace this with allowing user in builder to input values in future // TODO: replace this with allowing user in builder to input values in future
if ( if (
workflow.definition != null && automation.definition != null &&
workflow.definition.trigger != null && automation.definition.trigger != null &&
workflow.definition.trigger.inputs.modelId != null automation.definition.trigger.inputs.modelId != null
) { ) {
params = await fillRecordOutput(workflow, params) params = await fillRecordOutput(automation, params)
} }
workflowQueue.add({ workflow, event: params }) automationQueue.add({ automation, event: params })
} }
module.exports.workflowQueue = workflowQueue module.exports.automationQueue = automationQueue
module.exports.BUILTIN_DEFINITIONS = BUILTIN_DEFINITIONS module.exports.BUILTIN_DEFINITIONS = BUILTIN_DEFINITIONS

View File

@ -2,7 +2,7 @@ const EventEmitter = require("events").EventEmitter
/** /**
* keeping event emitter in one central location as it might be used for things other than * keeping event emitter in one central location as it might be used for things other than
* workflows (what it was for originally) - having a central emitter will be useful in the * automations (what it was for originally) - having a central emitter will be useful in the
* future. * future.
*/ */

View File

@ -2,7 +2,7 @@
module.exports.READ_MODEL = "read-model" module.exports.READ_MODEL = "read-model"
module.exports.WRITE_MODEL = "write-model" module.exports.WRITE_MODEL = "write-model"
module.exports.READ_VIEW = "read-view" module.exports.READ_VIEW = "read-view"
module.exports.EXECUTE_WORKFLOW = "execute-workflow" module.exports.EXECUTE_AUTOMATION = "execute-automation"
module.exports.USER_MANAGEMENT = "user-management" module.exports.USER_MANAGEMENT = "user-management"
module.exports.BUILDER = "builder" module.exports.BUILDER = "builder"
module.exports.LIST_USERS = "list-users" module.exports.LIST_USERS = "list-users"

View File

@ -1,6 +1,6 @@
const viewController = require("../api/controllers/view") const viewController = require("../api/controllers/view")
const modelController = require("../api/controllers/model") const modelController = require("../api/controllers/model")
const workflowController = require("../api/controllers/workflow") const automationController = require("../api/controllers/automation")
const accessLevels = require("./accessLevels") const accessLevels = require("./accessLevels")
// this has been broken out to reduce risk of circular dependency from utilities, no enums defined here // this has been broken out to reduce risk of circular dependency from utilities, no enums defined here
@ -26,13 +26,13 @@ const generatePowerUserPermissions = async instanceId => {
await viewController.fetch(fetchViewsCtx) await viewController.fetch(fetchViewsCtx)
const views = fetchViewsCtx.body const views = fetchViewsCtx.body
const fetchWorkflowsCtx = { const fetchAutomationsCtx = {
user: { user: {
instanceId, instanceId,
}, },
} }
await workflowController.fetch(fetchWorkflowsCtx) await automationController.fetch(fetchAutomationsCtx)
const workflows = fetchWorkflowsCtx.body const automations = fetchAutomationsCtx.body
const readModelPermissions = models.map(m => ({ const readModelPermissions = models.map(m => ({
itemId: m._id, itemId: m._id,
@ -49,16 +49,16 @@ const generatePowerUserPermissions = async instanceId => {
name: accessLevels.READ_VIEW, name: accessLevels.READ_VIEW,
})) }))
const executeWorkflowPermissions = workflows.map(w => ({ const executeAutomationPermissions = automations.map(w => ({
itemId: w._id, itemId: w._id,
name: accessLevels.EXECUTE_WORKFLOW, name: accessLevels.EXECUTE_AUTOMATION,
})) }))
return [ return [
...readModelPermissions, ...readModelPermissions,
...writeModelPermissions, ...writeModelPermissions,
...viewPermissions, ...viewPermissions,
...executeWorkflowPermissions, ...executeAutomationPermissions,
{ name: accessLevels.LIST_USERS }, { name: accessLevels.LIST_USERS },
] ]
} }