mirror of
https://github.com/n8n-io/n8n.git
synced 2025-01-11 12:57:29 -08:00
✨ 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:
parent
a58c251a28
commit
adc2515dee
|
@ -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;
|
||||||
|
|
|
@ -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);
|
||||||
|
|
|
@ -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);
|
||||||
|
|
|
@ -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';
|
||||||
|
|
|
@ -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',
|
||||||
|
|
|
@ -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',
|
||||||
|
|
|
@ -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',
|
||||||
|
|
|
@ -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',
|
||||||
|
|
|
@ -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',
|
||||||
|
|
|
@ -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',
|
||||||
|
|
|
@ -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',
|
||||||
|
|
|
@ -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',
|
||||||
|
|
|
@ -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',
|
||||||
|
|
|
@ -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[];
|
||||||
|
|
Loading…
Reference in a new issue