mirror of
https://github.com/n8n-io/n8n.git
synced 2024-09-19 22:37:31 -07:00
fix(editor): Color node connections correctly in execution preview for nodes that have pinned data (#9669)
This commit is contained in:
parent
6ae6a5ebdf
commit
ebba7c87cd
|
@ -84,7 +84,8 @@ describe('Current Workflow Executions', () => {
|
|||
executionsTab.actions.switchToExecutionsTab();
|
||||
cy.wait(['@getExecution']);
|
||||
|
||||
cy.getByTestId('workflow-preview-iframe')
|
||||
executionsTab.getters
|
||||
.workflowExecutionPreviewIframe()
|
||||
.should('be.visible')
|
||||
.its('0.contentDocument.body') // Access the body of the iframe document
|
||||
.should('not.be.empty') // Ensure the body is not empty
|
||||
|
|
65
cypress/e2e/2106-ADO-pinned-data-execution-preview.cy.ts
Normal file
65
cypress/e2e/2106-ADO-pinned-data-execution-preview.cy.ts
Normal file
|
@ -0,0 +1,65 @@
|
|||
import { v4 as uuid } from 'uuid';
|
||||
import { WorkflowExecutionsTab, WorkflowPage as WorkflowPageClass } from '../pages';
|
||||
import { BACKEND_BASE_URL } from '../constants';
|
||||
|
||||
const workflowPage = new WorkflowPageClass();
|
||||
const executionsTab = new WorkflowExecutionsTab();
|
||||
|
||||
describe('ADO-2106 connections should be colored correctly for pinned data in executions preview', () => {
|
||||
beforeEach(() => {
|
||||
workflowPage.actions.visit();
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
cy.createFixtureWorkflow('Webhook_set_pinned.json', `Webhook set pinned ${uuid()}`);
|
||||
workflowPage.actions.deselectAll();
|
||||
workflowPage.getters.zoomToFitButton().click();
|
||||
|
||||
workflowPage.getters.getConnectionBetweenNodes('Webhook', 'Set').should('have.class', 'pinned');
|
||||
});
|
||||
|
||||
it('should not color connections for pinned data nodes for production executions', () => {
|
||||
workflowPage.actions.activateWorkflow();
|
||||
|
||||
// Execute the workflow
|
||||
cy.request('POST', `${BACKEND_BASE_URL}/webhook/23fc3930-b8f9-41d9-89db-b647291a2201`, {
|
||||
here: 'is some data',
|
||||
}).then((response) => {
|
||||
expect(response.status).to.eq(200);
|
||||
});
|
||||
|
||||
executionsTab.actions.switchToExecutionsTab();
|
||||
|
||||
executionsTab.getters.successfulExecutionListItems().should('have.length', 1);
|
||||
|
||||
executionsTab.getters
|
||||
.workflowExecutionPreviewIframe()
|
||||
.should('be.visible')
|
||||
.its('0.contentDocument.body')
|
||||
.should('not.be.empty')
|
||||
.then(cy.wrap)
|
||||
.find(`.jtk-connector[data-source-node="Webhook"][data-target-node="Set"]`)
|
||||
.should('have.class', 'success')
|
||||
.should('have.class', 'has-run')
|
||||
.should('not.have.class', 'pinned');
|
||||
});
|
||||
|
||||
it('should color connections for pinned data nodes for manual executions', () => {
|
||||
workflowPage.actions.executeWorkflow();
|
||||
|
||||
executionsTab.actions.switchToExecutionsTab();
|
||||
|
||||
executionsTab.getters.successfulExecutionListItems().should('have.length', 1);
|
||||
|
||||
executionsTab.getters
|
||||
.workflowExecutionPreviewIframe()
|
||||
.should('be.visible')
|
||||
.its('0.contentDocument.body')
|
||||
.should('not.be.empty')
|
||||
.then(cy.wrap)
|
||||
.find(`.jtk-connector[data-source-node="Webhook"][data-target-node="Set"]`)
|
||||
.should('have.class', 'success')
|
||||
.should('have.class', 'has-run')
|
||||
.should('have.class', 'pinned');
|
||||
});
|
||||
});
|
67
cypress/fixtures/Webhook_set_pinned.json
Normal file
67
cypress/fixtures/Webhook_set_pinned.json
Normal file
|
@ -0,0 +1,67 @@
|
|||
{
|
||||
"nodes": [
|
||||
{
|
||||
"parameters": {
|
||||
"options": {}
|
||||
},
|
||||
"id": "bd816131-d8ad-4b4c-90d6-59fdab2e6307",
|
||||
"name": "Set",
|
||||
"type": "n8n-nodes-base.set",
|
||||
"typeVersion": 1,
|
||||
"position": [
|
||||
720,
|
||||
460
|
||||
]
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"httpMethod": "POST",
|
||||
"path": "23fc3930-b8f9-41d9-89db-b647291a2201",
|
||||
"options": {}
|
||||
},
|
||||
"id": "82fe0f6c-854a-4eb9-b311-d7b43025c047",
|
||||
"name": "Webhook",
|
||||
"type": "n8n-nodes-base.webhook",
|
||||
"typeVersion": 1,
|
||||
"position": [
|
||||
460,
|
||||
460
|
||||
],
|
||||
"webhookId": "23fc3930-b8f9-41d9-89db-b647291a2201"
|
||||
}
|
||||
],
|
||||
"connections": {
|
||||
"Webhook": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Set",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
}
|
||||
},
|
||||
"pinData": {
|
||||
"Webhook": [
|
||||
{
|
||||
"headers": {
|
||||
"host": "localhost:5678",
|
||||
"content-length": "37",
|
||||
"accept": "*/*",
|
||||
"content-type": "application/json",
|
||||
"accept-encoding": "gzip"
|
||||
},
|
||||
"params": {},
|
||||
"query": {},
|
||||
"body": {
|
||||
"here": "be",
|
||||
"dragons": true
|
||||
},
|
||||
"webhookUrl": "http://localhost:5678/webhook-test/23fc3930-b8f9-41d9-89db-b647291a2201",
|
||||
"executionMode": "test"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
|
@ -24,6 +24,7 @@ export class WorkflowExecutionsTab extends BasePage {
|
|||
executionPreviewId: () =>
|
||||
this.getters.executionPreviewDetails().find('[data-test-id="execution-preview-id"]'),
|
||||
executionDebugButton: () => cy.getByTestId('execution-debug-button'),
|
||||
workflowExecutionPreviewIframe: () => cy.getByTestId('workflow-preview-iframe'),
|
||||
};
|
||||
actions = {
|
||||
toggleNodeEnabled: (nodeName: string) => {
|
||||
|
|
|
@ -108,9 +108,9 @@
|
|||
</div>
|
||||
|
||||
<div
|
||||
v-if="showDisabledLinethrough"
|
||||
v-if="showDisabledLineThrough"
|
||||
:class="{
|
||||
'disabled-linethrough': true,
|
||||
'disabled-line-through': true,
|
||||
success: !['unknown'].includes(nodeExecutionStatus) && workflowDataItems > 0,
|
||||
}"
|
||||
></div>
|
||||
|
@ -187,7 +187,7 @@ import {
|
|||
LOCAL_STORAGE_PIN_DATA_DISCOVERY_CANVAS_FLAG,
|
||||
MANUAL_TRIGGER_NODE_TYPE,
|
||||
NODE_INSERT_SPACER_BETWEEN_INPUT_GROUPS,
|
||||
NOT_DUPLICATABE_NODE_TYPES,
|
||||
NOT_DUPLICATABLE_NODE_TYPES,
|
||||
SIMULATE_NODE_TYPE,
|
||||
SIMULATE_TRIGGER_NODE_TYPE,
|
||||
WAIT_TIME_UNLIMITED,
|
||||
|
@ -287,7 +287,7 @@ export default defineComponent({
|
|||
},
|
||||
isDuplicatable(): boolean {
|
||||
if (!this.nodeType) return true;
|
||||
if (NOT_DUPLICATABE_NODE_TYPES.includes(this.nodeType.name)) return false;
|
||||
if (NOT_DUPLICATABLE_NODE_TYPES.includes(this.nodeType.name)) return false;
|
||||
return (
|
||||
this.nodeType.maxNodes === undefined || this.sameTypeNodes.length < this.nodeType.maxNodes
|
||||
);
|
||||
|
@ -493,7 +493,7 @@ export default defineComponent({
|
|||
position(): XYPosition {
|
||||
return this.node ? this.node.position : [0, 0];
|
||||
},
|
||||
showDisabledLinethrough(): boolean {
|
||||
showDisabledLineThrough(): boolean {
|
||||
return (
|
||||
!this.isConfigurableNode &&
|
||||
!!(this.data?.disabled && this.inputs.length === 1 && this.outputs.length === 1)
|
||||
|
@ -1118,7 +1118,7 @@ export default defineComponent({
|
|||
}
|
||||
}
|
||||
|
||||
.disabled-linethrough {
|
||||
.disabled-line-through {
|
||||
border: 1px solid var(--color-foreground-dark);
|
||||
position: absolute;
|
||||
top: 49px;
|
||||
|
@ -1189,7 +1189,7 @@ export default defineComponent({
|
|||
overflow: auto;
|
||||
}
|
||||
|
||||
.disabled-linethrough {
|
||||
.disabled-line-through {
|
||||
z-index: 8;
|
||||
}
|
||||
|
||||
|
|
|
@ -27,7 +27,6 @@ import { onMounted, onBeforeUnmount, ref, computed, watch } from 'vue';
|
|||
import { useI18n } from '@/composables/useI18n';
|
||||
import { useToast } from '@/composables/useToast';
|
||||
import type { IWorkflowDb, IWorkflowTemplate } from '@/Interface';
|
||||
import { useRootStore } from '@/stores/n8nRoot.store';
|
||||
import { useExecutionsStore } from '@/stores/executions.store';
|
||||
|
||||
const props = withDefaults(
|
||||
|
@ -44,6 +43,9 @@ const props = withDefaults(
|
|||
{
|
||||
loading: false,
|
||||
mode: 'workflow',
|
||||
workflow: undefined,
|
||||
executionId: undefined,
|
||||
executionMode: undefined,
|
||||
loaderType: 'image',
|
||||
canOpenNDV: true,
|
||||
hideNodeIssues: false,
|
||||
|
@ -56,7 +58,6 @@ const emit = defineEmits<{
|
|||
|
||||
const i18n = useI18n();
|
||||
const toast = useToast();
|
||||
const rootStore = useRootStore();
|
||||
const executionsStore = useExecutionsStore();
|
||||
|
||||
const iframeRef = ref<HTMLIFrameElement | null>(null);
|
||||
|
@ -73,8 +74,8 @@ const iframeSrc = computed(() => {
|
|||
const showPreview = computed(() => {
|
||||
return (
|
||||
!props.loading &&
|
||||
((props.mode === 'workflow' && props.workflow) ||
|
||||
(props.mode === 'execution' && props.executionId)) &&
|
||||
((props.mode === 'workflow' && !!props.workflow) ||
|
||||
(props.mode === 'execution' && !!props.executionId)) &&
|
||||
ready.value
|
||||
);
|
||||
});
|
||||
|
@ -114,7 +115,7 @@ const loadExecution = () => {
|
|||
JSON.stringify({
|
||||
command: 'openExecution',
|
||||
executionId: props.executionId,
|
||||
executionMode: props.executionMode || '',
|
||||
executionMode: props.executionMode ?? '',
|
||||
canOpenNDV: props.canOpenNDV,
|
||||
}),
|
||||
'*',
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import type { ActionDropdownItem, XYPosition } from '@/Interface';
|
||||
import { NOT_DUPLICATABE_NODE_TYPES, STICKY_NODE_TYPE } from '@/constants';
|
||||
import { NOT_DUPLICATABLE_NODE_TYPES, STICKY_NODE_TYPE } from '@/constants';
|
||||
import { useNodeTypesStore } from '@/stores/nodeTypes.store';
|
||||
import { useSourceControlStore } from '@/stores/sourceControl.store';
|
||||
import { useUIStore } from '@/stores/ui.store';
|
||||
|
@ -72,7 +72,7 @@ export const useContextMenu = (onAction: ContextMenuActionCallback = () => {}) =
|
|||
const canDuplicateNode = (node: INode): boolean => {
|
||||
const nodeType = nodeTypesStore.getNodeType(node.type, node.typeVersion);
|
||||
if (!nodeType) return false;
|
||||
if (NOT_DUPLICATABE_NODE_TYPES.includes(nodeType.name)) return false;
|
||||
if (NOT_DUPLICATABLE_NODE_TYPES.includes(nodeType.name)) return false;
|
||||
|
||||
return canAddNodeOfType(nodeType);
|
||||
};
|
||||
|
|
|
@ -752,7 +752,7 @@ export const APPEND_ATTRIBUTION_DEFAULT_PATH = 'parameters.options.appendAttribu
|
|||
|
||||
export const DRAG_EVENT_DATA_KEY = 'nodesAndConnections';
|
||||
|
||||
export const NOT_DUPLICATABE_NODE_TYPES = [FORM_TRIGGER_NODE_TYPE];
|
||||
export const NOT_DUPLICATABLE_NODE_TYPES = [FORM_TRIGGER_NODE_TYPE];
|
||||
export const UPDATE_WEBHOOK_ID_NODE_TYPES = [FORM_TRIGGER_NODE_TYPE];
|
||||
|
||||
export const CREATOR_HUB_URL = 'https://creators.n8n.io/hub';
|
||||
|
|
|
@ -915,7 +915,7 @@ export default defineComponent({
|
|||
|
||||
setTimeout(() => {
|
||||
void this.usersStore.showPersonalizationSurvey();
|
||||
this.addPinDataConnections(this.workflowsStore.pinnedWorkflowData || ({} as IPinData));
|
||||
this.addPinDataConnections(this.workflowsStore.pinnedWorkflowData);
|
||||
}, 0);
|
||||
});
|
||||
|
||||
|
@ -2332,7 +2332,7 @@ export default defineComponent({
|
|||
|
||||
this.workflowsStore.addWorkflowTagIds(tagIds);
|
||||
setTimeout(() => {
|
||||
this.addPinDataConnections(this.workflowsStore.pinnedWorkflowData || ({} as IPinData));
|
||||
this.addPinDataConnections(this.workflowsStore.pinnedWorkflowData);
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
|
@ -3892,7 +3892,7 @@ export default defineComponent({
|
|||
});
|
||||
|
||||
setTimeout(() => {
|
||||
this.addPinDataConnections(this.workflowsStore.pinnedWorkflowData ?? ({} as IPinData));
|
||||
this.addPinDataConnections(this.workflowsStore.pinnedWorkflowData);
|
||||
});
|
||||
},
|
||||
__removeConnection(connection: [IConnection, IConnection], removeVisualConnection = false) {
|
||||
|
@ -4904,23 +4904,31 @@ export default defineComponent({
|
|||
await this.importWorkflowData(workflowData, 'url');
|
||||
}
|
||||
},
|
||||
addPinDataConnections(pinData: IPinData) {
|
||||
addPinDataConnections(pinData?: IPinData) {
|
||||
if (!pinData) {
|
||||
return;
|
||||
}
|
||||
|
||||
Object.keys(pinData).forEach((nodeName) => {
|
||||
const node = this.workflowsStore.getNodeByName(nodeName);
|
||||
if (!node) {
|
||||
return;
|
||||
}
|
||||
|
||||
const hasRun = this.workflowsStore.getWorkflowResultDataByNodeName(nodeName) !== null;
|
||||
const classNames = ['pinned'];
|
||||
|
||||
if (hasRun) {
|
||||
classNames.push('has-run');
|
||||
}
|
||||
const nodeElement = document.getElementById(node.id);
|
||||
if (!nodeElement) {
|
||||
return;
|
||||
}
|
||||
|
||||
const hasRun = this.workflowsStore.getWorkflowResultDataByNodeName(nodeName) !== null;
|
||||
// In case we are showing a production execution preview we want
|
||||
// to show pinned data connections as they wouldn't have been pinned
|
||||
const classNames = this.isProductionExecutionPreview ? [] : ['pinned'];
|
||||
|
||||
if (hasRun) {
|
||||
classNames.push('has-run');
|
||||
}
|
||||
|
||||
const connections = this.instance?.getConnections({
|
||||
source: nodeElement,
|
||||
});
|
||||
|
@ -5055,7 +5063,7 @@ export default defineComponent({
|
|||
});
|
||||
}
|
||||
|
||||
this.addPinDataConnections(this.workflowsStore.pinnedWorkflowData || ({} as IPinData));
|
||||
this.addPinDataConnections(this.workflowsStore.pinnedWorkflowData);
|
||||
},
|
||||
|
||||
async saveCurrentWorkflowExternal(callback: () => void) {
|
||||
|
|
Loading…
Reference in a new issue