mirror of
https://github.com/Abdulazizzn/n8n-enterprise-unlocked.git
synced 2025-12-17 18:12:04 +00:00
chore: Move router usage out of useCanvasOperation and useWorkflowHelpers (no-changelog) (#16041)
This commit is contained in:
@@ -1,10 +1,7 @@
|
||||
import {
|
||||
HTTP_REQUEST_NODE_TYPE,
|
||||
MODAL_CONFIRM,
|
||||
NON_ACTIVATABLE_TRIGGER_NODE_TYPES,
|
||||
PLACEHOLDER_EMPTY_WORKFLOW_ID,
|
||||
PLACEHOLDER_FILLED_AT_EXECUTION_TIME,
|
||||
VIEWS,
|
||||
} from '@/constants';
|
||||
|
||||
import type {
|
||||
@@ -21,7 +18,6 @@ import type {
|
||||
IRunExecutionData,
|
||||
IWebhookDescription,
|
||||
IWorkflowDataProxyAdditionalKeys,
|
||||
IWorkflowSettings,
|
||||
NodeParameterValue,
|
||||
Workflow,
|
||||
} from 'n8n-workflow';
|
||||
@@ -32,19 +28,14 @@ import type {
|
||||
INodeTypesMaxCount,
|
||||
INodeUi,
|
||||
ITag,
|
||||
IUpdateInformation,
|
||||
IWorkflowData,
|
||||
IWorkflowDataCreate,
|
||||
IWorkflowDataUpdate,
|
||||
IWorkflowDb,
|
||||
NotificationOptions,
|
||||
TargetItem,
|
||||
WorkflowTitleStatus,
|
||||
XYPosition,
|
||||
} from '@/Interface';
|
||||
|
||||
import { useMessage } from '@/composables/useMessage';
|
||||
import { useToast } from '@/composables/useToast';
|
||||
import { useNodeHelpers } from '@/composables/useNodeHelpers';
|
||||
|
||||
import get from 'lodash/get';
|
||||
@@ -53,19 +44,12 @@ import { useEnvironmentsStore } from '@/stores/environments.ee.store';
|
||||
import { useRootStore } from '@n8n/stores/useRootStore';
|
||||
import { useNDVStore } from '@/stores/ndv.store';
|
||||
import { useNodeTypesStore } from '@/stores/nodeTypes.store';
|
||||
import { useTemplatesStore } from '@/stores/templates.store';
|
||||
import { useUIStore } from '@/stores/ui.store';
|
||||
import { useWorkflowsStore } from '@/stores/workflows.store';
|
||||
import { getSourceItems } from '@/utils/pairedItemUtils';
|
||||
import { getCredentialTypeName, isCredentialOnlyNodeType } from '@/utils/credentialOnlyNodes';
|
||||
import { useDocumentTitle } from '@/composables/useDocumentTitle';
|
||||
import { useExternalHooks } from '@/composables/useExternalHooks';
|
||||
import { useCanvasStore } from '@/stores/canvas.store';
|
||||
import { useSourceControlStore } from '@/stores/sourceControl.store';
|
||||
import { tryToParseNumber } from '@/utils/typesUtils';
|
||||
import { useI18n } from '@n8n/i18n';
|
||||
import type { useRouter } from 'vue-router';
|
||||
import { useTelemetry } from '@/composables/useTelemetry';
|
||||
import { useProjectsStore } from '@/stores/projects.store';
|
||||
import { useTagsStore } from '@/stores/tags.store';
|
||||
import { useWorkflowsEEStore } from '@/stores/workflows.ee.store';
|
||||
@@ -450,11 +434,9 @@ export function executeData(
|
||||
return executeData;
|
||||
}
|
||||
|
||||
export function useWorkflowHelpers(options: { router: ReturnType<typeof useRouter> }) {
|
||||
const router = options.router;
|
||||
export function useWorkflowHelpers() {
|
||||
const nodeTypesStore = useNodeTypesStore();
|
||||
const rootStore = useRootStore();
|
||||
const templatesStore = useTemplatesStore();
|
||||
const workflowsStore = useWorkflowsStore();
|
||||
const workflowsEEStore = useWorkflowsEEStore();
|
||||
const uiStore = useUIStore();
|
||||
@@ -462,10 +444,7 @@ export function useWorkflowHelpers(options: { router: ReturnType<typeof useRoute
|
||||
const projectsStore = useProjectsStore();
|
||||
const tagsStore = useTagsStore();
|
||||
|
||||
const toast = useToast();
|
||||
const message = useMessage();
|
||||
const i18n = useI18n();
|
||||
const telemetry = useTelemetry();
|
||||
const documentTitle = useDocumentTitle();
|
||||
|
||||
const setDocumentTitle = (workflowName: string, status: WorkflowTitleStatus) => {
|
||||
@@ -814,319 +793,6 @@ export function useWorkflowHelpers(options: { router: ReturnType<typeof useRoute
|
||||
}
|
||||
}
|
||||
|
||||
function isNodeActivatable(node: INode): boolean {
|
||||
if (node.disabled) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const nodeType = nodeTypesStore.getNodeType(node.type, node.typeVersion);
|
||||
|
||||
return (
|
||||
nodeType !== null &&
|
||||
nodeType.group.includes('trigger') &&
|
||||
!NON_ACTIVATABLE_TRIGGER_NODE_TYPES.includes(node.type)
|
||||
);
|
||||
}
|
||||
|
||||
async function getWorkflowDeactivationInfo(
|
||||
workflowId: string,
|
||||
request: IWorkflowDataUpdate,
|
||||
): Promise<Partial<NotificationOptions> | undefined> {
|
||||
const missingActivatableTriggerNode =
|
||||
request.nodes !== undefined && !request.nodes.some(isNodeActivatable);
|
||||
|
||||
if (missingActivatableTriggerNode) {
|
||||
// Automatically deactivate if all activatable triggers are removed
|
||||
return {
|
||||
title: i18n.baseText('workflows.deactivated'),
|
||||
message: i18n.baseText('workflowActivator.thisWorkflowHasNoTriggerNodes'),
|
||||
type: 'info',
|
||||
};
|
||||
}
|
||||
|
||||
const conflictData = await checkConflictingWebhooks(workflowId);
|
||||
|
||||
if (conflictData) {
|
||||
// Workflow should not be active if there is live webhook with the same path
|
||||
return {
|
||||
title: 'Conflicting Webhook Path',
|
||||
message: `Workflow set to inactive: Workflow set to inactive: Live webhook in another workflow uses same path as node '${conflictData.trigger.name}'.`,
|
||||
type: 'error',
|
||||
};
|
||||
}
|
||||
|
||||
return undefined;
|
||||
}
|
||||
|
||||
async function saveCurrentWorkflow(
|
||||
{ id, name, tags }: { id?: string; name?: string; tags?: string[] } = {},
|
||||
redirect = true,
|
||||
forceSave = false,
|
||||
): Promise<boolean> {
|
||||
const readOnlyEnv = useSourceControlStore().preferences.branchReadOnly;
|
||||
if (readOnlyEnv) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const isLoading = useCanvasStore().isLoading;
|
||||
const currentWorkflow = id || (router.currentRoute.value.params.name as string);
|
||||
const parentFolderId = router.currentRoute.value.query.parentFolderId as string;
|
||||
|
||||
if (!currentWorkflow || ['new', PLACEHOLDER_EMPTY_WORKFLOW_ID].includes(currentWorkflow)) {
|
||||
return await saveAsNewWorkflow({ name, tags, parentFolderId }, redirect);
|
||||
}
|
||||
|
||||
// Workflow exists already so update it
|
||||
try {
|
||||
if (!forceSave && isLoading) {
|
||||
return true;
|
||||
}
|
||||
uiStore.addActiveAction('workflowSaving');
|
||||
|
||||
const workflowDataRequest: IWorkflowDataUpdate = await getWorkflowDataToSave();
|
||||
|
||||
// This can happen if the user has another workflow in the browser history and navigates
|
||||
// via the browser back button, encountering our warning dialog with the new route already set
|
||||
if (workflowDataRequest.id !== currentWorkflow) {
|
||||
throw new Error('Attempted to save a workflow different from the current workflow');
|
||||
}
|
||||
|
||||
if (name) {
|
||||
workflowDataRequest.name = name.trim();
|
||||
}
|
||||
|
||||
if (tags) {
|
||||
workflowDataRequest.tags = tags;
|
||||
}
|
||||
|
||||
workflowDataRequest.versionId = workflowsStore.workflowVersionId;
|
||||
|
||||
const deactivateReason = await getWorkflowDeactivationInfo(
|
||||
currentWorkflow,
|
||||
workflowDataRequest,
|
||||
);
|
||||
|
||||
if (deactivateReason !== undefined) {
|
||||
workflowDataRequest.active = false;
|
||||
|
||||
if (workflowsStore.isWorkflowActive) {
|
||||
toast.showMessage(deactivateReason);
|
||||
|
||||
workflowsStore.setWorkflowInactive(currentWorkflow);
|
||||
}
|
||||
}
|
||||
|
||||
const workflowData = await workflowsStore.updateWorkflow(
|
||||
currentWorkflow,
|
||||
workflowDataRequest,
|
||||
forceSave,
|
||||
);
|
||||
workflowsStore.setWorkflowVersionId(workflowData.versionId);
|
||||
|
||||
if (name) {
|
||||
workflowsStore.setWorkflowName({ newName: workflowData.name, setStateDirty: false });
|
||||
}
|
||||
|
||||
if (tags) {
|
||||
const createdTags = (workflowData.tags || []) as ITag[];
|
||||
const tagIds = createdTags.map((tag: ITag): string => tag.id);
|
||||
workflowsStore.setWorkflowTagIds(tagIds);
|
||||
}
|
||||
|
||||
uiStore.stateIsDirty = false;
|
||||
uiStore.removeActiveAction('workflowSaving');
|
||||
void useExternalHooks().run('workflow.afterUpdate', { workflowData });
|
||||
|
||||
return true;
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
|
||||
uiStore.removeActiveAction('workflowSaving');
|
||||
|
||||
if (error.errorCode === 100) {
|
||||
telemetry.track('User attempted to save locked workflow', {
|
||||
workflowId: currentWorkflow,
|
||||
sharing_role: getWorkflowProjectRole(currentWorkflow),
|
||||
});
|
||||
|
||||
const url = router.resolve({
|
||||
name: VIEWS.WORKFLOW,
|
||||
params: { name: currentWorkflow },
|
||||
}).href;
|
||||
|
||||
const overwrite = await message.confirm(
|
||||
i18n.baseText('workflows.concurrentChanges.confirmMessage.message', {
|
||||
interpolate: {
|
||||
url,
|
||||
},
|
||||
}),
|
||||
i18n.baseText('workflows.concurrentChanges.confirmMessage.title'),
|
||||
{
|
||||
confirmButtonText: i18n.baseText(
|
||||
'workflows.concurrentChanges.confirmMessage.confirmButtonText',
|
||||
),
|
||||
cancelButtonText: i18n.baseText(
|
||||
'workflows.concurrentChanges.confirmMessage.cancelButtonText',
|
||||
),
|
||||
},
|
||||
);
|
||||
|
||||
if (overwrite === MODAL_CONFIRM) {
|
||||
return await saveCurrentWorkflow({ id, name, tags }, redirect, true);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
toast.showMessage({
|
||||
title: i18n.baseText('workflowHelpers.showMessage.title'),
|
||||
message: error.message,
|
||||
type: 'error',
|
||||
});
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
async function saveAsNewWorkflow(
|
||||
{
|
||||
name,
|
||||
tags,
|
||||
resetWebhookUrls,
|
||||
resetNodeIds,
|
||||
openInNewWindow,
|
||||
parentFolderId,
|
||||
data,
|
||||
}: {
|
||||
name?: string;
|
||||
tags?: string[];
|
||||
resetWebhookUrls?: boolean;
|
||||
openInNewWindow?: boolean;
|
||||
resetNodeIds?: boolean;
|
||||
parentFolderId?: string;
|
||||
data?: IWorkflowDataCreate;
|
||||
} = {},
|
||||
redirect = true,
|
||||
): Promise<boolean> {
|
||||
try {
|
||||
uiStore.addActiveAction('workflowSaving');
|
||||
|
||||
const workflowDataRequest: IWorkflowDataCreate = data || (await getWorkflowDataToSave());
|
||||
const changedNodes = {} as IDataObject;
|
||||
|
||||
if (resetNodeIds) {
|
||||
workflowDataRequest.nodes = workflowDataRequest.nodes!.map((node) => {
|
||||
nodeHelpers.assignNodeId(node);
|
||||
|
||||
return node;
|
||||
});
|
||||
}
|
||||
|
||||
if (resetWebhookUrls) {
|
||||
workflowDataRequest.nodes = workflowDataRequest.nodes!.map((node) => {
|
||||
if (node.webhookId) {
|
||||
const newId = nodeHelpers.assignWebhookId(node);
|
||||
node.parameters.path = newId;
|
||||
changedNodes[node.name] = node.webhookId;
|
||||
}
|
||||
return node;
|
||||
});
|
||||
}
|
||||
|
||||
if (name) {
|
||||
workflowDataRequest.name = name.trim();
|
||||
}
|
||||
|
||||
if (tags) {
|
||||
workflowDataRequest.tags = tags;
|
||||
}
|
||||
|
||||
if (parentFolderId) {
|
||||
workflowDataRequest.parentFolderId = parentFolderId;
|
||||
}
|
||||
const workflowData = await workflowsStore.createNewWorkflow(workflowDataRequest);
|
||||
|
||||
workflowsStore.addWorkflow(workflowData);
|
||||
|
||||
if (openInNewWindow) {
|
||||
const routeData = router.resolve({
|
||||
name: VIEWS.WORKFLOW,
|
||||
params: { name: workflowData.id },
|
||||
});
|
||||
window.open(routeData.href, '_blank');
|
||||
uiStore.removeActiveAction('workflowSaving');
|
||||
return true;
|
||||
}
|
||||
|
||||
// workflow should not be active if there is live webhook with the same path
|
||||
if (workflowData.active) {
|
||||
const conflict = await checkConflictingWebhooks(workflowData.id);
|
||||
if (conflict) {
|
||||
workflowData.active = false;
|
||||
|
||||
toast.showMessage({
|
||||
title: 'Conflicting Webhook Path',
|
||||
message: `Workflow set to inactive: Live webhook in another workflow uses same path as node '${conflict.trigger.name}'.`,
|
||||
type: 'error',
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
workflowsStore.setActive(workflowData.active || false);
|
||||
workflowsStore.setWorkflowId(workflowData.id);
|
||||
workflowsStore.setWorkflowVersionId(workflowData.versionId);
|
||||
workflowsStore.setWorkflowName({ newName: workflowData.name, setStateDirty: false });
|
||||
workflowsStore.setWorkflowSettings((workflowData.settings as IWorkflowSettings) || {});
|
||||
uiStore.stateIsDirty = false;
|
||||
Object.keys(changedNodes).forEach((nodeName) => {
|
||||
const changes = {
|
||||
key: 'webhookId',
|
||||
value: changedNodes[nodeName],
|
||||
name: nodeName,
|
||||
} as IUpdateInformation;
|
||||
workflowsStore.setNodeValue(changes);
|
||||
});
|
||||
|
||||
const createdTags = (workflowData.tags || []) as ITag[];
|
||||
const tagIds = createdTags.map((tag: ITag): string => tag.id);
|
||||
workflowsStore.setWorkflowTagIds(tagIds);
|
||||
|
||||
const templateId = router.currentRoute.value.query.templateId;
|
||||
if (templateId) {
|
||||
telemetry.track('User saved new workflow from template', {
|
||||
template_id: tryToParseNumber(String(templateId)),
|
||||
workflow_id: workflowData.id,
|
||||
wf_template_repo_session_id: templatesStore.previousSessionId,
|
||||
});
|
||||
}
|
||||
|
||||
if (redirect) {
|
||||
await router.replace({
|
||||
name: VIEWS.WORKFLOW,
|
||||
params: { name: workflowData.id },
|
||||
query: { action: 'workflowSave' },
|
||||
});
|
||||
}
|
||||
|
||||
uiStore.removeActiveAction('workflowSaving');
|
||||
uiStore.stateIsDirty = false;
|
||||
void useExternalHooks().run('workflow.afterUpdate', { workflowData });
|
||||
|
||||
getCurrentWorkflow(true); // refresh cache
|
||||
return true;
|
||||
} catch (e) {
|
||||
uiStore.removeActiveAction('workflowSaving');
|
||||
|
||||
toast.showMessage({
|
||||
title: i18n.baseText('workflowHelpers.showMessage.title'),
|
||||
message: (e as Error).message,
|
||||
type: 'error',
|
||||
});
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Updates the position of all the nodes that the top-left node
|
||||
// is at the given position
|
||||
function updateNodePositions(
|
||||
@@ -1292,8 +958,6 @@ export function useWorkflowHelpers(options: { router: ReturnType<typeof useRoute
|
||||
getWebhookUrl,
|
||||
resolveExpression,
|
||||
updateWorkflow,
|
||||
saveCurrentWorkflow,
|
||||
saveAsNewWorkflow,
|
||||
updateNodePositions,
|
||||
removeForeignCredentialsFromWorkflow,
|
||||
getWorkflowProjectRole,
|
||||
|
||||
Reference in New Issue
Block a user