refactor(editor): Refactor workflowHelpers mixin to composable (no-changelog) (#8600)

This commit is contained in:
oleg 2024-02-12 10:45:05 +01:00 committed by GitHub
parent a6211c9a5d
commit 510bf8905d
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
47 changed files with 1487 additions and 1403 deletions

View file

@ -4,7 +4,7 @@ import InputTriple from '@/components/InputTriple/InputTriple.vue';
import ParameterInputFull from '@/components/ParameterInputFull.vue';
import ParameterInputHint from '@/components/ParameterInputHint.vue';
import ParameterIssues from '@/components/ParameterIssues.vue';
import { resolveParameter } from '@/mixins/workflowHelpers';
import { resolveParameter } from '@/composables/useWorkflowHelpers';
import { isExpression } from '@/utils/expressions';
import { isObject } from '@jsplumb/util';
import type { AssignmentValue, INodeProperties } from 'n8n-workflow';

View file

@ -3,7 +3,7 @@ import { useNDVStore } from '@/stores/ndv.store';
import { createTestingPinia } from '@pinia/testing';
import userEvent from '@testing-library/user-event';
import { fireEvent, within } from '@testing-library/vue';
import * as workflowHelpers from '@/mixins/workflowHelpers';
import * as workflowHelpers from '@/composables/useWorkflowHelpers';
import AssignmentCollection from '../AssignmentCollection.vue';
import { createPinia, setActivePinia } from 'pinia';

View file

@ -1,6 +1,6 @@
import { isObject } from 'lodash-es';
import type { AssignmentValue, IDataObject } from 'n8n-workflow';
import { resolveParameter } from '@/mixins/workflowHelpers';
import { resolveParameter } from '@/composables/useWorkflowHelpers';
import { v4 as uuid } from 'uuid';
export function nameFromExpression(expression: string): string {

View file

@ -63,7 +63,6 @@ import { python } from '@codemirror/lang-python';
import type { CodeExecutionMode, CodeNodeEditorLanguage } from 'n8n-workflow';
import { CODE_EXECUTION_MODES, CODE_LANGUAGES } from 'n8n-workflow';
import { workflowHelpers } from '@/mixins/workflowHelpers'; // for json field completions
import { ASK_AI_EXPERIMENT, CODE_NODE_TYPE } from '@/constants';
import { codeNodeEditorEventBus } from '@/event-bus';
import { useRootStore } from '@/stores/n8nRoot.store';
@ -83,7 +82,7 @@ export default defineComponent({
components: {
AskAI,
},
mixins: [linterExtension, completerExtension, workflowHelpers],
mixins: [linterExtension, completerExtension],
props: {
aiButtonEnabled: {
type: Boolean,

View file

@ -37,7 +37,6 @@ import { defineComponent } from 'vue';
import { mapStores } from 'pinia';
import type { IN8nPromptResponse } from '@/Interface';
import { VALID_EMAIL_REGEX } from '@/constants';
import { workflowHelpers } from '@/mixins/workflowHelpers';
import Modal from '@/components/Modal.vue';
import { useSettingsStore } from '@/stores/settings.store';
import { useRootStore } from '@/stores/n8nRoot.store';
@ -47,7 +46,6 @@ import { useToast } from '@/composables/useToast';
export default defineComponent({
name: 'ContactPromptModal',
components: { Modal },
mixins: [workflowHelpers],
props: ['modalName'],
setup() {
return {

View file

@ -51,7 +51,6 @@
import { defineComponent } from 'vue';
import { mapStores } from 'pinia';
import { MAX_WORKFLOW_NAME_LENGTH, PLACEHOLDER_EMPTY_WORKFLOW_ID } from '@/constants';
import { workflowHelpers } from '@/mixins/workflowHelpers';
import { useToast } from '@/composables/useToast';
import TagsDropdown from '@/components/TagsDropdown.vue';
import Modal from '@/components/Modal.vue';
@ -63,15 +62,20 @@ import { getWorkflowPermissions } from '@/permissions';
import { useUsersStore } from '@/stores/users.store';
import { createEventBus } from 'n8n-design-system/utils';
import { useCredentialsStore } from '@/stores/credentials.store';
import { useWorkflowHelpers } from '@/composables/useWorkflowHelpers';
import { useRouter } from 'vue-router';
export default defineComponent({
name: 'DuplicateWorkflow',
components: { TagsDropdown, Modal },
mixins: [workflowHelpers],
props: ['modalName', 'isActive', 'data'],
setup() {
const router = useRouter();
const workflowHelpers = useWorkflowHelpers(router);
return {
...useToast(),
workflowHelpers,
};
},
data() {
@ -156,13 +160,13 @@ export default defineComponent({
await this.workflowsStore.fetchWorkflow(this.data.id);
workflowToUpdate = workflow;
this.removeForeignCredentialsFromWorkflow(
this.workflowHelpers.removeForeignCredentialsFromWorkflow(
workflowToUpdate,
this.credentialsStore.allCredentials,
);
}
const saved = await this.saveAsNewWorkflow({
const saved = await this.workflowHelpers.saveAsNewWorkflow({
name,
data: workflowToUpdate,
tags: this.currentTagIds,

View file

@ -45,7 +45,8 @@ import { useWorkflowsStore } from '@/stores/workflows.store';
import { PLACEHOLDER_EMPTY_WORKFLOW_ID, WORKFLOW_SETTINGS_MODAL_KEY } from '@/constants';
import type { IWorkflowSettings } from 'n8n-workflow';
import { deepCopy } from 'n8n-workflow';
import { workflowHelpers } from '@/mixins/workflowHelpers';
import { useWorkflowHelpers } from '@/composables/useWorkflowHelpers';
import { useRouter } from 'vue-router';
interface IWorkflowSaveSettings {
saveFailedExecutions: boolean;
@ -55,13 +56,20 @@ interface IWorkflowSaveSettings {
export default defineComponent({
name: 'ExecutionsInfoAccordion',
mixins: [workflowHelpers],
props: {
initiallyExpanded: {
type: Boolean,
default: false,
},
},
setup() {
const router = useRouter();
const workflowHelpers = useWorkflowHelpers(router);
return {
workflowHelpers,
};
},
data() {
return {
defaultValues: {
@ -211,7 +219,7 @@ export default defineComponent({
} else if (this.$route.params.name && this.$route.params.name !== 'new') {
currentId = this.$route.params.name;
}
const saved = await this.saveCurrentWorkflow({
const saved = await this.workflowHelpers.saveCurrentWorkflow({
id: currentId,
name: this.workflowName,
tags: this.currentWorkflowTagIds,

View file

@ -55,12 +55,11 @@ import { NodeHelpers } from 'n8n-workflow';
import { useMessage } from '@/composables/useMessage';
import { useToast } from '@/composables/useToast';
import { v4 as uuid } from 'uuid';
import type { Route } from 'vue-router';
import { useRouter, type Route } from 'vue-router';
import { executionHelpers } from '@/mixins/executionsHelpers';
import { range as _range } from 'lodash-es';
import { NO_NETWORK_ERROR_CODE } from '@/utils/apiUtils';
import { getNodeViewTab } from '@/utils/canvasUtils';
import { workflowHelpers } from '@/mixins/workflowHelpers';
import { useWorkflowsStore } from '@/stores/workflows.store';
import { useUIStore } from '@/stores/ui.store';
import { useSettingsStore } from '@/stores/settings.store';
@ -69,6 +68,7 @@ import { useTagsStore } from '@/stores/tags.store';
import { executionFilterToQueryFilter } from '@/utils/executionUtils';
import { useExternalHooks } from '@/composables/useExternalHooks';
import { useDebounce } from '@/composables/useDebounce';
import { useWorkflowHelpers } from '@/composables/useWorkflowHelpers';
// Number of execution pages that are fetched before temporary execution card is shown
const MAX_LOADING_ATTEMPTS = 5;
@ -80,13 +80,16 @@ export default defineComponent({
components: {
ExecutionsSidebar,
},
mixins: [executionHelpers, workflowHelpers],
mixins: [executionHelpers],
setup() {
const externalHooks = useExternalHooks();
const router = useRouter();
const workflowHelpers = useWorkflowHelpers(router);
const { callDebounced } = useDebounce();
return {
externalHooks,
workflowHelpers,
callDebounced,
...useToast(),
...useMessage(),
@ -168,7 +171,7 @@ export default defineComponent({
);
if (confirmModal === MODAL_CONFIRM) {
const saved = await this.saveCurrentWorkflow({}, false);
const saved = await this.workflowHelpers.saveCurrentWorkflow({}, false);
if (saved) {
await this.settingsStore.fetchPromptsData();
}

View file

@ -8,7 +8,6 @@ import { EditorView, keymap } from '@codemirror/view';
import { EditorState, Prec } from '@codemirror/state';
import { history, redo, undo } from '@codemirror/commands';
import { workflowHelpers } from '@/mixins/workflowHelpers';
import { expressionManager } from '@/mixins/expressionManager';
import { completionManager } from '@/mixins/completionManager';
import { expressionInputHandler } from '@/plugins/codemirror/inputHandlers/expression.inputHandler';
@ -22,7 +21,7 @@ import type { IVariableItemSelected } from '@/Interface';
export default defineComponent({
name: 'ExpressionEditorModalInput',
mixins: [expressionManager, completionManager, workflowHelpers],
mixins: [expressionManager, completionManager],
props: {
modelValue: {
type: String,

View file

@ -4,7 +4,7 @@ import InputTriple from '@/components/InputTriple/InputTriple.vue';
import ParameterInputFull from '@/components/ParameterInputFull.vue';
import ParameterIssues from '@/components/ParameterIssues.vue';
import { useI18n } from '@/composables/useI18n';
import { resolveParameter } from '@/mixins/workflowHelpers';
import { resolveParameter } from '@/composables/useWorkflowHelpers';
import { DateTime } from 'luxon';
import {
FilterError,
@ -240,7 +240,7 @@ const onBlur = (): void => {
@operatorChange="onOperatorChange"
></OperatorSelect>
</template>
<template #right="{ breakpoint }" v-if="!operator.singleValue">
<template v-if="!operator.singleValue" #right="{ breakpoint }">
<ParameterInputFull
:key="rightParameter.type"
display-options

View file

@ -21,7 +21,7 @@ import { useI18n } from '@/composables/useI18n';
import { useDebounce } from '@/composables/useDebounce';
import Condition from './Condition.vue';
import CombinatorSelect from './CombinatorSelect.vue';
import { resolveParameter } from '@/mixins/workflowHelpers';
import { resolveParameter } from '@/composables/useWorkflowHelpers';
import { v4 as uuid } from 'uuid';
interface Props {

View file

@ -12,7 +12,6 @@ import { history, redo, undo } from '@codemirror/commands';
import { acceptCompletion, autocompletion, completionStatus } from '@codemirror/autocomplete';
import { useNDVStore } from '@/stores/ndv.store';
import { workflowHelpers } from '@/mixins/workflowHelpers';
import { expressionManager } from '@/mixins/expressionManager';
import { highlighter } from '@/plugins/codemirror/resolvableHighlighter';
import { expressionInputHandler } from '@/plugins/codemirror/inputHandlers/expression.inputHandler';
@ -25,7 +24,7 @@ const editableConf = new Compartment();
export default defineComponent({
name: 'InlineExpressionEditorInput',
mixins: [completionManager, expressionManager, workflowHelpers],
mixins: [completionManager, expressionManager],
props: {
modelValue: {
type: String,

View file

@ -174,7 +174,6 @@ import type {
Workflow,
} from 'n8n-workflow';
import RunData from './RunData.vue';
import { workflowHelpers } from '@/mixins/workflowHelpers';
import NodeExecuteButton from './NodeExecuteButton.vue';
import WireMeUp from './WireMeUp.vue';
import {
@ -186,13 +185,13 @@ import {
import { useWorkflowsStore } from '@/stores/workflows.store';
import { useNDVStore } from '@/stores/ndv.store';
import { useNodeTypesStore } from '@/stores/nodeTypes.store';
import { useUIStore } from '@/stores/ui.store';
type MappingMode = 'debugging' | 'mapping';
export default defineComponent({
name: 'InputPanel',
components: { RunData, NodeExecuteButton, WireMeUp },
mixins: [workflowHelpers],
props: {
currentNodeName: {
type: String,
@ -236,7 +235,7 @@ export default defineComponent({
};
},
computed: {
...mapStores(useNodeTypesStore, useNDVStore, useWorkflowsStore),
...mapStores(useNodeTypesStore, useNDVStore, useWorkflowsStore, useUIStore),
focusedMappableInput(): string {
return this.ndvStore.focusedMappableInput;
},

View file

@ -29,7 +29,6 @@ import {
VIEWS,
} from '@/constants';
import type { INodeUi, ITabBarItem } from '@/Interface';
import { workflowHelpers } from '@/mixins/workflowHelpers';
import { useNDVStore } from '@/stores/ndv.store';
import { useSourceControlStore } from '@/stores/sourceControl.store';
import { useUIStore } from '@/stores/ui.store';
@ -40,13 +39,11 @@ export default defineComponent({
WorkflowDetails,
TabBar,
},
mixins: [pushConnection, workflowHelpers],
mixins: [pushConnection],
setup(props, ctx) {
return {
// eslint-disable-next-line @typescript-eslint/no-misused-promises
...pushConnection.setup?.(props, ctx),
// eslint-disable-next-line @typescript-eslint/no-misused-promises
...workflowHelpers.setup?.(props, ctx),
};
},
data() {

View file

@ -158,7 +158,6 @@ import ShortenName from '@/components/ShortenName.vue';
import TagsContainer from '@/components/TagsContainer.vue';
import PushConnectionTracker from '@/components/PushConnectionTracker.vue';
import WorkflowActivator from '@/components/WorkflowActivator.vue';
import { workflowHelpers } from '@/mixins/workflowHelpers';
import SaveButton from '@/components/SaveButton.vue';
import TagsDropdown from '@/components/TagsDropdown.vue';
import InlineTextEdit from '@/components/InlineTextEdit.vue';
@ -185,6 +184,8 @@ import { createEventBus } from 'n8n-design-system/utils';
import { nodeViewEventBus } from '@/event-bus';
import { hasPermission } from '@/rbac/permissions';
import { useCanvasStore } from '@/stores/canvas.store';
import { useRouter } from 'vue-router';
import { useWorkflowHelpers } from '@/composables/useWorkflowHelpers';
const hasChanged = (prev: string[], curr: string[]) => {
if (prev.length !== curr.length) {
@ -208,7 +209,6 @@ export default defineComponent({
BreakpointsObserver,
CollaborationPane,
},
mixins: [workflowHelpers],
props: {
readOnly: {
type: Boolean,
@ -216,7 +216,11 @@ export default defineComponent({
},
},
setup() {
const router = useRouter();
const workflowHelpers = useWorkflowHelpers(router);
return {
workflowHelpers,
...useTitleChange(),
...useToast(),
...useMessage(),
@ -390,7 +394,7 @@ export default defineComponent({
} else if (this.$route.params.name && this.$route.params.name !== 'new') {
currentId = this.$route.params.name;
}
const saved = await this.saveCurrentWorkflow({
const saved = await this.workflowHelpers.saveCurrentWorkflow({
id: currentId,
name: this.workflowName,
tags: this.currentWorkflowTagIds,
@ -443,7 +447,7 @@ export default defineComponent({
}
this.tagsSaving = true;
const saved = await this.saveCurrentWorkflow({ tags });
const saved = await this.workflowHelpers.saveCurrentWorkflow({ tags });
this.$telemetry.track('User edited workflow tags', {
workflow_id: this.currentWorkflowId,
new_tag_count: tags.length,
@ -494,7 +498,7 @@ export default defineComponent({
return;
}
const saved = await this.saveCurrentWorkflow({ name });
const saved = await this.workflowHelpers.saveCurrentWorkflow({ name });
if (saved) {
this.isNameEditEnabled = false;
}
@ -539,7 +543,7 @@ export default defineComponent({
break;
}
case WORKFLOW_MENU_ACTIONS.DOWNLOAD: {
const workflowData = await this.getWorkflowDataToSave();
const workflowData = await this.workflowHelpers.getWorkflowDataToSave();
const { tags, ...data } = workflowData;
const exportData: IWorkflowToShare = {
...data,

View file

@ -189,7 +189,6 @@ import {
WAIT_TIME_UNLIMITED,
} from '@/constants';
import { nodeBase } from '@/mixins/nodeBase';
import { workflowHelpers } from '@/mixins/workflowHelpers';
import type {
ConnectionTypes,
IExecutionsSummary,
@ -227,7 +226,7 @@ export default defineComponent({
FontAwesomeIcon,
NodeIcon,
},
mixins: [nodeBase, workflowHelpers],
mixins: [nodeBase],
props: {
isProductionExecutionPreview: {
type: Boolean,

View file

@ -146,7 +146,6 @@ import type {
} from 'n8n-workflow';
import { jsonParse, NodeHelpers, NodeConnectionType } from 'n8n-workflow';
import type { IExecutionResponse, INodeUi, IUpdateInformation, TargetItem } from '@/Interface';
import { workflowHelpers } from '@/mixins/workflowHelpers';
import NodeSettings from '@/components/NodeSettings.vue';
import NDVDraggablePanels from './NDVDraggablePanels.vue';
@ -174,6 +173,8 @@ import { useNodeHelpers } from '@/composables/useNodeHelpers';
import { useMessage } from '@/composables/useMessage';
import { useExternalHooks } from '@/composables/useExternalHooks';
import { usePinnedData } from '@/composables/usePinnedData';
import { useRouter } from 'vue-router';
import { useWorkflowHelpers } from '@/composables/useWorkflowHelpers';
export default defineComponent({
name: 'NodeDetailsView',
@ -184,7 +185,7 @@ export default defineComponent({
NDVDraggablePanels,
TriggerPanel,
},
mixins: [workflowHelpers, workflowActivate],
mixins: [workflowActivate],
props: {
readOnly: {
type: Boolean,
@ -203,11 +204,14 @@ export default defineComponent({
const nodeHelpers = useNodeHelpers();
const { activeNode } = storeToRefs(ndvStore);
const pinnedData = usePinnedData(activeNode);
const router = useRouter();
const workflowHelpers = useWorkflowHelpers(router);
return {
externalHooks,
nodeHelpers,
pinnedData,
workflowHelpers,
...useDeviceSupport(),
...useMessage(),
// eslint-disable-next-line @typescript-eslint/no-misused-promises
@ -289,7 +293,7 @@ export default defineComponent({
);
},
workflow(): Workflow {
return this.getCurrentWorkflow();
return this.workflowHelpers.getCurrentWorkflow();
},
hasOutputConnection() {
if (!this.activeNode) return false;
@ -482,7 +486,7 @@ export default defineComponent({
nodeSubtitle: this.nodeHelpers.getNodeSubtitle(
node,
this.activeNodeType,
this.getCurrentWorkflow(),
this.workflowHelpers.getCurrentWorkflow(),
),
});

View file

@ -14,8 +14,8 @@
:size="size"
:icon="!isListeningForEvents && 'flask'"
:transparent-background="transparent"
@click="onClick"
:title="!isTriggerNode ? $locale.baseText('ndv.execute.testNode.description') : ''"
@click="onClick"
/>
</div>
</n8n-tooltip>

View file

@ -29,7 +29,7 @@
<div v-if="isWebhookMethodVisible(webhook)" class="webhook-wrapper">
<div class="http-field">
<div class="http-method">
{{ getWebhookExpressionValue(webhook, 'httpMethod') }}<br />
{{ workflowHelpers.getWebhookExpressionValue(webhook, 'httpMethod') }}<br />
</div>
</div>
<div class="url-field">
@ -62,21 +62,23 @@ import {
OPEN_URL_PANEL_TRIGGER_NODE_TYPES,
PRODUCTION_ONLY_TRIGGER_NODE_TYPES,
} from '@/constants';
import { workflowHelpers } from '@/mixins/workflowHelpers';
import { useClipboard } from '@/composables/useClipboard';
import { useWorkflowHelpers } from '@/composables/useWorkflowHelpers';
import { useRouter } from 'vue-router';
export default defineComponent({
name: 'NodeWebhooks',
mixins: [workflowHelpers],
props: [
'node', // NodeUi
'nodeType', // INodeTypeDescription
],
setup() {
const router = useRouter();
const clipboard = useClipboard();
const workflowHelpers = useWorkflowHelpers(router);
return {
clipboard,
workflowHelpers,
...useToast(),
};
},
@ -102,7 +104,7 @@ export default defineComponent({
visibleWebhookUrls(): IWebhookDescription[] {
return this.webhooksNode.filter((webhook) => {
if (typeof webhook.ndvHideUrl === 'string') {
return !this.getWebhookExpressionValue(webhook, 'ndvHideUrl');
return !this.workflowHelpers.getWebhookExpressionValue(webhook, 'ndvHideUrl');
}
return !webhook.ndvHideUrl;
@ -184,7 +186,7 @@ export default defineComponent({
},
getWebhookUrlDisplay(webhookData: IWebhookDescription): string {
if (this.node) {
return this.getWebhookUrl(
return this.workflowHelpers.getWebhookUrl(
webhookData,
this.node,
this.isProductionOnly ? 'production' : this.showUrlFor,
@ -194,7 +196,7 @@ export default defineComponent({
},
isWebhookMethodVisible(webhook: IWebhookDescription): boolean {
if (typeof webhook.ndvHideMethod === 'string') {
return !this.getWebhookExpressionValue(webhook, 'ndvHideMethod');
return !this.workflowHelpers.getWebhookExpressionValue(webhook, 'ndvHideMethod');
}
return !webhook.ndvHideMethod;

View file

@ -502,7 +502,6 @@ import JsEditor from '@/components/JsEditor/JsEditor.vue';
import JsonEditor from '@/components/JsonEditor/JsonEditor.vue';
import SqlEditor from '@/components/SqlEditor/SqlEditor.vue';
import { workflowHelpers } from '@/mixins/workflowHelpers';
import { hasExpressionMapping, isValueExpression } from '@/utils/nodeTypesUtils';
import { isResourceLocatorValue } from '@/utils/typeGuards';
@ -523,6 +522,8 @@ import type { N8nInput } from 'n8n-design-system';
import { isCredentialOnlyNodeType } from '@/utils/credentialOnlyNodes';
import { useExternalHooks } from '@/composables/useExternalHooks';
import { useDebounce } from '@/composables/useDebounce';
import { useWorkflowHelpers } from '@/composables/useWorkflowHelpers';
import { useRouter } from 'vue-router';
type Picker = { $emit: (arg0: string, arg1: Date) => void };
@ -541,7 +542,6 @@ export default defineComponent({
ResourceLocator,
TextEdit,
},
mixins: [workflowHelpers],
props: {
additionalExpressionData: {
type: Object as PropType<IDataObject>,
@ -618,11 +618,14 @@ export default defineComponent({
const i18n = useI18n();
const nodeHelpers = useNodeHelpers();
const { callDebounced } = useDebounce();
const router = useRouter();
const workflowHelpers = useWorkflowHelpers(router);
return {
externalHooks,
i18n,
nodeHelpers,
workflowHelpers,
callDebounced,
};
},
@ -724,7 +727,7 @@ export default defineComponent({
// Get the resolved parameter values of the current node
const currentNodeParameters = this.ndvStore.activeNode?.parameters;
try {
const resolvedNodeParameters = this.resolveParameter(currentNodeParameters);
const resolvedNodeParameters = this.workflowHelpers.resolveParameter(currentNodeParameters);
const returnValues: string[] = [];
for (const parameterPath of loadOptionsDependsOn) {
@ -969,7 +972,7 @@ export default defineComponent({
return shortPath.join('.');
},
workflow(): Workflow {
return this.getCurrentWorkflow();
return this.workflowHelpers.getCurrentWorkflow();
},
isResourceLocatorParameter(): boolean {
return this.parameter.type === 'resourceLocator';
@ -1092,7 +1095,7 @@ export default defineComponent({
try {
const currentNodeParameters = (this.ndvStore.activeNode as INodeUi).parameters;
const resolvedNodeParameters = this.resolveRequiredParameters(
const resolvedNodeParameters = this.workflowHelpers.resolveRequiredParameters(
this.parameter,
currentNodeParameters,
) as INodeParameters;

View file

@ -182,7 +182,6 @@ import ResourceMapper from '@/components/ResourceMapper/ResourceMapper.vue';
import FilterConditions from '@/components/FilterConditions/FilterConditions.vue';
import AssignmentCollection from '@/components/AssignmentCollection/AssignmentCollection.vue';
import { KEEP_AUTH_IN_NDV_FOR_NODES } from '@/constants';
import { workflowHelpers } from '@/mixins/workflowHelpers';
import { useNDVStore } from '@/stores/ndv.store';
import { useNodeTypesStore } from '@/stores/nodeTypes.store';
import {
@ -192,6 +191,8 @@ import {
} from '@/utils/nodeTypesUtils';
import { get, set } from 'lodash-es';
import { useNodeHelpers } from '@/composables/useNodeHelpers';
import { useRouter } from 'vue-router';
import { useWorkflowHelpers } from '@/composables/useWorkflowHelpers';
const FixedCollectionParameter = defineAsyncComponent(
async () => await import('./FixedCollectionParameter.vue'),
@ -212,7 +213,6 @@ export default defineComponent({
FilterConditions,
AssignmentCollection,
},
mixins: [workflowHelpers],
props: {
nodeValues: {
type: Object as PropType<INodeParameters>,
@ -250,6 +250,8 @@ export default defineComponent({
setup() {
const nodeHelpers = useNodeHelpers();
const asyncLoadingError = ref(false);
const router = useRouter();
const workflowHelpers = useWorkflowHelpers(router);
// This will catch errors in async components
onErrorCaptured((e, component) => {
@ -274,6 +276,7 @@ export default defineComponent({
return {
nodeHelpers,
asyncLoadingError,
workflowHelpers,
};
},
computed: {
@ -482,7 +485,7 @@ export default defineComponent({
} else {
// Contains probably no expression with a missing parameter so resolve
try {
nodeValues[key] = this.resolveExpression(
nodeValues[key] = this.workflowHelpers.resolveExpression(
rawValues[key],
nodeValues,
) as NodeParameterValue;
@ -559,7 +562,7 @@ export default defineComponent({
// Get the resolved parameter values of the current node
const currentNodeParameters = this.ndvStore.activeNode?.parameters;
try {
const resolvedNodeParameters = this.resolveParameter(currentNodeParameters);
const resolvedNodeParameters = this.workflowHelpers.resolveParameter(currentNodeParameters);
const returnValues: string[] = [];
for (const parameterPath of loadOptionsDependsOn) {

View file

@ -53,7 +53,6 @@ import { defineComponent } from 'vue';
import type { INodeUi, IUpdateInformation, TargetItem } from '@/Interface';
import ParameterInput from '@/components/ParameterInput.vue';
import InputHint from '@/components/ParameterInputHint.vue';
import { workflowHelpers } from '@/mixins/workflowHelpers';
import { useEnvironmentsStore } from '@/stores/environments.ee.store';
import { useExternalSecretsStore } from '@/stores/externalSecrets.ee.store';
import { useNDVStore } from '@/stores/ndv.store';
@ -71,6 +70,8 @@ import { isResourceLocatorValue } from 'n8n-workflow';
import { get } from 'lodash-es';
import type { EventBus } from 'n8n-design-system/utils';
import { createEventBus } from 'n8n-design-system/utils';
import { useRouter } from 'vue-router';
import { useWorkflowHelpers } from '@/composables/useWorkflowHelpers';
export default defineComponent({
name: 'ParameterInputWrapper',
@ -78,7 +79,6 @@ export default defineComponent({
ParameterInput,
InputHint,
},
mixins: [workflowHelpers],
props: {
additionalExpressionData: {
type: Object as PropType<IDataObject>,
@ -149,6 +149,14 @@ export default defineComponent({
default: () => createEventBus(),
},
},
setup() {
const router = useRouter();
const workflowHelpers = useWorkflowHelpers(router);
return {
workflowHelpers,
};
},
computed: {
...mapStores(useNDVStore, useExternalSecretsStore, useEnvironmentsStore),
isValueExpression() {
@ -209,7 +217,7 @@ export default defineComponent({
};
}
return { ok: true, result: this.resolveExpression(value, undefined, opts) };
return { ok: true, result: this.workflowHelpers.resolveExpression(value, undefined, opts) };
} catch (error) {
return { ok: false, error };
}

View file

@ -139,7 +139,6 @@ import {
REPORTED_SOURCE_OTHER_KEY,
VIEWS,
} from '@/constants';
import { workflowHelpers } from '@/mixins/workflowHelpers';
import { useToast } from '@/composables/useToast';
import Modal from '@/components/Modal.vue';
import type { IFormInputs, IPersonalizationLatestVersion, IUser } from '@/Interface';
@ -160,7 +159,6 @@ const SURVEY_VERSION = 'v4';
export default defineComponent({
name: 'PersonalizationModal',
components: { Modal },
mixins: [workflowHelpers],
props: {
teleported: {
type: Boolean,

View file

@ -148,7 +148,6 @@ import type { IResourceLocatorReqParams, IResourceLocatorResultExpanded } from '
import DraggableTarget from '@/components/DraggableTarget.vue';
import ExpressionParameterInput from '@/components/ExpressionParameterInput.vue';
import ParameterIssues from '@/components/ParameterIssues.vue';
import { workflowHelpers } from '@/mixins/workflowHelpers';
import { useRootStore } from '@/stores/n8nRoot.store';
import { useNDVStore } from '@/stores/ndv.store';
import { useNodeTypesStore } from '@/stores/nodeTypes.store';
@ -174,6 +173,8 @@ import type { PropType } from 'vue';
import { defineComponent } from 'vue';
import ResourceLocatorDropdown from './ResourceLocatorDropdown.vue';
import { useDebounce } from '@/composables/useDebounce';
import { useWorkflowHelpers } from '@/composables/useWorkflowHelpers';
import { useRouter } from 'vue-router';
interface IResourceLocatorQuery {
results: INodeListSearchItems[];
@ -190,7 +191,6 @@ export default defineComponent({
ParameterIssues,
ResourceLocatorDropdown,
},
mixins: [workflowHelpers],
props: {
parameter: {
type: Object as PropType<INodeProperties>,
@ -257,6 +257,14 @@ export default defineComponent({
default: () => createEventBus(),
},
},
setup() {
const router = useRouter();
const workflowHelpers = useWorkflowHelpers(router);
const { callDebounced } = useDebounce();
return { callDebounced, workflowHelpers };
},
data() {
return {
resourceDropdownVisible: false,
@ -267,11 +275,6 @@ export default defineComponent({
width: 0,
};
},
setup() {
const { callDebounced } = useDebounce();
return { callDebounced };
},
computed: {
...mapStores(useNodeTypesStore, useNDVStore, useRootStore, useUIStore, useWorkflowsStore),
appName(): string {
@ -370,7 +373,7 @@ export default defineComponent({
const value = this.isValueExpression ? this.expressionComputedValue : this.valueToDisplay;
if (typeof value === 'string') {
const expression = this.currentMode.url.replace(/\{\{\$value\}\}/g, value);
const resolved = this.resolveExpression(expression);
const resolved = this.workflowHelpers.resolveExpression(expression);
return typeof resolved === 'string' ? resolved : null;
}
@ -683,7 +686,7 @@ export default defineComponent({
});
}
const resolvedNodeParameters = this.resolveRequiredParameters(
const resolvedNodeParameters = this.workflowHelpers.resolveRequiredParameters(
this.parameter,
params.parameters,
) as INodeParameters;

View file

@ -1,6 +1,6 @@
<script setup lang="ts">
import type { IUpdateInformation, ResourceMapperReqParams } from '@/Interface';
import { resolveRequiredParameters } from '@/mixins/workflowHelpers';
import { resolveRequiredParameters } from '@/composables/useWorkflowHelpers';
import { useNodeTypesStore } from '@/stores/nodeTypes.store';
import type {
INode,

View file

@ -106,7 +106,6 @@ import { defineComponent, ref } from 'vue';
import { mapStores } from 'pinia';
import { nodeBase } from '@/mixins/nodeBase';
import { workflowHelpers } from '@/mixins/workflowHelpers';
import { isNumber, isString } from '@/utils/typeGuards';
import type {
INodeUi,
@ -126,7 +125,7 @@ import { useDeviceSupport } from 'n8n-design-system';
export default defineComponent({
name: 'Sticky',
mixins: [nodeBase, workflowHelpers],
mixins: [nodeBase],
props: {
nodeViewScale: {
type: Number,
@ -320,7 +319,7 @@ export default defineComponent({
this.workflowsStore.updateNodeProperties(updateInformation);
},
touchStart() {
if (this.deviceSupport.isTouchDevice === true && !this.isMacOs && !this.isTouchActive) {
if (this.deviceSupport.isTouchDevice && !this.isMacOs && !this.isTouchActive) {
this.isTouchActive = true;
setTimeout(() => {
this.isTouchActive = false;

View file

@ -46,7 +46,7 @@
</n8n-text>
</div>
<div v-if="displayChatButton">
<n8n-button @click="openWebhookUrl()" class="mb-xl">
<n8n-button class="mb-xl" @click="openWebhookUrl()">
{{ $locale.baseText('ndv.trigger.chatTrigger.openChat') }}
</n8n-button>
</div>
@ -121,7 +121,6 @@ import type { INodeUi } from '@/Interface';
import type { INodeTypeDescription } from 'n8n-workflow';
import { getTriggerNodeServiceName } from '@/utils/nodeTypesUtils';
import NodeExecuteButton from '@/components/NodeExecuteButton.vue';
import { workflowHelpers } from '@/mixins/workflowHelpers';
import CopyInput from '@/components/CopyInput.vue';
import NodeIcon from '@/components/NodeIcon.vue';
import { useUIStore } from '@/stores/ui.store';
@ -129,6 +128,8 @@ import { useWorkflowsStore } from '@/stores/workflows.store';
import { useNDVStore } from '@/stores/ndv.store';
import { useNodeTypesStore } from '@/stores/nodeTypes.store';
import { createEventBus } from 'n8n-design-system/utils';
import { useRouter } from 'vue-router';
import { useWorkflowHelpers } from '@/composables/useWorkflowHelpers';
export default defineComponent({
name: 'TriggerPanel',
@ -137,7 +138,6 @@ export default defineComponent({
CopyInput,
NodeIcon,
},
mixins: [workflowHelpers],
props: {
nodeName: {
type: String,
@ -146,6 +146,14 @@ export default defineComponent({
type: String,
},
},
setup() {
const router = useRouter();
const workflowHelpers = useWorkflowHelpers(router);
return {
workflowHelpers,
};
},
data: () => {
return {
executionsHelpEventBus: createEventBus(),
@ -178,12 +186,9 @@ export default defineComponent({
}
if (this.node) {
const hideContentValue = this.getCurrentWorkflow().expression.getSimpleParameterValue(
this.node,
hideContent,
'internal',
{},
);
const hideContentValue = this.workflowHelpers
.getCurrentWorkflow()
.expression.getSimpleParameterValue(this.node, hideContent, 'internal', {});
if (typeof hideContentValue === 'boolean') {
return hideContentValue;
@ -220,14 +225,17 @@ export default defineComponent({
return undefined;
}
return this.getWebhookExpressionValue(this.nodeType.webhooks[0], 'httpMethod');
return this.workflowHelpers.getWebhookExpressionValue(
this.nodeType.webhooks[0],
'httpMethod',
);
},
webhookTestUrl(): string | undefined {
if (!this.node || !this.nodeType?.webhooks?.length) {
return undefined;
}
return this.getWebhookUrl(this.nodeType.webhooks[0], this.node, 'test');
return this.workflowHelpers.getWebhookUrl(this.nodeType.webhooks[0], this.node, 'test');
},
isWebhookBasedNode(): boolean {
return Boolean(this.nodeType?.webhooks?.length);

View file

@ -62,7 +62,6 @@ import type { IN8nPromptResponse } from '@/Interface';
import ModalDrawer from '@/components/ModalDrawer.vue';
import { workflowHelpers } from '@/mixins/workflowHelpers';
import { useSettingsStore } from '@/stores/settings.store';
import { useRootStore } from '@/stores/n8nRoot.store';
import { createEventBus } from 'n8n-design-system/utils';
@ -79,7 +78,6 @@ export default defineComponent({
components: {
ModalDrawer,
},
mixins: [workflowHelpers],
props: ['isActive'],
setup() {
return {

View file

@ -45,11 +45,11 @@ import { NodeConnectionType, WorkflowDataProxy } from 'n8n-workflow';
import VariableSelectorItem from '@/components/VariableSelectorItem.vue';
import type { INodeUi, IVariableItemSelected, IVariableSelectorOption } from '@/Interface';
import { workflowHelpers } from '@/mixins/workflowHelpers';
import { useWorkflowsStore } from '@/stores/workflows.store';
import { useRootStore } from '@/stores/n8nRoot.store';
import { useNDVStore } from '@/stores/ndv.store';
import { useRouter } from 'vue-router';
import { useWorkflowHelpers } from '@/composables/useWorkflowHelpers';
// Node types that should not be displayed in variable selector
const SKIPPED_NODE_TYPES = [STICKY_NODE_TYPE];
@ -59,8 +59,15 @@ export default defineComponent({
components: {
VariableSelectorItem,
},
mixins: [workflowHelpers],
props: ['path', 'redactValues'],
setup() {
const router = useRouter();
const workflowHelpers = useWorkflowHelpers(router);
return {
workflowHelpers,
};
},
data() {
return {
variableFilter: '',
@ -74,7 +81,10 @@ export default defineComponent({
if (!activeNode) {
return null;
}
return this.getParentMainInputNode(this.getCurrentWorkflow(), activeNode);
return this.workflowHelpers.getParentMainInputNode(
this.workflowHelpers.getCurrentWorkflow(),
activeNode,
);
},
extendAll(): boolean {
if (this.variableFilter) {
@ -87,7 +97,7 @@ export default defineComponent({
return this.getFilterResults(this.variableFilter.toLowerCase(), 0);
},
workflow(): Workflow {
return this.getCurrentWorkflow();
return this.workflowHelpers.getCurrentWorkflow();
},
},
methods: {
@ -482,7 +492,7 @@ export default defineComponent({
parentNode[0],
inputName,
);
const connectionInputData = this.connectionInputData(
const connectionInputData = this.workflowHelpers.connectionInputData(
parentNode,
nodeName,
inputName,

View file

@ -64,7 +64,7 @@
</div>
</div>
</div>
<MessageTyping ref="messageContainer" v-if="isLoading" />
<MessageTyping v-if="isLoading" ref="messageContainer" />
</div>
<div v-if="node" class="logs-wrapper" data-test-id="lm-chat-logs">
<n8n-text class="logs-title" tag="p" size="large">{{
@ -149,6 +149,9 @@ import { useExternalHooks } from '@/composables/useExternalHooks';
// eslint-disable-next-line import/no-unresolved
import MessageTyping from '@n8n/chat/components/MessageTyping.vue';
import { useRouter } from 'vue-router';
import { useWorkflowHelpers } from '@/composables/useWorkflowHelpers';
import { useNodeTypesStore } from '@/stores/nodeTypes.store';
const RunDataAi = defineAsyncComponent(
async () => await import('@/components/RunDataAi/RunDataAi.vue'),
@ -181,9 +184,12 @@ export default defineComponent({
mixins: [workflowRun],
setup(props, ctx) {
const externalHooks = useExternalHooks();
const router = useRouter();
const workflowHelpers = useWorkflowHelpers(router);
return {
externalHooks,
workflowHelpers,
...useToast(),
// eslint-disable-next-line @typescript-eslint/no-misused-promises
...workflowRun.setup?.(props, ctx),
@ -201,7 +207,7 @@ export default defineComponent({
},
computed: {
...mapStores(useWorkflowsStore, useUIStore),
...mapStores(useWorkflowsStore, useUIStore, useNodeTypesStore),
isLoading(): boolean {
return this.uiStore.isActionActive('workflowRunning');
},
@ -213,7 +219,7 @@ export default defineComponent({
},
methods: {
displayExecution(executionId: string) {
const workflow = this.getCurrentWorkflow();
const workflow = this.workflowHelpers.getCurrentWorkflow();
const route = this.$router.resolve({
name: VIEWS.EXECUTION_PREVIEW,
params: { name: workflow.id, executionId },
@ -264,9 +270,9 @@ export default defineComponent({
);
return;
}
const workflow = this.getCurrentWorkflow();
const workflow = this.workflowHelpers.getCurrentWorkflow();
const chatNode = this.workflowsStore.getNodes().find((node: INodeUi): boolean => {
const chatNode = this.workflowHelpers.getNodes().find((node: INodeUi): boolean => {
const nodeType = this.nodeTypesStore.getNodeType(node.type, node.typeVersion);
if (!nodeType) return false;
@ -317,7 +323,7 @@ export default defineComponent({
getChatMessages(): ChatMessage[] {
if (!this.connectedNode) return [];
const workflow = this.getCurrentWorkflow();
const workflow = this.workflowHelpers.getCurrentWorkflow();
const connectedMemoryInputs =
workflow.connectionsByDestinationNode[this.connectedNode.name][NodeConnectionType.AiMemory];
if (!connectedMemoryInputs) return [];
@ -369,7 +375,7 @@ export default defineComponent({
return;
}
const workflow = this.getCurrentWorkflow();
const workflow = this.workflowHelpers.getCurrentWorkflow();
const childNodes = workflow.getChildNodes(triggerNode.name);
for (const childNode of childNodes) {
@ -389,7 +395,7 @@ export default defineComponent({
},
getTriggerNode(): INode | null {
const workflow = this.getCurrentWorkflow();
const workflow = this.workflowHelpers.getCurrentWorkflow();
const triggerNode = workflow.queryNodes((nodeType: INodeType) =>
[CHAT_TRIGGER_NODE_TYPE, MANUAL_CHAT_TRIGGER_NODE_TYPE].includes(nodeType.description.name),
);

View file

@ -6,7 +6,7 @@ import {
} from './utils/ResourceMapper.utils';
import { useNodeTypesStore } from '@/stores/nodeTypes.store';
import { waitAllPromises } from '@/__tests__/utils';
import * as workflowHelpers from '@/mixins/workflowHelpers';
import * as workflowHelpers from '@/composables/useWorkflowHelpers';
import ResourceMapper from '@/components/ResourceMapper/ResourceMapper.vue';
import userEvent from '@testing-library/user-event';
import { createComponentRenderer } from '@/__tests__/render';

View file

@ -44,7 +44,7 @@ export function useDebounce() {
): ReturnType<T> => {
const debouncedFn = debounce(fn, options);
return debouncedFn(...inputParameters) as ReturnType<T>;
return debouncedFn(...inputParameters);
};
return {

File diff suppressed because it is too large Load diff

View file

@ -2,7 +2,7 @@ import { createPinia, setActivePinia } from 'pinia';
import { useSettingsStore } from '@/stores/settings.store';
import { useWorkflowsStore } from '@/stores/workflows.store';
import { setupServer } from '@/__tests__/server';
import { executeData } from '@/mixins/workflowHelpers';
import { executeData } from '@/composables/useWorkflowHelpers';
import type { IExecutionResponse } from '@/Interface';
describe('workflowHelpers', () => {

View file

@ -6,16 +6,15 @@ import type { IDataObject } from 'n8n-workflow';
import { Expression, ExpressionExtensions } from 'n8n-workflow';
import { ensureSyntaxTree } from '@codemirror/language';
import { workflowHelpers } from '@/mixins/workflowHelpers';
import { useNDVStore } from '@/stores/ndv.store';
import { EXPRESSION_EDITOR_PARSER_TIMEOUT } from '@/constants';
import type { EditorView } from '@codemirror/view';
import type { TargetItem } from '@/Interface';
import type { Html, Plaintext, RawSegment, Resolvable, Segment } from '@/types/expressions';
import { useWorkflowHelpers } from '@/composables/useWorkflowHelpers';
export const expressionManager = defineComponent({
mixins: [workflowHelpers],
props: {
targetItem: {
type: Object as PropType<TargetItem | null>,
@ -197,6 +196,7 @@ export const expressionManager = defineComponent({
try {
const ndvStore = useNDVStore();
const workflowHelpers = useWorkflowHelpers(this.$router);
if (!ndvStore.activeNode) {
// e.g. credential modal
result.resolved = Expression.resolveWithoutWorkflow(resolvable, this.additionalData);
@ -211,7 +211,7 @@ export const expressionManager = defineComponent({
additionalKeys: this.additionalData,
};
}
result.resolved = this.resolveExpression('=' + resolvable, undefined, opts);
result.resolved = workflowHelpers.resolveExpression('=' + resolvable, undefined, opts);
}
} catch (error) {
result.resolved = `[${error.message}]`;

View file

@ -8,7 +8,6 @@ import type {
import { useNodeHelpers } from '@/composables/useNodeHelpers';
import { useTitleChange } from '@/composables/useTitleChange';
import { useToast } from '@/composables/useToast';
import { workflowHelpers } from '@/mixins/workflowHelpers';
import type {
ExpressionError,
@ -39,14 +38,19 @@ import { useOrchestrationStore } from '@/stores/orchestration.store';
import { usePushConnectionStore } from '@/stores/pushConnection.store';
import { useCollaborationStore } from '@/stores/collaboration.store';
import { useExternalHooks } from '@/composables/useExternalHooks';
import { useRouter } from 'vue-router';
import { useWorkflowHelpers } from '@/composables/useWorkflowHelpers';
export const pushConnection = defineComponent({
mixins: [workflowHelpers],
setup() {
const router = useRouter();
const workflowHelpers = useWorkflowHelpers(router);
const nodeHelpers = useNodeHelpers();
return {
...useTitleChange(),
...useToast(),
nodeHelpers: useNodeHelpers(),
nodeHelpers,
workflowHelpers,
};
},
data() {
@ -312,7 +316,7 @@ export const pushConnection = defineComponent({
codeNodeEditorEventBus.emit('error-line-number', lineNumber || 'final');
const workflow = this.getCurrentWorkflow();
const workflow = this.workflowHelpers.getCurrentWorkflow();
if (runDataExecuted.waitTill !== undefined) {
const activeExecutionId = this.workflowsStore.activeExecutionId;
const workflowSettings = this.workflowsStore.workflowSettings;
@ -328,7 +332,8 @@ export const pushConnection = defineComponent({
globalLinkActionsEventBus.emit('registerGlobalLinkAction', {
key: 'open-settings',
action: async () => {
if (this.workflowsStore.isNewWorkflow) await this.saveAsNewWorkflow();
if (this.workflowsStore.isNewWorkflow)
await this.workflowHelpers.saveAsNewWorkflow();
this.uiStore.openModal(WORKFLOW_SETTINGS_MODAL_KEY);
},
});
@ -357,7 +362,7 @@ export const pushConnection = defineComponent({
) {
const error = runDataExecuted.data.resultData.error as ExpressionError;
void this.getWorkflowDataToSave().then((workflowData) => {
void this.workflowHelpers.getWorkflowDataToSave().then((workflowData) => {
const eventData: IDataObject = {
caused_by_credential: false,
error_message: error.description,
@ -366,7 +371,7 @@ export const pushConnection = defineComponent({
node_graph_string: JSON.stringify(
TelemetryHelpers.generateNodesGraph(
workflowData as IWorkflowBase,
this.getNodeTypes(),
this.workflowHelpers.getNodeTypes(),
).nodeGraph,
),
workflow_id: this.workflowsStore.workflowId,

View file

@ -2,7 +2,6 @@ import { defineComponent } from 'vue';
import { mapStores } from 'pinia';
import { useStorage } from '@/composables/useStorage';
import { workflowHelpers } from '@/mixins/workflowHelpers';
import { useToast } from '@/composables/useToast';
import {
@ -14,11 +13,15 @@ import { useUIStore } from '@/stores/ui.store';
import { useSettingsStore } from '@/stores/settings.store';
import { useWorkflowsStore } from '@/stores/workflows.store';
import { useExternalHooks } from '@/composables/useExternalHooks';
import { useRouter } from 'vue-router';
import { useWorkflowHelpers } from '@/composables/useWorkflowHelpers';
export const workflowActivate = defineComponent({
mixins: [workflowHelpers],
setup() {
const router = useRouter();
const workflowHelpers = useWorkflowHelpers(router);
return {
workflowHelpers,
...useToast(),
};
},
@ -45,7 +48,7 @@ export const workflowActivate = defineComponent({
let currWorkflowId: string | undefined = workflowId;
if (!currWorkflowId || currWorkflowId === PLACEHOLDER_EMPTY_WORKFLOW_ID) {
const saved = await this.saveCurrentWorkflow();
const saved = await this.workflowHelpers.saveCurrentWorkflow();
if (!saved) {
this.updatingWorkflowActivation = false;
return;
@ -92,7 +95,7 @@ export const workflowActivate = defineComponent({
return;
}
await this.updateWorkflow(
await this.workflowHelpers.updateWorkflow(
{ workflowId: currWorkflowId, active: newActiveState },
!this.uiStore.stateIsDirty,
);

File diff suppressed because it is too large Load diff

View file

@ -18,7 +18,6 @@ import {
import { useToast } from '@/composables/useToast';
import { useNodeHelpers } from '@/composables/useNodeHelpers';
import { workflowHelpers } from '@/mixins/workflowHelpers';
import { FORM_TRIGGER_NODE_TYPE, WAIT_NODE_TYPE } from '@/constants';
import { useTitleChange } from '@/composables/useTitleChange';
@ -27,16 +26,20 @@ import { useUIStore } from '@/stores/ui.store';
import { useWorkflowsStore } from '@/stores/workflows.store';
import { openPopUpWindow } from '@/utils/executionUtils';
import { useExternalHooks } from '@/composables/useExternalHooks';
import { useWorkflowHelpers } from '@/composables/useWorkflowHelpers';
import { useRouter } from 'vue-router';
export const workflowRun = defineComponent({
mixins: [workflowHelpers],
setup() {
const nodeHelpers = useNodeHelpers();
const router = useRouter();
const workflowHelpers = useWorkflowHelpers(router);
return {
...useTitleChange(),
...useToast(),
nodeHelpers,
workflowHelpers,
};
},
computed: {
@ -81,7 +84,7 @@ export const workflowRun = defineComponent({
| { triggerNode: string; nodeData: ITaskData; source?: string }
| { source?: string },
): Promise<IExecutionPushResponse | undefined> {
const workflow = this.getCurrentWorkflow();
const workflow = this.workflowHelpers.getCurrentWorkflow();
if (this.uiStore.isActionActive('workflowRunning')) {
return;
@ -97,7 +100,10 @@ export const workflowRun = defineComponent({
const issuesExist = this.workflowsStore.nodesIssuesExist;
if (issuesExist) {
// If issues exist get all of the issues of all nodes
const workflowIssues = this.checkReadyForExecution(workflow, options.destinationNode);
const workflowIssues = this.workflowHelpers.checkReadyForExecution(
workflow,
options.destinationNode,
);
if (workflowIssues !== null) {
const errorMessages = [];
let nodeIssues: string[];
@ -143,7 +149,7 @@ export const workflowRun = defineComponent({
nodeName: options.destinationNode,
});
await this.getWorkflowDataToSave().then((workflowData) => {
await this.workflowHelpers.getWorkflowDataToSave().then((workflowData) => {
this.$telemetry.track('Workflow execution preflight failed', {
workflow_id: workflow.id,
workflow_name: workflow.name,
@ -152,7 +158,7 @@ export const workflowRun = defineComponent({
node_graph_string: JSON.stringify(
TelemetryHelpers.generateNodesGraph(
workflowData as IWorkflowBase,
this.getNodeTypes(),
this.workflowHelpers.getNodeTypes(),
).nodeGraph,
),
error_node_types: JSON.stringify(trackErrorNodeTypes),
@ -231,10 +237,10 @@ export const workflowRun = defineComponent({
}
if (this.workflowsStore.isNewWorkflow) {
await this.saveCurrentWorkflow();
await this.workflowHelpers.saveCurrentWorkflow();
}
const workflowData = await this.getWorkflowDataToSave();
const workflowData = await this.workflowHelpers.getWorkflowDataToSave();
const startRunData: IStartRunData = {
workflowData,

View file

@ -2,7 +2,7 @@ import { createTestingPinia } from '@pinia/testing';
import { createPinia, setActivePinia } from 'pinia';
import { DateTime } from 'luxon';
import * as workflowHelpers from '@/mixins/workflowHelpers';
import * as workflowHelpers from '@/composables/useWorkflowHelpers';
import { dollarOptions } from '@/plugins/codemirror/completions/dollar.completions';
import * as utils from '@/plugins/codemirror/completions/utils';
import {

View file

@ -1,4 +1,4 @@
import { resolveParameter } from '@/mixins/workflowHelpers';
import { resolveParameter } from '@/composables/useWorkflowHelpers';
import { prefixMatch, longestCommonPrefix } from './utils';
import type { IDataObject } from 'n8n-workflow';
import type { Completion, CompletionContext, CompletionResult } from '@codemirror/autocomplete';

View file

@ -2,7 +2,7 @@ import type { IDataObject, DocMetadata, NativeDoc } from 'n8n-workflow';
import { Expression, ExpressionExtensions, NativeMethods } from 'n8n-workflow';
import { DateTime } from 'luxon';
import { i18n } from '@/plugins/i18n';
import { resolveParameter } from '@/mixins/workflowHelpers';
import { resolveParameter } from '@/composables/useWorkflowHelpers';
import {
setRank,
hasNoParams,

View file

@ -1,4 +1,4 @@
import type { resolveParameter } from '@/mixins/workflowHelpers';
import type { resolveParameter } from '@/composables/useWorkflowHelpers';
import type { DocMetadata } from 'n8n-workflow';
export type Resolved = ReturnType<typeof resolveParameter>;

View file

@ -1,7 +1,7 @@
import { NODE_TYPES_EXCLUDED_FROM_AUTOCOMPLETION } from '@/components/CodeNodeEditor/constants';
import { CREDENTIAL_EDIT_MODAL_KEY, SPLIT_IN_BATCHES_NODE_TYPE } from '@/constants';
import { useWorkflowsStore } from '@/stores/workflows.store';
import { resolveParameter } from '@/mixins/workflowHelpers';
import { resolveParameter } from '@/composables/useWorkflowHelpers';
import { useNDVStore } from '@/stores/ndv.store';
import { useUIStore } from '@/stores/ui.store';
import type { Completion, CompletionContext } from '@codemirror/autocomplete';

View file

@ -1,5 +1,5 @@
<template>
<div :class="$style['content']" ref="nodeViewRootRef">
<div ref="nodeViewRootRef" :class="$style['content']">
<div
id="node-view-root"
class="node-view-root do-not-select"
@ -258,7 +258,6 @@ import { useUniqueNodeName } from '@/composables/useUniqueNodeName';
import { useI18n } from '@/composables/useI18n';
import { useMessage } from '@/composables/useMessage';
import { useToast } from '@/composables/useToast';
import { workflowHelpers } from '@/mixins/workflowHelpers';
import { workflowRun } from '@/mixins/workflowRun';
import NodeDetailsView from '@/components/NodeDetailsView.vue';
@ -316,7 +315,7 @@ import type {
ToggleNodeCreatorOptions,
} from '@/Interface';
import type { Route, RawLocation } from 'vue-router';
import { type Route, type RawLocation, useRouter } from 'vue-router';
import { dataPinningEventBus, nodeViewEventBus } from '@/event-bus';
import { useCanvasStore } from '@/stores/canvas.store';
import { useCollaborationStore } from '@/stores/collaboration.store';
@ -381,6 +380,7 @@ import { useDeviceSupport } from 'n8n-design-system';
import { useDebounce } from '@/composables/useDebounce';
import { useCanvasPanning } from '@/composables/useCanvasPanning';
import { tryToParseNumber } from '@/utils/typesUtils';
import { useWorkflowHelpers } from '@/composables/useWorkflowHelpers';
interface AddNodeOptions {
position?: XYPosition;
@ -412,7 +412,7 @@ export default defineComponent({
ContextMenu,
SetupWorkflowCredentialsButton,
},
mixins: [workflowHelpers, workflowRun],
mixins: [workflowRun],
async beforeRouteLeave(to, from, next) {
if (
getNodeViewTab(to) === MAIN_HEADER_TABS.EXECUTIONS ||
@ -440,7 +440,7 @@ export default defineComponent({
if (confirmModal === MODAL_CONFIRM) {
// Make sure workflow id is empty when leaving the editor
this.workflowsStore.setWorkflowId(PLACEHOLDER_EMPTY_WORKFLOW_ID);
const saved = await this.saveCurrentWorkflow({}, false);
const saved = await this.workflowHelpers.saveCurrentWorkflow({}, false);
if (saved) {
await this.settingsStore.fetchPromptsData();
}
@ -477,6 +477,7 @@ export default defineComponent({
const nodeViewRootRef = ref(null);
const nodeViewRef = ref(null);
const onMouseMoveEnd = ref(null);
const router = useRouter();
const ndvStore = useNDVStore();
const externalHooks = useExternalHooks();
@ -490,6 +491,7 @@ export default defineComponent({
const deviceSupport = useDeviceSupport();
const { callDebounced } = useDebounce();
const canvasPanning = useCanvasPanning(nodeViewRootRef, { onMouseMoveEnd });
const workflowHelpers = useWorkflowHelpers(router);
return {
locale,
@ -504,6 +506,7 @@ export default defineComponent({
nodeViewRootRef,
nodeViewRef,
onMouseMoveEnd,
workflowHelpers,
callDebounced,
...useCanvasMouseSelect(),
...useGlobalLinkActions(),
@ -1068,12 +1071,14 @@ export default defineComponent({
this.uiStore.openModal(WORKFLOW_LM_CHAT_MODAL_KEY);
},
async onRunWorkflow() {
void this.getWorkflowDataToSave().then((workflowData) => {
void this.workflowHelpers.getWorkflowDataToSave().then((workflowData) => {
const telemetryPayload = {
workflow_id: this.workflowsStore.workflowId,
node_graph_string: JSON.stringify(
TelemetryHelpers.generateNodesGraph(workflowData as IWorkflowBase, this.getNodeTypes())
.nodeGraph,
TelemetryHelpers.generateNodesGraph(
workflowData as IWorkflowBase,
this.workflowHelpers.getNodeTypes(),
).nodeGraph,
),
};
this.$telemetry.track('User clicked execute workflow button', telemetryPayload);
@ -1150,7 +1155,7 @@ export default defineComponent({
this.nodeHelpers.updateNodesExecutionIssues();
},
async onSaveKeyboardShortcut(e: KeyboardEvent) {
let saved = await this.saveCurrentWorkflow();
let saved = await this.workflowHelpers.saveCurrentWorkflow();
if (saved) {
await this.settingsStore.fetchPromptsData();
@ -1636,7 +1641,7 @@ export default defineComponent({
return;
}
const workflow = this.getCurrentWorkflow();
const workflow = this.workflowHelpers.getCurrentWorkflow();
if (!workflow.connectionsByDestinationNode.hasOwnProperty(lastSelectedNode.name)) {
return;
@ -1664,7 +1669,7 @@ export default defineComponent({
return;
}
const workflow = this.getCurrentWorkflow();
const workflow = this.workflowHelpers.getCurrentWorkflow();
if (!workflow.connectionsByDestinationNode.hasOwnProperty(lastSelectedNode.name)) {
return;
@ -1793,9 +1798,13 @@ export default defineComponent({
this.deselectAllNodes();
// Get all upstream nodes and select them
const workflow = this.getCurrentWorkflow();
const workflow = this.workflowHelpers.getCurrentWorkflow();
const checkNodes = this.getConnectedNodes('upstream', workflow, lastSelectedNode.name);
const checkNodes = this.workflowHelpers.getConnectedNodes(
'upstream',
workflow,
lastSelectedNode.name,
);
for (const nodeName of checkNodes) {
this.nodeSelectedByName(nodeName);
}
@ -1812,9 +1821,13 @@ export default defineComponent({
this.deselectAllNodes();
// Get all downstream nodes and select them
const workflow = this.getCurrentWorkflow();
const workflow = this.workflowHelpers.getCurrentWorkflow();
const checkNodes = this.getConnectedNodes('downstream', workflow, lastSelectedNode.name);
const checkNodes = this.workflowHelpers.getConnectedNodes(
'downstream',
workflow,
lastSelectedNode.name,
);
for (const nodeName of checkNodes) {
this.nodeSelectedByName(nodeName);
}
@ -1826,9 +1839,13 @@ export default defineComponent({
pushDownstreamNodes(sourceNodeName: string, margin: number, recordHistory = false) {
const sourceNode = this.workflowsStore.nodesByName[sourceNodeName];
const workflow = this.getCurrentWorkflow();
const workflow = this.workflowHelpers.getCurrentWorkflow();
const checkNodes = this.getConnectedNodes('downstream', workflow, sourceNodeName);
const checkNodes = this.workflowHelpers.getConnectedNodes(
'downstream',
workflow,
sourceNodeName,
);
for (const nodeName of checkNodes) {
const node = this.workflowsStore.nodesByName[nodeName];
const oldPosition = node.position;
@ -1877,7 +1894,7 @@ export default defineComponent({
...data,
};
this.removeForeignCredentialsFromWorkflow(
this.workflowHelpers.removeForeignCredentialsFromWorkflow(
workflowToCopy,
this.credentialsStore.allCredentials,
);
@ -1962,12 +1979,14 @@ export default defineComponent({
}
this.stopExecutionInProgress = false;
void this.getWorkflowDataToSave().then((workflowData) => {
void this.workflowHelpers.getWorkflowDataToSave().then((workflowData) => {
const trackProps = {
workflow_id: this.workflowsStore.workflowId,
node_graph_string: JSON.stringify(
TelemetryHelpers.generateNodesGraph(workflowData as IWorkflowBase, this.getNodeTypes())
.nodeGraph,
TelemetryHelpers.generateNodesGraph(
workflowData as IWorkflowBase,
this.workflowHelpers.getNodeTypes(),
).nodeGraph,
),
};
@ -2090,9 +2109,9 @@ export default defineComponent({
workflowData.nodes.forEach((node: INode) => {
//generate new webhookId if workflow already contains a node with the same webhookId
if (node.webhookId && UPDATE_WEBHOOK_ID_NODE_TYPES.includes(node.type)) {
const isDuplicate = Object.values(this.getCurrentWorkflow().nodes).some(
(n) => n.webhookId === node.webhookId,
);
const isDuplicate = Object.values(
this.workflowHelpers.getCurrentWorkflow().nodes,
).some((n) => n.webhookId === node.webhookId);
if (isDuplicate) {
node.webhookId = uuid();
}
@ -2114,13 +2133,17 @@ export default defineComponent({
const currInstanceId = this.rootStore.instanceId;
const nodeGraph = JSON.stringify(
TelemetryHelpers.generateNodesGraph(workflowData as IWorkflowBase, this.getNodeTypes(), {
nodeIdMap,
sourceInstanceId:
workflowData.meta && workflowData.meta.instanceId !== currInstanceId
? workflowData.meta.instanceId
: '',
}).nodeGraph,
TelemetryHelpers.generateNodesGraph(
workflowData as IWorkflowBase,
this.workflowHelpers.getNodeTypes(),
{
nodeIdMap,
sourceInstanceId:
workflowData.meta && workflowData.meta.instanceId !== currInstanceId
? workflowData.meta.instanceId
: '',
},
).nodeGraph,
);
if (source === 'paste') {
this.$telemetry.track('User pasted nodes', {
@ -2147,7 +2170,7 @@ export default defineComponent({
// Fix the node position as it could be totally offscreen
// and the pasted nodes would so not be directly visible to
// the user
this.updateNodePositions(
this.workflowHelpers.updateNodePositions(
workflowData,
NodeViewUtils.getNewNodePosition(this.nodes, this.lastClickPosition),
);
@ -2387,7 +2410,7 @@ export default defineComponent({
if (
nodeTypeData.maxNodes !== undefined &&
this.getNodeTypeCount(nodeTypeName) >= nodeTypeData.maxNodes
this.workflowHelpers.getNodeTypeCount(nodeTypeName) >= nodeTypeData.maxNodes
) {
this.showMaxNodeTypeError(nodeTypeData);
return;
@ -2428,7 +2451,7 @@ export default defineComponent({
this.canvasStore.newNodeInsertPosition = null;
} else {
let yOffset = 0;
const workflow = this.getCurrentWorkflow();
const workflow = this.workflowHelpers.getCurrentWorkflow();
if (lastSelectedConnection) {
const sourceNodeType = this.nodeTypesStore.getNodeType(
@ -2487,8 +2510,8 @@ export default defineComponent({
const lastSelectedNodeWorkflow = workflow.getNode(lastSelectedNode.name);
const lastSelectedInputs = NodeHelpers.getNodeInputs(
workflow,
lastSelectedNodeWorkflow!,
lastSelectedNodeType!,
lastSelectedNodeWorkflow,
lastSelectedNodeType,
);
const lastSelectedInputTypes = NodeHelpers.getConnectionTypes(lastSelectedInputs);
@ -2512,7 +2535,7 @@ export default defineComponent({
const inputs = NodeHelpers.getNodeInputs(
workflow,
lastSelectedNode,
lastSelectedNodeType!,
lastSelectedNodeType,
);
const inputsTypes = NodeHelpers.getConnectionTypes(inputs);
@ -2698,7 +2721,7 @@ export default defineComponent({
if (
lastSelectedEndpoint &&
this.checkNodeConnectionAllowed(
lastSelectedNode!,
lastSelectedNode,
newNodeData,
lastSelectedEndpoint.scope as NodeConnectionType,
)
@ -2752,7 +2775,7 @@ export default defineComponent({
},
getNodeCreatorFilter(nodeName: string, outputType?: NodeConnectionType) {
let filter;
const workflow = this.getCurrentWorkflow();
const workflow = this.workflowHelpers.getCurrentWorkflow();
const workflowNode = workflow.getNode(nodeName);
if (!workflowNode) return { nodes: [] };
@ -2884,11 +2907,11 @@ export default defineComponent({
);
if (targetNodeType?.inputs?.length) {
const workflow = this.getCurrentWorkflow();
const workflow = this.workflowHelpers.getCurrentWorkflow();
const workflowNode = workflow.getNode(targetNode.name);
let inputs: Array<ConnectionTypes | INodeInputConfiguration> = [];
if (targetNodeType) {
inputs = NodeHelpers.getNodeInputs(workflow, workflowNode!, targetNodeType);
inputs = NodeHelpers.getNodeInputs(workflow, workflowNode, targetNodeType);
}
for (const input of inputs || []) {
@ -3543,7 +3566,7 @@ export default defineComponent({
},
);
if (confirmModal === MODAL_CONFIRM) {
const saved = await this.saveCurrentWorkflow();
const saved = await this.workflowHelpers.saveCurrentWorkflow();
if (saved) await this.settingsStore.fetchPromptsData();
} else if (confirmModal === MODAL_CLOSE) {
return;
@ -3869,13 +3892,13 @@ export default defineComponent({
// connect nodes before/after deleted node
const nodeType = this.nodeTypesStore.getNodeType(node.type, node.typeVersion);
const workflow = this.getCurrentWorkflow();
const workflow = this.workflowHelpers.getCurrentWorkflow();
const workflowNode = workflow.getNode(node.name);
let inputs: Array<ConnectionTypes | INodeInputConfiguration> = [];
let outputs: Array<ConnectionTypes | INodeOutputConfiguration> = [];
if (nodeType) {
inputs = NodeHelpers.getNodeInputs(workflow, workflowNode!, nodeType);
outputs = NodeHelpers.getNodeOutputs(workflow, workflowNode!, nodeType);
inputs = NodeHelpers.getNodeInputs(workflow, workflowNode, nodeType);
outputs = NodeHelpers.getNodeOutputs(workflow, workflowNode, nodeType);
}
if (outputs.length === 1 && inputs.length === 1) {
@ -4000,7 +4023,7 @@ export default defineComponent({
this.historyStore.startRecordingUndo();
}
const activeNodeName = this.activeNode && this.activeNode.name;
const activeNodeName = this.activeNode?.name;
const isActive = activeNodeName === currentName;
if (isActive) {
this.renamingActive = true;
@ -4009,7 +4032,7 @@ export default defineComponent({
newName = this.uniqueNodeName(newName);
// Rename the node and update the connections
const workflow = this.getCurrentWorkflow(true);
const workflow = this.workflowHelpers.getCurrentWorkflow(true);
workflow.renameNode(currentName, newName);
if (trackHistory) {
@ -4249,7 +4272,7 @@ export default defineComponent({
// Get how many of the nodes of the types which have
// a max limit set already exist
const nodeTypesCount = this.getNodeTypesMaxCount();
const nodeTypesCount = this.workflowHelpers.getNodeTypesMaxCount();
let oldName: string;
let newName: string;
@ -4334,7 +4357,7 @@ export default defineComponent({
// Create a workflow with the new nodes and connections that we can use
// the rename method
const tempWorkflow: Workflow = this.getWorkflow(createNodes, newConnections);
const tempWorkflow: Workflow = this.workflowHelpers.getWorkflow(createNodes, newConnections);
// Rename all the nodes of which the name changed
for (oldName in nodeNameTable) {
@ -4398,7 +4421,7 @@ export default defineComponent({
const exportNodeNames: string[] = [];
for (const node of nodes) {
nodeData = this.getNodeDataToSave(node);
nodeData = this.workflowHelpers.getNodeDataToSave(node);
exportNodeNames.push(node.name);
data.nodes.push(nodeData);
@ -4728,7 +4751,7 @@ export default defineComponent({
}
const lastAddedNode = this.nodes[this.nodes.length - 1];
const workflow = this.getCurrentWorkflow();
const workflow = this.workflowHelpers.getCurrentWorkflow();
const lastNodeInputs = workflow.getParentNodesByDepth(lastAddedNode.name, 1);
// If the last added node has multiple inputs, move them down
@ -4748,7 +4771,7 @@ export default defineComponent({
},
async saveCurrentWorkflowExternal(callback: () => void) {
await this.saveCurrentWorkflow();
await this.workflowHelpers.saveCurrentWorkflow();
callback?.();
},
setSuspendRecordingDetachedConnections(suspend: boolean) {

View file

@ -57,7 +57,6 @@ import TemplateDetails from '@/components/TemplateDetails.vue';
import TemplateList from '@/components/TemplateList.vue';
import TemplatesView from './TemplatesView.vue';
import { workflowHelpers } from '@/mixins/workflowHelpers';
import type {
ITemplatesCollection,
ITemplatesCollectionFull,
@ -80,7 +79,6 @@ export default defineComponent({
TemplateList,
TemplatesView,
},
mixins: [workflowHelpers],
setup() {
const externalHooks = useExternalHooks();

View file

@ -64,7 +64,6 @@ import TemplatesView from './TemplatesView.vue';
import WorkflowPreview from '@/components/WorkflowPreview.vue';
import type { ITemplatesWorkflowFull } from '@/Interface';
import { workflowHelpers } from '@/mixins/workflowHelpers';
import { setPageTitle } from '@/utils/htmlUtils';
import { useTemplatesStore } from '@/stores/templates.store';
import { usePostHog } from '@/stores/posthog.store';
@ -79,7 +78,6 @@ export default defineComponent({
TemplatesView,
WorkflowPreview,
},
mixins: [workflowHelpers],
setup() {
const externalHooks = useExternalHooks();