From 7a3b36839955cf4fde2ee940a4b7ca29a067245b Mon Sep 17 00:00:00 2001 From: Martin McKeaveney Date: Thu, 28 May 2020 23:31:55 +0100 Subject: [PATCH] make alerts live, more hooks, app notifications --- packages/builder/package.json | 1 + packages/builder/rollup.config.js | 2 +- packages/builder/src/App.svelte | 5 +- packages/builder/src/budibase.css | 3 +- .../builderStore/store/workflow/Workflow.js | 8 ++- .../src/builderStore/store/workflow/index.js | 2 +- .../EventsEditor/EventsEditor.svelte | 1 + .../EventsEditor/StateBindingCascader.svelte | 34 +++++++----- .../src/pages/[application]/_layout.svelte | 14 +++-- .../SetupPanel/DeleteWorkflowModal.svelte | 2 + .../workflow/SetupPanel/SetupPanel.svelte | 5 +- .../SetupPanel/WorkflowBlockSetup.svelte | 21 +++++--- .../WorkflowBuilder/WorkflowBuilder.svelte | 50 ++++++++++++----- .../WorkflowBuilder/svelte-flows/Arrow.svelte | 9 ++++ .../svelte-flows/FlowChart.svelte | 3 +- .../svelte-flows/FlowItem.svelte | 32 +++++++++-- .../WorkflowPanel/BlockList/BlockList.svelte | 9 +++- .../WorkflowList/CreateWorkflowModal.svelte | 2 + .../WorkflowList/WorkflowList.svelte | 13 +++-- .../WorkflowPanel/WorkflowPanel.svelte | 22 -------- .../WorkflowPanel/blockDefinitions.js | 53 +++++++++++++++---- .../client/src/api/workflow/orchestrator.js | 9 +++- 22 files changed, 208 insertions(+), 92 deletions(-) create mode 100644 packages/builder/src/pages/[application]/workflow/WorkflowBuilder/svelte-flows/Arrow.svelte diff --git a/packages/builder/package.json b/packages/builder/package.json index 0926b82fb0..1cb53bc99b 100644 --- a/packages/builder/package.json +++ b/packages/builder/package.json @@ -38,6 +38,7 @@ ] }, "dependencies": { + "@beyonk/svelte-notifications": "^2.0.3", "@budibase/client": "^0.0.32", "@nx-js/compiler-util": "^2.0.0", "codemirror": "^5.51.0", diff --git a/packages/builder/rollup.config.js b/packages/builder/rollup.config.js index fd7c487c3f..ae9a7e069e 100644 --- a/packages/builder/rollup.config.js +++ b/packages/builder/rollup.config.js @@ -152,7 +152,7 @@ export default { { find: "builderStore", replacement: path.resolve(projectRootDir, "src/builderStore"), - }, + } ], customResolver, }), diff --git a/packages/builder/src/App.svelte b/packages/builder/src/App.svelte index c6b76090f9..54968a52c2 100644 --- a/packages/builder/src/App.svelte +++ b/packages/builder/src/App.svelte @@ -7,6 +7,8 @@ import AppNotification, { showAppNotification, } from "components/common/AppNotification.svelte" + import { NotificationDisplay } from '@beyonk/svelte-notifications' + function showErrorBanner() { showAppNotification({ @@ -24,8 +26,7 @@ $basepath = "/_builder" - - + diff --git a/packages/builder/src/budibase.css b/packages/builder/src/budibase.css index f433b016da..2117926dd2 100644 --- a/packages/builder/src/budibase.css +++ b/packages/builder/src/budibase.css @@ -77,7 +77,8 @@ } .budibase__input { - width: 250px; + width: 100%; + max-width: 250px; height: 35px; border-radius: 3px; border: 1px solid #DBDBDB; diff --git a/packages/builder/src/builderStore/store/workflow/Workflow.js b/packages/builder/src/builderStore/store/workflow/Workflow.js index b3e50466f7..4a7daa5279 100644 --- a/packages/builder/src/builderStore/store/workflow/Workflow.js +++ b/packages/builder/src/builderStore/store/workflow/Workflow.js @@ -1,6 +1,7 @@ import mustache from "mustache" // TODO: tidy up import import blockDefinitions from "../../../pages/[application]/workflow/WorkflowPanel/blockDefinitions" +import { generate } from "shortid" /** * Class responsible for the traversing of the workflow definition. @@ -14,7 +15,10 @@ export default class Workflow { addBlock(block) { let node = this.workflow.definition while (node.next) node = node.next - node.next = block + node.next = { + id: generate(), + ...block + } } updateBlock(updatedBlock, id) { @@ -70,7 +74,7 @@ export default class Workflow { type: block.type, params: block.params, args, - heading: block.actionId, + heading: definition.actionId, body: mustache.render(tagline, args), }) diff --git a/packages/builder/src/builderStore/store/workflow/index.js b/packages/builder/src/builderStore/store/workflow/index.js index fcdc299271..971312c2ef 100644 --- a/packages/builder/src/builderStore/store/workflow/index.js +++ b/packages/builder/src/builderStore/store/workflow/index.js @@ -82,7 +82,7 @@ const workflowActions = store => ({ }, deleteWorkflowBlock: block => { store.update(state => { - state.currentWorkflow.deleteBlock(block._id) + state.currentWorkflow.deleteBlock(block.id) state.selectedWorkflowBlock = null return state }) diff --git a/packages/builder/src/components/userInterface/EventsEditor/EventsEditor.svelte b/packages/builder/src/components/userInterface/EventsEditor/EventsEditor.svelte index 2a61912173..d897ad9c2f 100644 --- a/packages/builder/src/components/userInterface/EventsEditor/EventsEditor.svelte +++ b/packages/builder/src/components/userInterface/EventsEditor/EventsEditor.svelte @@ -32,6 +32,7 @@ $: { events = Object.keys(component) + // TODO: use real events .filter(propName => ["onChange", "onClick", "onLoad"].includes(propName)) .map(propName => ({ name: propName, diff --git a/packages/builder/src/components/userInterface/EventsEditor/StateBindingCascader.svelte b/packages/builder/src/components/userInterface/EventsEditor/StateBindingCascader.svelte index e3811cc176..09aa7ae9ad 100644 --- a/packages/builder/src/components/userInterface/EventsEditor/StateBindingCascader.svelte +++ b/packages/builder/src/components/userInterface/EventsEditor/StateBindingCascader.svelte @@ -9,7 +9,7 @@ EVENT_TYPE_MEMBER_NAME, allHandlers, } from "components/common/eventHandlers" - import { store } from "builderStore" + import { store, workflowStore } from "builderStore" import StateBindingOptions from "../PropertyCascader/StateBindingOptions.svelte" import { ArrowDownIcon } from "components/common/Icons/" @@ -22,18 +22,26 @@
{parameter.name}
- - - {#if isOpen} - { - onChange(option) - isOpen = false - }} /> + {#if parameter.name === 'workflow'} + + {:else} + + + {#if isOpen} + { + onChange(option) + isOpen = false + }} /> + {/if} {/if}
diff --git a/packages/builder/src/pages/[application]/_layout.svelte b/packages/builder/src/pages/[application]/_layout.svelte index 5de5e3379b..6e1cd982e8 100644 --- a/packages/builder/src/pages/[application]/_layout.svelte +++ b/packages/builder/src/pages/[application]/_layout.svelte @@ -56,11 +56,10 @@ on:click={() => $goto(`/settings`)}> - (location = `/${application}`)}> - + + + + @@ -84,6 +83,11 @@ flex-direction: column; } + a { + text-transform: none; + color: var(--ink-lighter); + } + .top-nav { flex: 0 0 auto; height: 60px; diff --git a/packages/builder/src/pages/[application]/workflow/SetupPanel/DeleteWorkflowModal.svelte b/packages/builder/src/pages/[application]/workflow/SetupPanel/DeleteWorkflowModal.svelte index 82af963cfc..a3eef9d9d2 100644 --- a/packages/builder/src/pages/[application]/workflow/SetupPanel/DeleteWorkflowModal.svelte +++ b/packages/builder/src/pages/[application]/workflow/SetupPanel/DeleteWorkflowModal.svelte @@ -1,5 +1,6 @@ diff --git a/packages/builder/src/pages/[application]/workflow/SetupPanel/SetupPanel.svelte b/packages/builder/src/pages/[application]/workflow/SetupPanel/SetupPanel.svelte index b23e7f4c4a..a7417002de 100644 --- a/packages/builder/src/pages/[application]/workflow/SetupPanel/SetupPanel.svelte +++ b/packages/builder/src/pages/[application]/workflow/SetupPanel/SetupPanel.svelte @@ -1,13 +1,14 @@ diff --git a/packages/builder/src/pages/[application]/workflow/SetupPanel/WorkflowBlockSetup.svelte b/packages/builder/src/pages/[application]/workflow/SetupPanel/WorkflowBlockSetup.svelte index f3d0bdaba8..e719da9fc2 100644 --- a/packages/builder/src/pages/[application]/workflow/SetupPanel/WorkflowBlockSetup.svelte +++ b/packages/builder/src/pages/[application]/workflow/SetupPanel/WorkflowBlockSetup.svelte @@ -1,5 +1,5 @@ @@ -18,7 +19,15 @@
- {#if type === 'number'} + {#if Array.isArray(type)} + + {:else if type === 'number'} {:else if type === 'component'} - + {:else if type === 'string'} import { onMount } from "svelte" import { workflowStore, backendUiStore } from "builderStore" + import { notifier } from "@beyonk/svelte-notifications" import Flowchart from "./svelte-flows/Flowchart.svelte" import api from "builderStore/api" - let canvas - let workflow + let selectedWorkflow let uiTree let instanceId = $backendUiStore.selectedDatabase._id - $: workflow = $workflowStore.currentWorkflow + // TODO: better naming + $: selectedWorkflow = $workflowStore.currentWorkflow - $: if (workflow) uiTree = workflow ? workflow.createUiTree() : [] + $: workflowLive = selectedWorkflow && selectedWorkflow.workflow.live + + $: if (selectedWorkflow) + uiTree = selectedWorkflow ? selectedWorkflow.createUiTree() : [] + + $: instanceId = $backendUiStore.selectedDatabase._id function onDelete(block) { // TODO finish @@ -24,17 +30,37 @@ return state }) } + + function setWorkflowLive(live) { + const { workflow } = selectedWorkflow + workflow.live = live + workflowStore.actions.save({ instanceId, workflow }) + if (live) { + notifier.info(`Workflow ${workflow.name} enabled.`) + } else { + notifier.danger(`Workflow ${workflow.name} disabled.`) + } + }
- - + {#if selectedWorkflow} + + + {/if}
@@ -61,11 +87,11 @@ margin-right: 24px; } - .play-button:hover { + .play-button.highlighted { background: var(--primary); } - .stop-button:hover { + .stop-button.highlighted { background: var(--coral); } diff --git a/packages/builder/src/pages/[application]/workflow/WorkflowBuilder/svelte-flows/Arrow.svelte b/packages/builder/src/pages/[application]/workflow/WorkflowBuilder/svelte-flows/Arrow.svelte new file mode 100644 index 0000000000..f5b855c341 --- /dev/null +++ b/packages/builder/src/pages/[application]/workflow/WorkflowBuilder/svelte-flows/Arrow.svelte @@ -0,0 +1,9 @@ + + + + diff --git a/packages/builder/src/pages/[application]/workflow/WorkflowBuilder/svelte-flows/FlowChart.svelte b/packages/builder/src/pages/[application]/workflow/WorkflowBuilder/svelte-flows/FlowChart.svelte index dc35b682c4..40c54246e9 100644 --- a/packages/builder/src/pages/[application]/workflow/WorkflowBuilder/svelte-flows/FlowChart.svelte +++ b/packages/builder/src/pages/[application]/workflow/WorkflowBuilder/svelte-flows/FlowChart.svelte @@ -1,5 +1,6 @@ -
-
{block.heading}
+
+
+ {#if block.type === 'TRIGGER'} + + When this happens... + {:else if block.type === 'ACTION'} + + Do this... + {:else if block.type === 'LOGIC'} + + Only continue if... + {/if} +

{@html block.body} @@ -21,7 +32,6 @@ div { width: 320px; padding: 20px; - margin-bottom: 60px; border-radius: 5px; transition: 0.3s all; box-shadow: 0 4px 30px 0 rgba(57, 60, 68, 0.08); @@ -30,6 +40,18 @@ color: var(--white); } + header { + font-size: 16px; + font-weight: 500; + display: flex; + align-items: center; + } + + header i { + font-size: 20px; + margin-right: 5px; + } + .ACTION { background-color: var(--white); color: var(--font); diff --git a/packages/builder/src/pages/[application]/workflow/WorkflowPanel/BlockList/BlockList.svelte b/packages/builder/src/pages/[application]/workflow/WorkflowPanel/BlockList/BlockList.svelte index 8db781af36..e288e78e94 100644 --- a/packages/builder/src/pages/[application]/workflow/WorkflowPanel/BlockList/BlockList.svelte +++ b/packages/builder/src/pages/[application]/workflow/WorkflowPanel/BlockList/BlockList.svelte @@ -52,16 +52,21 @@ grid-gap: 5px; grid-auto-flow: column; grid-auto-columns: 1fr 1fr 1fr; + margin-bottom: 10px; } .subtabs span { + transition: 0.3s all; text-align: center; - color: var(--font); + color: var(--dark-grey); font-weight: 500; + padding: 10px; } .subtabs span.selected { - border-bottom: 4px solid var(--primary); + background: var(--dark-grey); + color: var(--white); + border-radius: 2px; } .subtabs span:not(.selected) { diff --git a/packages/builder/src/pages/[application]/workflow/WorkflowPanel/WorkflowList/CreateWorkflowModal.svelte b/packages/builder/src/pages/[application]/workflow/WorkflowPanel/WorkflowList/CreateWorkflowModal.svelte index 510b21704e..0a31cf3702 100644 --- a/packages/builder/src/pages/[application]/workflow/WorkflowPanel/WorkflowList/CreateWorkflowModal.svelte +++ b/packages/builder/src/pages/[application]/workflow/WorkflowPanel/WorkflowList/CreateWorkflowModal.svelte @@ -1,5 +1,6 @@ diff --git a/packages/builder/src/pages/[application]/workflow/WorkflowPanel/WorkflowList/WorkflowList.svelte b/packages/builder/src/pages/[application]/workflow/WorkflowPanel/WorkflowList/WorkflowList.svelte index 2218414ad9..2dc3e05a80 100644 --- a/packages/builder/src/pages/[application]/workflow/WorkflowPanel/WorkflowList/WorkflowList.svelte +++ b/packages/builder/src/pages/[application]/workflow/WorkflowPanel/WorkflowList/WorkflowList.svelte @@ -1,5 +1,6 @@ @@ -82,7 +85,7 @@ display: flex; align-items: center; border-radius: 3px; - height: 40px; + height: 32px; font-weight: 500; } diff --git a/packages/builder/src/pages/[application]/workflow/WorkflowPanel/WorkflowPanel.svelte b/packages/builder/src/pages/[application]/workflow/WorkflowPanel/WorkflowPanel.svelte index c9625d2441..016c789c64 100644 --- a/packages/builder/src/pages/[application]/workflow/WorkflowPanel/WorkflowPanel.svelte +++ b/packages/builder/src/pages/[application]/workflow/WorkflowPanel/WorkflowPanel.svelte @@ -5,17 +5,6 @@ import api from "builderStore/api" import blockDefinitions from "./blockDefinitions" - const WORKFLOW_TABS = [ - { - name: "Workflows", - key: "WORKFLOWS", - }, - { - name: "Add", - key: "ADD", - }, - ] - let selectedTab = "WORKFLOWS" let definitions = [] @@ -57,15 +46,4 @@ span:not(.selected) { color: var(--dark-grey); } - - .delete-workflow-button { - font-family: Roboto; - width: 100%; - border: solid 1px #f2f2f2; - border-radius: 2px; - background: var(--white); - height: 32px; - font-size: 12px; - font-weight: 500; - } diff --git a/packages/builder/src/pages/[application]/workflow/WorkflowPanel/blockDefinitions.js b/packages/builder/src/pages/[application]/workflow/WorkflowPanel/blockDefinitions.js index c0d81b95e8..1d6402ebe5 100644 --- a/packages/builder/src/pages/[application]/workflow/WorkflowPanel/blockDefinitions.js +++ b/packages/builder/src/pages/[application]/workflow/WorkflowPanel/blockDefinitions.js @@ -2,7 +2,7 @@ const ACTION = { SET_STATE: { name: "Update UI State", tagline: "Update {{path}} to {{value}}", - icon: "", + icon: "ri-refresh-line", description: "Update your User Interface with some data.", environment: "CLIENT", params: { @@ -38,8 +38,8 @@ const ACTION = { }, }, FIND_RECORD: { - description: "Delete a record from your database.", - icon: "ri-delete-bin-line", + description: "Find a record in your database.", + icon: "ri-search-line", name: "Find Record", environment: "SERVER", params: { @@ -59,7 +59,7 @@ const ACTION = { }, SEND_EMAIL: { description: "Send an email.", - tagline: "Send email to {{to}}", + tagline: "Send email to {{to}}", icon: "ri-mail-open-fill", name: "Send Email", environment: "SERVER", @@ -73,9 +73,9 @@ const ACTION = { } const TRIGGER = { - SAVE_RECORD: { + RECORD_SAVED: { name: "Record Saved", - icon: "ri-delete-bin-line", + icon: "ri-save-line", tagline: "Record is added to {{model}}", description: "Save a record to your database.", environment: "SERVER", @@ -83,39 +83,72 @@ const TRIGGER = { model: "model", }, }, + RECORD_DELETED: { + name: "Record Deleted", + icon: "ri-delete-bin-line", + tagline: "Record is deleted from {{model}}", + description: "Fired when a record is deleted from your database.", + environment: "SERVER", + params: { + model: "model" + }, + }, CLICK: { name: "Click", icon: "ri-cursor-line", + tagline: "{{component}} is clicked", description: "Trigger when you click on an element in the UI.", + environment: "CLIENT", + params: { + component: "component" + } }, LOAD: { name: "Load", icon: "ri-loader-line", + tagline: "{{component}} is loaded", description: "Trigger an element has finished loading.", + environment: "CLIENT", + params: { + component: "component" + } }, INPUT: { name: "Input", icon: "ri-text", - description: "Trigger when you environment into an input box.", + tagline: "Text entered into {{component}", + description: "Trigger when you type into an input box.", + environment: "CLIENT", + params: { + component: "component" + } }, } const LOGIC = { FILTER: { name: "Filter", - tagline: "{{key}} {{condition}} {{value}}", + tagline: "{{field}} {{condition}} {{value}}", icon: "ri-git-branch-line", description: "Filter any workflows which do not meet certain conditions.", environment: "CLIENT", params: { - if: "string", + field: "string", + condition: [ + "equals" + ], + value: "string" }, }, DELAY: { name: "Delay", - icon: "ri-git-branch-line", + icon: "ri-time-fill", + tagline: "Delay for {{time}} milliseconds", description: "Delay the workflow until an amount of time has passed.", environment: "CLIENT", + params: { + time: "number", + }, }, } diff --git a/packages/client/src/api/workflow/orchestrator.js b/packages/client/src/api/workflow/orchestrator.js index afa92d007e..532219eebc 100644 --- a/packages/client/src/api/workflow/orchestrator.js +++ b/packages/client/src/api/workflow/orchestrator.js @@ -1,7 +1,7 @@ import get from "lodash/fp/get" /** - * The workflow orhestrator is a class responsible for executing workflows. + * The workflow orchestrator is a class responsible for executing workflows. * It relies on the strategy pattern, which allows composable behaviour to be * passed into its execute() function. This allows custom execution behaviour based * on where the orchestrator is run. @@ -30,6 +30,7 @@ export default class Orchestrator { // Execute a workflow from a running budibase app export const clientStrategy = { + delay: ms => new Promise(resolve => setTimeout(resolve, ms)), context: {}, bindContextArgs: function(args, api) { const mappedArgs = { ...args } @@ -80,6 +81,10 @@ export const clientStrategy = { SET_STATE: block.args, } } + + if (block.actionId === "DELAY") { + await this.delay(block.args.time) + } } // this workflow block gets executed on the server @@ -102,6 +107,6 @@ export const clientStrategy = { console.log("workflowContext", this.context) // TODO: clean this up, don't pass all those args - this.run({ workflow: workflow.next, instanceId, api }) + await this.run({ workflow: workflow.next, instanceId, api }) }, }