workflow DAO tests

This commit is contained in:
Martin McKeaveney 2020-06-01 11:52:15 +01:00
parent a220822e3a
commit 2e42f8033e
23 changed files with 193 additions and 61 deletions

View File

@ -25,7 +25,11 @@
$basepath = "/_builder" $basepath = "/_builder"
</script> </script>
<AppNotification />
<!-- svelte-notifications -->
<NotificationDisplay /> <NotificationDisplay />
<Modal> <Modal>
<Router {routes} /> <Router {routes} />
</Modal> </Modal>

View File

@ -1,5 +1,4 @@
import mustache from "mustache" import mustache from "mustache"
// TODO: tidy up import
import blockDefinitions from "components/workflow/WorkflowPanel/blockDefinitions" import blockDefinitions from "components/workflow/WorkflowPanel/blockDefinitions"
import { generate } from "shortid" import { generate } from "shortid"
@ -13,12 +12,19 @@ export default class Workflow {
} }
isEmpty() { isEmpty() {
// return this.workflow.definition.next return this.workflow.definition.trigger.length === 0
return this.workflow.length > 0
} }
addBlock(block) { addBlock(block) {
// Make sure to add trigger if doesn't exist // Make sure to add trigger if doesn't exist
// if (this.isEmpty()) {
// this.workflow.definition.triggers.push({
// id: generate(),
// ...block,
// })
// return;
// }
this.workflow.definition.steps.push({ this.workflow.definition.steps.push({
id: generate(), id: generate(),
...block, ...block,
@ -27,13 +33,10 @@ export default class Workflow {
updateBlock(updatedBlock, id) { updateBlock(updatedBlock, id) {
const { steps, trigger } = this.workflow.definition const { steps, trigger } = this.workflow.definition
// TODO: Account for trigger
// if the block is a trigger do X
// if step
const stepIdx = steps.findIndex(step => step.id === id) const stepIdx = steps.findIndex(step => step.id === id)
// while (block.id !== id) block = block.next
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)
@ -41,6 +44,7 @@ export default class Workflow {
deleteBlock(id) { deleteBlock(id) {
const { steps, trigger } = this.workflow.definition const { steps, trigger } = this.workflow.definition
// TODO: Account for trigger
const stepIdx = steps.findIndex(step => step.id === id) const stepIdx = steps.findIndex(step => step.id === id)
@ -84,38 +88,4 @@ export default class Workflow {
} }
}) })
} }
// static buildUiTree(block, tree = []) {
// if (!block) return tree
// // The client side display definition for the block
// const definition = blockDefinitions[block.type][block.actionId]
// if (!definition) {
// throw new Error(
// `No block definition exists for the chosen block. Check there's an entry in the block definitions for ${block.actionId}`
// )
// }
// if (!definition.params) {
// throw new Error(
// `Blocks should always have parameters. Ensure that the block definition is correct for ${block.actionId}`
// )
// }
// const tagline = definition.tagline || ""
// const args = block.args || {}
// // all the fields the workflow block needs to render in the UI
// tree.push({
// id: block.id,
// type: block.type,
// params: block.params,
// args,
// heading: block.actionId,
// body: mustache.render(tagline, args),
// name: definition.name
// })
// return this.buildUiTree(block.next, tree)
// }
} }

View File

@ -68,7 +68,6 @@ const workflowActions = store => ({
}, },
select: workflow => { select: workflow => {
store.update(state => { store.update(state => {
// TODO: better naming
state.currentWorkflow = new Workflow(workflow) state.currentWorkflow = new Workflow(workflow)
state.selectedWorkflowBlock = null state.selectedWorkflowBlock = null
return state return state

View File

@ -1 +0,0 @@
describe("Workflow Data Object", () => {})

View File

@ -0,0 +1,57 @@
import Workflow from "../Workflow";
import TEST_WORKFLOW from "./testWorkflow";
const TEST_BLOCK = {
id: "VFWeZcIPx",
name: "Update UI State",
tagline: "Update <b>{{path}}</b> to <b>{{value}}</b>",
icon: "ri-refresh-line",
description: "Update your User Interface with some data.",
environment: "CLIENT",
params: {
path: "string",
value: "longText",
},
args: {
path: "foo",
value: "started...",
},
actionId: "SET_STATE",
type: "ACTION",
}
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);
})
it("builds a tree that gets rendered in the flowchart builder", () => {
expect(Workflow.buildUiTree(TEST_WORKFLOW.definition)).toMatchSnapshot();
})
})

View File

@ -0,0 +1,49 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`Workflow Data Object builds a tree that gets rendered in the flowchart builder 1`] = `
Array [
Object {
"args": Object {
"time": 3000,
},
"body": "Delay for <b>3000</b> milliseconds",
"heading": "DELAY",
"id": "zJQcZUgDS",
"name": "Delay",
"params": Object {
"time": "number",
},
"type": "LOGIC",
},
Object {
"args": Object {
"path": "foo",
"value": "finished",
},
"body": "Update <b>foo</b> to <b>finished</b>",
"heading": "SET_STATE",
"id": "3RSTO7BMB",
"name": "Update UI State",
"params": Object {
"path": "string",
"value": "longText",
},
"type": "ACTION",
},
Object {
"args": Object {
"path": "foo",
"value": "started...",
},
"body": "Update <b>foo</b> to <b>started...</b>",
"heading": "SET_STATE",
"id": "VFWeZcIPx",
"name": "Update UI State",
"params": Object {
"path": "string",
"value": "longText",
},
"type": "ACTION",
},
]
`;

View File

@ -0,0 +1,64 @@
export default {
_id: "53b6148c65d1429c987e046852d11611",
_rev: "4-02c6659734934895812fa7be0215ee59",
name: "Test Workflow",
definition: {
trigger: {},
steps: [
{
id: "VFWeZcIPx",
name: "Update UI State",
tagline: "Update <b>{{path}}</b> to <b>{{value}}</b>",
icon: "ri-refresh-line",
description: "Update your User Interface with some data.",
environment: "CLIENT",
params: {
path: "string",
value: "longText",
},
args: {
path: "foo",
value: "started...",
},
actionId: "SET_STATE",
type: "ACTION",
},
{
id: "zJQcZUgDS",
name: "Delay",
icon: "ri-time-fill",
tagline: "Delay for <b>{{time}}</b> milliseconds",
description: "Delay the workflow until an amount of time has passed.",
environment: "CLIENT",
params: {
time: "number",
},
args: {
time: 3000,
},
actionId: "DELAY",
type: "LOGIC",
},
{
id: "3RSTO7BMB",
name: "Update UI State",
tagline: "Update <b>{{path}}</b> to <b>{{value}}</b>",
icon: "ri-refresh-line",
description: "Update your User Interface with some data.",
environment: "CLIENT",
params: {
path: "string",
value: "longText",
},
args: {
path: "foo",
value: "finished",
},
actionId: "SET_STATE",
type: "ACTION",
},
],
},
type: "workflow",
live: true,
}

View File

@ -2,14 +2,13 @@
import { onMount } from "svelte" import { onMount } from "svelte"
import { workflowStore, backendUiStore } from "builderStore" import { workflowStore, backendUiStore } from "builderStore"
import { notifier } from "@beyonk/svelte-notifications" import { notifier } from "@beyonk/svelte-notifications"
import Flowchart from "./svelte-flows/Flowchart.svelte" import Flowchart from "./flowchart/Flowchart.svelte"
import api from "builderStore/api" import api from "builderStore/api"
let selectedWorkflow let selectedWorkflow
let uiTree let uiTree
let instanceId = $backendUiStore.selectedDatabase._id let instanceId = $backendUiStore.selectedDatabase._id
// TODO: better naming
$: selectedWorkflow = $workflowStore.currentWorkflow $: selectedWorkflow = $workflowStore.currentWorkflow
$: workflowLive = selectedWorkflow && selectedWorkflow.workflow.live $: workflowLive = selectedWorkflow && selectedWorkflow.workflow.live

View File

@ -6,17 +6,17 @@ const delay = ms => new Promise(resolve => setTimeout(resolve, ms))
export default { export default {
SET_STATE: ({ context, args, id }) => { SET_STATE: ({ context, args, id }) => {
// get props from the workflow context if required
setState(...Object.values(args)) setState(...Object.values(args))
// update the context with the data
context = { context = {
...context, ...context,
[id]: args, [id]: args,
} }
}, },
NAVIGATE: ({ context, args, id }) => {}, NAVIGATE: ({ context, args, id }) => {
// TODO client navigation
},
DELAY: async ({ context, args }) => await delay(args.time), DELAY: async ({ context, args }) => await delay(args.time),
FILTER: (context, args) => { FILTER: ({ context, args }) => {
const { field, condition, value } = args const { field, condition, value } = args
switch (condition) { switch (condition) {
case "equals": case "equals":

View File

@ -43,7 +43,7 @@ export const clientStrategy = ({ api, instanceId }) => ({
// We don't want to render mustache templates on non-strings // We don't want to render mustache templates on non-strings
if (typeof argValue !== "string") continue if (typeof argValue !== "string") continue
// Means that it's bound to state or workflow context // Render the string with values from the workflow context and state
mappedArgs[arg] = mustache.render(argValue, { mappedArgs[arg] = mustache.render(argValue, {
context: this.context, context: this.context,
state: get(appStore), state: get(appStore),
@ -82,8 +82,6 @@ export const clientStrategy = ({ api, instanceId }) => ({
[block.actionId]: response, [block.actionId]: response,
} }
} }
console.log("workflowContext", this.context)
} }
}, },
}) })

View File

@ -61,7 +61,6 @@ export const createApp = ({
let rootTreeNode let rootTreeNode
const pageStateManager = createStateManager({ const pageStateManager = createStateManager({
// store: writable({ _bbuser: user }),
frontendDefinition, frontendDefinition,
componentLibraries, componentLibraries,
onScreenSlotRendered, onScreenSlotRendered,

View File

@ -11,7 +11,6 @@ export const loadBudibase = async opts => {
const frontendDefinition = _window["##BUDIBASE_FRONTEND_DEFINITION##"] const frontendDefinition = _window["##BUDIBASE_FRONTEND_DEFINITION##"]
// TODO: update
const user = {} const user = {}
const componentLibraryModules = (opts && opts.componentLibraries) || {} const componentLibraryModules = (opts && opts.componentLibraries) || {}

View File

@ -1,5 +1,4 @@
import { setState } from "./setState" import { setState } from "./setState"
// import { isBound } from "./parseBinding"
import { attachChildren } from "../render/attachChildren" import { attachChildren } from "../render/attachChildren"
import { getContext, setContext } from "./getSetContext" import { getContext, setContext } from "./getSetContext"

View File

@ -1,7 +1,6 @@
import { setState } from "./setState" import { setState } from "./setState"
import { getState } from "./getState" import { getState } from "./getState"
import { isArray, isUndefined } from "lodash/fp" import { isArray, isUndefined } from "lodash/fp"
import { appStore } from "./store"
import { createApi } from "../api" import { createApi } from "../api"

View File

@ -1,4 +1,3 @@
// import { isUndefined, isObject } from "lodash/fp"
import { get } from "svelte/store" import { get } from "svelte/store"
import getOr from "lodash/fp/getOr" import getOr from "lodash/fp/getOr"
import { appStore } from "./store" import { appStore } from "./store"

View File

@ -1,8 +1,6 @@
const recordController = require("../../record") const recordController = require("../../record")
module.exports = async function saveRecord(args) { module.exports = async function saveRecord(args) {
console.log("SAVING this record", args.record)
const ctx = { const ctx = {
params: { params: {
instanceId: "inst_60dd510_700f7dc06735403e81d5af91072d7241", instanceId: "inst_60dd510_700f7dc06735403e81d5af91072d7241",

View File

@ -19,7 +19,7 @@ emitter.on("record:save", async function(event) {
) )
for (let workflow of workflowsToTrigger) { for (let workflow of workflowsToTrigger) {
// SERVER SIDE STUFF!! // TODO: server side workflow triggers
} }
}) })
@ -30,7 +30,7 @@ emitter.on("record:delete", async function(event) {
) )
for (let workflow of workflowsToTrigger) { for (let workflow of workflowsToTrigger) {
// SERVER SIDE STUFF!! // TODO: server side workflow triggers
} }
}) })