Improve Waiting Webhook call state in WF Canvas (#2430)

* N8N-2586 Improve Waiting Webhook call state in WF Canvas

* N8N-2586 Added watcher for showing Webhook's Node Tooltip on execution

* N8N-2586 Show helping tooltip for trigger node if wokrflow is running, it is a trigger node, if it is only one trigger node in WF

* N8N-2586 Rework/Move logic to computed property, Created getter for ActveTriggerNodesInWokrflow, Add style to trigger node's tooltip, remove comments

* N8N-2586 Added EventTriggerDescription prop in INodeTypeDescription Interface, Updated Logic for TriggerNode Tooltip based on the new prop

* N8N-2586 Add new use cases/watcher to show Trigger Nodes Tooltip / If has issues, if paused, if wokrlfow is running, Refactor Getter

* N8N-2586 Added z-index to tooltip, Added new Scenario for Tooltip if it is Draged&Droped on the WF

* N8N-2586 Refactor computed property for draged nodes

* N8N-2586 Fixed Conflicts

* N8N-2586 Fixed Tooltip

* N8N-2586 Dont show tooltip on core trigger nodes that execute automatically

* N8N-2586 Fixed Webhook tooltip when adding/deleting canvas during WF execution

* N8N-2586 Updated Logic, Simplify the code

* N8N-2586 Simplify Code

* N8N-2586 Added check for nodetype

* update dragging to use local state

* N8N-2586 Added eventTriggerDescription to Interval Node

* add comment, use new getter

* update to always check

Co-authored-by: Mutasem <mutdmour@gmail.com>
This commit is contained in:
Oliver Trajceski 2021-11-25 23:33:41 +01:00 committed by GitHub
parent a58c251a28
commit adc2515dee
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
14 changed files with 96 additions and 7 deletions

View file

@ -26,6 +26,13 @@
<font-awesome-icon icon="sync-alt" spin /> <font-awesome-icon icon="sync-alt" spin />
</div> </div>
<div class="node-trigger-tooltip__wrapper">
<n8n-tooltip placement="top" :manual="true" :value="showTriggerNodeTooltip" popper-class="node-trigger-tooltip__wrapper--item">
<div slot="content" v-text="getTriggerNodeTooltip"></div>
<span />
</n8n-tooltip>
</div>
<NodeIcon class="node-icon" :nodeType="nodeType" :size="40" :shrink="false" :disabled="this.data.disabled"/> <NodeIcon class="node-icon" :nodeType="nodeType" :size="40" :shrink="false" :disabled="this.data.disabled"/>
</div> </div>
@ -106,11 +113,41 @@ export default mixins(externalHooks, nodeBase, nodeHelpers, workflowHelpers).ext
return workflowResultDataNode.length; return workflowResultDataNode.length;
}, },
canvasOffsetPosition() {
return this.$store.getters.getNodeViewOffsetPosition;
},
getTriggerNodeTooltip (): string | undefined {
if (this.nodeType !== null && this.nodeType.hasOwnProperty('eventTriggerDescription')) {
return this.nodeType.eventTriggerDescription;
} else {
return `Waiting for you to create an event in ${this.nodeType && this.nodeType.displayName.replace(/Trigger/, "")}`;
}
},
isExecuting (): boolean { isExecuting (): boolean {
return this.$store.getters.executingNode === this.data.name; return this.$store.getters.executingNode === this.data.name;
}, },
isSingleActiveTriggerNode (): boolean {
const nodes = this.$store.getters.workflowTriggerNodes.filter((node: INodeUi) => {
const nodeType = this.$store.getters.nodeType(node.type) as INodeTypeDescription | null;
return nodeType && nodeType.eventTriggerDescription !== '' && !node.disabled;
});
return nodes.length === 1;
},
isTriggerNode (): boolean {
return !!(this.nodeType && this.nodeType.group.includes('trigger'));
},
isTriggerNodeTooltipEmpty () : boolean {
return this.nodeType !== null ? this.nodeType.eventTriggerDescription === '' : false;
},
isNodeDisabled (): boolean | undefined {
return this.node && this.node.disabled;
},
nodeType (): INodeTypeDescription | null { nodeType (): INodeTypeDescription | null {
return this.$store.getters.nodeType(this.data.type); return this.data && this.$store.getters.nodeType(this.data.type);
},
node (): INodeUi | undefined { // same as this.data but reactive..
return this.$store.getters.nodesByName[this.name] as INodeUi | undefined;
}, },
nodeClass (): object { nodeClass (): object {
return { return {
@ -136,9 +173,7 @@ export default mixins(externalHooks, nodeBase, nodeHelpers, workflowHelpers).ext
} }
}, },
position (): XYPosition { position (): XYPosition {
const node = this.$store.getters.nodesByName[this.name] as INodeUi; // position responsive to store changes return this.node ? this.node.position : [0, 0];
return node.position;
}, },
showDisabledLinethrough(): boolean { showDisabledLinethrough(): boolean {
return !!(this.data.disabled && this.nodeType && this.nodeType.inputs.length === 1 && this.nodeType.outputs.length === 1); return !!(this.data.disabled && this.nodeType && this.nodeType.inputs.length === 1 && this.nodeType.outputs.length === 1);
@ -207,13 +242,33 @@ export default mixins(externalHooks, nodeBase, nodeHelpers, workflowHelpers).ext
shiftOutputCount (): boolean { shiftOutputCount (): boolean {
return !!(this.nodeType && this.nodeType.outputs.length > 2); return !!(this.nodeType && this.nodeType.outputs.length > 2);
}, },
}, shouldShowTriggerTooltip () : boolean {
return !!this.node && this.workflowRunning && this.isTriggerNode && this.isSingleActiveTriggerNode && !this.isTriggerNodeTooltipEmpty && !this.isNodeDisabled && !this.hasIssues && !this.dragging;
},
},
watch: { watch: {
isActive(newValue, oldValue) { isActive(newValue, oldValue) {
if (!newValue && oldValue) { if (!newValue && oldValue) {
this.setSubtitle(); this.setSubtitle();
} }
}, },
canvasOffsetPosition() {
if (this.showTriggerNodeTooltip) {
this.showTriggerNodeTooltip = false;
setTimeout(() => {
this.showTriggerNodeTooltip = this.shouldShowTriggerTooltip;
}, 200);
}
},
shouldShowTriggerTooltip(shouldShowTriggerTooltip) {
if (shouldShowTriggerTooltip) {
setTimeout(() => {
this.showTriggerNodeTooltip = this.shouldShowTriggerTooltip;
}, 2500);
} else {
this.showTriggerNodeTooltip = false;
}
},
nodeRunData(newValue) { nodeRunData(newValue) {
this.$emit('run', {name: this.data.name, data: newValue, waiting: !!this.waiting}); this.$emit('run', {name: this.data.name, data: newValue, waiting: !!this.waiting});
}, },
@ -228,6 +283,8 @@ export default mixins(externalHooks, nodeBase, nodeHelpers, workflowHelpers).ext
return { return {
isTouchActive: false, isTouchActive: false,
nodeSubtitle: '', nodeSubtitle: '',
showTriggerNodeTooltip: false,
dragging: false,
}; };
}, },
methods: { methods: {
@ -459,7 +516,6 @@ export default mixins(externalHooks, nodeBase, nodeHelpers, workflowHelpers).ext
border-color: var(--color-success-light); border-color: var(--color-success-light);
} }
} }
</style> </style>
<style lang="scss"> <style lang="scss">
@ -468,6 +524,20 @@ export default mixins(externalHooks, nodeBase, nodeHelpers, workflowHelpers).ext
z-index: 2; z-index: 2;
} }
.node-trigger-tooltip {
&__wrapper {
top: -22px;
left: 50px;
position: relative;
&--item {
max-width: 160px;
position: fixed;
z-index: 0!important;
}
}
}
/** connector */ /** connector */
.jtk-connector { .jtk-connector {
z-index: 3; z-index: 3;

View file

@ -170,6 +170,8 @@ export const nodeBase = mixins(
// Do not allow to move nodes in readOnly mode // Do not allow to move nodes in readOnly mode
return false; return false;
} }
// @ts-ignore
this.dragging = true;
if (params.e && !this.$store.getters.isNodeSelected(this.data.name)) { if (params.e && !this.$store.getters.isNodeSelected(this.data.name)) {
// Only the node which gets dragged directly gets an event, for all others it is // Only the node which gets dragged directly gets an event, for all others it is
@ -183,6 +185,8 @@ export const nodeBase = mixins(
return true; return true;
}, },
stop: (params: { e: MouseEvent }) => { stop: (params: { e: MouseEvent }) => {
// @ts-ignore
this.dragging = false;
if (this.$store.getters.isActionActive('dragActive')) { if (this.$store.getters.isActionActive('dragActive')) {
const moveNodes = this.$store.getters.getSelectedNodes.slice(); const moveNodes = this.$store.getters.getSelectedNodes.slice();
const selectedNodeNames = moveNodes.map((node: INodeUi) => node.name); const selectedNodeNames = moveNodes.map((node: INodeUi) => node.name);

View file

@ -738,6 +738,11 @@ export const store = new Vuex.Store({
return state.activeWorkflows; return state.activeWorkflows;
}, },
workflowTriggerNodes: (state, getters) => {
return state.workflow.nodes.filter(node => {
return getters.nodeType(node.type).group.includes('trigger');
});
},
// Node-Index // Node-Index
getNodeIndex: (state) => (nodeName: string): number => { getNodeIndex: (state) => (nodeName: string): number => {
return state.nodeIndex.indexOf(nodeName); return state.nodeIndex.indexOf(nodeName);

View file

@ -261,7 +261,7 @@ export default mixins(
} }
if (this.executionWaitingForWebhook === true) { if (this.executionWaitingForWebhook === true) {
return 'Waiting for Webhook-Call'; return 'Waiting for Trigger Event';
} }
return 'Executing Workflow'; return 'Executing Workflow';

View file

@ -25,6 +25,7 @@ export class Cron implements INodeType {
group: ['trigger'], group: ['trigger'],
version: 1, version: 1,
description: 'Triggers the workflow at a specific time', description: 'Triggers the workflow at a specific time',
eventTriggerDescription: '',
defaults: { defaults: {
name: 'Cron', name: 'Cron',
color: '#00FF00', color: '#00FF00',

View file

@ -36,6 +36,7 @@ export class EmailReadImap implements INodeType {
group: ['trigger'], group: ['trigger'],
version: 1, version: 1,
description: 'Triggers the workflow when a new email is received', description: 'Triggers the workflow when a new email is received',
eventTriggerDescription: 'Waiting for you to receive an email',
defaults: { defaults: {
name: 'IMAP Email', name: 'IMAP Email',
color: '#44AA22', color: '#44AA22',

View file

@ -14,6 +14,7 @@ export class ErrorTrigger implements INodeType {
group: ['trigger'], group: ['trigger'],
version: 1, version: 1,
description: 'Triggers the workflow when another workflow has an error', description: 'Triggers the workflow when another workflow has an error',
eventTriggerDescription: '',
maxNodes: 1, maxNodes: 1,
defaults: { defaults: {
name: 'Error Trigger', name: 'Error Trigger',

View file

@ -15,6 +15,7 @@ export class Interval implements INodeType {
group: ['trigger'], group: ['trigger'],
version: 1, version: 1,
description: 'Triggers the workflow in a given interval', description: 'Triggers the workflow in a given interval',
eventTriggerDescription: '',
defaults: { defaults: {
name: 'Interval', name: 'Interval',
color: '#00FF00', color: '#00FF00',

View file

@ -18,6 +18,7 @@ export class LocalFileTrigger implements INodeType {
version: 1, version: 1,
subtitle: '=Path: {{$parameter["path"]}}', subtitle: '=Path: {{$parameter["path"]}}',
description: 'Triggers a workflow on file system changes', description: 'Triggers a workflow on file system changes',
eventTriggerDescription: '',
defaults: { defaults: {
name: 'Local File Trigger', name: 'Local File Trigger',
color: '#404040', color: '#404040',

View file

@ -15,6 +15,7 @@ export class N8nTrigger implements INodeType {
group: ['trigger'], group: ['trigger'],
version: 1, version: 1,
description: 'Handle events from your n8n instance', description: 'Handle events from your n8n instance',
eventTriggerDescription: '',
defaults: { defaults: {
name: 'n8n Trigger', name: 'n8n Trigger',
color: '#ff6d5a', color: '#ff6d5a',

View file

@ -15,6 +15,7 @@ export class SseTrigger implements INodeType {
group: ['trigger'], group: ['trigger'],
version: 1, version: 1,
description: 'Triggers the workflow when Server-Sent Events occur', description: 'Triggers the workflow when Server-Sent Events occur',
eventTriggerDescription: '',
defaults: { defaults: {
name: 'SSE Trigger', name: 'SSE Trigger',
color: '#225577', color: '#225577',

View file

@ -45,6 +45,7 @@ export class Webhook implements INodeType {
group: ['trigger'], group: ['trigger'],
version: 1, version: 1,
description: 'Starts the workflow when a webhook is called', description: 'Starts the workflow when a webhook is called',
eventTriggerDescription: 'Waiting for you to ping the Test URL',
defaults: { defaults: {
name: 'Webhook', name: 'Webhook',
color: '#885577', color: '#885577',

View file

@ -16,6 +16,7 @@ export class WorkflowTrigger implements INodeType {
group: ['trigger'], group: ['trigger'],
version: 1, version: 1,
description: 'Triggers based on various lifecycle events, like when a workflow is activated', description: 'Triggers based on various lifecycle events, like when a workflow is activated',
eventTriggerDescription: '',
defaults: { defaults: {
name: 'Workflow Trigger', name: 'Workflow Trigger',
color: '#ff6d5a', color: '#ff6d5a',

View file

@ -803,6 +803,7 @@ export interface INodeTypeBaseDescription {
export interface INodeTypeDescription extends INodeTypeBaseDescription { export interface INodeTypeDescription extends INodeTypeBaseDescription {
version: number; version: number;
defaults: INodeParameters; defaults: INodeParameters;
eventTriggerDescription?: string;
inputs: string[]; inputs: string[];
inputNames?: string[]; inputNames?: string[];
outputs: string[]; outputs: string[];