mirror of
https://github.com/Abdulazizzn/n8n-enterprise-unlocked.git
synced 2025-12-17 01:56:46 +00:00
feat(editor): Include NodeDetailsView in URL (#14349)
This commit is contained in:
@@ -1,6 +1,10 @@
|
|||||||
import { createResource } from '../composables/create';
|
import { createResource } from '../composables/create';
|
||||||
import { setCredentialValues } from '../composables/modals/credential-modal';
|
import { setCredentialValues } from '../composables/modals/credential-modal';
|
||||||
import { clickCreateNewCredential, selectResourceLocatorItem } from '../composables/ndv';
|
import {
|
||||||
|
clickCreateNewCredential,
|
||||||
|
getNdvContainer,
|
||||||
|
selectResourceLocatorItem,
|
||||||
|
} from '../composables/ndv';
|
||||||
import * as projects from '../composables/projects';
|
import * as projects from '../composables/projects';
|
||||||
import {
|
import {
|
||||||
EDIT_FIELDS_SET_NODE_NAME,
|
EDIT_FIELDS_SET_NODE_NAME,
|
||||||
@@ -302,7 +306,8 @@ describe('Projects', { disableAutoLogin: true }, () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
selectResourceLocatorItem('workflowId', 0, 'Create a');
|
selectResourceLocatorItem('workflowId', 0, 'Create a');
|
||||||
|
// Need to wait for the trigger node to auto-open after a delay
|
||||||
|
getNdvContainer().should('be.visible');
|
||||||
cy.get('body').type('{esc}');
|
cy.get('body').type('{esc}');
|
||||||
workflowPage.actions.addNodeToCanvas(NOTION_NODE_NAME, true, true);
|
workflowPage.actions.addNodeToCanvas(NOTION_NODE_NAME, true, true);
|
||||||
clickCreateNewCredential();
|
clickCreateNewCredential();
|
||||||
|
|||||||
@@ -3,8 +3,9 @@ import {
|
|||||||
getCloseSaveChangesButton,
|
getCloseSaveChangesButton,
|
||||||
getSaveChangesModal,
|
getSaveChangesModal,
|
||||||
} from '../composables/modals/save-changes-modal';
|
} from '../composables/modals/save-changes-modal';
|
||||||
|
import { getNdvContainer } from '../composables/ndv';
|
||||||
import { getHomeButton } from '../composables/projects';
|
import { getHomeButton } from '../composables/projects';
|
||||||
import { addNodeToCanvas } from '../composables/workflow';
|
import { addNodeToCanvas, saveWorkflowOnButtonClick } from '../composables/workflow';
|
||||||
import {
|
import {
|
||||||
getCreateWorkflowButton,
|
getCreateWorkflowButton,
|
||||||
getNewWorkflowCardButton,
|
getNewWorkflowCardButton,
|
||||||
@@ -12,6 +13,7 @@ import {
|
|||||||
visitWorkflowsPage,
|
visitWorkflowsPage,
|
||||||
} from '../composables/workflowsPage';
|
} from '../composables/workflowsPage';
|
||||||
import { EDIT_FIELDS_SET_NODE_NAME } from '../constants';
|
import { EDIT_FIELDS_SET_NODE_NAME } from '../constants';
|
||||||
|
import { warningToast } from '../pages/notifications';
|
||||||
|
|
||||||
describe('Workflows', () => {
|
describe('Workflows', () => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
@@ -42,11 +44,11 @@ describe('Workflows', () => {
|
|||||||
getCreateWorkflowButton().click();
|
getCreateWorkflowButton().click();
|
||||||
|
|
||||||
cy.createFixtureWorkflow('Test_workflow_1.json', 'Empty State Card Workflow');
|
cy.createFixtureWorkflow('Test_workflow_1.json', 'Empty State Card Workflow');
|
||||||
addNodeToCanvas(EDIT_FIELDS_SET_NODE_NAME);
|
addNodeToCanvas(EDIT_FIELDS_SET_NODE_NAME, true, true);
|
||||||
|
|
||||||
// Here we go back via browser rather than the home button
|
// Here we go back via browser rather than the home button
|
||||||
// As this already updates the route
|
// As this already updates the route
|
||||||
cy.go(-1);
|
cy.go(-2);
|
||||||
|
|
||||||
cy.url().should('include', getWorkflowsPageUrl());
|
cy.url().should('include', getWorkflowsPageUrl());
|
||||||
|
|
||||||
@@ -56,4 +58,79 @@ describe('Workflows', () => {
|
|||||||
// Confirm the url is back to the workflow
|
// Confirm the url is back to the workflow
|
||||||
cy.url().should('include', '/workflow/');
|
cy.url().should('include', '/workflow/');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should correct route when opening and closing NDV via browser button', () => {
|
||||||
|
getCreateWorkflowButton().click();
|
||||||
|
saveWorkflowOnButtonClick();
|
||||||
|
cy.url().then((startUrl) => {
|
||||||
|
cy.createFixtureWorkflow('Test_workflow_1.json', 'Empty State Card Workflow');
|
||||||
|
cy.url().should('equal', startUrl);
|
||||||
|
|
||||||
|
addNodeToCanvas(EDIT_FIELDS_SET_NODE_NAME, true, true);
|
||||||
|
|
||||||
|
// Getting the generated nodeId is awkward, so we just ensure the URL changed
|
||||||
|
cy.url().should('not.equal', startUrl);
|
||||||
|
|
||||||
|
// Here we go back via browser rather than the home button
|
||||||
|
// As this already updates the route
|
||||||
|
cy.go(-1);
|
||||||
|
|
||||||
|
cy.url().should('equal', startUrl);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should correct route when opening and closing NDV via browser button', () => {
|
||||||
|
getCreateWorkflowButton().click();
|
||||||
|
saveWorkflowOnButtonClick();
|
||||||
|
cy.url().then((startUrl) => {
|
||||||
|
cy.createFixtureWorkflow('Test_workflow_1.json', 'Empty State Card Workflow');
|
||||||
|
cy.url().should('equal', startUrl);
|
||||||
|
|
||||||
|
addNodeToCanvas(EDIT_FIELDS_SET_NODE_NAME, true, true);
|
||||||
|
|
||||||
|
// Getting the generated nodeId is awkward, so we just ensure the URL changed
|
||||||
|
cy.url().should('not.equal', startUrl);
|
||||||
|
|
||||||
|
cy.get('body').type('{esc}');
|
||||||
|
|
||||||
|
cy.url().should('equal', startUrl);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should open ndv via URL', () => {
|
||||||
|
getCreateWorkflowButton().click();
|
||||||
|
saveWorkflowOnButtonClick();
|
||||||
|
cy.createFixtureWorkflow('Test_workflow_1.json', 'Empty State Card Workflow');
|
||||||
|
|
||||||
|
addNodeToCanvas(EDIT_FIELDS_SET_NODE_NAME, true, true);
|
||||||
|
cy.url().then((ndvUrl) => {
|
||||||
|
cy.get('body').type('{esc}');
|
||||||
|
saveWorkflowOnButtonClick();
|
||||||
|
|
||||||
|
getNdvContainer().should('not.be.visible');
|
||||||
|
|
||||||
|
cy.visit(ndvUrl);
|
||||||
|
|
||||||
|
getNdvContainer().should('be.visible');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should open show warning and drop nodeId from URL if it contained an unknown nodeId', () => {
|
||||||
|
getCreateWorkflowButton().click();
|
||||||
|
saveWorkflowOnButtonClick();
|
||||||
|
cy.createFixtureWorkflow('Test_workflow_1.json', 'Empty State Card Workflow');
|
||||||
|
|
||||||
|
addNodeToCanvas(EDIT_FIELDS_SET_NODE_NAME, true, true);
|
||||||
|
cy.url().then((ndvUrl) => {
|
||||||
|
cy.get('body').type('{esc}');
|
||||||
|
saveWorkflowOnButtonClick();
|
||||||
|
|
||||||
|
getNdvContainer().should('not.be.visible');
|
||||||
|
|
||||||
|
cy.visit(ndvUrl + 'thisMessesUpTheNodeId');
|
||||||
|
|
||||||
|
warningToast().should('be.visible');
|
||||||
|
cy.url().should('equal', ndvUrl.split('/').slice(0, -1).join('/'));
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -7,8 +7,10 @@ export async function loadSubWorkflowInputs(
|
|||||||
const { fields, subworkflowInfo, dataMode } = await loadWorkflowInputMappings.bind(this)();
|
const { fields, subworkflowInfo, dataMode } = await loadWorkflowInputMappings.bind(this)();
|
||||||
let emptyFieldsNotice: string | undefined;
|
let emptyFieldsNotice: string | undefined;
|
||||||
if (fields.length === 0) {
|
if (fields.length === 0) {
|
||||||
const subworkflowLink = subworkflowInfo?.id
|
const { triggerId, workflowId } = subworkflowInfo ?? {};
|
||||||
? `<a href="/workflow/${subworkflowInfo?.id}" target="_blank">sub-workflow’s trigger</a>`
|
const path = (workflowId ?? '') + (triggerId ? `/${triggerId.slice(0, 6)}` : '');
|
||||||
|
const subworkflowLink = workflowId
|
||||||
|
? `<a href="/workflow/${path}" target="_blank">sub-workflow’s trigger</a>`
|
||||||
: 'sub-workflow’s trigger';
|
: 'sub-workflow’s trigger';
|
||||||
|
|
||||||
switch (dataMode) {
|
switch (dataMode) {
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ import { useWorkflowResourcesLocator } from './useWorkflowResourcesLocator';
|
|||||||
import { useProjectsStore } from '@/stores/projects.store';
|
import { useProjectsStore } from '@/stores/projects.store';
|
||||||
import { useTelemetry } from '@/composables/useTelemetry';
|
import { useTelemetry } from '@/composables/useTelemetry';
|
||||||
import { VIEWS } from '@/constants';
|
import { VIEWS } from '@/constants';
|
||||||
import { SAMPLE_SUBWORKFLOW_WORKFLOW } from '@/constants.workflows';
|
import { SAMPLE_SUBWORKFLOW_TRIGGER_ID, SAMPLE_SUBWORKFLOW_WORKFLOW } from '@/constants.workflows';
|
||||||
import type { IWorkflowDataCreate } from '@/Interface';
|
import type { IWorkflowDataCreate } from '@/Interface';
|
||||||
import { useDocumentVisibility } from '@/composables/useDocumentVisibility';
|
import { useDocumentVisibility } from '@/composables/useDocumentVisibility';
|
||||||
|
|
||||||
@@ -260,7 +260,10 @@ const onAddResourceClicked = async () => {
|
|||||||
telemetry.track('User clicked create new sub-workflow button', {}, { withPostHog: true });
|
telemetry.track('User clicked create new sub-workflow button', {}, { withPostHog: true });
|
||||||
|
|
||||||
const newWorkflow = await workflowsStore.createNewWorkflow(workflow);
|
const newWorkflow = await workflowsStore.createNewWorkflow(workflow);
|
||||||
const { href } = router.resolve({ name: VIEWS.WORKFLOW, params: { name: newWorkflow.id } });
|
const { href } = router.resolve({
|
||||||
|
name: VIEWS.WORKFLOW,
|
||||||
|
params: { name: newWorkflow.id, nodeId: SAMPLE_SUBWORKFLOW_TRIGGER_ID },
|
||||||
|
});
|
||||||
await reloadWorkflows();
|
await reloadWorkflows();
|
||||||
onInputChange(newWorkflow.id);
|
onInputChange(newWorkflow.id);
|
||||||
hideDropdown();
|
hideDropdown();
|
||||||
|
|||||||
@@ -1,11 +1,12 @@
|
|||||||
import { NodeConnectionTypes } from 'n8n-workflow';
|
import { NodeConnectionTypes } from 'n8n-workflow';
|
||||||
import type { INodeUi, IWorkflowDataCreate } from './Interface';
|
import type { INodeUi, IWorkflowDataCreate } from './Interface';
|
||||||
|
|
||||||
|
export const SAMPLE_SUBWORKFLOW_TRIGGER_ID = 'c055762a-8fe7-4141-a639-df2372f30060';
|
||||||
export const SAMPLE_SUBWORKFLOW_WORKFLOW: IWorkflowDataCreate = {
|
export const SAMPLE_SUBWORKFLOW_WORKFLOW: IWorkflowDataCreate = {
|
||||||
name: 'My Sub-Workflow',
|
name: 'My Sub-Workflow',
|
||||||
nodes: [
|
nodes: [
|
||||||
{
|
{
|
||||||
id: 'c055762a-8fe7-4141-a639-df2372f30060',
|
id: SAMPLE_SUBWORKFLOW_TRIGGER_ID,
|
||||||
typeVersion: 1.1,
|
typeVersion: 1.1,
|
||||||
name: 'When Executed by Another Workflow',
|
name: 'When Executed by Another Workflow',
|
||||||
type: 'n8n-nodes-base.executeWorkflowTrigger',
|
type: 'n8n-nodes-base.executeWorkflowTrigger',
|
||||||
|
|||||||
@@ -1463,6 +1463,8 @@
|
|||||||
"nodeView.showMessage.debug.content": "You can make edits and re-execute. Once you're done, unpin the the first node.",
|
"nodeView.showMessage.debug.content": "You can make edits and re-execute. Once you're done, unpin the the first node.",
|
||||||
"nodeView.showMessage.debug.missingNodes.title": "Some execution data wasn't imported",
|
"nodeView.showMessage.debug.missingNodes.title": "Some execution data wasn't imported",
|
||||||
"nodeView.showMessage.debug.missingNodes.content": "Some nodes have been deleted or renamed or added to the workflow since the execution ran.",
|
"nodeView.showMessage.debug.missingNodes.content": "Some nodes have been deleted or renamed or added to the workflow since the execution ran.",
|
||||||
|
"nodeView.showMessage.ndvUrl.missingNodes.title": "Node not found",
|
||||||
|
"nodeView.showMessage.ndvUrl.missingNodes.content": "URL contained a reference to an unknown node. Maybe the node was deleted?",
|
||||||
"nodeView.stopCurrentExecution": "Stop current execution",
|
"nodeView.stopCurrentExecution": "Stop current execution",
|
||||||
"nodeView.stopWaitingForWebhookCall": "Stop waiting for webhook call",
|
"nodeView.stopWaitingForWebhookCall": "Stop waiting for webhook call",
|
||||||
"nodeView.stoppingCurrentExecution": "Stopping current execution",
|
"nodeView.stoppingCurrentExecution": "Stopping current execution",
|
||||||
|
|||||||
@@ -45,6 +45,8 @@ describe('router', () => {
|
|||||||
['/workflow', VIEWS.NEW_WORKFLOW],
|
['/workflow', VIEWS.NEW_WORKFLOW],
|
||||||
['/workflow/new', VIEWS.NEW_WORKFLOW],
|
['/workflow/new', VIEWS.NEW_WORKFLOW],
|
||||||
['/workflow/R9JFXwkUCL1jZBuw', VIEWS.WORKFLOW],
|
['/workflow/R9JFXwkUCL1jZBuw', VIEWS.WORKFLOW],
|
||||||
|
['/workflow/R9JFXwkUCL1jZBuw/myNodeId', VIEWS.WORKFLOW],
|
||||||
|
['/workflow/R9JFXwkUCL1jZBuw/398-1ewq213', VIEWS.WORKFLOW],
|
||||||
['/workflow/R9JFXwkUCL1jZBuw/executions/29021', VIEWS.EXECUTION_PREVIEW],
|
['/workflow/R9JFXwkUCL1jZBuw/executions/29021', VIEWS.EXECUTION_PREVIEW],
|
||||||
['/workflows/templates/R9JFXwkUCL1jZBuw', VIEWS.TEMPLATE_IMPORT],
|
['/workflows/templates/R9JFXwkUCL1jZBuw', VIEWS.TEMPLATE_IMPORT],
|
||||||
['/workflows/demo', VIEWS.DEMO],
|
['/workflows/demo', VIEWS.DEMO],
|
||||||
|
|||||||
@@ -389,7 +389,7 @@ export const routes: RouteRecordRaw[] = [
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: '/workflow/:name',
|
path: '/workflow/:name/:nodeId?',
|
||||||
name: VIEWS.WORKFLOW,
|
name: VIEWS.WORKFLOW,
|
||||||
components: {
|
components: {
|
||||||
default: NodeView,
|
default: NodeView,
|
||||||
|
|||||||
@@ -815,6 +815,44 @@ describe('useWorkflowsStore', () => {
|
|||||||
);
|
);
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
|
describe('findNodeByPartialId', () => {
|
||||||
|
test.each([
|
||||||
|
[[], 'D', undefined],
|
||||||
|
[['A', 'B', 'C'], 'D', undefined],
|
||||||
|
[['A', 'B', 'C'], 'B', 1],
|
||||||
|
[['AA', 'BB', 'CC'], 'B', 1],
|
||||||
|
[['AA', 'BB', 'BC'], 'B', 1],
|
||||||
|
[['AA', 'BB', 'BC'], 'BC', 2],
|
||||||
|
] as Array<[string[], string, number | undefined]>)(
|
||||||
|
'with input %s , %s returns node with index %s',
|
||||||
|
(ids, id, expectedIndex) => {
|
||||||
|
workflowsStore.workflow.nodes = ids.map((x) => ({ id: x }) as never);
|
||||||
|
|
||||||
|
expect(workflowsStore.findNodeByPartialId(id)).toBe(
|
||||||
|
workflowsStore.workflow.nodes[expectedIndex ?? -1],
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('getPartialIdForNode', () => {
|
||||||
|
test.each([
|
||||||
|
[[], 'Alphabet', 'Alphabet'],
|
||||||
|
[['Alphabet'], 'Alphabet', 'Alphab'],
|
||||||
|
[['Alphabet', 'Alphabeta'], 'Alphabeta', 'Alphabeta'],
|
||||||
|
[['Alphabet', 'Alphabeta', 'Alphabetagamma'], 'Alphabet', 'Alphabet'],
|
||||||
|
[['Alphabet', 'Alphabeta', 'Alphabetagamma'], 'Alphabeta', 'Alphabeta'],
|
||||||
|
[['Alphabet', 'Alphabeta', 'Alphabetagamma'], 'Alphabetagamma', 'Alphabetag'],
|
||||||
|
] as Array<[string[], string, string]>)(
|
||||||
|
'with input %s , %s returns %s',
|
||||||
|
(ids, id, expected) => {
|
||||||
|
workflowsStore.workflow.nodes = ids.map((x) => ({ id: x }) as never);
|
||||||
|
|
||||||
|
expect(workflowsStore.getPartialIdForNode(id)).toBe(expected);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
function getMockEditFieldsNode() {
|
function getMockEditFieldsNode() {
|
||||||
|
|||||||
@@ -343,6 +343,22 @@ export const useWorkflowsStore = defineStore(STORES.WORKFLOWS, () => {
|
|||||||
return workflow.value.nodes.find((node) => node.id === nodeId);
|
return workflow.value.nodes.find((node) => node.id === nodeId);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Finds the full id for a given partial id for a node, relying on order for uniqueness in edge cases
|
||||||
|
function findNodeByPartialId(partialId: string): INodeUi | undefined {
|
||||||
|
return workflow.value.nodes.find((node) => node.id.startsWith(partialId));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Finds a uniquely identifying partial id for a node, relying on order for uniqueness in edge cases
|
||||||
|
function getPartialIdForNode(fullId: string): string {
|
||||||
|
for (let length = 6; length < fullId.length; ++length) {
|
||||||
|
const partialId = fullId.slice(0, length);
|
||||||
|
if (workflow.value.nodes.filter((x) => x.id.startsWith(partialId)).length === 1) {
|
||||||
|
return partialId;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return fullId;
|
||||||
|
}
|
||||||
|
|
||||||
function getNodesByIds(nodeIds: string[]): INodeUi[] {
|
function getNodesByIds(nodeIds: string[]): INodeUi[] {
|
||||||
return nodeIds.map(getNodeById).filter(isPresent);
|
return nodeIds.map(getNodeById).filter(isPresent);
|
||||||
}
|
}
|
||||||
@@ -1875,6 +1891,8 @@ export const useWorkflowsStore = defineStore(STORES.WORKFLOWS, () => {
|
|||||||
setNodes,
|
setNodes,
|
||||||
setConnections,
|
setConnections,
|
||||||
markExecutionAsStopped,
|
markExecutionAsStopped,
|
||||||
|
findNodeByPartialId,
|
||||||
|
getPartialIdForNode,
|
||||||
totalWorkflowCount,
|
totalWorkflowCount,
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -234,6 +234,7 @@ const workflowId = computed(() => {
|
|||||||
? undefined
|
? undefined
|
||||||
: workflowIdParam;
|
: workflowIdParam;
|
||||||
});
|
});
|
||||||
|
const routeNodeId = computed(() => route.params.nodeId as string | undefined);
|
||||||
|
|
||||||
const isNewWorkflowRoute = computed(() => route.name === VIEWS.NEW_WORKFLOW || !workflowId.value);
|
const isNewWorkflowRoute = computed(() => route.name === VIEWS.NEW_WORKFLOW || !workflowId.value);
|
||||||
const isWorkflowRoute = computed(() => !!route?.meta?.nodeView || isDemoRoute.value);
|
const isWorkflowRoute = computed(() => !!route?.meta?.nodeView || isDemoRoute.value);
|
||||||
@@ -1606,6 +1607,23 @@ function showAddFirstStepIfEnabled() {
|
|||||||
* Routing
|
* Routing
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
function updateNodeRoute(nodeId: string) {
|
||||||
|
const nodeUi = workflowsStore.findNodeByPartialId(nodeId);
|
||||||
|
if (nodeUi) {
|
||||||
|
setNodeActive(nodeUi.id);
|
||||||
|
} else {
|
||||||
|
toast.showToast({
|
||||||
|
title: i18n.baseText('nodeView.showMessage.ndvUrl.missingNodes.title'),
|
||||||
|
message: i18n.baseText('nodeView.showMessage.ndvUrl.missingNodes.content'),
|
||||||
|
type: 'warning',
|
||||||
|
});
|
||||||
|
void router.replace({
|
||||||
|
name: route.name,
|
||||||
|
params: { name: workflowId.value },
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
watch(
|
watch(
|
||||||
() => route.name,
|
() => route.name,
|
||||||
async (newRouteName, oldRouteName) => {
|
async (newRouteName, oldRouteName) => {
|
||||||
@@ -1617,6 +1635,35 @@ watch(
|
|||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// This keeps the selected node in sync if the URL is updated
|
||||||
|
watch(
|
||||||
|
() => route.params.nodeId,
|
||||||
|
async (newId) => {
|
||||||
|
if (typeof newId !== 'string' || newId === '') ndvStore.activeNodeName = null;
|
||||||
|
else {
|
||||||
|
updateNodeRoute(newId);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
// This keeps URL in sync if the activeNode is changed
|
||||||
|
watch(
|
||||||
|
() => ndvStore.activeNode,
|
||||||
|
async (val) => {
|
||||||
|
// This is just out of caution
|
||||||
|
if (!([VIEWS.WORKFLOW] as string[]).includes(String(route.name))) return;
|
||||||
|
|
||||||
|
// Route params default to '' instead of undefined if not present
|
||||||
|
const nodeId = val?.id ? workflowsStore.getPartialIdForNode(val?.id) : '';
|
||||||
|
|
||||||
|
if (nodeId !== route.params.nodeId) {
|
||||||
|
await router.push({
|
||||||
|
name: route.name,
|
||||||
|
params: { name: workflowId.value, nodeId },
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
);
|
||||||
onBeforeRouteLeave(async (to, from, next) => {
|
onBeforeRouteLeave(async (to, from, next) => {
|
||||||
const toNodeViewTab = getNodeViewTab(to);
|
const toNodeViewTab = getNodeViewTab(to);
|
||||||
|
|
||||||
@@ -1689,6 +1736,13 @@ onMounted(() => {
|
|||||||
|
|
||||||
void externalHooks.run('nodeView.mount').catch(() => {});
|
void externalHooks.run('nodeView.mount').catch(() => {});
|
||||||
|
|
||||||
|
// A delay here makes opening the NDV a bit less jarring
|
||||||
|
setTimeout(() => {
|
||||||
|
if (routeNodeId.value) {
|
||||||
|
updateNodeRoute(routeNodeId.value);
|
||||||
|
}
|
||||||
|
}, 500);
|
||||||
|
|
||||||
emitPostMessageReady();
|
emitPostMessageReady();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -8,8 +8,10 @@ export async function loadSubWorkflowInputs(
|
|||||||
const { fields, dataMode, subworkflowInfo } = await loadWorkflowInputMappings.bind(this)();
|
const { fields, dataMode, subworkflowInfo } = await loadWorkflowInputMappings.bind(this)();
|
||||||
let emptyFieldsNotice: string | undefined;
|
let emptyFieldsNotice: string | undefined;
|
||||||
if (fields.length === 0) {
|
if (fields.length === 0) {
|
||||||
const subworkflowLink = subworkflowInfo?.id
|
const { triggerId, workflowId } = subworkflowInfo ?? {};
|
||||||
? `<a href="/workflow/${subworkflowInfo?.id}" target="_blank">sub-workflow’s trigger</a>`
|
const path = (workflowId ?? '') + (triggerId ? `/${triggerId.slice(0, 6)}` : '');
|
||||||
|
const subworkflowLink = workflowId
|
||||||
|
? `<a href="/workflow/${path}" target="_blank">sub-workflow’s trigger</a>`
|
||||||
: 'sub-workflow’s trigger';
|
: 'sub-workflow’s trigger';
|
||||||
|
|
||||||
switch (dataMode) {
|
switch (dataMode) {
|
||||||
|
|||||||
@@ -96,7 +96,12 @@ export function getFieldEntries(context: IWorkflowNodeContext): {
|
|||||||
if (Array.isArray(result)) {
|
if (Array.isArray(result)) {
|
||||||
const dataMode = String(inputSource);
|
const dataMode = String(inputSource);
|
||||||
const workflow = context.getWorkflow();
|
const workflow = context.getWorkflow();
|
||||||
return { fields: result, dataMode, subworkflowInfo: { id: workflow.id } };
|
const node = context.getNode();
|
||||||
|
return {
|
||||||
|
fields: result,
|
||||||
|
dataMode,
|
||||||
|
subworkflowInfo: { workflowId: workflow.id, triggerId: node.id },
|
||||||
|
};
|
||||||
}
|
}
|
||||||
throw new NodeOperationError(context.getNode(), result);
|
throw new NodeOperationError(context.getNode(), result);
|
||||||
}
|
}
|
||||||
@@ -153,7 +158,7 @@ export async function loadWorkflowInputMappings(
|
|||||||
const nodeLoadContext = await this.getWorkflowNodeContext(EXECUTE_WORKFLOW_TRIGGER_NODE_TYPE);
|
const nodeLoadContext = await this.getWorkflowNodeContext(EXECUTE_WORKFLOW_TRIGGER_NODE_TYPE);
|
||||||
let fields: ResourceMapperField[] = [];
|
let fields: ResourceMapperField[] = [];
|
||||||
let dataMode: string = PASSTHROUGH;
|
let dataMode: string = PASSTHROUGH;
|
||||||
let subworkflowInfo: { id?: string } | undefined;
|
let subworkflowInfo: { workflowId?: string; triggerId?: string } | undefined;
|
||||||
|
|
||||||
if (nodeLoadContext) {
|
if (nodeLoadContext) {
|
||||||
const fieldValues = getFieldEntries(nodeLoadContext);
|
const fieldValues = getFieldEntries(nodeLoadContext);
|
||||||
|
|||||||
@@ -2658,7 +2658,7 @@ export interface ResourceMapperFields {
|
|||||||
export interface WorkflowInputsData {
|
export interface WorkflowInputsData {
|
||||||
fields: ResourceMapperField[];
|
fields: ResourceMapperField[];
|
||||||
dataMode: string;
|
dataMode: string;
|
||||||
subworkflowInfo?: { id?: string };
|
subworkflowInfo?: { workflowId?: string; triggerId?: string };
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ResourceMapperField {
|
export interface ResourceMapperField {
|
||||||
|
|||||||
Reference in New Issue
Block a user