Merge pull request #626 from Budibase/rename-workflow-automation
Rename workflow -> automation
This commit is contained in:
commit
91edce8e9d
|
@ -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
|
||||||
|
|
|
@ -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")
|
||||||
})
|
})
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
@ -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 {
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -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
|
||||||
|
}
|
|
@ -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
|
||||||
|
)
|
||||||
|
})
|
||||||
|
})
|
|
@ -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",
|
|
@ -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
|
|
||||||
}
|
|
|
@ -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
|
|
||||||
)
|
|
||||||
})
|
|
||||||
})
|
|
|
@ -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}
|
Before Width: | Height: | Size: 290 B After Width: | Height: | Size: 290 B |
|
@ -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>
|
|
@ -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>
|
||||||
|
|
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
|
@ -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>
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
|
@ -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>
|
|
@ -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"
|
|
@ -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">
|
|
@ -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>
|
|
@ -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}
|
|
@ -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"
|
|
@ -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)
|
||||||
}
|
}
|
||||||
|
|
|
@ -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'}
|
||||||
|
|
|
@ -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"
|
|
|
@ -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"
|
|
|
@ -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))
|
||||||
|
|
|
@ -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 />
|
|
@ -0,0 +1,5 @@
|
||||||
|
<script>
|
||||||
|
import { AutomationBuilder } from "components/automation"
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<AutomationBuilder />
|
|
@ -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"
|
||||||
|
|
|
@ -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"
|
||||||
|
|
|
@ -1,5 +0,0 @@
|
||||||
<script>
|
|
||||||
import { WorkflowBuilder } from "components/workflow"
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<WorkflowBuilder />
|
|
|
@ -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,
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -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)
|
||||||
|
|
|
@ -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)
|
||||||
}
|
}
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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())
|
||||||
|
|
|
@ -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
|
|
@ -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,
|
||||||
}
|
}
|
||||||
|
|
|
@ -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,
|
||||||
})
|
})
|
|
@ -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()
|
||||||
})
|
})
|
||||||
|
|
|
@ -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)
|
||||||
}
|
}
|
||||||
})
|
})
|
|
@ -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: {
|
|
@ -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: {
|
|
@ -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()
|
||||||
}
|
}
|
|
@ -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(
|
||||||
|
"database/by_automation_trigger",
|
||||||
|
{
|
||||||
key: [eventType],
|
key: [eventType],
|
||||||
include_docs: true,
|
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
|
|
@ -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.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
|
|
@ -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"
|
||||||
|
|
|
@ -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 },
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue