Skip to content

Commit

Permalink
feat(editor, core, cli): implement new workflow experience (#4358)
Browse files Browse the repository at this point in the history
* feat(ExecuteWorkflowTrigger node): Implement ExecuteWorkflowTrigger node (#4108)

* feat(ExecuteWorkflowTrigger node): Implement ExecuteWorkflowTrigger node

* feat(editor): Do not show duplicate button if canvas contains `maxNodes` amount of nodes

* feat(ManualTrigger node): Implement ManualTrigger node (#4110)

* feat(ManualTrigger node): Implement ManualTrigger node

* 📝 Remove generics doc items from ManualTrigger node

* feat(editor-ui): Trigger tab redesign (#4150)

* 🚧 Begin with TriggerPanel implementation, add Other Trigger Nodes subcategory

* 🚧 Extracted categorized categories/subcategory/nodes rendering into its own component — CategorizedItems, removed SubcategoryPanel, added translations

* ✨ Implement MainPanel background scrim

* ♻️ Move `categoriesWithNodes`, 'visibleNodeTypes` and 'categorizedItems` to store, implemented dynamic categories count based on `selectedType`

* 🐛 Fix SlideTransition for all the NodeCreato panels

* 💄 Fix cursos for CategoryItem and NodeItem

* 🐛 Make sure ALL_NODE_FILTER is always set when MainPanel is mounted

* 🎨 Address PR comments

* label: Use Array type for CategorizedItems props

* 🏷️ Add proper types for Vue props

* 🎨 Use standard component registration for CategorizedItems inside TriggerHelperPanel

* 🎨 Use kebab case for main-panel and icon component

* 🏷️ Improve types

* feat(editor-ui): Redesign search input inside node creator panel (#4204)

* 🚧 Begin with TriggerPanel implementation, add Other Trigger Nodes subcategory

* 🚧 Extracted categorized categories/subcategory/nodes rendering into its own component — CategorizedItems, removed SubcategoryPanel, added translations

* ✨ Implement MainPanel background scrim

* ♻️ Move `categoriesWithNodes`, 'visibleNodeTypes` and 'categorizedItems` to store, implemented dynamic categories count based on `selectedType`

* 🐛 Fix SlideTransition for all the NodeCreato panels

* 💄 Fix cursos for CategoryItem and NodeItem

* 🐛 Make sure ALL_NODE_FILTER is always set when MainPanel is mounted

* 🎨 Address PR comments

* label: Use Array type for CategorizedItems props

* 🏷️ Add proper types for Vue props

* 🎨 Use standard component registration for CategorizedItems inside TriggerHelperPanel

* ✨ Redesign search input and unify usage of categorized items

* 🏷️ Use lowercase "Boolean" as `isSearchVisible` computed return type

* 🔥 Remove useless emit

* ✨ Implement no result view based on subcategory, minor fixes

* 🎨 Remove unused properties

* feat(node-email): Change EmailReadImap display name and name (#4239)

* feat(editor-ui):  Implement "Choose a Triger" action and related behaviour (#4226)

* ✨ Implement "Choose a Triger" action and related behaviour

* 🔇 Lint fix

* ♻️ Remove PlaceholderTrigger node, add a button instead

* 🎨 Merge onMouseEnter and onMouseLeave to a single function

* 💡 Add comment

* 🔥 Remove PlaceholderNode registration

* 🎨 Rename TriggerPlaceholderButton to CanvasAddButton

* ✨ Add method to unregister custom action and rework CanvasAddButton centering logic

* 🎨 Run `setRecenteredCanvasAddButtonPosition` on `CanvasAddButton` mount

* fix(editor): Fix selecting of node from node-creator panel by clicking

* 🔀 Merge fixes

* fix(editor): Show execute workflow trigger instead of workflow trigger in the trigger helper panel

* feat(editor): Fix node creator panel slide transition (#4261)

* fix(editor): Fix node creator panel slide-in/slide-out transitions

* 🎨 Fix naming

* 🎨 Use kebab-case for transition component name

* feat(editor): Disable execution and show notice when user tries to run workflow without enabled triggers

* fix(editor): Address first batch of new WF experience review (#4279)

* fix(editor): Fix first batch of review items

* bug(editor): Fix nodeview canvas add button centering

* 🔇 Fix linter errors

* bug(ManualTrigger Node): Fix manual trigger node execution

* fix(editor): Do not show canvas add button in execution or demo mode and prevent clicking if creator is open

* fix(editor): do not show pin data tooltip for manual trigger node

* fix(editor): do not use nodeViewOffset on zoomToFit

* 💄 Add margin for last node creator item and set font-weight to 700 for category title

* ✨ Position welcome note next to the added trigger node

* 🐛 Remve always true welcome note

* feat(editor): Minor UI and UX tweaks (#4328)

* 💄 Make top viewport buttons less prominent

* ✨ Allow user to switch to all tabs if it contains filter results, move nodecreator state props to its own module

* 🔇 Fix linting errors

* 🔇 Fix linting errors

* 🔇 Fix linting errors

* chore(build): Ping Turbo version to 1.5.5

* 💄 Minor traigger panel and node view style changes

* 💬 Update display name of execute workflow trigger

* feat(core, editor): Update subworkflow execution logic (#4269)

* ✨ Implement `findWorkflowStart`

* ⚡ Extend `WorkflowOperationError`

* ⚡ Add `WorkflowOperationError` to toast

* 📘 Extend interface

* ✨ Add `subworkflowExecutionError` to store

* ✨ Create `SubworkflowOperationError`

* ⚡ Render subworkflow error as node error

* 🚚 Move subworkflow start validation to `cli`

* ⚡ Reset subworkflow execution error state

* 🔥 Remove unused import

* ⚡ Adjust CLI commands

* 🔥 Remove unneeded check

* 🔥 Remove stray log

* ⚡ Simplify syntax

* ⚡ Sort in case both Start and EWT present

* ♻️ Address Omar's feedback

* 🔥 Remove unneeded lint exception

* ✏️ Fix copy

* 👕 Fix lint

* fix: moved find start node function to catchable place

Co-authored-by: Omar Ajoue <krynble@gmail.com>

* 💄 Change ExecuteWorkflow node to primary

* ✨ Allow user to navigate to all tab if it contains search results

* 🐛 Fixed canvas control button while in demo, disable workflow activation for non-activavle nodes and revert zoomToFit bottom offset

* :fix: Do not chow request text if there's results

* 💬 Update noResults text

Co-authored-by: Iván Ovejero <ivov.src@gmail.com>
Co-authored-by: Omar Ajoue <krynble@gmail.com>
  • Loading branch information
3 people committed Oct 18, 2022
1 parent 128c3b8 commit dae01f3
Show file tree
Hide file tree
Showing 65 changed files with 2,194 additions and 968 deletions.
12 changes: 6 additions & 6 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

35 changes: 11 additions & 24 deletions packages/cli/commands/execute.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
import { promises as fs } from 'fs';
import { Command, flags } from '@oclif/command';
import { BinaryDataManager, UserSettings, PLACEHOLDER_EMPTY_WORKFLOW_ID } from 'n8n-core';
import { INode, LoggerProxy } from 'n8n-workflow';
import { LoggerProxy } from 'n8n-workflow';

import {
ActiveExecutions,
Expand All @@ -25,6 +25,7 @@ import {
import { getLogger } from '../src/Logger';
import config from '../config';
import { getInstanceOwner } from '../src/UserManagement/UserManagementHelper';
import { findCliWorkflowStart } from '../src/utils';

export class Execute extends Command {
static description = '\nExecutes a given workflow';
Expand Down Expand Up @@ -116,6 +117,10 @@ export class Execute extends Command {
}
}

if (!workflowData) {
throw new Error('Failed to retrieve workflow data for requested workflow');
}

// Make sure the settings exist
await UserSettings.prepareUserSettings();

Expand Down Expand Up @@ -144,33 +149,14 @@ export class Execute extends Command {
workflowId = undefined;
}

// Check if the workflow contains the required "Start" node
// "requiredNodeTypes" are also defined in editor-ui/views/NodeView.vue
const requiredNodeTypes = ['n8n-nodes-base.start'];
let startNode: INode | undefined;
// eslint-disable-next-line no-restricted-syntax, @typescript-eslint/no-non-null-assertion
for (const node of workflowData!.nodes) {
if (requiredNodeTypes.includes(node.type)) {
startNode = node;
break;
}
}

if (startNode === undefined) {
// If the workflow does not contain a start-node we can not know what
// should be executed and with which data to start.
console.info(`The workflow does not contain a "Start" node. So it can not be executed.`);
// eslint-disable-next-line consistent-return
return Promise.resolve();
}

try {
const startingNode = findCliWorkflowStart(workflowData.nodes);

const user = await getInstanceOwner();
const runData: IWorkflowExecutionDataProcess = {
executionMode: 'cli',
startNodes: [startNode.name],
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
workflowData: workflowData!,
startNodes: [startingNode.name],
workflowData,
userId: user.id,
};

Expand Down Expand Up @@ -207,6 +193,7 @@ export class Execute extends Command {
logger.error('\nExecution error:');
logger.info('====================================');
logger.error(e.message);
if (e.description) logger.error(e.description);
logger.error(e.stack);
this.exit(1);
}
Expand Down
23 changes: 4 additions & 19 deletions packages/cli/commands/executeBatch.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ import {
import config from '../config';
import { User } from '../src/databases/entities/User';
import { getInstanceOwner } from '../src/UserManagement/UserManagementHelper';
import { findCliWorkflowStart } from '../src/utils';

export class ExecuteBatch extends Command {
static description = '\nExecutes multiple workflows once';
Expand Down Expand Up @@ -613,16 +614,6 @@ export class ExecuteBatch extends Command {
coveredNodes: {},
};

const requiredNodeTypes = ['n8n-nodes-base.start'];
let startNode: INode | undefined;
// eslint-disable-next-line no-restricted-syntax
for (const node of workflowData.nodes) {
if (requiredNodeTypes.includes(node.type)) {
startNode = node;
break;
}
}

// We have a cool feature here.
// On each node, on the Settings tab in the node editor you can change
// the `Notes` field to add special cases for comparison and snapshots.
Expand Down Expand Up @@ -659,14 +650,6 @@ export class ExecuteBatch extends Command {
});

return new Promise(async (resolve) => {
if (startNode === undefined) {
// If the workflow does not contain a start-node we can not know what
// should be executed and with which data to start.
executionResult.error = 'Workflow cannot be started as it does not contain a "Start" node.';
executionResult.executionStatus = 'warning';
resolve(executionResult);
}

let gotCancel = false;

// Timeouts execution after 5 minutes.
Expand All @@ -678,9 +661,11 @@ export class ExecuteBatch extends Command {
}, ExecuteBatch.executionTimeout);

try {
const startingNode = findCliWorkflowStart(workflowData.nodes);

const runData: IWorkflowExecutionDataProcess = {
executionMode: 'cli',
startNodes: [startNode!.name],
startNodes: [startingNode.name],
workflowData,
userId: ExecuteBatch.instanceOwner.id,
};
Expand Down
20 changes: 4 additions & 16 deletions packages/cli/src/WorkflowExecuteAdditionalData.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ import {
IWorkflowHooksOptionalParameters,
IWorkflowSettings,
LoggerProxy as Logger,
SubworkflowOperationError,
Workflow,
WorkflowExecuteMode,
WorkflowHooks,
Expand Down Expand Up @@ -67,6 +68,7 @@ import {
} from './UserManagement/UserManagementHelper';
import { whereClause } from './WorkflowHelpers';
import { IWorkflowErrorData } from './Interfaces';
import { findSubworkflowStart } from './utils';

const ERROR_TRIGGER_TYPE = config.getEnv('nodes.errorTriggerType');

Expand Down Expand Up @@ -748,21 +750,7 @@ export async function getRunData(
): Promise<IWorkflowExecutionDataProcess> {
const mode = 'integrated';

// Find Start-Node
const requiredNodeTypes = ['n8n-nodes-base.start'];
let startNode: INode | undefined;
// eslint-disable-next-line no-restricted-syntax
for (const node of workflowData.nodes) {
if (requiredNodeTypes.includes(node.type)) {
startNode = node;
break;
}
}
if (startNode === undefined) {
// If the workflow does not contain a start-node we can not know what
// should be executed and with what data to start.
throw new Error(`The workflow does not contain a "Start" node and can so not be executed.`);
}
const startingNode = findSubworkflowStart(workflowData.nodes);

// Always start with empty data if no inputData got supplied
inputData = inputData || [
Expand All @@ -774,7 +762,7 @@ export async function getRunData(
// Initialize the incoming data
const nodeExecutionStack: IExecuteData[] = [];
nodeExecutionStack.push({
node: startNode,
node: startingNode,
data: {
main: [inputData],
},
Expand Down
9 changes: 5 additions & 4 deletions packages/cli/src/WorkflowRunnerProcess.ts
Original file line number Diff line number Diff line change
Expand Up @@ -361,11 +361,12 @@ export class WorkflowRunnerProcess {
) {
// Execute all nodes

const pinDataKeys = this.data?.pinData ? Object.keys(this.data.pinData) : [];
const noPinData = pinDataKeys.length === 0;
const isPinned = (nodeName: string) => pinDataKeys.includes(nodeName);

let startNode;
if (
this.data.startNodes?.length === 1 &&
Object.keys(this.data.pinData ?? {}).includes(this.data.startNodes[0])
) {
if (this.data.startNodes?.length === 1 && (noPinData || isPinned(this.data.startNodes[0]))) {
startNode = this.workflow.getNode(this.data.startNodes[0]) ?? undefined;
}

Expand Down
30 changes: 30 additions & 0 deletions packages/cli/src/utils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import { CliWorkflowOperationError, SubworkflowOperationError } from 'n8n-workflow';
import type { INode } from 'n8n-workflow';

function findWorkflowStart(executionMode: 'integrated' | 'cli') {
return function (nodes: INode[]) {
const executeWorkflowTriggerNode = nodes.find(
(node) => node.type === 'n8n-nodes-base.executeWorkflowTrigger',
);

if (executeWorkflowTriggerNode) return executeWorkflowTriggerNode;

const startNode = nodes.find((node) => node.type === 'n8n-nodes-base.start');

if (startNode) return startNode;

const title = 'Missing node to start execution';
const description =
"Please make sure the workflow you're calling contains an Execute Workflow Trigger node";

if (executionMode === 'integrated') {
throw new SubworkflowOperationError(title, description);
}

throw new CliWorkflowOperationError(title, description);
};
}

export const findSubworkflowStart = findWorkflowStart('integrated');

export const findCliWorkflowStart = findWorkflowStart('cli');
9 changes: 9 additions & 0 deletions packages/editor-ui/public/static/webhook-icon.svg
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
22 changes: 22 additions & 0 deletions packages/editor-ui/src/Interface.ts
Original file line number Diff line number Diff line change
Expand Up @@ -269,6 +269,11 @@ export interface IWorkflowTemplate {
};
}

export interface INewWorkflowData {
name: string;
onboardingFlowEnabled: boolean;
}

// Almost identical to cli.Interfaces.ts
export interface IWorkflowDb {
id: string;
Expand Down Expand Up @@ -756,6 +761,13 @@ export type WorkflowTitleStatus = 'EXECUTING' | 'IDLE' | 'ERROR';
export interface ISubcategoryItemProps {
subcategory: string;
description: string;
icon?: string;
defaults?: INodeParameters;
iconData?: {
type: string;
icon?: string;
fileBuffer?: string;
};
}

export interface INodeItemProps {
Expand Down Expand Up @@ -876,6 +888,7 @@ export interface IRootState {
instanceId: string;
nodeMetadata: {[nodeName: string]: INodeMetadata};
isNpmAvailable: boolean;
subworkflowExecutionError: Error | null;
}

export interface ICommunityPackageMap {
Expand Down Expand Up @@ -981,6 +994,15 @@ export type IFakeDoor = {

export type IFakeDoorLocation = 'settings' | 'credentialsModal';

export type INodeFilterType = "Regular" | "Trigger" | "All";

export interface INodeCreatorState {
itemsFilter: string;
showTabs: boolean;
showScrim: boolean;
selectedType: INodeFilterType;
}

export interface ISettingsState {
settings: IN8nUISettings;
promptsData: IN8nPrompts;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ export default mixins(
<style lang="scss">
.main-header {
background-color: var(--color-background-xlight);
height: 65px;
height: $header-height;
width: 100%;
box-sizing: border-box;
border-bottom: var(--border-width-base) var(--border-style-base) var(--color-foreground-base);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,7 @@
<WorkflowActivator :workflow-active="isWorkflowActive" :workflow-id="currentWorkflowId"/>
</span>
<SaveButton
type="secondary"
:saved="!this.isDirty && !this.isNewWorkflow"
:disabled="isWorkflowSaving"
@click="onSaveButtonClick"
Expand Down

0 comments on commit dae01f3

Please sign in to comment.