refactor(editor): Migrate part of the vuex store to pinia (#4484)

*  Added pinia support. Migrated community nodes module.
*  Added ui pinia store, moved some data from root store to it, updated modals to work with pinia stores
*  Added ui pinia store and migrated a part of the root store
*  Migrated `settings` store to pinia
*  Removing vuex store refs from router
*  Migrated `users` module to pinia store
*  Fixing errors after sync with master
*  One more error after merge
*  Created `workflows` pinia store. Moved large part of root store to it. Started updating references.
*  Finished migrating workflows store to pinia
*  Renaming some getters and actions to make more sense
*  Finished migrating the root store to pinia
*  Migrated ndv store to pinia
*  Renaming main panel dimensions getter so it doesn't clash with data prop name
* ✔️ Fixing lint errors
*  Migrated `templates` store to pinia
*  Migrated the `nodeTypes`store
*  Removed unused pieces of code and oold vuex modules
*  Adding vuex calls to pinia store, fi	xing wrong references
* 💄 Removing leftover $store refs
*  Added legacy getters and mutations to store to support webhooks
*  Added missing front-end hooks, updated vuex state subscriptions to pinia
* ✔️ Fixing linting errors
*  Removing vue composition api plugin
*  Fixing main sidebar state when loading node view
* 🐛 Fixing an error when activating workflows
* 🐛 Fixing isses with workflow settings and executions auto-refresh
* 🐛 Removing duplicate listeners which cause import error
* 🐛 Fixing route authentication
*  Updating freshly pulled $store refs
* Adding deleted const
*  Updating store references in ee features. Reseting NodeView credentials update flag when resetting workspace
*  Adding return type to email submission modal
*  Making NodeView only react to paste event when active
* 🐛 Fixing signup view errors
* 👌 Addressing PR review comments
* 👌 Addressing new PR comments
* 👌 Updating invite id logic in signup view
This commit is contained in:
Milorad FIlipović 2022-11-04 14:04:31 +01:00 committed by GitHub
parent c2c7927414
commit 40e413d958
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
160 changed files with 5141 additions and 4378 deletions

100
package-lock.json generated
View file

@ -9090,6 +9090,11 @@
"yallist": "^2.1.2"
}
},
"node_modules/@vue/devtools-api": {
"version": "6.4.5",
"resolved": "https://registry.npmjs.org/@vue/devtools-api/-/devtools-api-6.4.5.tgz",
"integrity": "sha512-JD5fcdIuFxU4fQyXUu3w2KpAJHzTVdN+p4iOX2lMWSHMOoQdMAcpFLZzm9Z/2nmsoZ1a96QEhZ26e50xLBsgOQ=="
},
"node_modules/@vue/eslint-config-typescript": {
"version": "11.0.2",
"resolved": "https://registry.npmjs.org/@vue/eslint-config-typescript/-/eslint-config-typescript-11.0.2.tgz",
@ -27057,6 +27062,31 @@
"node": ">=6"
}
},
"node_modules/pinia": {
"version": "2.0.23",
"resolved": "https://registry.npmjs.org/pinia/-/pinia-2.0.23.tgz",
"integrity": "sha512-N15hFf4o5STrxpNrib1IEb1GOArvPYf1zPvQVRGOO1G1d74Ak0J0lVyalX/SmrzdT4Q0nlEFjbURsmBmIGUR5Q==",
"dependencies": {
"@vue/devtools-api": "^6.4.4",
"vue-demi": "*"
},
"funding": {
"url": "https://github.com/sponsors/posva"
},
"peerDependencies": {
"@vue/composition-api": "^1.4.0",
"typescript": ">=4.4.4",
"vue": "^2.6.14 || ^3.2.0"
},
"peerDependenciesMeta": {
"@vue/composition-api": {
"optional": true
},
"typescript": {
"optional": true
}
}
},
"node_modules/pinkie": {
"version": "2.0.4",
"resolved": "https://registry.npmjs.org/pinkie/-/pinkie-2.0.4.tgz",
@ -35718,6 +35748,31 @@
"tinycolor2": "^1.1.2"
}
},
"node_modules/vue-demi": {
"version": "0.13.11",
"resolved": "https://registry.npmjs.org/vue-demi/-/vue-demi-0.13.11.tgz",
"integrity": "sha512-IR8HoEEGM65YY3ZJYAjMlKygDQn25D5ajNFNoKh9RSDMQtlzCxtfQjdQgv9jjK+m3377SsJXY8ysq8kLCZL25A==",
"hasInstallScript": true,
"bin": {
"vue-demi-fix": "bin/vue-demi-fix.js",
"vue-demi-switch": "bin/vue-demi-switch.js"
},
"engines": {
"node": ">=12"
},
"funding": {
"url": "https://github.com/sponsors/antfu"
},
"peerDependencies": {
"@vue/composition-api": "^1.0.0-rc.1",
"vue": "^3.0.0-0 || ^2.6.0"
},
"peerDependenciesMeta": {
"@vue/composition-api": {
"optional": true
}
}
},
"node_modules/vue-docgen-api": {
"version": "4.54.2",
"resolved": "https://registry.npmjs.org/vue-docgen-api/-/vue-docgen-api-4.54.2.tgz",
@ -38517,6 +38572,7 @@
"n8n-design-system": "~0.40.0",
"n8n-workflow": "~0.122.1",
"normalize-wheel": "^1.0.1",
"pinia": "^2.0.22",
"prismjs": "^1.17.1",
"quill": "2.0.0-dev.4",
"quill-autoformat": "^0.1.1",
@ -42784,7 +42840,7 @@
"functional-red-black-tree": "^1.0.1",
"glob-parent": "^6.0.1",
"globals": "^13.15.0",
"globby": "^11.0.2",
"globby": "^11.1.0",
"grapheme-splitter": "^1.0.4",
"ignore": "^5.2.0",
"import-fresh": "^3.0.0",
@ -43016,7 +43072,7 @@
"@oclif/errors": "^1.3.6",
"@oclif/parser": "^3.8.8",
"debug": "^4.3.4",
"globby": "^11.0.2",
"globby": "^11.1.0",
"is-wsl": "^2.1.1",
"tslib": "^2.3.1"
},
@ -43042,10 +43098,10 @@
"clean-stack": "^3.0.1",
"cli-progress": "^3.10.0",
"debug": "^4.3.4",
"ejs": "^3.1.8",
"ejs": "^3.1.6",
"fs-extra": "^9.1.0",
"get-package-type": "^0.1.0",
"globby": "^11.0.2",
"globby": "^11.1.0",
"hyperlinker": "^1.0.0",
"indent-string": "^4.0.0",
"is-wsl": "^2.2.0",
@ -43262,7 +43318,7 @@
"@oclif/errors": "^1.3.3",
"@oclif/parser": "^3.8.0",
"debug": "^4.1.1",
"globby": "^11.0.2",
"globby": "^11.0.1",
"is-wsl": "^2.1.1",
"tslib": "^2.0.0"
}
@ -43855,7 +43911,7 @@
"css-loader": "^3.6.0",
"file-loader": "^6.2.0",
"find-up": "^5.0.0",
"fork-ts-checker-webpack-plugin": "^6.0.4",
"fork-ts-checker-webpack-plugin": "^4.1.6",
"glob": "^7.1.6",
"glob-promise": "^3.4.0",
"global": "^4.4.0",
@ -45971,7 +46027,7 @@
"@typescript-eslint/types": "5.42.0",
"@typescript-eslint/visitor-keys": "5.42.0",
"debug": "^4.3.4",
"globby": "^11.0.2",
"globby": "^11.1.0",
"is-glob": "^4.0.3",
"semver": "^7.3.7",
"tsutils": "^3.21.0"
@ -46249,6 +46305,11 @@
}
}
},
"@vue/devtools-api": {
"version": "6.4.5",
"resolved": "https://registry.npmjs.org/@vue/devtools-api/-/devtools-api-6.4.5.tgz",
"integrity": "sha512-JD5fcdIuFxU4fQyXUu3w2KpAJHzTVdN+p4iOX2lMWSHMOoQdMAcpFLZzm9Z/2nmsoZ1a96QEhZ26e50xLBsgOQ=="
},
"@vue/eslint-config-typescript": {
"version": "11.0.2",
"resolved": "https://registry.npmjs.org/@vue/eslint-config-typescript/-/eslint-config-typescript-11.0.2.tgz",
@ -47273,7 +47334,7 @@
"integrity": "sha512-eM9d/swFopRt5gdJ7jrpCwgvEMIayITpojhkkSMRsFHYuH5bkSQ4p/9qTEHtmNudUZh22Tehu7I6CxAW0IXTKA==",
"dev": true,
"requires": {
"browserslist": "^4.21.3",
"browserslist": "^4.12.0",
"caniuse-lite": "^1.0.30001109",
"normalize-range": "^0.1.2",
"num2fraction": "^1.2.2",
@ -49868,7 +49929,7 @@
"integrity": "sha512-piOX9Go+Z4f9ZiBFLnZ5VrOpBl0h7IGCkiFUN11QTe6LjAvOT3ifL/5TdoizMh99hcGy5SoLyWbapIY/PIb/3A==",
"dev": true,
"requires": {
"browserslist": "^4.21.3"
"browserslist": "^4.21.4"
}
},
"core-js-pure": {
@ -49936,7 +49997,7 @@
"requires": {
"arrify": "^2.0.1",
"cp-file": "^7.0.0",
"globby": "^11.0.2",
"globby": "^9.2.0",
"has-glob": "^1.0.0",
"junk": "^3.1.0",
"nested-error-stacks": "^2.1.0",
@ -58963,6 +59024,7 @@
"n8n-design-system": "~0.40.0",
"n8n-workflow": "~0.122.1",
"normalize-wheel": "^1.0.1",
"pinia": "^2.0.22",
"prismjs": "^1.17.1",
"quill": "2.0.0-dev.4",
"quill-autoformat": "^0.1.1",
@ -61151,6 +61213,15 @@
"integrity": "sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==",
"dev": true
},
"pinia": {
"version": "2.0.23",
"resolved": "https://registry.npmjs.org/pinia/-/pinia-2.0.23.tgz",
"integrity": "sha512-N15hFf4o5STrxpNrib1IEb1GOArvPYf1zPvQVRGOO1G1d74Ak0J0lVyalX/SmrzdT4Q0nlEFjbURsmBmIGUR5Q==",
"requires": {
"@vue/devtools-api": "^6.4.4",
"vue-demi": "*"
}
},
"pinkie": {
"version": "2.0.4",
"resolved": "https://registry.npmjs.org/pinkie/-/pinkie-2.0.4.tgz",
@ -61952,7 +62023,7 @@
"fs-extra": "^6.0.1",
"get-stream": "^5.1.0",
"glob": "^7.1.2",
"globby": "^11.0.2",
"globby": "^10.0.1",
"http-call": "^5.1.2",
"load-json-file": "^6.2.0",
"pkg-dir": "^4.2.0",
@ -67643,7 +67714,7 @@
"consola": "^2.15.3",
"dotenv": "^16.0.0",
"dotenv-expand": "^8.0.2",
"ejs": "^3.1.8",
"ejs": "^3.1.6",
"fast-glob": "^3.2.11",
"fs-extra": "^10.0.1",
"html-minifier-terser": "^6.1.0",
@ -67820,6 +67891,11 @@
"tinycolor2": "^1.1.2"
}
},
"vue-demi": {
"version": "0.13.11",
"resolved": "https://registry.npmjs.org/vue-demi/-/vue-demi-0.13.11.tgz",
"integrity": "sha512-IR8HoEEGM65YY3ZJYAjMlKygDQn25D5ajNFNoKh9RSDMQtlzCxtfQjdQgv9jjK+m3377SsJXY8ysq8kLCZL25A=="
},
"vue-docgen-api": {
"version": "4.54.2",
"resolved": "https://registry.npmjs.org/vue-docgen-api/-/vue-docgen-api-4.54.2.tgz",

View file

@ -57,6 +57,7 @@
"n8n-design-system": "~0.41.0",
"n8n-workflow": "~0.123.0",
"normalize-wheel": "^1.0.1",
"pinia": "^2.0.22",
"prismjs": "^1.17.1",
"quill": "2.0.0-dev.4",
"quill-autoformat": "^0.1.1",

View file

@ -6,7 +6,7 @@
id="app"
:class="{
[$style.container]: true,
[$style.sidebarCollapsed]: sidebarMenuCollapsed
[$style.sidebarCollapsed]: uiStore.sidebarMenuCollapsed
}"
>
<div id="header" :class="$style.header">
@ -35,11 +35,17 @@ import { HIRING_BANNER, LOCAL_STORAGE_THEME, VIEWS } from './constants';
import mixins from 'vue-typed-mixins';
import { showMessage } from './components/mixins/showMessage';
import { IUser } from './Interface';
import { mapGetters } from 'vuex';
import { userHelpers } from './components/mixins/userHelpers';
import { loadLanguage } from './plugins/i18n';
import { restApi } from '@/components/mixins/restApi';
import { globalLinkActions } from '@/components/mixins/globalLinkActions';
import { mapStores } from 'pinia';
import { useUIStore } from './stores/ui';
import { useSettingsStore } from './stores/settings';
import { useUsersStore } from './stores/users';
import { useRootStore } from './stores/n8nRootStore';
import { useTemplatesStore } from './stores/templates';
import { useNodeTypesStore } from './stores/nodeTypes';
export default mixins(
showMessage,
@ -54,11 +60,16 @@ export default mixins(
Modals,
},
computed: {
...mapGetters('settings', ['isHiringBannerEnabled', 'isTemplatesEnabled', 'isTemplatesEndpointReachable', 'isUserManagementEnabled', 'showSetupPage']),
...mapGetters('users', ['currentUser']),
...mapGetters('ui', ['sidebarMenuCollapsed']),
...mapStores(
useNodeTypesStore,
useRootStore,
useSettingsStore,
useTemplatesStore,
useUIStore,
useUsersStore,
),
defaultLocale (): string {
return this.$store.getters.defaultLocale;
return this.rootStore.defaultLocale;
},
},
data() {
@ -69,7 +80,7 @@ export default mixins(
methods: {
async initSettings(): Promise<void> {
try {
await this.$store.dispatch('settings/getSettings');
await this.settingsStore.getSettings();
} catch (e) {
this.$showToast({
title: this.$locale.baseText('startupError'),
@ -81,23 +92,22 @@ export default mixins(
throw e;
}
},
async loginWithCookie(): Promise<void> {
loginWithCookie(): void {
try {
await this.$store.dispatch('users/loginWithCookie');
this.usersStore.loginWithCookie();
} catch (e) {}
},
async initTemplates(): Promise<void> {
if (!this.isTemplatesEnabled) {
if (!this.settingsStore.isTemplatesEnabled) {
return;
}
try {
await this.$store.dispatch('settings/testTemplatesEndpoint');
} catch (e) {
await this.settingsStore.testTemplatesEndpoint();
} catch (e) {
}
},
logHiringBanner() {
if (this.isHiringBannerEnabled && this.$route.name !== VIEWS.DEMO) {
if (this.settingsStore.isHiringBannerEnabled && this.$route.name !== VIEWS.DEMO) {
console.log(HIRING_BANNER); // eslint-disable-line no-console
}
},
@ -105,20 +115,20 @@ export default mixins(
await this.initSettings();
await Promise.all([this.loginWithCookie(), this.initTemplates()]);
},
trackPage() {
this.$store.commit('ui/setCurrentView', this.$route.name);
trackPage(): void {
this.uiStore.currentView = this.$route.name || '';
if (this.$route && this.$route.meta && this.$route.meta.templatesEnabled) {
this.$store.commit('templates/setSessionId');
this.templatesStore.setSessionId();
}
else {
this.$store.commit('templates/resetSessionId'); // reset telemetry session id when user leaves template pages
this.templatesStore.resetSessionId(); // reset telemetry session id when user leaves template pages
}
this.$telemetry.page(this.$route);
},
authenticate() {
// redirect to setup page. user should be redirected to this only once
if (this.isUserManagementEnabled && this.showSetupPage) {
if (this.settingsStore.isUserManagementEnabled && this.settingsStore.showSetupPage) {
if (this.$route.name === VIEWS.SETUP) {
return;
}
@ -132,7 +142,7 @@ export default mixins(
}
// if cannot access page and not logged in, ask to sign in
const user = this.currentUser as IUser | null;
const user = this.usersStore.currentUser;
if (!user) {
const redirect =
this.$route.query.redirect ||
@ -154,7 +164,7 @@ export default mixins(
this.$router.replace({ name: VIEWS.HOMEPAGE });
},
redirectIfNecessary() {
const redirect = this.$route.meta && typeof this.$route.meta.getRedirect === 'function' && this.$route.meta.getRedirect(this.$store);
const redirect = this.$route.meta && typeof this.$route.meta.getRedirect === 'function' && this.$route.meta.getRedirect();
if (redirect) {
this.$router.replace(redirect);
}
@ -176,10 +186,11 @@ export default mixins(
this.loading = false;
this.trackPage();
// TODO: Un-comment once front-end hooks are updated to work with pinia store
this.$externalHooks().run('app.mount');
if (this.defaultLocale !== 'en') {
void this.$store.dispatch('nodeTypes/getNodeTranslationHeaders');
await this.nodeTypesStore.getNodeTranslationHeaders();
}
},
watch: {

View file

@ -1,3 +1,4 @@
import { IMenuItem } from 'n8n-design-system';
import {
jsPlumbInstance,
DragOptions,
@ -33,6 +34,7 @@ import {
ILoadOptions,
INodeCredentials,
INodeListSearchItems,
NodeParameterValueType,
} from 'n8n-workflow';
import { FAKE_DOOR_FEATURES } from './constants';
@ -152,8 +154,8 @@ export type IJsPlumbInstance = Omit<jsPlumbInstance, 'addEndpoint' | 'draggable'
export interface IUpdateInformation {
name: string;
key: string;
value: string | number | { [key: string]: string | number | boolean }; // with null makes problems in NodeSettings.vue
key?: string;
value: string | number | { [key: string]: string | number | boolean } | NodeParameterValueType | INodeParameters; // with null makes problems in NodeSettings.vue
node?: string;
oldValue?: string | number;
}
@ -887,6 +889,47 @@ export interface INodeMetadata {
parametersLastUpdatedAt?: number;
}
export interface WorkflowsState {
activeExecutions: IExecutionsCurrentSummaryExtended[];
activeWorkflows: string[];
activeWorkflowExecution: IExecutionsSummary | null;
currentWorkflowExecutions: IExecutionsSummary[];
activeExecutionId: string | null;
executingNode: string | null;
executionWaitingForWebhook: boolean;
finishedExecutionsCount: number;
nodeMetadata: NodeMetadataMap;
subWorkflowExecutionError: Error | null;
workflow: IWorkflowDb;
workflowExecutionData: IExecutionResponse | null;
workflowExecutionPairedItemMappings: {[itemId: string]: Set<string>};
workflowsById: IWorkflowsMap;
}
export interface RootState {
baseUrl: string;
defaultLocale: string;
endpointWebhook: string;
endpointWebhookTest: string;
pushConnectionActive: boolean;
timezone: string;
executionTimeout: number;
maxExecutionTimeout: number;
versionCli: string;
oauthCallbackUrls: object;
n8nMetadata: {
[key: string]: string | number | undefined;
};
sessionId: string;
urlBaseWebhook: string;
urlBaseEditor: string;
instanceId: string;
isNpmAvailable: boolean;
}
export interface NodeMetadataMap {
[nodeName: string]: INodeMetadata;
}
export interface IRootState {
activeExecutions: IExecutionsCurrentSummaryExtended[];
activeWorkflows: string[];
@ -924,12 +967,12 @@ export interface IRootState {
workflowsById: IWorkflowsMap;
sidebarMenuItems: IMenuItem[];
instanceId: string;
nodeMetadata: {[nodeName: string]: INodeMetadata};
nodeMetadata: NodeMetadataMap;
isNpmAvailable: boolean;
subworkflowExecutionError: Error | null;
}
export interface ICommunityPackageMap {
export interface CommunityPackageMap {
[name: string]: PublicInstalledPackage;
}
@ -964,6 +1007,7 @@ export interface IModalState {
}
export type IRunDataDisplayMode = 'table' | 'json' | 'binary';
export type nodePanelType = 'input' | 'output';
export interface TargetItem {
nodeName: string;
@ -1023,6 +1067,37 @@ export interface IUiState {
executionSidebarAutoRefresh: boolean;
}
export interface UIState {
activeActions: string[];
activeCredentialType: string | null;
sidebarMenuCollapsed: boolean;
modalStack: string[];
modals: {
[key: string]: IModalState;
};
isPageLoading: boolean;
currentView: string;
mainPanelPosition: number;
fakeDoorFeatures: IFakeDoor[];
draggable: {
isDragging: boolean;
type: string;
data: string;
canDrop: boolean;
stickyPosition: null | XYPosition;
};
stateIsDirty: boolean;
lastSelectedNode: string | null;
lastSelectedNodeOutputIndex: number | null;
nodeViewOffsetPosition: XYPosition;
nodeViewMoveInProgress: boolean;
selectedNodes: INodeUi[];
sidebarMenuItems: IMenuItem[];
nodeViewInitialized: boolean;
addFirstStepOnLoad: boolean;
executionSidebarAutoRefresh: boolean;
}
export type ILogLevel = 'info' | 'debug' | 'warn' | 'error' | 'verbose';
export type IFakeDoor = {
@ -1059,6 +1134,9 @@ export interface ISettingsState {
path: string;
};
onboardingCallPromptEnabled: boolean;
saveDataErrorExecution: string;
saveDataSuccessExecution: string;
saveManualExecutions: boolean;
}
export interface INodeTypesState {
@ -1109,11 +1187,9 @@ export interface IWorkflowsState {
[name: string]: IWorkflowDb;
}
export interface IWorkflowsState {}
export interface ICommunityNodesState {
export interface CommunityNodesState {
availablePackageCount: number;
installedPackages: ICommunityPackageMap;
installedPackages: CommunityPackageMap;
}
export interface IRestApiContext {
@ -1149,8 +1225,8 @@ export interface IOnboardingCallPromptResponse {
export interface IOnboardingCallPrompt {
title: string;
body: string;
index: number;
description: string;
toast_sequence_number: number;
}
export interface ITab {
@ -1182,3 +1258,14 @@ export interface IResourceLocatorReqParams {
export interface IResourceLocatorResultExpanded extends INodeListSearchItems {
linkAlt?: string;
}
export interface CurlToJSONResponse {
"parameters.url": string;
"parameters.authentication": string;
"parameters.method": string;
"parameters.sendHeaders": boolean;
"parameters.headerParameters.parameters.0.name": string;
"parameters.headerParameters.parameters.0.value": string;
"parameters.sendQuery": boolean;
"parameters.sendBody": boolean;
}

View file

@ -15,6 +15,6 @@ export async function uninstallPackage(context: IRestApiContext, name: string):
return await makeRestApiRequest(context, 'DELETE', '/nodes', { name });
}
export async function updatePackage(context: IRestApiContext, name: string): Promise<void> {
export async function updatePackage(context: IRestApiContext, name: string): Promise<PublicInstalledPackage> {
return await makeRestApiRequest(context, 'PATCH', '/nodes', { name });
}

View file

@ -1,6 +1,6 @@
import {IRestApiContext} from "@/Interface";
import {CurlToJSONResponse, IRestApiContext} from "@/Interface";
import {makeRestApiRequest} from "@/api/helpers";
export function getCurlToJson(context: IRestApiContext, curlCommand: string): Promise<{ curlCommand: string | null }> {
export function getCurlToJson(context: IRestApiContext, curlCommand: string): Promise<CurlToJSONResponse> {
return makeRestApiRequest(context, 'POST', '/curl-to-json', { curlCommand });
}

View file

@ -1,4 +1,4 @@
import { IRestApiContext, IN8nPrompts, IN8nValueSurveyData, IN8nUISettings } from '../Interface';
import { IRestApiContext, IN8nPrompts, IN8nValueSurveyData, IN8nUISettings, IN8nPromptResponse } from '../Interface';
import { makeRestApiRequest, get, post } from './helpers';
import { N8N_IO_BASE_URL, NPM_COMMUNITY_NODE_SEARCH_API_URL } from '@/constants';
@ -10,11 +10,11 @@ export async function getPromptsData(instanceId: string, userId: string): Promis
return await get(N8N_IO_BASE_URL, '/prompts', {}, {'n8n-instance-id': instanceId, 'n8n-user-id': userId});
}
export async function submitContactInfo(instanceId: string, userId: string, email: string): Promise<void> {
export async function submitContactInfo(instanceId: string, userId: string, email: string): Promise<IN8nPromptResponse> {
return await post(N8N_IO_BASE_URL, '/prompt', { email }, {'n8n-instance-id': instanceId, 'n8n-user-id': userId});
}
export async function submitValueSurvey(instanceId: string, userId: string, params: IN8nValueSurveyData): Promise<IN8nPrompts> {
export async function submitValueSurvey(instanceId: string, userId: string, params: IN8nValueSurveyData): Promise<IN8nPromptResponse> {
return await post(N8N_IO_BASE_URL, '/value-survey', params, {'n8n-instance-id': instanceId, 'n8n-user-id': userId});
}

View file

@ -1,11 +1,11 @@
import { IOnboardingCallPromptResponse, IUser } from "@/Interface";
import { IOnboardingCallPrompt, IOnboardingCallPromptResponse, IUser } from "@/Interface";
import { get, post } from "./helpers";
const N8N_API_BASE_URL = 'https://api.n8n.io/api';
const ONBOARDING_PROMPTS_ENDPOINT = '/prompts/onboarding';
const CONTACT_EMAIL_SUBMISSION_ENDPOINT = '/accounts/onboarding';
export async function fetchNextOnboardingPrompt(instanceId: string, currentUer: IUser): Promise<IOnboardingCallPromptResponse> {
export async function fetchNextOnboardingPrompt(instanceId: string, currentUer: IUser): Promise<IOnboardingCallPrompt> {
return await get(
N8N_API_BASE_URL,
ONBOARDING_PROMPTS_ENDPOINT,
@ -35,7 +35,7 @@ export async function applyForOnboardingCall(instanceId: string, currentUer: IUs
}
}
export async function submitEmailOnSignup(instanceId: string, currentUer: IUser, email: string, agree: boolean): Promise<string> {
export async function submitEmailOnSignup(instanceId: string, currentUer: IUser, email: string | undefined, agree: boolean): Promise<string> {
return await post(
N8N_API_BASE_URL,
CONTACT_EMAIL_SUBMISSION_ENDPOINT,

View file

@ -13,7 +13,7 @@
<n8n-text>{{ $locale.baseText('about.n8nVersion') }}</n8n-text>
</el-col>
<el-col :span="16">
<n8n-text>{{ versionCli }}</n8n-text>
<n8n-text>{{ rootStore.versionCli }}</n8n-text>
</el-col>
</el-row>
<el-row>
@ -39,7 +39,7 @@
<n8n-text>{{ $locale.baseText('about.instanceID') }}</n8n-text>
</el-col>
<el-col :span="16">
<n8n-text>{{ instanceId }}</n8n-text>
<n8n-text>{{ rootStore.instanceId }}</n8n-text>
</el-col>
</el-row>
</div>
@ -55,9 +55,11 @@
<script lang="ts">
import Vue from 'vue';
import { mapGetters } from 'vuex';
import Modal from './Modal.vue';
import { ABOUT_MODAL_KEY } from '../constants';
import { mapStores } from 'pinia';
import { useSettingsStore } from '@/stores/settings';
import { useRootStore } from '@/stores/n8nRootStore';
export default Vue.extend({
name: 'About',
@ -71,8 +73,10 @@ export default Vue.extend({
};
},
computed: {
...mapGetters('settings', ['versionCli']),
...mapGetters(['instanceId']),
...mapStores(
useRootStore,
useSettingsStore,
),
},
methods: {
closeDialog() {

View file

@ -37,9 +37,13 @@
import Vue from 'vue';
import Modal from '@/components/Modal.vue';
import { WORKFLOW_ACTIVE_MODAL_KEY, EXECUTIONS_MODAL_KEY, WORKFLOW_SETTINGS_MODAL_KEY, LOCAL_STORAGE_ACTIVATION_FLAG, VIEWS } from '../constants';
import { WORKFLOW_ACTIVE_MODAL_KEY, WORKFLOW_SETTINGS_MODAL_KEY, LOCAL_STORAGE_ACTIVATION_FLAG, VIEWS } from '../constants';
import { getActivatableTriggerNodes, getTriggerNodeServiceName } from './helpers';
import { INodeTypeDescription } from 'n8n-workflow';
import { mapStores } from 'pinia';
import { useUIStore } from '@/stores/ui';
import { useWorkflowsStore } from '@/stores/workflows';
import { useNodeTypesStore } from '@/stores/nodeTypes';
export default Vue.extend({
name: 'ActivationModal',
@ -58,8 +62,8 @@ export default Vue.extend({
},
methods: {
async showExecutionsList () {
const activeExecution = this.$store.getters['workflows/getActiveWorkflowExecution'];
const currentWorkflow = this.$store.getters.workflowId;
const activeExecution = this.workflowsStore.activeWorkflowExecution;
const currentWorkflow = this.workflowsStore.workflowId;
if (activeExecution) {
this.$router.push({
@ -69,10 +73,10 @@ export default Vue.extend({
} else {
this.$router.push({ name: VIEWS.EXECUTION_HOME, params: { name: currentWorkflow } }).catch(() => {});
}
this.$store.commit('ui/closeModal', WORKFLOW_ACTIVE_MODAL_KEY);
this.uiStore.closeModal(WORKFLOW_ACTIVE_MODAL_KEY);
},
async showSettings() {
this.$store.dispatch('ui/openModal', WORKFLOW_SETTINGS_MODAL_KEY);
this.uiStore.openModal(WORKFLOW_SETTINGS_MODAL_KEY);
},
handleCheckboxChange (checkboxValue: boolean) {
this.checked = checkboxValue;
@ -80,8 +84,13 @@ export default Vue.extend({
},
},
computed: {
...mapStores(
useNodeTypesStore,
useUIStore,
useWorkflowsStore,
),
triggerContent (): string {
const foundTriggers = getActivatableTriggerNodes(this.$store.getters.workflowTriggerNodes);
const foundTriggers = getActivatableTriggerNodes(this.workflowsStore.workflowTriggerNodes);
if (!foundTriggers.length) {
return '';
}
@ -92,27 +101,28 @@ export default Vue.extend({
const trigger = foundTriggers[0];
const triggerNodeType = this.$store.getters['nodeTypes/getNodeType'](trigger.type, trigger.typeVersion) as INodeTypeDescription;
if (triggerNodeType.activationMessage) {
return triggerNodeType.activationMessage;
}
const triggerNodeType = this.nodeTypesStore.getNodeType(trigger.type, trigger.typeVersion);
if (triggerNodeType) {
if (triggerNodeType.activationMessage) {
return triggerNodeType.activationMessage;
}
const serviceName = getTriggerNodeServiceName(triggerNodeType);
if (trigger.webhookId) {
return this.$locale.baseText('activationModal.yourWorkflowWillNowListenForEvents', {
interpolate: {
serviceName,
},
});
} else if (triggerNodeType.polling) {
return this.$locale.baseText('activationModal.yourWorkflowWillNowRegularlyCheck', {
interpolate: {
serviceName,
},
});
} else {
return this.$locale.baseText('activationModal.yourTriggerWillNowFire');
const serviceName = getTriggerNodeServiceName(triggerNodeType);
if (trigger.webhookId) {
return this.$locale.baseText('activationModal.yourWorkflowWillNowListenForEvents', {
interpolate: {
serviceName,
},
});
} else if (triggerNodeType.polling) {
return this.$locale.baseText('activationModal.yourWorkflowWillNowRegularlyCheck', {
interpolate: {
serviceName,
},
});
}
}
return this.$locale.baseText('activationModal.yourTriggerWillNowFire');
},
},
});

View file

@ -32,6 +32,8 @@ import { nodeHelpers } from '@/components/mixins/nodeHelpers';
import mixins from 'vue-typed-mixins';
import { restApi } from '@/components/mixins/restApi';
import { mapStores } from 'pinia';
import { useWorkflowsStore } from '@/stores/workflows';
export default mixins(
nodeHelpers,
@ -47,6 +49,9 @@ export default mixins(
'windowVisible', // boolean
],
computed: {
...mapStores(
useWorkflowsStore,
),
binaryData (): IBinaryData | null {
const binaryData = this.getBinaryData(this.workflowRunData, this.displayData.node, this.displayData.runIndex, this.displayData.outputIndex);
@ -72,12 +77,12 @@ export default mixins(
},
workflowRunData (): IRunData | null {
const workflowExecution = this.$store.getters.getWorkflowExecution;
const workflowExecution = this.workflowsStore.getWorkflowExecution;
if (workflowExecution === null) {
return null;
}
const executionData: IRunExecutionData = workflowExecution.data;
return executionData.resultData.runData;
const executionData = workflowExecution.data;
return executionData? executionData.resultData.runData : null;
},
},

View file

@ -31,6 +31,8 @@ import Modal from "./Modal.vue";
import Vue from "vue";
import { IFormInputs } from "@/Interface";
import { CHANGE_PASSWORD_MODAL_KEY } from '../constants';
import { mapStores } from "pinia";
import { useUsersStore } from "@/stores/users";
export default mixins(showMessage).extend({
components: { Modal },
@ -50,6 +52,9 @@ export default mixins(showMessage).extend({
CHANGE_PASSWORD_MODAL_KEY,
};
},
computed: {
...mapStores(useUsersStore),
},
mounted() {
this.config = [
{
@ -115,7 +120,7 @@ export default mixins(showMessage).extend({
async onSubmit(values: {[key: string]: string}) {
try {
this.loading = true;
await this.$store.dispatch('users/updateCurrentUserPassword', values);
await this.usersStore.updateCurrentUserPassword(values);
this.$showMessage({
type: 'success',

View file

@ -32,6 +32,10 @@ import {
PLACEHOLDER_FILLED_AT_EXECUTION_TIME,
} from '@/constants';
import { CodeEditor } from './forms';
import { mapStores } from 'pinia';
import { useWorkflowsStore } from '@/stores/workflows';
import { useRootStore } from '@/stores/n8nRootStore';
import { useNDVStore } from '@/stores/ndv';
export default mixins(
genericHelpers,
@ -42,6 +46,13 @@ export default mixins(
CodeEditor,
},
props: ['codeAutocomplete', 'parameter', 'path', 'type', 'value', 'readonly'],
computed: {
...mapStores(
useNDVStore,
useRootStore,
useWorkflowsStore,
),
},
methods: {
loadAutocompleteData(): string[] {
if (['function', 'functionItem'].includes(this.codeAutocomplete)) {
@ -50,16 +61,16 @@ export default mixins(
const mode = 'manual';
let runIndex = 0;
const executedWorkflow: IExecutionResponse | null = this.$store.getters.getWorkflowExecution;
const executedWorkflow = this.workflowsStore.getWorkflowExecution;
const workflow = this.getCurrentWorkflow();
const activeNode: INodeUi | null = this.$store.getters['ndv/activeNode'];
const activeNode: INodeUi | null = this.ndvStore.activeNode;
const parentNode = workflow.getParentNodes(activeNode!.name, inputName, 1);
const nodeConnection = workflow.getNodeConnectionIndexes(activeNode!.name, parentNode[0]) || {
sourceIndex: 0,
destinationIndex: 0,
};
const executionData = this.$store.getters.getWorkflowExecution as IExecutionResponse | null;
const executionData = this.workflowsStore.getWorkflowExecution;
let runExecutionData: IRunExecutionData;
if (!executionData || !executionData.data) {
@ -89,7 +100,7 @@ export default mixins(
$resumeWebhookUrl: PLACEHOLDER_FILLED_AT_EXECUTION_TIME,
};
const dataProxy = new WorkflowDataProxy(workflow, runExecutionData, runIndex, itemIndex, activeNode!.name, connectionInputData || [], {}, mode, this.$store.getters.timezone, additionalProxyKeys);
const dataProxy = new WorkflowDataProxy(workflow, runExecutionData, runIndex, itemIndex, activeNode!.name, connectionInputData || [], {}, mode, this.rootStore.timezone, additionalProxyKeys);
const proxy = dataProxy.getDataProxy();
const autoCompleteItems = [

View file

@ -17,6 +17,8 @@ import { workflowHelpers } from '../mixins/workflowHelpers'; // for json field c
import { codeNodeEditorEventBus } from '@/event-bus/code-node-editor-event-bus';
import { CODE_NODE_TYPE } from '@/constants';
import { ALL_ITEMS_PLACEHOLDER, EACH_ITEM_PLACEHOLDER } from './constants';
import { mapStores } from 'pinia';
import { useRootStore } from '@/stores/n8nRootStore';
export default mixins(linterExtension, completerExtension, workflowHelpers).extend({
name: 'code-node-editor',
@ -47,6 +49,9 @@ export default mixins(linterExtension, completerExtension, workflowHelpers).exte
},
},
computed: {
...mapStores(
useRootStore,
),
content(): string {
if (!this.editor) return '';
@ -119,7 +124,7 @@ export default mixins(linterExtension, completerExtension, workflowHelpers).exte
}
this.$telemetry.track('User autocompleted code', {
instance_id: this.$store.getters.instanceId,
instance_id: this.rootStore.instanceId,
node_type: CODE_NODE_TYPE,
field_name: this.mode === 'runOnceForAllItems' ? 'jsCodeAllItems' : 'jsCodeEachItem',
field_type: 'code',

View file

@ -4,6 +4,8 @@ import { addVarType } from '../utils';
import type { Completion, CompletionContext, CompletionResult } from '@codemirror/autocomplete';
import type { INodeUi } from '@/Interface';
import type { CodeNodeEditorMixin } from '../types';
import { mapStores } from 'pinia';
import { useWorkflowsStore } from '@/stores/workflows';
function getAutocompletableNodeNames(nodes: INodeUi[]) {
return nodes
@ -12,6 +14,11 @@ function getAutocompletableNodeNames(nodes: INodeUi[]) {
}
export const baseCompletions = (Vue as CodeNodeEditorMixin).extend({
computed: {
...mapStores(
useWorkflowsStore,
),
},
methods: {
/**
* - Complete `$` to `$execution $input $prevNode $runIndex $workflow $now $today
@ -58,7 +65,7 @@ export const baseCompletions = (Vue as CodeNodeEditorMixin).extend({
const options: Completion[] = TOP_LEVEL_COMPLETIONS_IN_BOTH_MODES.map(addVarType);
options.push(
...getAutocompletableNodeNames(this.$store.getters.allNodes).map((nodeName) => {
...getAutocompletableNodeNames(this.workflowsStore.allNodes).map((nodeName) => {
return {
label: `$('${nodeName}')`,
type: 'variable',
@ -96,7 +103,7 @@ export const baseCompletions = (Vue as CodeNodeEditorMixin).extend({
if (!preCursor || (preCursor.from === preCursor.to && !context.explicit)) return null;
const options: Completion[] = getAutocompletableNodeNames(this.$store.getters.allNodes).map(
const options: Completion[] = getAutocompletableNodeNames(this.workflowsStore.allNodes).map(
(nodeName) => {
return {
label: `$('${nodeName}')`,

View file

@ -3,8 +3,17 @@ import { isAllowedInDotNotation, escape, toVariableOption } from '../utils';
import type { Completion, CompletionContext, CompletionResult } from '@codemirror/autocomplete';
import type { IDataObject, IPinData, IRunData } from 'n8n-workflow';
import type { CodeNodeEditorMixin } from '../types';
import { mapStores } from 'pinia';
import { useWorkflowsStore } from '@/stores/workflows';
import { useNDVStore } from '@/stores/ndv';
export const jsonFieldCompletions = (Vue as CodeNodeEditorMixin).extend({
computed: {
...mapStores(
useNDVStore,
useWorkflowsStore,
),
},
methods: {
/**
* - Complete `x.first().json.` to `.field`.
@ -206,11 +215,13 @@ export const jsonFieldCompletions = (Vue as CodeNodeEditorMixin).extend({
getInputNodeName() {
try {
const activeNode = this.$store.getters['ndv/activeNode'];
const workflow = this.getCurrentWorkflow();
const input = workflow.connectionsByDestinationNode[activeNode.name];
const activeNode = this.ndvStore.activeNode;
if (activeNode) {
const workflow = this.getCurrentWorkflow();
const input = workflow.connectionsByDestinationNode[activeNode.name];
return input.main[0][0].node;
return input.main[0][0].node;
}
} catch (_) {
return null;
}
@ -263,7 +274,7 @@ export const jsonFieldCompletions = (Vue as CodeNodeEditorMixin).extend({
getJsonOutput(quotedNodeName: string, options?: { accessor?: string; index?: number }) {
const nodeName = quotedNodeName.replace(/['"]/g, '');
const pinData: IPinData | undefined = this.$store.getters.pinData;
const pinData: IPinData | undefined = this.workflowsStore.getPinData;
const nodePinData = pinData && pinData[nodeName];
@ -279,7 +290,7 @@ export const jsonFieldCompletions = (Vue as CodeNodeEditorMixin).extend({
} catch (_) {}
}
const runData: IRunData | null = this.$store.getters.getWorkflowRunData;
const runData: IRunData | null = this.workflowsStore.getWorkflowRunData;
const nodeRunData = runData && runData[nodeName];

View file

@ -2,6 +2,7 @@ import Vue from 'vue';
import { AUTOCOMPLETABLE_BUILT_IN_MODULES } from '../constants';
import type { Completion, CompletionContext, CompletionResult } from '@codemirror/autocomplete';
import type { CodeNodeEditorMixin } from '../types';
import { useSettingsStore } from '@/stores/settings';
export const requireCompletions = (Vue as CodeNodeEditorMixin).extend({
methods: {
@ -14,22 +15,26 @@ export const requireCompletions = (Vue as CodeNodeEditorMixin).extend({
if (!preCursor || (preCursor.from === preCursor.to && !context.explicit)) return null;
const options: Completion[] = [];
const allowedModules = this.$store.getters['settings/allowedModules'];
const settingsStore = useSettingsStore();
const allowedModules = settingsStore.allowedModules;
const toOption = (moduleName: string) => ({
label: `require('${moduleName}');`,
type: 'variable',
});
if (allowedModules?.builtIn?.includes('*')) {
options.push(...AUTOCOMPLETABLE_BUILT_IN_MODULES.map(toOption));
} else if (allowedModules?.builtIn?.length > 0) {
options.push(...allowedModules.builtIn.map(toOption));
if (allowedModules.builtIn) {
if (allowedModules.builtIn.includes('*')) {
options.push(...AUTOCOMPLETABLE_BUILT_IN_MODULES.map(toOption));
} else if (allowedModules?.builtIn?.length > 0) {
options.push(...allowedModules.builtIn.map(toOption));
}
}
if (allowedModules?.external?.length > 0) {
options.push(...allowedModules.external.map(toOption));
if (allowedModules.external) {
if (allowedModules?.external?.length > 0) {
options.push(...allowedModules.external.map(toOption));
}
}
return {

View file

@ -49,6 +49,8 @@ import { get } from 'lodash';
import mixins from 'vue-typed-mixins';
import {Component} from "vue";
import { mapStores } from 'pinia';
import { useNDVStore } from '@/stores/ndv';
export default mixins(
nodeHelpers,
@ -72,6 +74,9 @@ export default mixins(
};
},
computed: {
...mapStores(
useNDVStore,
),
getPlaceholderText (): string {
const placeholder = this.$locale.nodeText().placeholder(this.parameter, this.path);
return placeholder ? placeholder : this.$locale.baseText('collectionParameter.choose');
@ -93,8 +98,8 @@ export default mixins(
return this.displayNodeParameter(option as INodeProperties);
});
},
node (): INodeUi {
return this.$store.getters['ndv/activeNode'];
node (): INodeUi | null {
return this.ndvStore.activeNode;
},
// Returns all the options which did not get added already
parameterOptions (): Array<INodePropertyOptions | INodeProperties> {

View file

@ -55,7 +55,9 @@
</template>
<script lang="ts">
import { useUIStore } from '@/stores/ui';
import { PublicInstalledPackage } from 'n8n-workflow';
import { mapStores } from 'pinia';
import mixins from 'vue-typed-mixins';
import {
NPM_PACKAGE_DOCS_BASE_URL,
@ -91,6 +93,9 @@ export default mixins(
],
};
},
computed: {
...mapStores(useUIStore),
},
methods: {
async onAction(value: string) {
switch (value) {
@ -102,14 +107,14 @@ export default mixins(
window.open(`${NPM_PACKAGE_DOCS_BASE_URL}${this.communityPackage.packageName}`, '_blank');
break;
case COMMUNITY_PACKAGE_MANAGE_ACTIONS.UNINSTALL:
this.$store.dispatch('ui/openCommunityPackageUninstallConfirmModal', this.communityPackage.packageName);
this.uiStore.openCommunityPackageUninstallConfirmModal(this.communityPackage.packageName);
break;
default:
break;
}
},
onUpdateClick() {
this.$store.dispatch('ui/openCommunityPackageUpdateConfirmModal', this.communityPackage.packageName);
this.uiStore.openCommunityPackageUpdateConfirmModal(this.communityPackage.packageName);
},
},
});

View file

@ -92,6 +92,8 @@ import {
} from '../constants';
import mixins from 'vue-typed-mixins';
import { showMessage } from './mixins/showMessage';
import { mapStores } from 'pinia';
import { useCommunityNodesStore } from '@/stores/communityNodes';
export default mixins(
showMessage,
@ -114,6 +116,9 @@ export default mixins(
COMMUNITY_NODES_RISKS_DOCS_URL,
};
},
computed: {
...mapStores(useCommunityNodesStore),
},
methods: {
openNPMPage() {
this.$telemetry.track('user clicked cnr browse button', { source: 'cnr install modal' });
@ -127,9 +132,9 @@ export default mixins(
this.$telemetry.track('user started cnr package install', { input_string: this.packageName, source: 'cnr settings page' });
this.infoTextErrorMessage = '';
this.loading = true;
await this.$store.dispatch('communityNodes/installPackage', this.packageName);
await this.communityNodesStore.installPackage(this.packageName);
// TODO: We need to fetch a fresh list of installed packages until proper response is implemented on the back-end
await this.$store.dispatch('communityNodes/fetchInstalledPackages');
await this.communityNodesStore.fetchInstalledPackages();
this.loading = false;
this.modalBus.$emit('close');
this.$showMessage({

View file

@ -37,6 +37,8 @@ import mixins from 'vue-typed-mixins';
import Modal from './Modal.vue';
import { COMMUNITY_PACKAGE_CONFIRM_MODAL_KEY, COMMUNITY_PACKAGE_MANAGE_ACTIONS } from '../constants';
import { showMessage } from './mixins/showMessage';
import { mapStores } from 'pinia';
import { useCommunityNodesStore } from '@/stores/communityNodes';
export default mixins(showMessage).extend({
name: 'CommunityPackageManageConfirmModal',
@ -65,8 +67,9 @@ export default mixins(showMessage).extend({
};
},
computed: {
...mapStores(useCommunityNodesStore),
activePackage() {
return this.$store.getters['communityNodes/getInstalledPackageByName'](this.activePackageName);
return this.communityNodesStore.getInstalledPackageByName(this.activePackageName);
},
getModalContent() {
if (this.mode === COMMUNITY_PACKAGE_MANAGE_ACTIONS.UNINSTALL) {
@ -120,7 +123,7 @@ export default mixins(showMessage).extend({
package_author_email: this.activePackage.authorEmail,
});
this.loading = true;
await this.$store.dispatch('communityNodes/uninstallPackage', this.activePackageName);
await this.communityNodesStore.uninstallPackage(this.activePackageName);
this.$showMessage({
title: this.$locale.baseText('settings.communityNodes.messages.uninstall.success.title'),
type: 'success',
@ -144,7 +147,7 @@ export default mixins(showMessage).extend({
});
this.loading = true;
const updatedVersion = this.activePackage.updateAvailable;
await this.$store.dispatch('communityNodes/updatePackage', this.activePackageName);
await this.communityNodesStore.updatePackage(this.activePackageName);
this.$showMessage({
title: this.$locale.baseText('settings.communityNodes.messages.update.success.title'),
message: this.$locale.baseText('settings.communityNodes.messages.update.success.message', {

View file

@ -35,12 +35,14 @@
<script lang="ts">
import Vue from 'vue';
import mixins from 'vue-typed-mixins';
import { mapGetters } from 'vuex';
import { IN8nPromptResponse } from '@/Interface';
import { VALID_EMAIL_REGEX } from '@/constants';
import { workflowHelpers } from '@/components/mixins/workflowHelpers';
import Modal from './Modal.vue';
import { mapStores } from 'pinia';
import { useSettingsStore } from '@/stores/settings';
import { useRootStore } from '@/stores/n8nRootStore';
export default mixins(workflowHelpers).extend({
components: { Modal },
@ -53,19 +55,20 @@ export default mixins(workflowHelpers).extend({
};
},
computed: {
...mapGetters({
promptsData: 'settings/getPromptsData',
}),
...mapStores(
useRootStore,
useSettingsStore,
),
title(): string {
if (this.promptsData && this.promptsData.title) {
return this.promptsData.title;
if (this.settingsStore.promptsData && this.settingsStore.promptsData.title) {
return this.settingsStore.promptsData.title;
}
return 'Youre a power user 💪';
},
description(): string {
if (this.promptsData && this.promptsData.message) {
return this.promptsData.message;
if (this.settingsStore.promptsData && this.settingsStore.promptsData.message) {
return this.settingsStore.promptsData.message;
}
return 'Your experience with n8n can help us improve — for you and our entire community.';
@ -78,21 +81,18 @@ export default mixins(workflowHelpers).extend({
closeDialog(): void {
if (!this.isEmailValid) {
this.$telemetry.track('User closed email modal', {
instance_id: this.$store.getters.instanceId,
instance_id: this.rootStore.instanceId,
email: null,
});
}
},
async send() {
if (this.isEmailValid) {
const response: IN8nPromptResponse = await this.$store.dispatch(
'settings/submitContactInfo',
this.email,
);
const response = await this.settingsStore.submitContactInfo(this.email) as IN8nPromptResponse;
if (response.updated) {
this.$telemetry.track('User closed email modal', {
instance_id: this.$store.getters.instanceId,
instance_id: this.rootStore.instanceId,
email: this.email,
});
this.$showMessage({

View file

@ -46,8 +46,10 @@ import {EnterpriseEditionFeature} from '@/constants';
import {showMessage} from "@/components/mixins/showMessage";
import CredentialIcon from '@/components/CredentialIcon.vue';
import {getCredentialPermissions, IPermissions} from "@/permissions";
import {mapGetters} from "vuex";
import dateformat from "dateformat";
import { mapStores } from 'pinia';
import { useUIStore } from '@/stores/ui';
import { useUsersStore } from '@/stores/users';
export const CREDENTIAL_LIST_ITEM_ACTIONS = {
OPEN: 'open',
@ -86,8 +88,12 @@ export default mixins(
},
},
computed: {
...mapStores(
useUIStore,
useUsersStore,
),
currentUser (): IUser {
return this.$store.getters['users/currentUser'];
return this.usersStore.currentUser || {} as IUser;
},
credentialType(): ICredentialType {
return this.$store.getters['credentials/getCredentialTypeByName'](this.data.type);
@ -114,7 +120,7 @@ export default mixins(
},
methods: {
async onClick() {
this.$store.dispatch('ui/openExistingCredential', { id: this.data.id});
this.uiStore.openExistingCredential(this.data.id);
},
async onAction(action: string) {
if (action === CREDENTIAL_LIST_ITEM_ACTIONS.OPEN) {

View file

@ -102,6 +102,11 @@ import { addCredentialTranslation } from '@/plugins/i18n';
import mixins from 'vue-typed-mixins';
import { BUILTIN_CREDENTIALS_DOCS_URL, EnterpriseEditionFeature } from '@/constants';
import { IPermissions } from "@/permissions";
import { mapStores } from 'pinia';
import { useUIStore } from '@/stores/ui';
import { useWorkflowsStore } from '@/stores/workflows';
import { useRootStore } from '@/stores/n8nRootStore';
import { useNDVStore } from '@/stores/ndv';
export default mixins(restApi).extend({
name: 'CredentialConfig',
@ -160,9 +165,9 @@ export default mixins(restApi).extend({
};
},
async beforeMount() {
if (this.$store.getters.defaultLocale === 'en') return;
if (this.rootStore.defaultLocale === 'en') return;
this.$store.commit('setActiveCredentialType', this.credentialType.name);
this.uiStore.activeCredentialType = this.credentialType.name;
const key = `n8n-nodes-base.credentials.${this.credentialType.name}`;
@ -172,10 +177,16 @@ export default mixins(restApi).extend({
addCredentialTranslation(
{ [this.credentialType.name]: credTranslation },
this.$store.getters.defaultLocale,
this.rootStore.defaultLocale,
);
},
computed: {
...mapStores(
useNDVStore,
useRootStore,
useUIStore,
useWorkflowsStore,
),
appName(): string {
if (!this.credentialType) {
return '';
@ -195,7 +206,7 @@ export default mixins(restApi).extend({
},
documentationUrl(): string {
const type = this.credentialType as ICredentialType;
const activeNode = this.$store.getters['ndv/activeNode'];
const activeNode = this.ndvStore.activeNode;
const isCommunityNode = activeNode ? isCommunityPackageName(activeNode.type) : false;
if (!type || !type.documentationUrl) {
@ -219,7 +230,7 @@ export default mixins(restApi).extend({
this.parentTypes.includes('oAuth2Api')
? 'oauth2'
: 'oauth1';
return this.$store.getters.oauthCallbackUrls[oauthType];
return this.rootStore.oauthCallbackUrls[oauthType as keyof {}];
},
showOAuthSuccessBanner(): boolean {
return this.isOAuthType && this.requiredPropertiesFilled && this.isOAuthConnected && !this.authError;
@ -234,7 +245,7 @@ export default mixins(restApi).extend({
docs_link: this.documentationUrl,
credential_type: this.credentialTypeName,
source: 'modal',
workflow_id: this.$store.getters.workflowId,
workflow_id: this.workflowsStore.workflowId,
});
},
},

View file

@ -109,6 +109,7 @@ import {
ICredentialsDecryptedResponse,
ICredentialsResponse,
IFakeDoor,
IUser,
} from '@/Interface';
import {
@ -123,7 +124,6 @@ import {
INodeProperties,
INodeTypeDescription,
ITelemetryTrackProperties,
IUser,
NodeHelpers,
} from 'n8n-workflow';
import CredentialIcon from '../CredentialIcon.vue';
@ -141,10 +141,15 @@ import InlineNameEdit from '../InlineNameEdit.vue';
import {EnterpriseEditionFeature} from "@/constants";
import {IDataObject} from "n8n-workflow";
import FeatureComingSoon from '../FeatureComingSoon.vue';
import {mapGetters} from "vuex";
import {getCredentialPermissions, IPermissions} from "@/permissions";
import { IMenuItem } from 'n8n-design-system';
import { BaseTextKey } from '@/plugins/i18n';
import { mapStores } from 'pinia';
import { useUIStore } from '@/stores/ui';
import { useSettingsStore } from '@/stores/settings';
import { useUsersStore } from '@/stores/users';
import { useWorkflowsStore } from '@/stores/workflows';
import { useNDVStore } from '@/stores/ndv';
interface NodeAccessMap {
[nodeType: string]: ICredentialNodeAccess | null;
@ -236,7 +241,7 @@ export default mixins(showMessage, nodeHelpers).extend({
this.$externalHooks().run('credentialsEdit.credentialModalOpened', {
credentialType: this.credentialTypeName,
isEditingCredential: this.mode === 'edit',
activeNode: this.$store.getters['ndv/activeNode'],
activeNode: this.ndvStore.activeNode,
});
setTimeout(() => {
@ -253,7 +258,16 @@ export default mixins(showMessage, nodeHelpers).extend({
this.loading = false;
},
computed: {
...mapGetters('users', ['currentUser']),
...mapStores(
useNDVStore,
useSettingsStore,
useUIStore,
useUsersStore,
useWorkflowsStore,
),
currentUser(): IUser {
return this.usersStore.currentUser || {} as IUser;
},
currentCredential(): ICredentialsResponse | null {
if (!this.credentialId) {
return null;
@ -387,7 +401,7 @@ export default mixins(showMessage, nodeHelpers).extend({
return true;
},
credentialsFakeDoorFeatures(): IFakeDoor[] {
return this.$store.getters['ui/getFakeDoorByLocation']('credentialsModal');
return this.uiStore.getFakeDoorByLocation('credentialsModal');
},
credentialPermissions(): IPermissions {
if (this.loading) {
@ -431,7 +445,7 @@ export default mixins(showMessage, nodeHelpers).extend({
return items;
},
isSharingAvailable(): boolean {
return this.$store.getters['settings/isEnterpriseFeatureEnabled'](EnterpriseEditionFeature.Sharing) === true;
return this.settingsStore.isEnterpriseFeatureEnabled(EnterpriseEditionFeature.Sharing);
},
},
methods: {
@ -561,13 +575,13 @@ export default mixins(showMessage, nodeHelpers).extend({
this.activeTab = tab;
const tabName: string = tab.replaceAll('coming-soon/', '');
const credType: string = this.credentialType ? this.credentialType.name : '';
const activeNode: INode | null = this.$store.getters['ndv/activeNode'];
const activeNode: INode | null = this.ndvStore.activeNode;
this.$telemetry.track('User viewed credential tab', {
credential_type: credType,
node_type: activeNode ? activeNode.type : null,
tab: tabName,
workflow_id: this.$store.getters.workflowId,
workflow_id: this.workflowsStore.workflowId,
credential_id: this.credentialId,
sharing_enabled: EnterpriseEditionFeature.Sharing,
});
@ -715,7 +729,7 @@ export default mixins(showMessage, nodeHelpers).extend({
let sharedWith: IUser[] | undefined;
let ownedBy: IUser | undefined;
if (this.$store.getters['settings/isEnterpriseFeatureEnabled'](EnterpriseEditionFeature.Sharing)) {
if (this.settingsStore.isEnterpriseFeatureEnabled(EnterpriseEditionFeature.Sharing)) {
sharedWith = this.credentialData.sharedWith as unknown as IUser[];
ownedBy = this.credentialData.ownedBy as unknown as IUser;
}
@ -765,7 +779,7 @@ export default mixins(showMessage, nodeHelpers).extend({
const trackProperties: ITelemetryTrackProperties = {
credential_type: credentialDetails.type,
workflow_id: this.$store.getters.workflowId,
workflow_id: this.workflowsStore.workflowId,
credential_id: credential.id,
is_complete: !!this.requiredPropertiesFilled,
is_new: isNewCredential,
@ -777,8 +791,8 @@ export default mixins(showMessage, nodeHelpers).extend({
trackProperties.is_valid = !!this.testedSuccessfully;
}
if (this.$store.getters['ndv/activeNode']) {
trackProperties.node_type = this.$store.getters['ndv/activeNode'].type;
if (this.ndvStore.activeNode) {
trackProperties.node_type = this.ndvStore.activeNode.type;
}
if (this.authError && this.authError !== '') {
@ -821,7 +835,7 @@ export default mixins(showMessage, nodeHelpers).extend({
this.$telemetry.track('User created credentials', {
credential_type: credentialDetails.type,
credential_id: credential.id,
workflow_id: this.$store.getters.workflowId,
workflow_id: this.workflowsStore.workflowId,
});
return credential;

View file

@ -15,7 +15,7 @@
v-if="credentialPermissions.updateSharing"
size="large"
:users="usersList"
:currentUserId="currentUser.id"
:currentUserId="usersStore.currentUser.id"
:placeholder="$locale.baseText('credentialEdit.credentialSharing.select.placeholder')"
@input="onAddSharee"
>
@ -25,7 +25,7 @@
</n8n-user-select>
<n8n-users-list
:users="sharedWithList"
:currentUserId="currentUser.id"
:currentUserId="usersStore.currentUser.id"
:delete-label="$locale.baseText('credentialEdit.credentialSharing.list.delete')"
:readonly="!credentialPermissions.updateSharing"
@delete="onRemoveSharee"
@ -34,11 +34,11 @@
</template>
<script lang="ts">
import Vue from 'vue';
import { mapGetters } from 'vuex';
import {IUser} from "@/Interface";
import mixins from "vue-typed-mixins";
import {showMessage} from "@/components/mixins/showMessage";
import { mapStores } from 'pinia';
import { useUsersStore } from '@/stores/users';
export default mixins(
showMessage,
@ -46,10 +46,10 @@ export default mixins(
name: 'CredentialSharing',
props: ['credential', 'credentialId', 'credentialData', 'sharedWith', 'credentialPermissions'],
computed: {
...mapGetters('users', ['allUsers', 'currentUser']),
...mapStores(useUsersStore),
usersList(): IUser[] {
return this.allUsers.filter((user: IUser) => {
const isCurrentUser = user.id === this.currentUser.id;
return this.usersStore.allUsers.filter((user: IUser) => {
const isCurrentUser = user.id === this.usersStore.currentUser?.id;
const isAlreadySharedWithUser = (this.credentialData.sharedWith || []).find((sharee: IUser) => sharee.id === user.id);
return !isCurrentUser && !isAlreadySharedWithUser;
@ -58,7 +58,7 @@ export default mixins(
sharedWithList(): IUser[] {
return [
{
...(this.credential ? this.credential.ownedBy : this.currentUser),
...(this.credential ? this.credential.ownedBy : this.usersStore.currentUser),
isOwner: true,
},
].concat(this.credentialData.sharedWith || []);
@ -69,30 +69,30 @@ export default mixins(
},
methods: {
async onAddSharee(userId: string) {
const { id, firstName, lastName, email } = this.$store.getters['users/getUserById'](userId);
const sharee = { id, firstName, lastName, email };
const sharee = this.usersStore.getUserById(userId);
this.$emit('change', (this.credentialData.sharedWith || []).concat(sharee));
},
async onRemoveSharee(userId: string) {
const user = this.$store.getters['users/getUserById'](userId);
const user = this.usersStore.getUserById(userId);
const confirm = await this.confirmMessage(
this.$locale.baseText('credentialEdit.credentialSharing.list.delete.confirm.message', { interpolate: { name: user.fullName } }),
this.$locale.baseText('credentialEdit.credentialSharing.list.delete.confirm.title'),
null,
this.$locale.baseText('credentialEdit.credentialSharing.list.delete.confirm.confirmButtonText'),
this.$locale.baseText('credentialEdit.credentialSharing.list.delete.confirm.cancelButtonText'),
);
if (user) {
const confirm = await this.confirmMessage(
this.$locale.baseText('credentialEdit.credentialSharing.list.delete.confirm.message', { interpolate: { name: user.fullName || '' } }),
this.$locale.baseText('credentialEdit.credentialSharing.list.delete.confirm.title'),
null,
this.$locale.baseText('credentialEdit.credentialSharing.list.delete.confirm.confirmButtonText'),
this.$locale.baseText('credentialEdit.credentialSharing.list.delete.confirm.cancelButtonText'),
);
if (confirm) {
this.$emit('change', this.credentialData.sharedWith.filter((sharee: IUser) => {
return sharee.id !== user.id;
}));
if (confirm) {
this.$emit('change', this.credentialData.sharedWith.filter((sharee: IUser) => {
return sharee.id !== user.id;
}));
}
}
},
async loadUsers() {
await this.$store.dispatch('users/fetchUsers');
await this.usersStore.fetchUsers();
},
},
mounted() {

View file

@ -17,6 +17,8 @@
</template>
<script lang="ts">
import { useRootStore } from '@/stores/n8nRootStore';
import { mapStores } from 'pinia';
import Vue from 'vue';
import mixins from 'vue-typed-mixins';
@ -27,8 +29,11 @@ export default Vue.extend({
},
},
computed: {
...mapStores(
useRootStore,
),
basePath(): string {
return this.$store.getters.getBaseUrl;
return this.rootStore.baseUrl;
},
},
});

View file

@ -7,7 +7,10 @@
</template>
<script lang="ts">
import { useRootStore } from '@/stores/n8nRootStore';
import { useNodeTypesStore } from '@/stores/nodeTypes';
import { ICredentialType, INodeTypeDescription } from 'n8n-workflow';
import { mapStores } from 'pinia';
import Vue from 'vue';
export default Vue.extend({
@ -17,6 +20,10 @@ export default Vue.extend({
},
},
computed: {
...mapStores(
useNodeTypesStore,
useRootStore,
),
credentialWithIcon(): ICredentialType | null {
return this.credentialTypeName ? this.getCredentialWithIcon(this.credentialTypeName) : null;
},
@ -26,15 +33,14 @@ export default Vue.extend({
return null;
}
const restUrl = this.$store.getters.getRestUrl;
const restUrl = this.rootStore.getRestUrl;
return `${restUrl}/credential-icon/${this.credentialWithIcon.name}`;
},
relevantNode(): INodeTypeDescription | null {
if (this.credentialWithIcon && this.credentialWithIcon.icon && this.credentialWithIcon.icon.startsWith('node:')) {
const nodeType = this.credentialWithIcon.icon.replace('node:', '');
return this.$store.getters['nodeTypes/getNodeType'](nodeType);
return this.nodeTypesStore.getNodeType(nodeType);
}
const nodesWithAccess = this.$store.getters['credentials/getNodesWithAccess'](this.credentialTypeName);

View file

@ -56,6 +56,9 @@ import mixins from 'vue-typed-mixins';
import Modal from './Modal.vue';
import { CREDENTIAL_SELECT_MODAL_KEY } from '../constants';
import { externalHooks } from '@/components/mixins/externalHooks';
import { mapStores } from 'pinia';
import { useUIStore } from '@/stores/ui';
import { useWorkflowsStore } from '@/stores/workflows';
export default mixins(externalHooks).extend({
name: 'CredentialsSelectModal',
@ -85,6 +88,10 @@ export default mixins(externalHooks).extend({
};
},
computed: {
...mapStores(
useUIStore,
useWorkflowsStore,
),
...mapGetters('credentials', ['allCredentialTypes']),
},
methods: {
@ -93,13 +100,13 @@ export default mixins(externalHooks).extend({
},
openCredentialType () {
this.modalBus.$emit('close');
this.$store.dispatch('ui/openNewCredential', { type: this.selected });
this.uiStore.openNewCredential(this.selected);
const telemetryPayload = {
credential_type: this.selected,
source: 'primary_menu',
new_credential: true,
workflow_id: this.$store.getters.workflowId,
workflow_id: this.workflowsStore.workflowId,
};
this.$telemetry.track('User opened Credential modal', telemetryPayload);

View file

@ -20,10 +20,10 @@
<div :class="$style.optionInput" v-if="operation === 'transfer'">
<n8n-input-label :label="$locale.baseText('settings.users.userToTransferTo')">
<n8n-user-select
:users="allUsers"
:users="usersStore.allUsers"
:value="transferId"
:ignoreIds="ignoreIds"
:currentUserId="currentUserId"
:currentUserId="usersStore.currentUserId"
@input="setTransferId"
/>
</n8n-input-label>
@ -53,7 +53,8 @@ import { showMessage } from "@/components/mixins/showMessage";
import Modal from "./Modal.vue";
import Vue from "vue";
import { IUser } from "../Interface";
import { mapGetters } from "vuex";
import { mapStores } from "pinia";
import { useUsersStore } from '@/stores/users';
export default mixins(showMessage).extend({
components: {
@ -79,13 +80,12 @@ export default mixins(showMessage).extend({
};
},
computed: {
...mapGetters('users', ['allUsers', 'currentUserId']),
userToDelete(): IUser {
const getUserById = this.$store.getters['users/getUserById'];
return getUserById(this.activeId);
...mapStores(useUsersStore),
userToDelete(): IUser | null {
return this.usersStore.getUserById(this.activeId);
},
isPending(): boolean {
return this.userToDelete && !this.userToDelete.firstName;
return this.userToDelete ? this.userToDelete && !this.userToDelete.firstName : false;
},
title(): string {
const user = this.userToDelete && (this.userToDelete.fullName || this.userToDelete.email) || '';
@ -133,16 +133,17 @@ export default mixins(showMessage).extend({
params.transferId = this.transferId;
}
await this.$store.dispatch('users/deleteUser', params);
await this.usersStore.deleteUser(params);
let message = '';
if (this.transferId) {
const getUserById = this.$store.getters['users/getUserById'];
const transferUser: IUser = getUserById(this.transferId);
message = this.$locale.baseText(
'settings.users.transferredToUser',
{ interpolate: { user: transferUser.fullName || '' }},
);
const transferUser: IUser | null = this.usersStore.getUserById(this.transferId);
if (transferUser) {
message = this.$locale.baseText(
'settings.users.transferredToUser',
{ interpolate: { user: transferUser.fullName || '' }},
);
}
}
this.$showMessage({

View file

@ -21,6 +21,8 @@
<script lang="ts">
import { XYPosition } from '@/Interface';
import { useNDVStore } from '@/stores/ndv';
import { mapStores } from 'pinia';
import Vue from 'vue';
// @ts-ignore
@ -66,11 +68,14 @@ export default Vue.extend({
};
},
computed: {
...mapStores(
useNDVStore,
),
canDrop(): boolean {
return this.$store.getters['ndv/canDraggableDrop'];
return this.ndvStore.canDraggableDrop;
},
stickyPosition(): XYPosition | null {
return this.$store.getters['ndv/draggableStickyPos'];
return this.ndvStore.draggableStickyPos;
},
},
methods: {
@ -111,7 +116,7 @@ export default Vue.extend({
this.isDragging = true;
const data = this.targetDataKey && this.draggingEl ? this.draggingEl.dataset.value : (this.data || '');
this.$store.commit('ndv/draggableStartDragging', {type: this.type, data });
this.ndvStore.draggableStartDragging({type: this.type, data: data || '' });
this.$emit('dragstart', this.draggingEl);
document.body.style.cursor = 'grabbing';
@ -141,7 +146,7 @@ export default Vue.extend({
this.$emit('dragend', this.draggingEl);
this.isDragging = false;
this.draggingEl = null;
this.$store.commit('ndv/draggableStopDragging');
this.ndvStore.draggableStopDragging();
}, 0);
},
},

View file

@ -5,6 +5,8 @@
</template>
<script lang="ts">
import { useNDVStore } from '@/stores/ndv';
import { mapStores } from 'pinia';
import Vue from 'vue';
export default Vue.extend({
@ -37,11 +39,14 @@ export default Vue.extend({
window.removeEventListener('mouseup', this.onMouseUp);
},
computed: {
...mapStores(
useNDVStore,
),
isDragging(): boolean {
return this.$store.getters['ndv/isDraggableDragging'];
return this.ndvStore.isDraggableDragging;
},
draggableType(): string {
return this.$store.getters['ndv/draggableType'];
return this.ndvStore.draggableType;
},
droppable(): boolean {
return !this.disabled && this.isDragging && this.draggableType === this.type;
@ -60,20 +65,20 @@ export default Vue.extend({
this.hovering = e.clientX >= dim.left && e.clientX <= dim.right && e.clientY >= dim.top && e.clientY <= dim.bottom;
if (!this.disabled && this.sticky && this.hovering) {
this.$store.commit('ndv/setDraggableStickyPos', [dim.left + this.stickyOffset, dim.top + this.stickyOffset]);
this.ndvStore.setDraggableStickyPos([dim.left + this.stickyOffset, dim.top + this.stickyOffset]);
}
}
},
onMouseUp(e: MouseEvent) {
if (this.activeDrop) {
const data = this.$store.getters['ndv/draggableData'];
const data = this.ndvStore.draggableData;
this.$emit('drop', data);
}
},
},
watch: {
activeDrop(active) {
this.$store.commit('ndv/setDraggableCanDrop', active);
this.ndvStore.setDraggableCanDrop(active);
},
},
});

View file

@ -16,7 +16,7 @@
:maxlength="MAX_WORKFLOW_NAME_LENGTH"
/>
<TagsDropdown
v-if="areTagsEnabled"
v-if="settingsStore.areTagsEnabled"
:createEnabled="true"
:currentTagIds="currentTagIds"
:eventBus="dropdownBus"
@ -46,9 +46,10 @@ import { workflowHelpers } from "@/components/mixins/workflowHelpers";
import { showMessage } from "@/components/mixins/showMessage";
import TagsDropdown from "@/components/TagsDropdown.vue";
import Modal from "./Modal.vue";
import { mapGetters } from "vuex";
import {restApi} from "@/components/mixins/restApi";
import {IWorkflowDb} from "@/Interface";
import { mapStores } from "pinia";
import { useSettingsStore } from "@/stores/settings";
import { useWorkflowsStore } from "@/stores/workflows";
export default mixins(showMessage, workflowHelpers, restApi).extend({
components: { TagsDropdown, Modal },
@ -68,11 +69,14 @@ export default mixins(showMessage, workflowHelpers, restApi).extend({
};
},
async mounted() {
this.name = await this.$store.dispatch('workflows/getDuplicateCurrentWorkflowName', this.data.name);
this.name = await this.workflowsStore.getDuplicateCurrentWorkflowName(this.data.name);
this.$nextTick(() => this.focusOnNameInput());
},
computed: {
...mapGetters('settings', ['areTagsEnabled']),
...mapStores(
useSettingsStore,
useWorkflowsStore,
),
},
watch: {
isActive(active) {

View file

@ -8,6 +8,8 @@
<script lang="ts">
import Vue from 'vue';
import {EnterpriseEditionFeature} from "@/constants";
import { mapStores } from 'pinia';
import { useSettingsStore } from '@/stores/settings';
export default Vue.extend({
name: 'EnterpriseEdition',
@ -18,9 +20,10 @@ export default Vue.extend({
},
},
computed: {
...mapStores(useSettingsStore),
canAccess(): boolean {
return this.features.reduce((acc: boolean, feature) => {
return acc && !!this.$store.getters['settings/isEnterpriseFeatureEnabled'](feature);
return acc && !!this.settingsStore.isEnterpriseFeatureEnabled(feature as EnterpriseEditionFeature);
}, true);
},
},

View file

@ -109,6 +109,9 @@ import {
INodeTypeDescription,
} from 'n8n-workflow';
import { sanitizeHtml } from '@/utils';
import { mapStores } from 'pinia';
import { useNDVStore } from '@/stores/ndv';
import { useNodeTypesStore } from '@/stores/nodeTypes';
export default mixins(
copyPaste,
@ -122,15 +125,19 @@ export default mixins(
VueJsonPretty,
},
computed: {
...mapStores(
useNodeTypesStore,
useNDVStore,
),
displayCause(): boolean {
return JSON.stringify(this.error.cause).length < MAX_DISPLAY_DATA_SIZE;
},
parameters (): INodeProperties[] {
const node = this.$store.getters['ndv/activeNode'];
const node = this.ndvStore.activeNode;
if (!node) {
return [];
}
const nodeType = this.$store.getters['nodeTypes/getNodeType'](node.type, node.typeVersion);
const nodeType = this.nodeTypesStore.getNodeType(node.type, node.typeVersion);
if (nodeType === null) {
return [];

View file

@ -203,6 +203,9 @@ import {
} from 'lodash';
import mixins from 'vue-typed-mixins';
import { mapStores } from 'pinia';
import { useUIStore } from '@/stores/ui';
import { useWorkflowsStore } from '@/stores/workflows';
export default mixins(
externalHooks,
@ -249,7 +252,7 @@ export default mixins(
this.handleAutoRefreshToggle();
this.$externalHooks().run('executionsList.openDialog');
this.$telemetry.track('User opened Executions log', { workflow_id: this.$store.getters.workflowId });
this.$telemetry.track('User opened Executions log', { workflow_id: this.workflowsStore.workflowId });
},
beforeDestroy() {
if (this.autoRefreshInterval) {
@ -258,6 +261,10 @@ export default mixins(
}
},
computed: {
...mapStores(
useUIStore,
useWorkflowsStore,
),
statuses () {
return [
{
@ -283,7 +290,7 @@ export default mixins(
];
},
activeExecutions (): IExecutionsCurrentSummaryExtended[] {
return this.$store.getters.getActiveExecutions;
return this.workflowsStore.activeExecutions;
},
combinedExecutions (): IExecutionsSummary[] {
const returnData: IExecutionsSummary[] = [];
@ -408,27 +415,28 @@ export default mixins(
await this.restApi().deleteExecutions(sendData);
let removedCurrentlyLoadedExecution = false;
let removedActiveExecution = false;
const currentWorkflow: string = this.$store.getters.workflowId;
const activeExecution: IExecutionsSummary = this.$store.getters['workflows/getActiveWorkflowExecution'];
const currentWorkflow: string = this.workflowsStore.workflowId;
const activeExecution: IExecutionsSummary | null = this.workflowsStore.activeWorkflowExecution;
// Also update current workflow executions view if needed
for (const selectedId of Object.keys(this.selectedItems)) {
const execution: IExecutionsSummary = this.$store.getters['workflows/getExecutionDataById'](selectedId);
const execution: IExecutionsSummary | undefined = this.workflowsStore.getExecutionDataById(selectedId);
if (execution && execution.workflowId === currentWorkflow) {
this.$store.commit('workflows/deleteExecution', execution);
this.workflowsStore.deleteExecution(execution);
removedCurrentlyLoadedExecution = true;
}
if (execution.id === activeExecution.id) {
if ((execution !== undefined && activeExecution !== null) && execution.id === activeExecution.id) {
removedActiveExecution = true;
}
}
// Also update route if needed
if (removedCurrentlyLoadedExecution) {
const currentWorkflowExecutions: IExecutionsSummary[] = this.$store.getters['workflows/currentWorkflowExecutions'];
const currentWorkflowExecutions: IExecutionsSummary[] = this.workflowsStore.currentWorkflowExecutions;
if (currentWorkflowExecutions.length === 0) {
this.$store.commit('workflows/setActiveWorkflowExecution', null);
this.workflowsStore.activeWorkflowExecution = null;
this.$router.push({ name: VIEWS.EXECUTION_HOME, params: { name: currentWorkflow } });
} else if (removedActiveExecution) {
this.$store.commit('workflows/setActiveWorkflowExecution', currentWorkflowExecutions[0]);
this.workflowsStore.activeWorkflowExecution = currentWorkflowExecutions[0];
this.$router.push({
name: VIEWS.EXECUTION_PREVIEW,
params: { name: currentWorkflow, executionId: currentWorkflowExecutions[0].id },
@ -468,7 +476,7 @@ export default mixins(
this.retryExecution(commandData.row, loadWorkflow);
this.$telemetry.track('User clicked retry execution button', {
workflow_id: this.$store.getters.workflowId,
workflow_id: this.workflowsStore.workflowId,
execution_id: commandData.row.id,
retry_type: loadWorkflow ? 'current' : 'original',
});
@ -497,7 +505,7 @@ export default mixins(
}
}
this.$store.commit('setActiveExecutions', activeExecutions);
this.workflowsStore.activeExecutions = activeExecutions;
},
async loadAutoRefresh () : Promise<void> {
const filter = this.workflowFilterPast;
@ -517,7 +525,7 @@ export default mixins(
}
}
this.$store.commit('setActiveExecutions', results[1]);
this.workflowsStore.activeExecutions = results[1];
// execution IDs are typed as string, int conversion is necessary so we can order.
const alreadyPresentExecutionIds = this.finishedExecutions.map(exec => parseInt(exec.id, 10));

View file

@ -65,6 +65,8 @@ import { showMessage } from '../mixins/showMessage';
import WorkflowPreview from '@/components/WorkflowPreview.vue';
import { executionHelpers, IExecutionUIData } from '../mixins/executionsHelpers';
import { VIEWS } from '../../constants';
import { mapStores } from 'pinia';
import { useUIStore } from '@/stores/ui';
export default mixins(restApi, showMessage, executionHelpers).extend({
name: 'execution-preview',
@ -77,11 +79,14 @@ export default mixins(restApi, showMessage, executionHelpers).extend({
};
},
computed: {
...mapStores(
useUIStore,
),
executionUIDetails(): IExecutionUIData | null {
return this.activeExecution ? this.getExecutionUIDetails(this.activeExecution) : null;
},
sidebarCollapsed(): boolean {
return this.$store.getters['ui/sidebarMenuCollapsed'];
return this.uiStore.sidebarMenuCollapsed;
},
},
methods: {

View file

@ -26,6 +26,11 @@
</template>
<script lang="ts">
import { useRootStore } from '@/stores/n8nRootStore';
import { useSettingsStore } from '@/stores/settings';
import { useUIStore } from '@/stores/ui';
import { useWorkflowsStore } from '@/stores/workflows';
import { mapStores } from 'pinia';
import { PLACEHOLDER_EMPTY_WORKFLOW_ID, WORKFLOW_SETTINGS_MODAL_KEY } from '@/constants';
import { deepCopy, IWorkflowSettings } from 'n8n-workflow';
import mixins from 'vue-typed-mixins';
@ -60,9 +65,9 @@ export default mixins(workflowHelpers).extend({
};
},
mounted() {
this.defaultValues.saveFailedExecutions = this.$store.getters.saveDataErrorExecution;
this.defaultValues.saveSuccessfulExecutions = this.$store.getters.saveDataSuccessExecution;
this.defaultValues.saveManualExecutions = this.$store.getters.saveManualExecutions;
this.defaultValues.saveFailedExecutions = this.settingsStore.saveDataErrorExecution;
this.defaultValues.saveSuccessfulExecutions = this.settingsStore.saveDataSuccessExecution;
this.defaultValues.saveManualExecutions = this.settingsStore.saveManualExecutions;
this.updateSettings(this.workflowSettings);
},
watch: {
@ -71,7 +76,13 @@ export default mixins(workflowHelpers).extend({
},
},
computed: {
accordionItems(): Object[] {
...mapStores(
useRootStore,
useSettingsStore,
useUIStore,
useWorkflowsStore,
),
accordionItems(): Object[] {
return [
{
id: 'productionExecutions',
@ -115,7 +126,7 @@ export default mixins(workflowHelpers).extend({
}
},
workflowSettings(): IWorkflowSettings {
const workflowSettings = deepCopy(this.$store.getters.workflowSettings);
const workflowSettings = deepCopy(this.workflowsStore.workflowSettings);
return workflowSettings;
},
accordionIcon(): { icon: string, color: string }|null {
@ -125,16 +136,16 @@ export default mixins(workflowHelpers).extend({
return null;
},
currentWorkflowId(): string {
return this.$store.getters.workflowId;
return this.workflowsStore.workflowId;
},
isNewWorkflow(): boolean {
return !this.currentWorkflowId || (this.currentWorkflowId === PLACEHOLDER_EMPTY_WORKFLOW_ID || this.currentWorkflowId === 'new');
},
workflowName(): string {
return this.$store.getters.workflowName;
return this.workflowsStore.workflowName;
},
currentWorkflowTagIds(): string[] {
return this.$store.getters.workflowTags;
return this.workflowsStore.workflowTags;
},
},
methods: {
@ -146,17 +157,17 @@ export default mixins(workflowHelpers).extend({
onAccordionClick(event: MouseEvent): void {
if (event.target instanceof HTMLAnchorElement) {
event.preventDefault();
this.$store.dispatch('ui/openModal', WORKFLOW_SETTINGS_MODAL_KEY);
this.uiStore.openModal(WORKFLOW_SETTINGS_MODAL_KEY);
}
},
onItemTooltipClick(item: string, event: MouseEvent): void {
if (item === 'productionExecutions' && event.target instanceof HTMLAnchorElement) {
event.preventDefault();
this.$store.dispatch('ui/openModal', WORKFLOW_SETTINGS_MODAL_KEY);
this.uiStore.openModal(WORKFLOW_SETTINGS_MODAL_KEY);
}
},
openWorkflowSettings(event: MouseEvent): void {
this.$store.dispatch('ui/openModal', WORKFLOW_SETTINGS_MODAL_KEY);
this.uiStore.openModal(WORKFLOW_SETTINGS_MODAL_KEY);
},
async onSaveWorkflowClick(event: MouseEvent): void {
let currentId = undefined;
@ -166,7 +177,7 @@ export default mixins(workflowHelpers).extend({
currentId = this.$route.params.name;
}
const saved = await this.saveCurrentWorkflow({ id: currentId, name: this.workflowName, tags: this.currentWorkflowTagIds });
if (saved) this.$store.dispatch('settings/fetchPromptsData');
if (saved) this.settingsStore.fetchPromptsData();
},
},
});

View file

@ -27,7 +27,9 @@
<script lang="ts">
import { PLACEHOLDER_EMPTY_WORKFLOW_ID, VIEWS } from '@/constants';
import { IExecutionsSummary } from '@/Interface';
import { useUIStore } from '@/stores/ui';
import { useWorkflowsStore } from '@/stores/workflows';
import { mapStores } from 'pinia';
import Vue from 'vue';
import ExecutionsInfoAccordion from './ExecutionsInfoAccordion.vue';
@ -37,24 +39,25 @@ export default Vue.extend({
ExecutionsInfoAccordion,
},
computed: {
...mapStores(
useUIStore,
useWorkflowsStore,
),
executionCount(): number {
return (this.$store.getters['workflows/currentWorkflowExecutions'] as IExecutionsSummary[]).length;
return this.workflowsStore.currentWorkflowExecutions.length;
},
containsTrigger(): boolean {
return this.$store.getters.workflowTriggerNodes.length > 0;
},
currentWorkflowId(): string {
return this.$store.getters.workflowId;
return this.workflowsStore.workflowTriggerNodes.length > 0;
},
},
methods: {
onSetupFirstStep(event: MouseEvent): void {
this.$store.commit('ui/setAddFirstStepOnLoad', true);
this.uiStore.addFirstStepOnLoad = true;
const workflowRoute = this.getWorkflowRoute();
this.$router.push(workflowRoute);
},
getWorkflowRoute(): { name: string, params: {}} {
const workflowId = this.currentWorkflowId || this.$route.params.name;
const workflowId = this.workflowsStore.workflowId || this.$route.params.name;
if (workflowId === PLACEHOLDER_EMPTY_WORKFLOW_ID) {
return { name: VIEWS.NEW_WORKFLOW, params: {} };
} else {

View file

@ -90,6 +90,8 @@ import { IExecutionsSummary } from "@/Interface";
import { Route } from 'vue-router';
import Vue from 'vue';
import { PropType } from 'vue';
import { mapStores } from 'pinia';
import { useUIStore } from '@/stores/ui';
export default Vue.extend({
name: 'executions-sidebar',
@ -122,6 +124,9 @@ export default Vue.extend({
};
},
computed: {
...mapStores(
useUIStore,
),
statusFilterApplied(): boolean {
return this.filter.status !== '';
},
@ -143,7 +148,7 @@ export default Vue.extend({
},
},
mounted() {
this.autoRefresh = this.$store.getters['ui/isExecutionSidebarAutoRefreshOn'];
this.autoRefresh = this.uiStore.executionSidebarAutoRefresh === true;
if (this.autoRefresh) {
this.autoRefreshInterval = setInterval(() => this.onRefresh(), 4000);
}
@ -179,7 +184,7 @@ export default Vue.extend({
this.$emit('reloadExecutions');
},
onAutoRefreshToggle(): void {
this.$store.commit('ui/setExecutionsSidebarAutoRefresh', this.autoRefresh);
this.uiStore.executionSidebarAutoRefresh = this.autoRefresh;
if (this.autoRefreshInterval) {
// Clear any previously existing intervals (if any - there shouldn't)
clearInterval(this.autoRefreshInterval);

View file

@ -37,6 +37,11 @@ import { range as _range } from 'lodash';
import { debounceHelper } from '../mixins/debounce';
import { getNodeViewTab } from '../helpers';
import { workflowHelpers } from '../mixins/workflowHelpers';
import { mapStores } from 'pinia';
import { useWorkflowsStore } from '@/stores/workflows';
import { useUIStore } from '@/stores/ui';
import { useSettingsStore } from '@/stores/settings';
import { useNodeTypesStore } from '@/stores/nodeTypes';
export default mixins(restApi, showMessage, executionHelpers, debounceHelper, workflowHelpers).extend({
name: 'executions-page',
@ -51,6 +56,12 @@ export default mixins(restApi, showMessage, executionHelpers, debounceHelper, wo
};
},
computed: {
...mapStores(
useNodeTypesStore,
useSettingsStore,
useUIStore,
useWorkflowsStore,
),
hidePreview(): boolean {
const nothingToShow = this.executions.length === 0 && this.filterApplied;
const activeNotPresent = this.filterApplied && (this.executions as IExecutionsSummary[]).find(ex => ex.id === this.activeExecution.id) === undefined;
@ -66,13 +77,13 @@ export default mixins(restApi, showMessage, executionHelpers, debounceHelper, wo
return this.filter.status !== '';
},
workflowDataNotLoaded(): boolean {
return this.$store.getters.workflowId === PLACEHOLDER_EMPTY_WORKFLOW_ID && this.$store.getters.workflowName === '';
return this.workflowsStore.workflowId === PLACEHOLDER_EMPTY_WORKFLOW_ID && this.workflowsStore.workflowName === '';
},
loadedFinishedExecutionsCount(): number {
return (this.$store.getters['workflows/getAllLoadedFinishedExecutions'] as IExecutionsSummary[]).length;
return this.workflowsStore.getAllLoadedFinishedExecutions.length;
},
totalFinishedExecutionsCount(): number {
return this.$store.getters['workflows/getTotalFinishedExecutionsCount'];
return this.workflowsStore.getTotalFinishedExecutionsCount;
},
},
watch:{
@ -81,9 +92,9 @@ export default mixins(restApi, showMessage, executionHelpers, debounceHelper, wo
this.initView(workflowChanged);
if (to.params.executionId) {
const execution = this.$store.getters['workflows/getExecutionDataById'](to.params.executionId);
const execution = this.workflowsStore.getExecutionDataById(to.params.executionId);
if (execution) {
this.$store.commit('workflows/setActiveWorkflowExecution', execution);
this.workflowsStore.activeWorkflowExecution = execution;
}
}
},
@ -92,7 +103,7 @@ export default mixins(restApi, showMessage, executionHelpers, debounceHelper, wo
const nextTab = getNodeViewTab(to);
// When leaving for a page that's not a workflow view tab, ask to save changes
if (!nextTab) {
const result = this.$store.getters.getStateIsDirty;
const result = this.uiStore.stateIsDirty;
if (result) {
const confirmModal = await this.confirmModal(
this.$locale.baseText('generic.unsavedWork.confirmMessage.message'),
@ -105,11 +116,11 @@ export default mixins(restApi, showMessage, executionHelpers, debounceHelper, wo
if (confirmModal === MODAL_CONFIRMED) {
const saved = await this.saveCurrentWorkflow({}, false);
if (saved) this.$store.dispatch('settings/fetchPromptsData');
this.$store.commit('setStateDirty', false);
if (saved) this.settingsStore.fetchPromptsData();
this.uiStore.stateIsDirty = false;
next();
} else if (confirmModal === MODAL_CANCEL) {
this.$store.commit('setStateDirty', false);
this.uiStore.stateIsDirty = false;
next();
} else if (confirmModal === MODAL_CLOSE) {
next(false);
@ -122,8 +133,8 @@ export default mixins(restApi, showMessage, executionHelpers, debounceHelper, wo
},
async mounted() {
this.loading = true;
const workflowUpdated = this.$route.params.name !== this.$store.getters.workflowId;
const onNewWorkflow = this.$route.params.name === 'new' && this.$store.getters.workflowId === PLACEHOLDER_EMPTY_WORKFLOW_ID;
const workflowUpdated = this.$route.params.name !== this.workflowsStore.workflowId;
const onNewWorkflow = this.$route.params.name === 'new' && this.workflowsStore.workflowId === PLACEHOLDER_EMPTY_WORKFLOW_ID;
const shouldUpdate = workflowUpdated && !onNewWorkflow;
await this.initView(shouldUpdate);
if (!shouldUpdate) {
@ -134,11 +145,11 @@ export default mixins(restApi, showMessage, executionHelpers, debounceHelper, wo
methods: {
async initView(loadWorkflow: boolean) : Promise<void> {
if (loadWorkflow) {
if (this.$store.getters['nodeTypes/allNodeTypes'].length === 0) {
await this.$store.dispatch('nodeTypes/getNodeTypes');
if (this.nodeTypesStore.allNodeTypes.length === 0) {
await this.nodeTypesStore.getNodeTypes();
}
await this.openWorkflow(this.$route.params.name);
this.$store.commit('ui/setNodeViewInitialized', false);
this.uiStore.nodeViewInitialized = false;
this.setExecutions();
if (this.activeExecution) {
this.$router.push({
@ -193,7 +204,7 @@ export default mixins(restApi, showMessage, executionHelpers, debounceHelper, wo
currentExecutions.push(newExecution);
}
}
this.$store.commit('workflows/setCurrentWorkflowExecutions', currentExecutions);
this.workflowsStore.currentWorkflowExecutions = currentExecutions;
this.loadingMore = false;
},
async onDeleteCurrentExecution(): Promise<void> {
@ -203,13 +214,13 @@ export default mixins(restApi, showMessage, executionHelpers, debounceHelper, wo
await this.setExecutions();
// Select first execution in the list after deleting the current one
if (this.executions.length > 0) {
this.$store.commit('workflows/setActiveWorkflowExecution', this.executions[0]);
this.workflowsStore.activeWorkflowExecution = this.executions[0];
this.$router.push({
name: VIEWS.EXECUTION_PREVIEW,
params: { name: this.currentWorkflow, executionId: this.executions[0].id },
}).catch(()=>{});;
} else { // If there are no executions left, show empty state and clear active execution from the store
this.$store.commit('workflows/setActiveWorkflowExecution', null);
this.workflowsStore.activeWorkflowExecution = null;
this.$router.push({ name: VIEWS.EXECUTION_HOME, params: { name: this.currentWorkflow } });
}
} catch (error) {
@ -233,7 +244,7 @@ export default mixins(restApi, showMessage, executionHelpers, debounceHelper, wo
},
async setExecutions(): Promise<void> {
const workflowExecutions = await this.loadExecutions();
this.$store.commit('workflows/setCurrentWorkflowExecutions', workflowExecutions);
this.workflowsStore.currentWorkflowExecutions = workflowExecutions;
this.setActiveExecution();
},
async loadAutoRefresh(): Promise<void> {
@ -284,12 +295,12 @@ export default mixins(restApi, showMessage, executionHelpers, debounceHelper, wo
}
existingExecutions = existingExecutions.filter(execution => !gaps.includes(parseInt(execution.id, 10)) && lastId >= parseInt(execution.id, 10));
this.$store.commit('workflows/setCurrentWorkflowExecutions', existingExecutions);
this.workflowsStore.currentWorkflowExecutions = existingExecutions;
if (updatedActiveExecution !== null) {
this.$store.commit('workflows/setActiveWorkflowExecution', updatedActiveExecution);
this.workflowsStore.activeWorkflowExecution = updatedActiveExecution;
} else {
const activeNotInTheList = existingExecutions.find(ex => ex.id === this.activeExecution.id) === undefined;
if (activeNotInTheList) {
if (activeNotInTheList && this.executions.length > 0) {
this.$router.push({
name: VIEWS.EXECUTION_PREVIEW,
params: { name: this.currentWorkflow, executionId: this.executions[0].id },
@ -303,7 +314,7 @@ export default mixins(restApi, showMessage, executionHelpers, debounceHelper, wo
}
try {
const executions: IExecutionsSummary[] =
await this.$store.dispatch('workflows/loadCurrentWorkflowExecutions', this.filter);
await this.workflowsStore.loadCurrentWorkflowExecutions(this.filter);
return executions;
} catch (error) {
this.$showError(
@ -316,14 +327,14 @@ export default mixins(restApi, showMessage, executionHelpers, debounceHelper, wo
setActiveExecution(): void {
const activeExecutionId = this.$route.params.executionId;
if (activeExecutionId) {
const execution = this.$store.getters['workflows/getExecutionDataById'](activeExecutionId);
const execution = this.workflowsStore.getExecutionDataById(activeExecutionId);
if (execution) {
this.$store.commit('workflows/setActiveWorkflowExecution', execution);
this.workflowsStore.activeWorkflowExecution = execution;
}
}
// If there is no execution in the route, select the first one
if (this.$store.getters['workflows/getActiveWorkflowExecution'] === null && this.executions.length > 0) {
this.$store.commit('workflows/setActiveWorkflowExecution', this.executions[0]);
if (this.workflowsStore.activeWorkflowExecution === null && this.executions.length > 0) {
this.workflowsStore.activeWorkflowExecution = this.executions[0];
this.$router.push({
name: VIEWS.EXECUTION_PREVIEW,
params: { name: this.currentWorkflow, executionId: this.executions[0].id },
@ -353,19 +364,20 @@ export default mixins(restApi, showMessage, executionHelpers, debounceHelper, wo
}
await this.addNodes(data.nodes, data.connections);
this.$store.commit('setActive', data.active || false);
this.$store.commit('setWorkflowId', workflowId);
this.$store.commit('setWorkflowName', { newName: data.name, setStateDirty: false });
this.$store.commit('setWorkflowSettings', data.settings || {});
this.$store.commit('setWorkflowPinData', data.pinData || {});
this.workflowsStore.setActive(data.active || false);
this.workflowsStore.setWorkflowId(workflowId);
this.workflowsStore.setWorkflowName({ newName: data.name, setStateDirty: false });
this.workflowsStore.setWorkflowSettings(data.settings || {});
this.workflowsStore.setWorkflowPinData(data.pinData || {});
const tags = (data.tags || []) as ITag[];
this.$store.commit('tags/upsertTags', tags);
const tagIds = tags.map((tag) => tag.id);
this.$store.commit('setWorkflowTagIds', tagIds || []);
this.$store.commit('setWorkflowHash', data.hash);
this.workflowsStore.setWorkflowTagIds(tagIds || []);
this.workflowsStore.setWorkflowHash(data.hash);
this.$store.commit('tags/upsertTags', tags);
this.$externalHooks().run('workflow.open', { workflowId, workflowName: data.name });
this.$store.commit('setStateDirty', false);
this.uiStore.stateIsDirty = false;
},
async addNodes(nodes: INodeUi[], connections?: IConnections) {
if (!nodes || !nodes.length) {
@ -380,7 +392,7 @@ export default mixins(restApi, showMessage, executionHelpers, debounceHelper, wo
node.id = uuid();
}
nodeType = this.$store.getters['nodeTypes/getNodeType'](node.type, node.typeVersion) as INodeTypeDescription | null;
nodeType = this.nodeTypesStore.getNodeType(node.type, node.typeVersion);
// Make sure that some properties always exist
if (!node.hasOwnProperty('disabled')) {
@ -409,7 +421,7 @@ export default mixins(restApi, showMessage, executionHelpers, debounceHelper, wo
}
}
this.$store.commit('addNode', node);
this.workflowsStore.addNode(node);
});
// Load the connections
@ -438,7 +450,7 @@ export default mixins(restApi, showMessage, executionHelpers, debounceHelper, wo
},
] as [IConnection, IConnection];
this.$store.commit('addConnection', { connection: connectionData, setStateDirty: false });
this.workflowsStore.addConnection({ connection: connectionData, setStateDirty: false });
});
}
}
@ -446,7 +458,7 @@ export default mixins(restApi, showMessage, executionHelpers, debounceHelper, wo
}
},
async loadNodesProperties(nodeInfos: INodeTypeNameVersion[]): Promise<void> {
const allNodes: INodeTypeDescription[] = this.$store.getters['nodeTypes/allNodeTypes'];
const allNodes: INodeTypeDescription[] = this.nodeTypesStore.allNodeTypes;
const nodesToBeFetched: INodeTypeNameVersion[] = [];
allNodes.forEach(node => {
@ -463,12 +475,12 @@ export default mixins(restApi, showMessage, executionHelpers, debounceHelper, wo
if (nodesToBeFetched.length > 0) {
// Only call API if node information is actually missing
await this.$store.dispatch('nodeTypes/getNodesInformation', nodesToBeFetched);
await this.nodeTypesStore.getNodesInformation(nodesToBeFetched);
}
},
async loadActiveWorkflows(): Promise<void> {
const activeWorkflows = await this.restApi().getActiveWorkflows();
this.$store.commit('setActiveWorkflows', activeWorkflows);
this.workflowsStore.activeWorkflows = activeWorkflows;
},
async onRetryExecution(payload: { execution: IExecutionsSummary, command: string }) {
const loadWorkflow = payload.command === 'current-workflow';
@ -482,7 +494,7 @@ export default mixins(restApi, showMessage, executionHelpers, debounceHelper, wo
this.loadAutoRefresh();
this.$telemetry.track('User clicked retry execution button', {
workflow_id: this.$store.getters.workflowId,
workflow_id: this.workflowsStore.workflowId,
execution_id: payload.execution.id,
retry_type: loadWorkflow ? 'current' : 'original',
});

View file

@ -54,6 +54,9 @@ import { genericHelpers } from '@/components/mixins/genericHelpers';
import mixins from 'vue-typed-mixins';
import { hasExpressionMapping } from './helpers';
import { debounceHelper } from './mixins/debounce';
import { mapStores } from 'pinia';
import { useWorkflowsStore } from '@/stores/workflows';
import { useNDVStore } from '@/stores/ndv';
export default mixins(
externalHooks,
@ -78,6 +81,12 @@ export default mixins(
latestValue: '',
};
},
computed: {
...mapStores(
useNDVStore,
useWorkflowsStore,
),
},
methods: {
valueChanged (value: string, forceUpdate = false) {
this.latestValue = value;
@ -120,11 +129,11 @@ export default mixins(
node_name: string;
} = {
event_version: '2',
node_type_dest: this.$store.getters['ndv/activeNode'].type,
node_type_dest: this.ndvStore.activeNode? this.ndvStore.activeNode.type : '',
parameter_name_dest: this.parameter.displayName,
is_immediate_input: false,
variable_expression: eventData.variable,
node_name: this.$store.getters['ndv/activeNode'].name,
node_name: this.ndvStore.activeNode? this.ndvStore.activeNode.name : '',
};
if (eventData.variable) {
@ -142,9 +151,9 @@ export default mixins(
if (splitVar[0].startsWith('$node')) {
const sourceNodeName = splitVar[0].split('"')[1];
trackProperties.node_type_source = this.$store.getters.getNodeByName(sourceNodeName).type;
const nodeConnections: Array<Array<{ node: string }>> = this.$store.getters.outgoingConnectionsByNodeName(sourceNodeName).main;
trackProperties.is_immediate_input = (nodeConnections && nodeConnections[0] && !!nodeConnections[0].find(({ node }) => node === this.$store.getters['ndv/activeNode'].name)) ? true : false;
trackProperties.node_type_source = this.workflowsStore.getNodeByName(sourceNodeName)?.type;
const nodeConnections: Array<Array<{ node: string }>> = this.workflowsStore.outgoingConnectionsByNodeName(sourceNodeName).main;
trackProperties.is_immediate_input = (nodeConnections && nodeConnections[0] && !!nodeConnections[0].find(({ node }) => node === this.ndvStore.activeNode?.name || '')) ? true : false;
if (splitVar[1].startsWith('parameter')) {
trackProperties.parameter_name_source = splitVar[1].split('"')[1];
@ -173,9 +182,9 @@ export default mixins(
if (!newValue) {
const telemetryPayload = {
empty_expression: (this.value === '=') || (this.value === '={{}}') || !this.value,
workflow_id: this.$store.getters.workflowId,
workflow_id: this.workflowsStore.workflowId,
source: this.eventSource,
session_id: this.$store.getters['ndv/ndvSessionId'],
session_id: this.ndvStore.sessionId,
has_parameter: this.value.includes('$parameter'),
has_mapping: hasExpressionMapping(this.value),
};

View file

@ -28,6 +28,11 @@
<script lang="ts">
import {IFakeDoor} from '@/Interface';
import { useRootStore } from '@/stores/n8nRootStore';
import { useSettingsStore } from '@/stores/settings';
import { useUIStore } from '@/stores/ui';
import { useUsersStore } from '@/stores/users';
import { mapStores } from 'pinia';
import Vue from 'vue';
export default Vue.extend({
@ -43,23 +48,28 @@ export default Vue.extend({
},
},
computed: {
...mapStores(
useRootStore,
useSettingsStore,
useUIStore,
useUsersStore,
),
userId(): string {
return this.$store.getters['users/currentUserId'];
},
versionCli(): string {
return this.$store.getters['settings/versionCli'];
return this.usersStore.currentUserId || '';
},
instanceId(): string {
return this.$store.getters.instanceId;
return this.rootStore.instanceId;
},
featureInfo(): IFakeDoor {
return this.$store.getters['ui/getFakeDoorById'](this.featureId);
featureInfo(): IFakeDoor | undefined {
return this.uiStore.getFakeDoorById(this.featureId);
},
},
methods: {
openLinkPage() {
window.open(`${this.featureInfo.linkURL}&u=${this.instanceId}#${this.userId}&v=${this.versionCli}`, '_blank');
this.$telemetry.track('user clicked feature waiting list button', {feature: this.featureId});
if (this.featureInfo) {
window.open(`${this.featureInfo.linkURL}&u=${this.instanceId}#${this.userId}&v=${this.rootStore.versionCli}`, '_blank');
this.$telemetry.track('user clicked feature waiting list button', {feature: this.featureId});
}
},
},
});

View file

@ -10,7 +10,7 @@ import { VIEWS } from '@/constants';
import Vue from 'vue';
export default Vue.extend({
name: 'TemplateList',
name: 'GoBackButton',
data() {
return {
routeHasHistory: false,

View file

@ -42,6 +42,8 @@ import Vue from 'vue';
import { ITemplatesNode } from '@/Interface';
import { INodeTypeDescription } from 'n8n-workflow';
import { mapStores } from 'pinia';
import { useRootStore } from '@/stores/n8nRootStore';
interface NodeIconData {
type: string;
@ -72,6 +74,9 @@ export default Vue.extend({
},
},
computed: {
...mapStores(
useRootStore,
),
fontStyleData(): object {
return {
'max-width': this.size + 'px',
@ -115,7 +120,7 @@ export default Vue.extend({
return (nodeType as ITemplatesNode).iconData;
}
const restUrl = this.$store.getters.getRestUrl;
const restUrl = this.rootStore.getRestUrl;
if (nodeType.icon) {
const [type, path] = nodeType.icon.split(':');

View file

@ -50,6 +50,9 @@ import {
import { showMessage } from './mixins/showMessage';
import mixins from 'vue-typed-mixins';
import { INodeUi } from '@/Interface';
import { mapStores } from 'pinia';
import { useUIStore } from '@/stores/ui';
import { useNDVStore } from '@/stores/ndv';
export default mixins(showMessage).extend({
name: 'ImportCurlModal',
@ -64,8 +67,12 @@ export default mixins(showMessage).extend({
};
},
computed: {
node(): INodeUi {
return this.$store.getters['ndv/activeNode'];
...mapStores(
useNDVStore,
useUIStore,
),
node(): INodeUi | null {
return this.ndvStore.activeNode;
},
},
methods: {
@ -80,8 +87,7 @@ export default mixins(showMessage).extend({
if (curlCommand === '') return;
try {
const parameters = await this.$store.dispatch('ui/getCurlToJson', curlCommand);
const parameters = await this.uiStore.getCurlToJson(curlCommand);
const url = parameters['parameters.url'];
const invalidProtocol = CURL_IMPORT_NOT_SUPPORTED_PROTOCOLS.find((p) =>
@ -89,7 +95,8 @@ export default mixins(showMessage).extend({
);
if (!invalidProtocol) {
this.$store.dispatch('ui/setHttpNodeParameters', {
this.uiStore.setHttpNodeParameters({
name: IMPORT_CURL_MODAL_KEY,
parameters: JSON.stringify(parameters),
});
@ -114,7 +121,7 @@ export default mixins(showMessage).extend({
this.sendTelemetry({ success: false, invalidProtocol: false });
} finally {
this.$store.dispatch('ui/setCurlCommand', { command: this.curlCommand });
this.uiStore.setCurlCommand({ name: IMPORT_CURL_MODAL_KEY, command: this.curlCommand });
}
},
showProtocolErrorWithSupportedNode(protocol: string, node: string): void {
@ -168,7 +175,7 @@ export default mixins(showMessage).extend({
},
},
mounted() {
this.curlCommand = this.$store.getters['ui/getCurlCommand'];
this.curlCommand = this.uiStore.getCurlCommand || '';
setTimeout(() => {
(this.$refs.input as HTMLTextAreaElement).focus();
});

View file

@ -12,6 +12,8 @@
<script lang="ts">
import { IMPORT_CURL_MODAL_KEY } from '@/constants';
import { useUIStore } from '@/stores/ui';
import { mapStores } from 'pinia';
import mixins from 'vue-typed-mixins';
import { showMessage } from './mixins/showMessage';
@ -23,9 +25,12 @@ export default mixins(showMessage).extend({
default: false,
},
},
computed: {
...mapStores(useUIStore),
},
methods: {
onImportCurlClicked() {
this.$store.dispatch('ui/openModal', IMPORT_CURL_MODAL_KEY);
this.uiStore.openModal(IMPORT_CURL_MODAL_KEY);
},
},
});

View file

@ -76,6 +76,10 @@ import mixins from 'vue-typed-mixins';
import NodeExecuteButton from './NodeExecuteButton.vue';
import WireMeUp from './WireMeUp.vue';
import { CRON_NODE_TYPE, INTERVAL_NODE_TYPE, LOCAL_STORAGE_MAPPING_FLAG, MANUAL_TRIGGER_NODE_TYPE, SCHEDULE_TRIGGER_NODE_TYPE, START_NODE_TYPE } from '@/constants';
import { mapStores } from 'pinia';
import { useWorkflowsStore } from '@/stores/workflows';
import { useNDVStore } from '@/stores/ndv';
import { useNodeTypesStore } from '@/stores/nodeTypes';
export default mixins(
workflowHelpers,
@ -111,8 +115,13 @@ export default mixins(
};
},
computed: {
...mapStores(
useNodeTypesStore,
useNDVStore,
useWorkflowsStore,
),
focusedMappableInput(): string {
return this.$store.getters['ndv/focusedMappableInput'];
return this.ndvStore.focusedMappableInput;
},
isUserOnboarded(): boolean {
return window.localStorage.getItem(LOCAL_STORAGE_MAPPING_FLAG) === 'true';
@ -129,8 +138,8 @@ export default mixins(
if (!this.workflowRunning) {
return false;
}
const triggeredNode = this.$store.getters.executedNode;
const executingNode = this.$store.getters.executingNode;
const triggeredNode = this.workflowsStore.executedNode;
const executingNode = this.workflowsStore.executingNode;
if (this.activeNode && triggeredNode === this.activeNode.name && this.activeNode.name !== executingNode) {
return true;
}
@ -141,16 +150,16 @@ export default mixins(
return false;
},
workflowRunning (): boolean {
return this.$store.getters.isActionActive('workflowRunning');
return this.uiStore.isActionActive('workflowRunning');
},
currentWorkflow(): Workflow {
return this.workflow as Workflow;
},
activeNode (): INodeUi | null {
return this.$store.getters['ndv/activeNode'];
return this.ndvStore.activeNode;
},
currentNode (): INodeUi | null {
return this.$store.getters.getNodeByName(this.currentNodeName);
return this.workflowsStore.getNodeByName(this.currentNodeName);
},
connectedCurrentNodeOutputs(): number[] | undefined {
const search = this.parentNodes.find(({name}) => name === this.currentNodeName);
@ -174,7 +183,7 @@ export default mixins(
activeNodeType () : INodeTypeDescription | null {
if (!this.activeNode) return null;
return this.$store.getters['nodeTypes/getNodeType'](this.activeNode.type, this.activeNode.typeVersion);
return this.nodeTypesStore.getNodeType(this.activeNode.type, this.activeNode.typeVersion);
},
isMultiInputNode (): boolean {
return this.activeNodeType !== null && this.activeNodeType.inputs.length > 1;
@ -214,7 +223,7 @@ export default mixins(
if (this.activeNode) {
this.$telemetry.track('User clicked ndv button', {
node_type: this.activeNode.type,
workflow_id: this.$store.getters.workflowId,
workflow_id: this.workflowsStore.workflowId,
session_id: this.sessionId,
pane: 'input',
type: 'executePrevious',
@ -238,7 +247,7 @@ export default mixins(
if (this.activeNode) {
this.$telemetry.track('User clicked ndv link', {
node_type: this.activeNode.type,
workflow_id: this.$store.getters.workflowId,
workflow_id: this.workflowsStore.workflowId,
session_id: this.sessionId,
pane: 'input',
type: 'not-connected-help',

View file

@ -32,6 +32,8 @@ import Vue from "vue";
import { IFormInputs, IInviteResponse } from "@/Interface";
import { VALID_EMAIL_REGEX, INVITE_USER_MODAL_KEY } from "@/constants";
import { ROLE } from "@/modules/userHelpers";
import { mapStores } from "pinia";
import { useUsersStore } from "@/stores/users";
const NAME_EMAIL_FORMAT_REGEX = /^.* <(.*)>$/;
@ -101,6 +103,7 @@ export default mixins(showMessage).extend({
];
},
computed: {
...mapStores(useUsersStore),
emailsCount(): number {
return this.emails.split(',').filter((email: string) => !!email.trim()).length;
},
@ -156,7 +159,7 @@ export default mixins(showMessage).extend({
throw new Error(this.$locale.baseText('settings.users.noUsersToInvite'));
}
const invited: IInviteResponse[] = await this.$store.dispatch('users/inviteUsers', emails);
const invited: IInviteResponse[] = await this.usersStore.inviteUsers(emails);
const invitedEmails = invited.reduce((accu, {user, error}) => {
if (error) {
accu.error.push(user.email);

View file

@ -7,12 +7,17 @@
</template>
<script lang="ts">
import { useRootStore } from '@/stores/n8nRootStore';
import { mapStores } from 'pinia';
import Vue from 'vue';
export default Vue.extend({
computed: {
...mapStores(
useRootStore,
),
basePath(): string {
return this.$store.getters.getBaseUrl;
return this.rootStore.baseUrl;
},
},
});

View file

@ -43,12 +43,14 @@
<script lang="ts">
import mixins from "vue-typed-mixins";
import { IExecutionResponse } from "../../../Interface";
import { IExecutionResponse, IExecutionsSummary } from "../../../Interface";
import { titleChange } from "@/components/mixins/titleChange";
import ShortenName from "@/components/ShortenName.vue";
import ReadOnly from "@/components/MainHeader/ExecutionDetails/ReadOnly.vue";
import { mapStores } from "pinia";
import { useWorkflowsStore } from "@/stores/workflows";
export default mixins(titleChange).extend({
name: "ExecutionDetails",
@ -57,24 +59,27 @@ export default mixins(titleChange).extend({
ReadOnly,
},
computed: {
...mapStores(
useWorkflowsStore,
),
executionId(): string | undefined {
return this.$route.params.id;
},
executionFinished(): boolean {
const fullExecution = this.$store.getters.getWorkflowExecution;
const fullExecution = this.workflowsStore.getWorkflowExecution;
return !!fullExecution && fullExecution.finished;
},
executionWaiting(): boolean {
const fullExecution = this.$store.getters.getWorkflowExecution;
const fullExecution = this.workflowsStore.getWorkflowExecution as IExecutionsSummary;
return !!fullExecution && !!fullExecution.waitTill;
},
workflowExecution(): IExecutionResponse | null {
return this.$store.getters.getWorkflowExecution;
return this.workflowsStore.getWorkflowExecution;
},
workflowName(): string {
return this.$store.getters.workflowName;
return this.workflowsStore.workflowName;
},
},
methods: {

View file

@ -1,6 +1,6 @@
<template>
<div>
<div :class="{'main-header': true, expanded: !sidebarMenuCollapsed}">
<div :class="{'main-header': true, expanded: !this.uiStore.sidebarMenuCollapsed}">
<div v-show="!hideMenuBar" class="top-menu">
<ExecutionDetails v-if="isExecutionPage" />
<WorkflowDetails v-else />
@ -12,7 +12,6 @@
<script lang="ts">
import mixins from 'vue-typed-mixins';
import { mapGetters } from 'vuex';
import { pushConnection } from '@/components/mixins/pushConnection';
import WorkflowDetails from '@/components/MainHeader/WorkflowDetails.vue';
import ExecutionDetails from '@/components/MainHeader/ExecutionDetails/ExecutionDetails.vue';
@ -21,6 +20,9 @@ import { MAIN_HEADER_TABS, PLACEHOLDER_EMPTY_WORKFLOW_ID, STICKY_NODE_TYPE, VIEW
import { IExecutionsSummary, INodeUi, ITabBarItem } from '@/Interface';
import { workflowHelpers } from '../mixins/workflowHelpers';
import { Route } from 'vue-router';
import { mapStores } from 'pinia';
import { useUIStore } from '@/stores/ui';
import { useNDVStore } from '@/stores/ndv';
export default mixins(
pushConnection,
@ -36,13 +38,14 @@ export default mixins(
return {
activeHeaderTab: MAIN_HEADER_TABS.WORKFLOW,
workflowToReturnTo: '',
dirtyState: this.$store.getters.getStateIsDirty,
dirtyState: false,
};
},
computed: {
...mapGetters('ui', [
'sidebarMenuCollapsed',
]),
...mapStores(
useNDVStore,
useUIStore,
),
tabBarItems(): ITabBarItem[] {
return [
{ value: MAIN_HEADER_TABS.WORKFLOW, label: this.$locale.baseText('generic.workflow') },
@ -53,25 +56,26 @@ export default mixins(
return this.$route.name === VIEWS.EXECUTION;
},
activeNode (): INodeUi | null {
return this.$store.getters['ndv/activeNode'];
return this.ndvStore.activeNode;
},
hideMenuBar(): boolean {
return Boolean(this.activeNode && this.activeNode.type !== STICKY_NODE_TYPE);
},
workflowName (): string {
return this.$store.getters.workflowName;
return this.workflowsStore.workflowName;
},
currentWorkflow (): string {
return this.$route.params.name || this.$store.getters.workflowId;
return this.$route.params.name || this.workflowsStore.workflowId;
},
onWorkflowPage(): boolean {
return this.$route.meta && (this.$route.meta.nodeView || this.$route.meta.keepWorkflowAlive === true);
},
activeExecution(): IExecutionsSummary {
return this.$store.getters['workflows/getActiveWorkflowExecution'];
return this.workflowsStore.activeWorkflowExecution as IExecutionsSummary;
},
},
mounted() {
this.dirtyState = this.uiStore.stateIsDirty;
this.syncTabsWithRoute(this.$route);
// Initialize the push connection
this.pushConnect();
@ -109,13 +113,13 @@ export default mixins(
} else {
if (this.$route.name !== VIEWS.NEW_WORKFLOW) {
this.$router.push({ name: VIEWS.NEW_WORKFLOW });
this.$store.commit('setStateDirty', this.dirtyState);
this.uiStore.stateIsDirty = this.dirtyState;
}
}
this.activeHeaderTab = MAIN_HEADER_TABS.WORKFLOW;
break;
case MAIN_HEADER_TABS.EXECUTIONS:
this.dirtyState = this.$store.getters.getStateIsDirty;
this.dirtyState = this.uiStore.stateIsDirty;
this.workflowToReturnTo = this.currentWorkflow;
const routeWorkflowId = this.currentWorkflow === PLACEHOLDER_EMPTY_WORKFLOW_ID ? 'new' : this.currentWorkflow;
if (this.activeExecution) {

View file

@ -12,6 +12,8 @@
import Vue, { PropType } from 'vue';
import { ITabBarItem } from '@/Interface';
import { MAIN_HEADER_TABS } from '@/constants';
import { mapStores } from 'pinia';
import { useUIStore } from '@/stores/ui';
export default Vue.extend({
name: 'tab-bar',
@ -31,8 +33,11 @@ export default Vue.extend({
},
},
computed: {
...mapStores(
useUIStore,
),
mainSidebarCollapsed(): boolean {
return this.$store.getters['ui/sidebarMenuCollapsed'];
return this.uiStore.sidebarMenuCollapsed;
},
},
methods: {

View file

@ -23,7 +23,7 @@
</template>
</BreakpointsObserver>
<span v-if="areTagsEnabled" class="tags">
<span v-if="settingsStore.areTagsEnabled" class="tags">
<div
v-if="isTagsEditEnabled">
<TagsDropdown
@ -105,6 +105,11 @@ import { IWorkflowDataUpdate, IWorkflowToShare } from "@/Interface";
import { saveAs } from 'file-saver';
import { titleChange } from "../mixins/titleChange";
import type { MessageBoxInputData } from 'element-ui/types/message-box';
import { mapStores } from "pinia";
import { useUIStore } from "@/stores/ui";
import { useSettingsStore } from "@/stores/settings";
import { useWorkflowsStore } from "@/stores/workflows";
import { useRootStore } from "@/stores/n8nRootStore";
const hasChanged = (prev: string[], curr: string[]) => {
if (prev.length !== curr.length) {
@ -138,24 +143,32 @@ export default mixins(workflowHelpers, titleChange).extend({
};
},
computed: {
...mapGetters({
isWorkflowActive: "isActive",
workflowName: "workflowName",
isDirty: "getStateIsDirty",
currentWorkflowTagIds: "workflowTags",
}),
...mapGetters('settings', ['areTagsEnabled']),
...mapStores(
useRootStore,
useSettingsStore,
useUIStore,
useWorkflowsStore,
),
isWorkflowActive(): boolean {
return this.workflowsStore.isWorkflowActive;
},
workflowName(): string {
return this.workflowsStore.workflowName;
},
isDirty(): boolean {
return this.uiStore.stateIsDirty;
},
currentWorkflowTagIds(): string[] {
return this.workflowsStore.workflowTags;
},
isNewWorkflow(): boolean {
return !this.currentWorkflowId || (this.currentWorkflowId === PLACEHOLDER_EMPTY_WORKFLOW_ID || this.currentWorkflowId === 'new');
},
isWorkflowSaving(): boolean {
return this.$store.getters.isActionActive("workflowSaving");
return this.uiStore.isActionActive('workflowSaving');
},
currentWorkflowId(): string {
return this.$store.getters.workflowId;
},
workflowName (): string {
return this.$store.getters.workflowName;
return this.workflowsStore.workflowId;
},
onWorkflowPage(): boolean {
return this.$route.meta && (this.$route.meta.nodeView || this.$route.meta.keepWorkflowAlive === true);
@ -209,7 +222,7 @@ export default mixins(workflowHelpers, titleChange).extend({
currentId = this.$route.params.name;
}
const saved = await this.saveCurrentWorkflow({ id: currentId, name: this.workflowName, tags: this.currentWorkflowTagIds });
if (saved) this.$store.dispatch('settings/fetchPromptsData');
if (saved) await this.settingsStore.fetchPromptsData();
},
onTagsEditEnable() {
this.$data.appliedTagIds = this.currentWorkflowTagIds;
@ -314,12 +327,12 @@ export default mixins(workflowHelpers, titleChange).extend({
async onWorkflowMenuSelect(action: string): Promise<void> {
switch (action) {
case WORKFLOW_MENU_ACTIONS.DUPLICATE: {
await this.$store.dispatch('ui/openModalWithData', {
this.uiStore.openModalWithData({
name: DUPLICATE_MODAL_KEY,
data: {
id: this.$store.getters.workflowId,
name: this.$store.getters.workflowName,
tags: this.$store.getters.workflowTags,
id: this.workflowsStore.workflowId,
name: this.workflowsStore.workflowName,
tags: this.workflowsStore.workflowTags,
},
});
break;
@ -334,7 +347,7 @@ export default mixins(workflowHelpers, titleChange).extend({
const exportData: IWorkflowToShare = {
...data,
meta: {
instanceId: this.$store.getters.instanceId,
instanceId: this.rootStore.instanceId,
},
tags: (tags || []).map(tagId => {
const {usageCount, ...tag} = this.$store.getters["tags/getTagById"](tagId);
@ -347,7 +360,7 @@ export default mixins(workflowHelpers, titleChange).extend({
type: 'application/json;charset=utf-8',
});
let workflowName = this.$store.getters.workflowName || 'unsaved_workflow';
let workflowName = this.workflowName || 'unsaved_workflow';
workflowName = workflowName.replace(/[^a-z0-9]/gi, '_');
this.$telemetry.track('User exported workflow', { workflow_id: workflowData.id });
@ -376,7 +389,7 @@ export default mixins(workflowHelpers, titleChange).extend({
break;
}
case WORKFLOW_MENU_ACTIONS.SETTINGS: {
this.$store.dispatch('ui/openModal', WORKFLOW_SETTINGS_MODAL_KEY);
this.uiStore.openModal(WORKFLOW_SETTINGS_MODAL_KEY);
break;
}
case WORKFLOW_MENU_ACTIONS.DELETE: {
@ -404,7 +417,7 @@ export default mixins(workflowHelpers, titleChange).extend({
);
return;
}
this.$store.commit('setStateDirty', false);
this.uiStore.stateIsDirty = false;
// Reset tab title since workflow is deleted.
this.$titleReset();
this.$showMessage({

View file

@ -31,7 +31,7 @@
<!-- This dropdown is only enabled when sidebar is collapsed -->
<el-dropdown :disabled="!isCollapsed" placement="right-end" trigger="click" @command="onUserActionToggle">
<div :class="{[$style.avatar]: true, ['clickable']: isCollapsed }">
<n8n-avatar :firstName="currentUser.firstName" :lastName="currentUser.lastName" size="small" />
<n8n-avatar :firstName="usersStore.currentUser.firstName" :lastName="usersStore.currentUser.lastName" size="small" />
<el-dropdown-menu slot="dropdown">
<el-dropdown-item command="settings">{{ $locale.baseText('settings') }}</el-dropdown-item>
<el-dropdown-item command="logout">{{ $locale.baseText('auth.signout') }}</el-dropdown-item>
@ -40,7 +40,7 @@
</el-dropdown>
</div>
<div :class="{ ['ml-2xs']: true, [$style.userName]: true, [$style.expanded]: fullyExpanded }">
<n8n-text size="small" :bold="true" color="text-dark">{{currentUser.fullName}}</n8n-text>
<n8n-text size="small" :bold="true" color="text-dark">{{usersStore.currentUser.fullName}}</n8n-text>
</div>
<div :class="{ [$style.userActions]: true, [$style.expanded]: fullyExpanded }">
<n8n-action-dropdown :items="userMenuItems" placement="top-start" @select="onUserActionToggle" />
@ -78,12 +78,17 @@ import {
VERSIONS_MODAL_KEY,
EXECUTIONS_MODAL_KEY,
VIEWS,
WORKFLOW_OPEN_MODAL_KEY,
PLACEHOLDER_EMPTY_WORKFLOW_ID,
} from '@/constants';
import { userHelpers } from './mixins/userHelpers';
import { debounceHelper } from './mixins/debounce';
import Vue from 'vue';
import { mapStores } from 'pinia';
import { useUIStore } from '@/stores/ui';
import { useSettingsStore } from '@/stores/settings';
import { useUsersStore } from '@/stores/users';
import { useWorkflowsStore } from '@/stores/workflows';
import { useRootStore } from '@/stores/n8nRootStore';
export default mixins(
genericHelpers,
@ -105,36 +110,34 @@ export default mixins(
data () {
return {
// @ts-ignore
basePath: this.$store.getters.getBaseUrl,
basePath: '',
fullyExpanded: false,
};
},
computed: {
...mapGetters('ui', {
isCollapsed: 'sidebarMenuCollapsed',
isNodeView: 'isNodeView',
}),
...mapStores(
useRootStore,
useSettingsStore,
useUIStore,
useUsersStore,
useWorkflowsStore,
),
...mapGetters('versions', [
'hasVersionUpdates',
'nextVersions',
]),
...mapGetters('users', [
'canUserAccessSidebarUserInfo',
'currentUser',
]),
...mapGetters('settings', [
'isTemplatesEnabled',
'isUserManagementEnabled',
]),
isCollapsed(): boolean {
return this.uiStore.sidebarMenuCollapsed;
},
canUserAccessSettings(): boolean {
const accessibleRoute = this.findFirstAccessibleSettingsRoute();
return accessibleRoute !== null;
},
showUserArea(): boolean {
return this.isUserManagementEnabled && this.canUserAccessSidebarUserInfo && this.currentUser;
return this.settingsStore.isUserManagementEnabled && this.usersStore.canUserAccessSidebarUserInfo && this.usersStore.currentUser !== null;
},
workflowExecution (): IExecutionResponse | null {
return this.$store.getters.getWorkflowExecution;
return this.workflowsStore.getWorkflowExecution;
},
userMenuItems (): object[] {
return [
@ -150,7 +153,7 @@ export default mixins(
},
mainMenuItems (): IMenuItem[] {
const items: IMenuItem[] = [];
const injectedItems = this.$store.getters.sidebarMenuItems as IMenuItem[];
const injectedItems = this.uiStore.sidebarMenuItems;
if (injectedItems && injectedItems.length > 0) {
for(const item of injectedItems) {
@ -182,7 +185,7 @@ export default mixins(
icon: 'box-open',
label: this.$locale.baseText('mainSidebar.templates'),
position: 'top',
available: this.isTemplatesEnabled,
available: this.settingsStore.isTemplatesEnabled,
activateOnRouteNames: [ VIEWS.TEMPLATES ],
},
{
@ -204,7 +207,7 @@ export default mixins(
icon: 'cog',
label: this.$locale.baseText('settings'),
position: 'bottom',
available: this.canUserAccessSettings && this.currentUser,
available: this.canUserAccessSettings && this.usersStore.currentUser !== null,
activateOnRouteNames: [ VIEWS.USERS_SETTINGS, VIEWS.API_SETTINGS, VIEWS.PERSONAL_SETTINGS ],
},
{
@ -266,13 +269,14 @@ export default mixins(
},
},
async mounted() {
this.basePath = this.rootStore.baseUrl;
if (this.$refs.user) {
this.$externalHooks().run('mainSidebar.mounted', { userRef: this.$refs.user });
}
if (window.innerWidth < 900 || this.isNodeView) {
this.$store.commit('ui/collapseSidebarMenu');
if (window.innerWidth < 900 || this.uiStore.isNodeView) {
this.uiStore.sidebarMenuCollapsed = true;
} else {
this.$store.commit('ui/expandSidebarMenu');
this.uiStore.sidebarMenuCollapsed = false;
}
await Vue.nextTick();
this.fullyExpanded = !this.isCollapsed;
@ -285,7 +289,7 @@ export default mixins(
},
methods: {
trackHelpItemClick (itemType: string) {
this.$telemetry.track('User clicked help resource', { type: itemType, workflow_id: this.$store.getters.workflowId });
this.$telemetry.track('User clicked help resource', { type: itemType, workflow_id: this.workflowsStore.workflowId });
},
async onUserActionToggle(action: string) {
switch (action) {
@ -301,8 +305,7 @@ export default mixins(
},
async onLogout() {
try {
await this.$store.dispatch('users/logout');
await this.usersStore.logout();
const route = this.$router.resolve({ name: VIEWS.SIGNIN });
window.open(route.href, '_self');
} catch (e) {
@ -310,7 +313,7 @@ export default mixins(
}
},
toggleCollapse () {
this.$store.commit('ui/toggleSidebarMenuCollapse');
this.uiStore.toggleSidebarMenuCollapse();
// When expanding, delay showing some element to ensure smooth animation
if (!this.isCollapsed) {
setTimeout(() => {
@ -321,7 +324,7 @@ export default mixins(
}
},
openUpdatesPanel() {
this.$store.dispatch('ui/openModal', VERSIONS_MODAL_KEY);
this.uiStore.openModal(VERSIONS_MODAL_KEY);
},
async handleSelect (key: string) {
switch (key) {
@ -344,7 +347,7 @@ export default mixins(
break;
}
case 'executions': {
this.$store.dispatch('ui/openModal', EXECUTIONS_MODAL_KEY);
this.uiStore.openModal(EXECUTIONS_MODAL_KEY);
break;
}
case 'settings': {
@ -359,7 +362,7 @@ export default mixins(
}
case 'about': {
this.trackHelpItemClick('about');
this.$store.dispatch('ui/openModal', ABOUT_MODAL_KEY);
this.uiStore.openModal(ABOUT_MODAL_KEY);
break;
}
case 'quickstart':
@ -373,7 +376,7 @@ export default mixins(
}
},
async createNewWorkflow (): Promise<void> {
const result = this.$store.getters.getStateIsDirty;
const result = this.uiStore.stateIsDirty;
if(result) {
const confirmModal = await this.confirmModal(
this.$locale.baseText('generic.unsavedWork.confirmMessage.message'),
@ -385,7 +388,7 @@ export default mixins(
);
if (confirmModal === MODAL_CONFIRMED) {
const saved = await this.saveCurrentWorkflow({}, false);
if (saved) this.$store.dispatch('settings/fetchPromptsData');
if (saved) await this.settingsStore.fetchPromptsData();
if (this.$router.currentRoute.name === VIEWS.NEW_WORKFLOW) {
this.$root.$emit('newWorkflow');
} else {
@ -396,11 +399,11 @@ export default mixins(
type: 'success',
});
} else if (confirmModal === MODAL_CANCEL) {
this.$store.commit('setStateDirty', false);
this.uiStore.stateIsDirty = false;
if (this.$router.currentRoute.name === VIEWS.NEW_WORKFLOW) {
this.$root.$emit('newWorkflow');
} else {
this.$store.commit('setWorkflowId', PLACEHOLDER_EMPTY_WORKFLOW_ID);
this.workflowsStore.setWorkflowId(PLACEHOLDER_EMPTY_WORKFLOW_ID);
this.$router.push({ name: VIEWS.NEW_WORKFLOW });
}
this.$showMessage({
@ -412,7 +415,7 @@ export default mixins(
}
} else {
if (this.$router.currentRoute.name !== VIEWS.NEW_WORKFLOW) {
this.$store.commit('setWorkflowId', PLACEHOLDER_EMPTY_WORKFLOW_ID);
this.workflowsStore.setWorkflowId(PLACEHOLDER_EMPTY_WORKFLOW_ID);
this.$router.push({ name: VIEWS.NEW_WORKFLOW });
}
this.$showMessage({
@ -447,7 +450,7 @@ export default mixins(
},
checkWidthAndAdjustSidebar (width: number) {
if (width < 900) {
this.$store.commit('ui/collapseSidebarMenu');
this.uiStore.sidebarMenuCollapsed = true;
Vue.nextTick(() => {
this.fullyExpanded = !this.isCollapsed;
});

View file

@ -1,6 +1,6 @@
<template>
<el-dialog
:visible="visible"
:visible="uiStore.isModalOpen(this.$props.name)"
:before-close="closeDialog"
:class="{'dialog-wrapper': true, [$style.center]: center, scrollable: scrollable}"
:width="width"
@ -38,6 +38,8 @@
<script lang="ts">
import Vue from "vue";
import { useUIStore } from '@/stores/ui';
import { mapStores } from "pinia";
export default Vue.extend({
name: "Modal",
@ -118,7 +120,7 @@ export default Vue.extend({
});
this.$props.eventBus.$on('closeAll', () => {
this.closeAllDialogs();
this.uiStore.closeAllModals();
});
}
@ -130,51 +132,8 @@ export default Vue.extend({
beforeDestroy() {
window.removeEventListener('keydown', this.onWindowKeydown);
},
methods: {
onWindowKeydown(event: KeyboardEvent) {
if (!this.isActive) {
return;
}
if (event && event.keyCode === 13) {
this.handleEnter();
}
},
handleEnter() {
if (this.isActive) {
this.$emit('enter');
}
},
closeAllDialogs() {
this.$store.commit('ui/closeAllModals');
},
async closeDialog() {
if (this.beforeClose) {
const shouldClose = await this.beforeClose();
if (shouldClose === false) { // must be strictly false to stop modal from closing
return;
}
}
this.$store.commit('ui/closeModal', this.$props.name);
},
getCustomClass() {
let classes = this.$props.customClass || '';
if (this.$props.classic) {
classes = `${classes} classic`;
}
return classes;
},
},
computed: {
isActive(): boolean {
return this.$store.getters['ui/isModalActive'](this.$props.name);
},
visible(): boolean {
return this.$store.getters['ui/isModalOpen'](this.$props.name);
},
...mapStores(useUIStore),
styles() {
const styles: {[prop: string]: string} = {};
if (this.height) {
@ -195,6 +154,40 @@ export default Vue.extend({
return styles;
},
},
methods: {
onWindowKeydown(event: KeyboardEvent) {
if (!this.uiStore.isModalActive(this.$props.name)) {
return;
}
if (event && event.keyCode === 13) {
this.handleEnter();
}
},
handleEnter() {
if (this.uiStore.isModalActive(this.$props.name)) {
this.$emit('enter');
}
},
async closeDialog() {
if (this.beforeClose) {
const shouldClose = await this.beforeClose();
if (shouldClose === false) { // must be strictly false to stop modal from closing
return;
}
}
this.uiStore.closeModal(this.$props.name);
},
getCustomClass() {
let classes = this.$props.customClass || '';
if (this.$props.classic) {
classes = `${classes} classic`;
}
return classes;
},
},
});
</script>

View file

@ -1,7 +1,7 @@
<template>
<el-drawer
:direction="direction"
:visible="visible"
:visible="uiStore.isModalOpen(this.$props.name)"
:size="width"
:before-close="close"
:modal="modal"
@ -19,6 +19,8 @@
</template>
<script lang="ts">
import { useUIStore } from "@/stores/ui";
import { mapStores } from "pinia";
import Vue from "vue";
export default Vue.extend({
@ -65,9 +67,12 @@ export default Vue.extend({
beforeDestroy() {
window.removeEventListener('keydown', this.onWindowKeydown);
},
computed: {
...mapStores(useUIStore),
},
methods: {
onWindowKeydown(event: KeyboardEvent) {
if (!this.isActive) {
if (!this.uiStore.isModalActive(this.$props.name)) {
return;
}
@ -76,7 +81,7 @@ export default Vue.extend({
}
},
handleEnter() {
if (this.isActive) {
if (this.uiStore.isModalActive(this.$props.name)) {
this.$emit('enter');
}
},
@ -87,16 +92,7 @@ export default Vue.extend({
return;
}
}
this.$store.commit('ui/closeModal', this.$props.name);
},
},
computed: {
isActive(): boolean {
return this.$store.getters['ui/isModalActive'](this.$props.name);
},
visible(): boolean {
return this.$store.getters['ui/isModalOpen'](this.$props.name);
this.uiStore.closeModal(this.$props.name);
},
},
});

View file

@ -1,40 +1,36 @@
<template>
<div
v-if="isOpen(name) || keepAlive"
v-if="uiStore.isModalOpen(name) || keepAlive"
>
<slot
:modalName="name"
:active="isActive(name)"
:open="isOpen(name)"
:activeId="getActiveId(name)"
:mode="getMode(name)"
:data="getData(name)"
:active="uiStore.isModalActive(name)"
:open="uiStore.isModalOpen(name)"
:activeId="uiStore.getModalActiveId(name)"
:mode="uiStore.getModalMode(name)"
:data="uiStore.getModalData(name)"
></slot>
</div>
</template>
<script lang="ts">
import Vue from "vue";
import { useUIStore } from '@/stores/ui';
import { mapStores } from "pinia";
export default Vue.extend({
name: "ModalRoot",
props: ["name", "keepAlive"],
methods: {
isActive(name: string) {
return this.$store.getters['ui/isModalActive'](name);
props: {
name: {
type: String,
required: true,
},
isOpen(name: string) {
return this.$store.getters['ui/isModalOpen'](name);
},
getData(name: string) {
return this.$store.getters['ui/getModalData'](name);
},
getMode(name: string) {
return this.$store.getters['ui/getModalMode'](name);
},
getActiveId(name: string) {
return this.$store.getters['ui/getModalActiveId'](name);
keepAlive: {
type: Boolean,
},
},
computed: {
...mapStores(useUIStore),
},
});
</script>

View file

@ -49,6 +49,9 @@ import {
} from '@/constants';
import mixins from 'vue-typed-mixins';
import { debounceHelper } from './mixins/debounce';
import { mapStores } from 'pinia';
import { useNDVStore } from '@/stores/ndv';
import { nodePanelType } from '@/Interface';
const SIDE_MARGIN = 24;
@ -116,32 +119,35 @@ export default mixins(debounceHelper).extend({
window.removeEventListener('resize', this.setTotalWidth);
},
computed: {
...mapStores(
useNDVStore,
),
mainPanelDimensions(): {
relativeWidth: number,
relativeLeft: number,
relativeRight: number
} {
return this.$store.getters['ndv/mainPanelDimensions'](this.currentNodePaneType);
return this.ndvStore.getMainPanelDimensions(this.currentNodePaneType as nodePanelType);
},
supportedResizeDirections() {
supportedResizeDirections(): string[] {
const supportedDirections = ['right'];
if(this.isDraggable) supportedDirections.push('left');
return supportedDirections;
},
currentNodePaneType() {
currentNodePaneType(): string {
if(!this.hasInputSlot) return 'inputless';
if(!this.isDraggable) return 'dragless';
if(this.nodeType === null) return 'unknown';
return get(this, 'nodeType.parameterPane') || 'regular';
},
hasInputSlot() {
hasInputSlot(): boolean {
return this.$slots.input !== undefined;
},
inputPanelMargin(): number {
return this.pxToRelativeWidth(SIDE_PANELS_MARGIN);
},
minWindowWidth() {
minWindowWidth(): number {
return 2 * (SIDE_MARGIN + SIDE_PANELS_MARGIN) + MIN_PANEL_WIDTH;
},
minimumLeftPosition(): number {
@ -196,7 +202,7 @@ export default mixins(debounceHelper).extend({
const currentRelativeLeftDelta = this.calculatedPositions.outputPanelRelativeLeft - panelMinLeft;
return currentRelativeLeftDelta > 0 ? currentRelativeLeftDelta : 0;
},
hasDoubleWidth() {
hasDoubleWidth(): boolean {
return get(this, 'nodeType.parameterPane') === 'wide';
},
fixedPanelWidth(): number {
@ -244,7 +250,7 @@ export default mixins(debounceHelper).extend({
setMainPanelWidth(relativeWidth?: number) {
const mainPanelRelativeWidth = relativeWidth || this.pxToRelativeWidth(initialMainPanelWidth[this.currentNodePaneType]);
this.$store.commit('ndv/setMainPanelDimensions', {
this.ndvStore.setMainPanelDimensions({
panelType: this.currentNodePaneType,
dimensions: {
relativeWidth: mainPanelRelativeWidth,
@ -260,7 +266,7 @@ export default mixins(debounceHelper).extend({
const isInputless = this.currentNodePaneType === 'inputless';
if(isMinLeft) {
this.$store.commit('ndv/setMainPanelDimensions', {
this.ndvStore.setMainPanelDimensions({
panelType: this.currentNodePaneType,
dimensions: {
relativeLeft: this.minimumLeftPosition,
@ -271,18 +277,18 @@ export default mixins(debounceHelper).extend({
}
if(isMaxRight) {
this.$store.commit('ndv/setMainPanelDimensions', {
panelType: this.currentNodePaneType,
this.ndvStore.setMainPanelDimensions({
panelType: this.currentNodePaneType as nodePanelType,
dimensions: {
relativeLeft: 1 - this.mainPanelDimensions.relativeWidth - this.maximumRightPosition,
relativeRight: this.maximumRightPosition,
relativeRight: this.maximumRightPosition as number,
},
});
return;
}
this.$store.commit('ndv/setMainPanelDimensions', {
panelType: this.currentNodePaneType,
this.ndvStore.setMainPanelDimensions({
panelType: this.currentNodePaneType as nodePanelType,
dimensions: {
relativeLeft: isInputless ? this.minimumLeftPosition : mainPanelRelativeLeft,
relativeRight: mainPanelRelativeRight,

View file

@ -111,8 +111,13 @@ import mixins from 'vue-typed-mixins';
import { get } from 'lodash';
import { getStyleTokenValue, getTriggerNodeServiceName } from './helpers';
import { INodeUi, XYPosition } from '@/Interface';
import { IExecutionsSummary, INodeUi, XYPosition } from '@/Interface';
import { debounceHelper } from './mixins/debounce';
import { mapStores } from 'pinia';
import { useUIStore } from '@/stores/ui';
import { useWorkflowsStore } from '@/stores/workflows';
import { useNDVStore } from '@/stores/ndv';
import { useNodeTypesStore } from '@/stores/nodeTypes';
export default mixins(
externalHooks,
@ -128,6 +133,12 @@ export default mixins(
NodeIcon,
},
computed: {
...mapStores(
useNodeTypesStore,
useNDVStore,
useUIStore,
useWorkflowsStore,
),
isDuplicatable(): boolean {
if(!this.nodeType) return true;
return this.nodeType.maxNodes === undefined || this.sameTypeNodes.length < this.nodeType.maxNodes;
@ -136,7 +147,7 @@ export default mixins(
return this.nodeType?.group.includes('schedule') === true;
},
nodeRunData(): ITaskData[] {
return this.$store.getters.getWorkflowResultDataByNodeName(this.data.name);
return this.workflowsStore.getWorkflowResultDataByNodeName(this.data?.name || '') || [];
},
hasIssues (): boolean {
if (this.hasPinData) return false;
@ -154,7 +165,7 @@ export default mixins(
return workflowResultDataNode.length;
},
canvasOffsetPosition() {
return this.$store.getters.getNodeViewOffsetPosition;
return this.uiStore.nodeViewOffsetPosition;
},
getTriggerNodeTooltip (): string | undefined {
if (this.nodeType !== null && this.nodeType.hasOwnProperty('eventTriggerDescription')) {
@ -179,11 +190,11 @@ export default mixins(
return !!(this.nodeType && this.nodeType.polling);
},
isExecuting (): boolean {
return this.$store.getters.executingNode === this.data.name;
return this.workflowsStore.executingNode === this.data.name;
},
isSingleActiveTriggerNode (): boolean {
const nodes = this.$store.getters.workflowTriggerNodes.filter((node: INodeUi) => {
const nodeType = this.$store.getters['nodeTypes/getNodeType'](node.type, node.typeVersion) as INodeTypeDescription | null;
const nodes = this.workflowsStore.workflowTriggerNodes.filter((node: INodeUi) => {
const nodeType = this.nodeTypesStore.getNodeType(node.type, node.typeVersion);
return nodeType && nodeType.eventTriggerDescription !== '' && !node.disabled;
});
@ -193,7 +204,7 @@ export default mixins(
return this.data.type === MANUAL_TRIGGER_NODE_TYPE;
},
isTriggerNode (): boolean {
return this.$store.getters['nodeTypes/isTriggerNode'](this.data.type);
return this.nodeTypesStore.isTriggerNode(this.data?.type || '');
},
isTriggerNodeTooltipEmpty () : boolean {
return this.nodeType !== null ? this.nodeType.eventTriggerDescription === '' : false;
@ -202,13 +213,13 @@ export default mixins(
return this.node && this.node.disabled;
},
nodeType (): INodeTypeDescription | null {
return this.data && this.$store.getters['nodeTypes/getNodeType'](this.data.type, this.data.typeVersion);
return this.data && this.nodeTypesStore.getNodeType(this.data.type, this.data.typeVersion);
},
node (): INodeUi | undefined { // same as this.data but reactive..
return this.$store.getters.nodesByName[this.name] as INodeUi | undefined;
return this.workflowsStore.nodesByName[this.name] as INodeUi | undefined;
},
sameTypeNodes (): INodeUi[] {
return this.$store.getters.allNodes.filter((node: INodeUi) => node.type === this.data.type);
return this.workflowsStore.allNodes.filter((node: INodeUi) => node.type === this.data.type);
},
nodeClass (): object {
return {
@ -261,7 +272,7 @@ export default mixins(
return this.data.name;
},
waiting (): string | undefined {
const workflowExecution = this.$store.getters.getWorkflowExecution;
const workflowExecution = this.workflowsStore.getWorkflowExecution as IExecutionsSummary;
if (workflowExecution && workflowExecution.waitTill) {
const lastNodeExecuted = get(workflowExecution, 'data.resultData.lastNodeExecuted');
@ -285,7 +296,7 @@ export default mixins(
return;
},
workflowRunning (): boolean {
return this.$store.getters.isActionActive('workflowRunning');
return this.uiStore.isActionActive('workflowRunning');
},
nodeStyle (): object {
let borderColor = getStyleTokenValue('--color-foreground-xdark');
@ -312,7 +323,7 @@ export default mixins(
return returnStyles;
},
isSelected (): boolean {
return this.$store.getters.getSelectedNodes.find((node: INodeUi) => node.name === this.data.name);
return this.uiStore.getSelectedNodes.find((node: INodeUi) => node.name === this.data.name) !== undefined;
},
shiftOutputCount (): boolean {
return !!(this.nodeType && this.nodeType.outputs.length > 2);
@ -408,14 +419,14 @@ export default mixins(
},
disableNode () {
this.disableNodes([this.data]);
this.$telemetry.track('User clicked node hover button', { node_type: this.data.type, button_name: 'disable', workflow_id: this.$store.getters.workflowId });
this.$telemetry.track('User clicked node hover button', { node_type: this.data.type, button_name: 'disable', workflow_id: this.workflowsStore.workflowId });
},
executeNode () {
this.$emit('runWorkflow', this.data.name, 'Node.executeNode');
this.$telemetry.track('User clicked node hover button', { node_type: this.data.type, button_name: 'execute', workflow_id: this.$store.getters.workflowId });
this.$telemetry.track('User clicked node hover button', { node_type: this.data.type, button_name: 'execute', workflow_id: this.workflowsStore.workflowId });
},
deleteNode () {
this.$telemetry.track('User clicked node hover button', { node_type: this.data.type, button_name: 'delete', workflow_id: this.$store.getters.workflowId });
this.$telemetry.track('User clicked node hover button', { node_type: this.data.type, button_name: 'delete', workflow_id: this.workflowsStore.workflowId });
Vue.nextTick(() => {
// Wait a tick else vue causes problems because the data is gone
@ -423,7 +434,7 @@ export default mixins(
});
},
duplicateNode () {
this.$telemetry.track('User clicked node hover button', { node_type: this.data.type, button_name: 'duplicate', workflow_id: this.$store.getters.workflowId });
this.$telemetry.track('User clicked node hover button', { node_type: this.data.type, button_name: 'duplicate', workflow_id: this.workflowsStore.workflowId });
Vue.nextTick(() => {
// Wait a tick else vue causes problems because the data is gone
this.$emit('duplicateNode', this.data.name);
@ -444,7 +455,7 @@ export default mixins(
},
setNodeActive () {
this.$store.commit('ndv/setActiveNodeName', this.data.name);
this.ndvStore.activeNodeName = this.data ? this.data.name : '';
this.pinDataDiscoveryTooltipVisible = false;
},
touchStart () {

View file

@ -20,6 +20,8 @@
import Vue from "vue";
import * as CanvasHelpers from "@/views/canvasHelpers";
import {DEFAULT_STICKY_HEIGHT, DEFAULT_STICKY_WIDTH, STICKY_NODE_TYPE} from "@/constants";
import { mapStores } from "pinia";
import { useUIStore } from "@/stores/ui";
export default Vue.extend({
name: 'node-creation',
@ -41,6 +43,9 @@ export default Vue.extend({
showStickyButton: false,
};
},
computed: {
...mapStores(useUIStore),
},
methods: {
onCreateMenuHoverIn(mouseinEvent: MouseEvent) {
const buttonsWrapper = mouseinEvent.target as Element;
@ -73,7 +78,7 @@ export default Vue.extend({
(document.activeElement as HTMLElement).blur();
}
const offset: [number, number] = [...(this.$store.getters.getNodeViewOffsetPosition as [number, number])];
const offset: [number, number] = [...(this.uiStore.nodeViewOffsetPosition)];
const position = CanvasHelpers.getMidCanvasPosition(this.nodeViewScale, offset);
position[0] -= DEFAULT_STICKY_WIDTH / 2;

View file

@ -92,6 +92,10 @@ import { matchesNodeType, matchesSelectType } from './helpers';
import { BaseTextKey } from '@/plugins/i18n';
import { intersection } from '@/utils';
import { sublimeSearch } from './sortUtils';
import { mapStores } from 'pinia';
import { useWorkflowsStore } from '@/stores/workflows';
import { useRootStore } from '@/stores/n8nRootStore';
import { useNodeTypesStore } from '@/stores/nodeTypes';
export default mixins(externalHooks, globalLinkActions).extend({
name: 'CategorizedItems',
@ -146,6 +150,11 @@ export default mixins(externalHooks, globalLinkActions).extend({
this.unregisterCustomAction('showAllNodeCreatorNodes');
},
computed: {
...mapStores(
useNodeTypesStore,
useRootStore,
useWorkflowsStore,
),
activeSubcategory(): INodeCreateElement | null {
return this.activeSubcategoryHistory[this.activeSubcategoryHistory.length - 1] || null;
},
@ -156,10 +165,10 @@ export default mixins(externalHooks, globalLinkActions).extend({
return this.$store.getters['nodeCreator/selectedType'];
},
categoriesWithNodes(): ICategoriesWithNodes {
return this.$store.getters['nodeTypes/categoriesWithNodes'];
return this.nodeTypesStore.categoriesWithNodes;
},
categorizedItems(): INodeCreateElement[] {
return this.$store.getters['nodeTypes/categorizedItems'];
return this.nodeTypesStore.categorizedItems;
},
activeSubcategoryTitle(): string {
if(!this.activeSubcategory || !this.activeSubcategory.properties) return '';
@ -178,7 +187,7 @@ export default mixins(externalHooks, globalLinkActions).extend({
return this.nodeFilter.toLowerCase().trim();
},
defaultLocale (): string {
return this.$store.getters.defaultLocale;
return this.rootStore.defaultLocale;
},
filteredNodeTypes(): INodeCreateElement[] {
const filter = this.searchFilter;
@ -337,7 +346,7 @@ export default mixins(externalHooks, globalLinkActions).extend({
newValue,
selectedType: this.selectedType,
filteredNodes: this.filteredNodeTypes,
workflow_id: this.$store.getters.workflowId,
workflow_id: this.workflowsStore.workflowId,
});
},
},
@ -444,7 +453,7 @@ export default mixins(externalHooks, globalLinkActions).extend({
);
} else {
this.activeCategory = [...this.activeCategory, category];
this.$telemetry.trackNodesPanel('nodeCreateList.onCategoryExpanded', { category_name: category, workflow_id: this.$store.getters.workflowId });
this.$telemetry.trackNodesPanel('nodeCreateList.onCategoryExpanded', { category_name: category, workflow_id: this.workflowsStore.workflowId });
}
this.activeIndex = this.categorized.findIndex(
@ -456,7 +465,7 @@ export default mixins(externalHooks, globalLinkActions).extend({
this.$store.commit('nodeCreator/setShowTabs', false);
this.activeSubcategoryIndex = 0;
this.activeSubcategoryHistory.push(selected);
this.$telemetry.trackNodesPanel('nodeCreateList.onSubcategorySelected', { selected, workflow_id: this.$store.getters.workflowId });
this.$telemetry.trackNodesPanel('nodeCreateList.onSubcategorySelected', { selected, workflow_id: this.workflowsStore.workflowId });
},
onSubcategoryClose() {

View file

@ -18,6 +18,8 @@ import camelcase from 'lodash.camelcase';
import { CategoryName } from '@/plugins/i18n';
import { INodeCreateElement, ICategoriesWithNodes } from '@/Interface';
import { NODE_TYPE_COUNT_MAPPER } from '@/constants';
import { mapStores } from 'pinia';
import { useNodeTypesStore } from '@/stores/nodeTypes';
export default Vue.extend({
@ -27,20 +29,23 @@ export default Vue.extend({
},
},
computed: {
...mapStores(
useNodeTypesStore,
),
selectedType(): "Regular" | "Trigger" | "All" {
return this.$store.getters['nodeCreator/selectedType'];
},
categoriesWithNodes(): ICategoriesWithNodes {
return this.$store.getters['nodeTypes/categoriesWithNodes'];
return this.nodeTypesStore.categoriesWithNodes;
},
categorizedItems(): INodeCreateElement[] {
return this.$store.getters['nodeTypes/categorizedItems'];
return this.nodeTypesStore.categorizedItems;
},
categoryName() {
return camelcase(this.item.category);
},
nodesCount(): number {
const currentCategory = this.categoriesWithNodes[this.item.category];
const currentCategory= (this.categoriesWithNodes as ICategoriesWithNodes)[this.item.category];
const subcategories = Object.keys(currentCategory);
// We need to sum subcategories count for the curent nodeType view

View file

@ -33,6 +33,8 @@ import { ALL_NODE_FILTER, TRIGGER_NODE_FILTER, OTHER_TRIGGER_NODES_SUBCATEGORY,
import CategorizedItems from './CategorizedItems.vue';
import TypeSelector from './TypeSelector.vue';
import { INodeCreateElement } from '@/Interface';
import { mapStores } from 'pinia';
import { useWorkflowsStore } from '@/stores/workflows';
export default mixins(externalHooks).extend({
name: 'NodeCreateList',
@ -55,6 +57,9 @@ export default mixins(externalHooks).extend({
};
},
computed: {
...mapStores(
useWorkflowsStore,
),
selectedType(): string {
return this.$store.getters['nodeCreator/selectedType'];
},
@ -68,7 +73,7 @@ export default mixins(externalHooks).extend({
this.$telemetry.trackNodesPanel('nodeCreateList.selectedTypeChanged', {
old_filter: oldValue,
new_filter: newValue,
workflow_id: this.$store.getters.workflowId,
workflow_id: this.workflowsStore.workflowId,
});
},
},
@ -80,7 +85,7 @@ export default mixins(externalHooks).extend({
destroyed() {
this.$store.commit('nodeCreator/setSelectedType', ALL_NODE_FILTER);
this.$externalHooks().run('nodeCreateList.destroyed');
this.$telemetry.trackNodesPanel('nodeCreateList.destroyed', { workflow_id: this.$store.getters.workflowId });
this.$telemetry.trackNodesPanel('nodeCreateList.destroyed', { workflow_id: this.workflowsStore.workflowId });
},
});
</script>

View file

@ -1,6 +1,6 @@
<template>
<div>
<aside :class="{'node-creator-scrim': true, expanded: !sidebarMenuCollapsed, active: showScrim}" />
<aside :class="{'node-creator-scrim': true, expanded: !uiStore.sidebarMenuCollapsed, active: showScrim}" />
<slide-transition>
<div
@ -29,6 +29,9 @@ import { INodeTypeDescription } from 'n8n-workflow';
import SlideTransition from '../../transitions/SlideTransition.vue';
import MainPanel from './MainPanel.vue';
import { mapStores } from 'pinia';
import { useUIStore } from '@/stores/ui';
import { useNodeTypesStore } from '@/stores/nodeTypes';
export default Vue.extend({
name: 'NodeCreator',
@ -42,14 +45,15 @@ export default Vue.extend({
},
},
computed: {
...mapStores(
useNodeTypesStore,
useUIStore,
),
showScrim(): boolean {
return this.$store.getters['nodeCreator/showScrim'];
},
sidebarMenuCollapsed(): boolean {
return this.$store.getters['ui/sidebarMenuCollapsed'];
},
visibleNodeTypes(): INodeTypeDescription[] {
return this.$store.getters['nodeTypes/visibleNodeTypes'];
return this.nodeTypesStore.visibleNodeTypes;
},
searchItems(): INodeCreateElement[] {
const sorted = [...this.visibleNodeTypes];

View file

@ -63,6 +63,7 @@ import {
ICredentialsResponse,
INodeUi,
INodeUpdatePropertiesInformation,
IUser,
} from '@/Interface';
import {
ICredentialType,
@ -81,6 +82,11 @@ import { mapGetters } from "vuex";
import mixins from 'vue-typed-mixins';
import {getCredentialPermissions} from "@/permissions";
import { mapStores } from 'pinia';
import { useUIStore } from '@/stores/ui';
import { useUsersStore } from '@/stores/users';
import { useWorkflowsStore } from '@/stores/workflows';
import { useNodeTypesStore } from '@/stores/nodeTypes';
export default mixins(
genericHelpers,
@ -103,11 +109,19 @@ export default mixins(
};
},
computed: {
...mapGetters('users', ['currentUser']),
...mapStores(
useNodeTypesStore,
useUIStore,
useUsersStore,
useWorkflowsStore,
),
...mapGetters('credentials', {
allCredentialsByType: 'allCredentialsByType',
getCredentialTypeByName: 'getCredentialTypeByName',
}),
currentUser (): IUser {
return this.usersStore.currentUser || {} as IUser;
},
credentialTypesNode (): string[] {
return this.credentialTypesNodeDescription
.map((credentialTypeDescription) => credentialTypeDescription.name);
@ -125,7 +139,7 @@ export default mixins(
if (credType) return [credType];
const activeNodeType = this.$store.getters['nodeTypes/getNodeType'](node.type, node.typeVersion) as INodeTypeDescription | null;
const activeNodeType = this.nodeTypesStore.getNodeType(node.type, node.typeVersion);
if (activeNodeType && activeNodeType.credentials) {
return activeNodeType.credentials;
}
@ -220,8 +234,8 @@ export default mixins(
onCredentialSelected (credentialType: string, credentialId: string | null | undefined) {
if (credentialId === this.NEW_CREDENTIALS_TEXT) {
this.listenForNewCredentials(credentialType);
this.$store.dispatch('ui/openNewCredential', { type: credentialType });
this.$telemetry.track('User opened Credential modal', { credential_type: credentialType, source: 'node', new_credential: true, workflow_id: this.$store.getters.workflowId });
this.uiStore.openNewCredential(credentialType);
this.$telemetry.track('User opened Credential modal', { credential_type: credentialType, source: 'node', new_credential: true, workflow_id: this.workflowsStore.workflowId });
return;
}
@ -231,7 +245,7 @@ export default mixins(
credential_type: credentialType,
node_type: this.node.type,
...(this.hasProxyAuth(this.node) ? { is_service_specific: true } : {}),
workflow_id: this.$store.getters.workflowId,
workflow_id: this.workflowsStore.workflowId,
credential_id: credentialId,
},
);
@ -244,7 +258,7 @@ export default mixins(
// if credentials has been string or neither id matched nor name matched uniquely
if (oldCredentials.id === null || (oldCredentials.id && !this.$store.getters['credentials/getCredentialByIdAndType'](oldCredentials.id, credentialType))) {
// update all nodes in the workflow with the same old/invalid credentials
this.$store.commit('replaceInvalidWorkflowCredentials', {
this.workflowsStore.replaceInvalidWorkflowCredentials({
credentials: selected,
invalid: oldCredentials,
type: credentialType,
@ -316,9 +330,9 @@ export default mixins(
editCredential(credentialType: string): void {
const { id } = this.node.credentials[credentialType];
this.$store.dispatch('ui/openExistingCredential', { id });
this.uiStore.openExistingCredential(id);
this.$telemetry.track('User opened Credential modal', { credential_type: credentialType, source: 'node', new_credential: false, workflow_id: this.$store.getters.workflowId });
this.$telemetry.track('User opened Credential modal', { credential_type: credentialType, source: 'node', new_credential: false, workflow_id: this.workflowsStore.workflowId });
this.listenForNewCredentials(credentialType);
},

View file

@ -141,6 +141,11 @@ import {
import { workflowActivate } from './mixins/workflowActivate';
import { pinData } from "@/components/mixins/pinData";
import { dataPinningEventBus } from '@/event-bus/data-pinning-event-bus';
import { mapStores } from 'pinia';
import { useWorkflowsStore } from '@/stores/workflows';
import { useNDVStore } from '@/stores/ndv';
import { useNodeTypesStore } from '@/stores/nodeTypes';
import { useUIStore } from '@/stores/ui';
export default mixins(
externalHooks,
@ -182,8 +187,7 @@ export default mixins(
};
},
mounted() {
this.$store.commit('ndv/setNDVSessionId');
this.ndvStore.setNDVSessionId;
dataPinningEventBus.$on('data-pinning-discovery', ({ isTooltipVisible }: { isTooltipVisible: boolean }) => {
this.pinDataDiscoveryTooltipVisible = isTooltipVisible;
});
@ -192,12 +196,17 @@ export default mixins(
dataPinningEventBus.$off('data-pinning-discovery');
},
computed: {
...mapGetters(['executionWaitingForWebhook']),
...mapStores(
useNodeTypesStore,
useNDVStore,
useUIStore,
useWorkflowsStore,
),
sessionId(): string {
return this.$store.getters['ndv/ndvSessionId'];
return this.ndvStore.sessionId;
},
workflowRunning(): boolean {
return this.$store.getters.isActionActive('workflowRunning');
return this.uiStore.isActionActive('workflowRunning');
},
showTriggerWaitingWarning(): boolean {
return (
@ -205,25 +214,25 @@ export default mixins(
!!this.activeNodeType &&
!this.activeNodeType.group.includes('trigger') &&
this.workflowRunning &&
this.executionWaitingForWebhook
this.workflowsStore.executionWaitingForWebhook
);
},
activeNode(): INodeUi | null {
return this.$store.getters['ndv/activeNode'];
return this.ndvStore.activeNode;
},
inputNodeName(): string | undefined {
return this.selectedInput || this.parentNode;
},
inputNode(): INodeUi | null {
if (this.inputNodeName) {
return this.$store.getters.getNodeByName(this.inputNodeName);
return this.workflowsStore.getNodeByName(this.inputNodeName);
}
return null;
},
activeNodeType(): INodeTypeDescription | null {
if (this.activeNode) {
return this.$store.getters['nodeTypes/getNodeType'](this.activeNode.type, this.activeNode.typeVersion);
return this.nodeTypesStore.getNodeType(this.activeNode.type, this.activeNode.typeVersion);
}
return null;
},
@ -257,11 +266,11 @@ export default mixins(
},
isActiveStickyNode(): boolean {
return (
!!this.$store.getters['ndv/activeNode'] && this.$store.getters['ndv/activeNode'].type === STICKY_NODE_TYPE
!!this.ndvStore.activeNode && this.ndvStore.activeNode .type === STICKY_NODE_TYPE
);
},
workflowExecution(): IExecutionResponse | null {
return this.$store.getters.getWorkflowExecution;
return this.workflowsStore.getWorkflowExecution;
},
workflowRunData(): IRunData | null {
if (this.workflowExecution === null) {
@ -340,13 +349,13 @@ export default mixins(
return `${BASE_NODE_SURVEY_URL}${this.activeNodeType.name}`;
},
outputPanelEditMode(): { enabled: boolean; value: string; } {
return this.$store.getters['ndv/outputPanelEditMode'];
return this.ndvStore.outputPanelEditMode;
},
isWorkflowRunning(): boolean {
return this.$store.getters.isActionActive('workflowRunning');
return this.uiStore.isActionActive('workflowRunning');
},
isExecutionWaitingForWebhook(): boolean {
return this.$store.getters.executionWaitingForWebhook;
return this.workflowsStore.executionWaitingForWebhook;
},
blockUi(): boolean {
return this.isWorkflowRunning || this.isExecutionWaitingForWebhook;
@ -364,7 +373,7 @@ export default mixins(
this.avgInputRowHeight = 0;
setTimeout(() => {
this.$store.commit('ndv/setNDVSessionId');
this.ndvStore.setNDVSessionId;
}, 0);
this.$externalHooks().run('dataDisplay.nodeTypeChanged', {
nodeSubtitle: this.getNodeSubtitle(node, this.activeNodeType, this.getCurrentWorkflow()),
@ -374,24 +383,24 @@ export default mixins(
setTimeout(() => {
if (this.activeNode) {
const outogingConnections = this.$store.getters.outgoingConnectionsByNodeName(
const outgoingConnections = this.workflowsStore.outgoingConnectionsByNodeName(
this.activeNode.name,
) as INodeConnections;
this.$telemetry.track('User opened node modal', {
node_type: this.activeNodeType ? this.activeNodeType.name : '',
workflow_id: this.$store.getters.workflowId,
workflow_id: this.workflowsStore.workflowId,
session_id: this.sessionId,
parameters_pane_position: this.mainPanelPosition,
input_first_connector_runs: this.maxInputRun,
output_first_connector_runs: this.maxOutputRun,
selected_view_inputs: this.isTriggerNode
? 'trigger'
: this.$store.getters['ndv/inputPanelDisplayMode'],
selected_view_outputs: this.$store.getters['ndv/outputPanelDisplayMode'],
: this.ndvStore.inputPanelDisplayMode,
selected_view_outputs: this.ndvStore.outputPanelDisplayMode,
input_connectors: this.parentNodes.length,
output_connectors:
outogingConnections && outogingConnections.main && outogingConnections.main.length,
outgoingConnections && outgoingConnections.main && outgoingConnections.main.length,
input_displayed_run_index: this.inputRun,
output_displayed_run_index: this.outputRun,
data_pinning_tooltip_presented: this.pinDataDiscoveryTooltipVisible,
@ -413,12 +422,12 @@ export default mixins(
},
inputNodeName(nodeName: string | undefined) {
setTimeout(() => {
this.$store.commit('ndv/setInputNodeName', nodeName);
this.ndvStore.setInputNodeName(nodeName);
}, 0);
},
inputRun() {
setTimeout(() => {
this.$store.commit('ndv/setInputRunIndex', this.inputRun);
this.ndvStore.setInputRunIndex(this.inputRun);
}, 0);
},
},
@ -428,7 +437,7 @@ export default mixins(
return;
}
if (e === null) {
this.$store.commit('ndv/setHoveringItem', null);
this.ndvStore.setHoveringItem(null);
return;
}
@ -438,11 +447,11 @@ export default mixins(
outputIndex: e.outputIndex,
itemIndex: e.itemIndex,
};
this.$store.commit('ndv/setHoveringItem', item);
this.ndvStore.setHoveringItem(item);
},
onOutputItemHover(e: {itemIndex: number, outputIndex: number} | null) {
if (e === null || !this.activeNode) {
this.$store.commit('ndv/setHoveringItem', null);
this.ndvStore.setHoveringItem(null);
return;
}
@ -452,7 +461,7 @@ export default mixins(
outputIndex: e.outputIndex,
itemIndex: e.itemIndex,
};
this.$store.commit('ndv/setHoveringItem', item);
this.ndvStore.setHoveringItem(item);
},
onInputTableMounted(e: { avgRowHeight: number }) {
this.avgInputRowHeight = e.avgRowHeight;
@ -461,7 +470,7 @@ export default mixins(
this.avgOutputRowHeight = e.avgRowHeight;
},
onWorkflowActivate() {
this.$store.commit('ndv/setActiveNodeName', null);
this.ndvStore.activeNodeName = null;
setTimeout(() => {
this.activateCurrentWorkflow('ndv');
}, 1000);
@ -471,7 +480,7 @@ export default mixins(
if (this.activeNode) {
this.$telemetry.track('User clicked ndv link', {
node_type: this.activeNode.type,
workflow_id: this.$store.getters.workflowId,
workflow_id: this.workflowsStore.workflowId,
session_id: this.sessionId,
pane: 'main',
type: 'i-wish-this-node-would',
@ -493,7 +502,7 @@ export default mixins(
end_position: e.position,
node_type: this.activeNodeType ? this.activeNodeType.name : '',
session_id: this.sessionId,
workflow_id: this.$store.getters.workflowId,
workflow_id: this.workflowsStore.workflowId,
});
this.mainPanelPosition = e.position;
},
@ -567,21 +576,23 @@ export default mixins(
return;
}
this.$store.commit('pinData', { node: this.activeNode, data: jsonParse(value) });
if (this.activeNode) {
this.workflowsStore.pinData({ node: this.activeNode, data: jsonParse(value) });
}
}
this.$store.commit('ndv/setOutputPanelEditModeEnabled', false);
this.ndvStore.setOutputPanelEditModeEnabled(false);
}
this.$externalHooks().run('dataDisplay.nodeEditingFinished');
this.$telemetry.track('User closed node modal', {
node_type: this.activeNodeType ? this.activeNodeType.name : '',
session_id: this.sessionId,
workflow_id: this.$store.getters.workflowId,
workflow_id: this.workflowsStore.workflowId,
});
this.triggerWaitingWarningEnabled = false;
this.$store.commit('ndv/setActiveNodeName', null);
this.$store.commit('ndv/resetNDVSessionId');
this.ndvStore.activeNodeName = null;
this.ndvStore.resetNDVSessionId();
},
onRunOutputIndexChange(run: number) {
this.runOutputIndex = run;
@ -610,7 +621,7 @@ export default mixins(
this.$telemetry.track('User changed ndv input dropdown', {
node_type: this.activeNode ? this.activeNode.type : '',
session_id: this.sessionId,
workflow_id: this.$store.getters.workflowId,
workflow_id: this.workflowsStore.workflowId,
selection_value: index,
input_node_type: this.inputNode ? this.inputNode.type : '',
});

View file

@ -23,6 +23,10 @@ import mixins from 'vue-typed-mixins';
import { workflowRun } from './mixins/workflowRun';
import { pinData } from './mixins/pinData';
import { dataPinningEventBus } from '@/event-bus/data-pinning-event-bus';
import { mapStores } from 'pinia';
import { useWorkflowsStore } from '@/stores/workflows';
import { useNDVStore } from '@/stores/ndv';
import { useNodeTypesStore } from '@/stores/nodeTypes';
export default mixins(
workflowRun,
@ -54,25 +58,30 @@ export default mixins(
},
},
computed: {
node (): INodeUi {
return this.$store.getters.getNodeByName(this.nodeName);
...mapStores(
useNodeTypesStore,
useNDVStore,
useWorkflowsStore,
),
node (): INodeUi | null {
return this.workflowsStore.getNodeByName(this.nodeName);
},
nodeType (): INodeTypeDescription | null {
if (this.node) {
return this.$store.getters['nodeTypes/getNodeType'](this.node.type, this.node.typeVersion);
return this.nodeTypesStore.getNodeType(this.node.type, this.node.typeVersion);
}
return null;
},
nodeRunning (): boolean {
const triggeredNode = this.$store.getters.executedNode;
const executingNode = this.$store.getters.executingNode;
const triggeredNode = this.workflowsStore.executedNode;
const executingNode = this.workflowsStore.executingNode;
return this.workflowRunning && (executingNode === this.node.name || triggeredNode === this.node.name);
},
workflowRunning (): boolean {
return this.$store.getters.isActionActive('workflowRunning');
return this.uiStore.isActionActive('workflowRunning');
},
isTriggerNode (): boolean {
return this.$store.getters['nodeTypes/isTriggerNode'](this.node.type);
return this.nodeTypesStore.isTriggerNode(this.node.type);
},
isManualTriggerNode (): boolean {
return Boolean(this.nodeType && this.nodeType.name === MANUAL_TRIGGER_NODE_TYPE);
@ -87,8 +96,8 @@ export default mixins(
return Boolean(this.nodeType && this.nodeType.name === WEBHOOK_NODE_TYPE);
},
isListeningForEvents(): boolean {
const waitingOnWebhook = this.$store.getters.executionWaitingForWebhook as boolean;
const executedNode = this.$store.getters.executedNode as string | undefined;
const waitingOnWebhook = this.workflowsStore.executionWaitingForWebhook;
const executedNode = this.workflowsStore.executedNode;
return (
this.node &&
@ -114,7 +123,8 @@ export default mixins(
}
if (this.isTriggerNode && this.hasIssues) {
if (this.$store.getters['ndv/activeNode'] && this.$store.getters['ndv/activeNode'].name !== this.nodeName) {
const activeNode = this.ndvStore.activeNode;
if (activeNode && activeNode.name !== this.nodeName) {
return this.$locale.baseText('ndv.execute.fixPrevious');
}
@ -154,7 +164,7 @@ export default mixins(
methods: {
async stopWaitingForWebhook () {
try {
await this.restApi().removeTestWebhook(this.$store.getters.workflowId);
await this.restApi().removeTestWebhook(this.workflowsStore.workflowId);
} catch (error) {
this.$showError(
error,
@ -182,14 +192,14 @@ export default mixins(
if (shouldUnpinAndExecute) {
dataPinningEventBus.$emit('data-unpinning', { source: 'unpin-and-execute-modal' });
this.$store.commit('unpinData', { node: this.node });
this.workflowsStore.unpinData({ node: this.node });
}
}
if (!this.hasPinData || shouldUnpinAndExecute) {
const telemetryPayload = {
node_type: this.nodeType ? this.nodeType.name : null,
workflow_id: this.$store.getters.workflowId,
workflow_id: this.workflowsStore.workflowId,
source: this.telemetrySource,
};
this.$telemetry.track('User clicked execute node button', telemetryPayload);

View file

@ -15,7 +15,9 @@
<script lang="ts">
import { IVersionNode } from '@/Interface';
import { useRootStore } from '@/stores/n8nRootStore';
import { INodeTypeDescription } from 'n8n-workflow';
import { mapStores } from 'pinia';
import Vue from 'vue';
interface NodeIconSource {
@ -47,6 +49,9 @@ export default Vue.extend({
},
},
computed: {
...mapStores(
useRootStore,
),
type (): string {
const nodeType = this.nodeType as INodeTypeDescription | IVersionNode | null;
let iconType = 'unknown';
@ -68,7 +73,7 @@ export default Vue.extend({
},
iconSource () : NodeIconSource {
const nodeType = this.nodeType as INodeTypeDescription | IVersionNode | null;
const restUrl = this.$store.getters.getRestUrl;
const restUrl = this.rootStore.getRestUrl;
const iconSource = {} as NodeIconSource;
if (nodeType) {

View file

@ -152,6 +152,11 @@ import { nodeHelpers } from '@/components/mixins/nodeHelpers';
import mixins from 'vue-typed-mixins';
import NodeExecuteButton from './NodeExecuteButton.vue';
import { isCommunityPackageName } from './helpers';
import { mapStores } from 'pinia';
import { useUIStore } from '@/stores/ui';
import { useWorkflowsStore } from '@/stores/workflows';
import { useNDVStore } from '@/stores/ndv';
import { useNodeTypesStore } from '@/stores/nodeTypes';
export default mixins(externalHooks, nodeHelpers).extend({
name: 'NodeSettings',
@ -165,8 +170,14 @@ export default mixins(externalHooks, nodeHelpers).extend({
NodeExecuteButton,
},
computed: {
isCurlImportModalOpen() {
return this.$store.getters['ui/isModalOpen'](IMPORT_CURL_MODAL_KEY);
...mapStores(
useNodeTypesStore,
useNDVStore,
useUIStore,
useWorkflowsStore,
),
isCurlImportModalOpen(): boolean {
return this.uiStore.isModalOpen(IMPORT_CURL_MODAL_KEY);
},
nodeTypeName(): string {
if (this.nodeType) {
@ -201,8 +212,8 @@ export default mixins(externalHooks, nodeHelpers).extend({
'background-color': this.node.color,
};
},
node(): INodeUi {
return this.$store.getters['ndv/activeNode'];
node(): INodeUi | null {
return this.ndvStore.activeNode;
},
parametersSetting(): INodeProperties[] {
return this.parameters.filter((item) => {
@ -222,13 +233,13 @@ export default mixins(externalHooks, nodeHelpers).extend({
return this.nodeType.properties;
},
outputPanelEditMode(): { enabled: boolean; value: string } {
return this.$store.getters['ndv/outputPanelEditMode'];
return this.ndvStore.outputPanelEditMode;
},
isCommunityNode(): boolean {
return isCommunityPackageName(this.node.type);
},
isTriggerNode(): boolean {
return this.$store.getters['nodeTypes/isTriggerNode'](this.node.type);
return this.nodeTypesStore.isTriggerNode(this.node.type);
},
},
props: {
@ -366,7 +377,7 @@ export default mixins(externalHooks, nodeHelpers).extend({
},
isCurlImportModalOpen(newValue, oldValue) {
if (newValue === false) {
let parameters = this.$store.getters['ui/getHttpNodeParameters'];
let parameters = this.uiStore.getHttpNodeParameters || '';
if (!parameters) return;
@ -382,7 +393,7 @@ export default mixins(externalHooks, nodeHelpers).extend({
value: parameters,
});
this.$store.dispatch('ui/setHttpNodeParameters', { parameters: '' });
this.uiStore.setHttpNodeParameters({ name: IMPORT_CURL_MODAL_KEY, parameters: '' });
} catch (_) {}
}
},
@ -452,12 +463,14 @@ export default mixins(externalHooks, nodeHelpers).extend({
},
credentialSelected(updateInformation: INodeUpdatePropertiesInformation) {
// Update the values on the node
this.$store.commit('updateNodeProperties', updateInformation);
this.workflowsStore.updateNodeProperties(updateInformation);
const node = this.$store.getters.getNodeByName(updateInformation.name);
const node = this.workflowsStore.getNodeByName(updateInformation.name);
// Update the issues
this.updateNodeCredentialIssues(node);
if (node) {
// Update the issues
this.updateNodeCredentialIssues(node);
}
this.$externalHooks().run('nodeSettings.credentialSelected', { updateInformation });
},
@ -481,7 +494,12 @@ export default mixins(externalHooks, nodeHelpers).extend({
// Save the node name before we commit the change because
// we need the old name to rename the node properly
const nodeNameBefore = parameterData.node || this.node.name;
const node = this.$store.getters.getNodeByName(nodeNameBefore);
const node = this.workflowsStore.getNodeByName(nodeNameBefore);
if (node === null) {
return;
}
if (parameterData.name === 'name') {
// Name of node changed so we have to set also the new node name as active
@ -494,10 +512,7 @@ export default mixins(externalHooks, nodeHelpers).extend({
this.$emit('valueChanged', sendData);
} else if (parameterData.name === 'parameters') {
const nodeType = this.$store.getters['nodeTypes/getNodeType'](
node.type,
node.typeVersion,
) as INodeTypeDescription | null;
const nodeType = this.nodeTypesStore.getNodeType(node.type, node.typeVersion);
if (!nodeType) {
return;
}
@ -573,23 +588,21 @@ export default mixins(externalHooks, nodeHelpers).extend({
}
}
// Update the data in vuex
const updateInformation = {
name: node.name,
value: nodeParameters,
};
if (nodeParameters) {
const updateInformation: IUpdateInformation = {
name: node.name,
value: nodeParameters,
};
this.$store.commit('setNodeParameters', updateInformation);
this.workflowsStore.setNodeParameters(updateInformation);
this.updateNodeParameterIssues(node, nodeType);
this.updateNodeCredentialIssues(node);
this.updateNodeParameterIssues(node, nodeType);
this.updateNodeCredentialIssues(node);
}
} else if (parameterData.name.startsWith('parameters.')) {
// A node parameter changed
const nodeType = this.$store.getters['nodeTypes/getNodeType'](
node.type,
node.typeVersion,
) as INodeTypeDescription | null;
const nodeType = this.nodeTypesStore.getNodeType(node.type, node.typeVersion);
if (!nodeType) {
return;
}
@ -657,7 +670,7 @@ export default mixins(externalHooks, nodeHelpers).extend({
value: nodeParameters,
};
this.$store.commit('setNodeParameters', updateInformation);
this.workflowsStore.setNodeParameters(updateInformation);
this.$externalHooks().run('nodeSettings.valueChanged', {
parameterPath,
@ -680,7 +693,8 @@ export default mixins(externalHooks, nodeHelpers).extend({
key: parameterData.name,
value: newValue,
};
this.$store.commit('setNodeValue', updateInformation);
this.workflowsStore.setNodeValue(updateInformation);
}
},
/**

View file

@ -6,7 +6,10 @@
import { externalHooks } from '@/components/mixins/externalHooks';
import { BUILTIN_NODES_DOCS_URL, COMMUNITY_NODES_INSTALLATION_DOCS_URL, NPM_PACKAGE_DOCS_BASE_URL } from '@/constants';
import { INodeUi, ITab } from '@/Interface';
import { useNDVStore } from '@/stores/ndv';
import { useWorkflowsStore } from '@/stores/workflows';
import { INodeTypeDescription } from 'n8n-workflow';
import { mapStores } from 'pinia';
import mixins from 'vue-typed-mixins';
import { isCommunityPackageName } from './helpers';
@ -26,8 +29,12 @@ export default mixins(
},
},
computed: {
activeNode(): INodeUi {
return this.$store.getters['ndv/activeNode'];
...mapStores(
useNDVStore,
useWorkflowsStore,
),
activeNode(): INodeUi | null {
return this.ndvStore.activeNode;
},
documentationUrl (): string {
const nodeType = this.nodeType as INodeTypeDescription | null;
@ -113,7 +120,7 @@ export default mixins(
this.$externalHooks().run('dataDisplay.onDocumentationUrlClick', { nodeType: this.nodeType as INodeTypeDescription, documentationUrl: this.documentationUrl });
this.$telemetry.track('User clicked ndv link', {
node_type: this.activeNode.type,
workflow_id: this.$store.getters.workflowId,
workflow_id: this.workflowsStore.workflowId,
session_id: this.sessionId,
pane: 'main',
type: 'docs',
@ -121,7 +128,7 @@ export default mixins(
}
if(tab === 'settings' && this.nodeType) {
this.$telemetry.track('User viewed node settings', { node_type: (this.nodeType as INodeTypeDescription).name, workflow_id: this.$store.getters.workflowId });
this.$telemetry.track('User viewed node settings', { node_type: (this.nodeType as INodeTypeDescription).name, workflow_id: this.workflowsStore.workflowId });
}
if (tab === 'settings' || tab === 'params') {

View file

@ -55,6 +55,8 @@ import Modal from './Modal.vue';
import mixins from 'vue-typed-mixins';
import { showMessage } from './mixins/showMessage';
import { mapStores } from 'pinia';
import { useUIStore } from '@/stores/ui';
export default mixins(
showMessage,
@ -75,6 +77,7 @@ export default mixins(
};
},
computed: {
...mapStores(useUIStore),
isEmailValid(): boolean {
return VALID_EMAIL_REGEX.test(String(this.email).toLowerCase());
},
@ -90,7 +93,7 @@ export default mixins(
this.okToClose = false;
try {
await this.$store.dispatch('ui/applyForOnboardingCall', { email: this.email });
await this.uiStore.applyForOnboardingCall(this.email);
this.$showMessage({
type: 'success',
title: this.$locale.baseText('onboardingCallSignupSucess.title'),

View file

@ -86,6 +86,11 @@ import RunData, { EnterEditModeArgs } from './RunData.vue';
import RunInfo from './RunInfo.vue';
import { pinData } from "@/components/mixins/pinData";
import mixins from 'vue-typed-mixins';
import { mapStores } from 'pinia';
import { useUIStore } from '@/stores/ui';
import { useWorkflowsStore } from '@/stores/workflows';
import { useNDVStore } from '@/stores/ndv';
import { useNodeTypesStore } from '@/stores/nodeTypes';
type RunDataRef = Vue & { enterEditMode: (args: EnterEditModeArgs) => void };
@ -116,17 +121,23 @@ export default mixins(
},
},
computed: {
node(): INodeUi {
return this.$store.getters['ndv/activeNode'];
...mapStores(
useNodeTypesStore,
useNDVStore,
useUIStore,
useWorkflowsStore,
),
node(): INodeUi | null {
return this.ndvStore.activeNode;
},
nodeType (): INodeTypeDescription | null {
if (this.node) {
return this.$store.getters['nodeTypes/getNodeType'](this.node.type, this.node.typeVersion);
return this.nodeTypesStore.getNodeType(this.node.type, this.node.typeVersion);
}
return null;
},
isTriggerNode (): boolean {
return this.$store.getters['nodeTypes/isTriggerNode'](this.node.type);
return this.nodeTypesStore.isTriggerNode(this.node.type);
},
isPollingTypeNode (): boolean {
return !!(this.nodeType && this.nodeType.polling);
@ -135,14 +146,14 @@ export default mixins(
return !!(this.nodeType && this.nodeType.group.includes('schedule'));
},
isNodeRunning(): boolean {
const executingNode = this.$store.getters.executingNode;
const executingNode = this.workflowsStore.executingNode;
return this.node && executingNode === this.node.name;
},
workflowRunning (): boolean {
return this.$store.getters.isActionActive('workflowRunning');
return this.uiStore.isActionActive('workflowRunning');
},
workflowExecution(): IExecutionResponse | null {
return this.$store.getters.getWorkflowExecution;
return this.workflowsStore.getWorkflowExecution;
},
workflowRunData(): IRunData | null {
if (this.workflowExecution === null) {
@ -155,7 +166,7 @@ export default mixins(
return executionData.resultData.runData;
},
hasNodeRun(): boolean {
if (this.$store.getters.subworkflowExecutionError) return true;
if (this.workflowsStore.subWorkflowExecutionError) return true;
return Boolean(
this.node && this.workflowRunData && this.workflowRunData.hasOwnProperty(this.node.name),
@ -199,7 +210,7 @@ export default mixins(
if (!this.node) {
return false;
}
const updatedAt = this.$store.getters.getParametersLastUpdated(this.node.name);
const updatedAt = this.workflowsStore.getParametersLastUpdate(this.node.name);
if (!updatedAt || !this.runTaskData) {
return false;
}
@ -207,7 +218,7 @@ export default mixins(
return updatedAt > runAt;
},
outputPanelEditMode(): { enabled: boolean; value: string; } {
return this.$store.getters['ndv/outputPanelEditMode'];
return this.ndvStore.outputPanelEditMode;
},
canPinData(): boolean {
return this.isPinDataNodeType && !this.isReadOnly;
@ -221,7 +232,7 @@ export default mixins(
});
this.$telemetry.track('User clicked ndv link', {
workflow_id: this.$store.getters.workflowId,
workflow_id: this.workflowsStore.workflowId,
session_id: this.sessionId,
node_type: this.node.type,
pane: 'output',
@ -239,7 +250,7 @@ export default mixins(
this.$emit('openSettings');
this.$telemetry.track('User clicked ndv link', {
node_type: this.node.type,
workflow_id: this.$store.getters.workflowId,
workflow_id: this.workflowsStore.workflowId,
session_id: this.sessionId,
pane: 'output',
type: 'settings',

View file

@ -339,6 +339,10 @@ import { mapGetters } from 'vuex';
import { CODE_NODE_TYPE } from '@/constants';
import { PropType } from 'vue';
import { debounceHelper } from './mixins/debounce';
import { mapStores } from 'pinia';
import { useWorkflowsStore } from '@/stores/workflows';
import { useNDVStore } from '@/stores/ndv';
import { useNodeTypesStore } from '@/stores/nodeTypes';
export default mixins(
externalHooks,
@ -472,6 +476,11 @@ export default mixins(
},
},
computed: {
...mapStores(
useNodeTypesStore,
useNDVStore,
useWorkflowsStore,
),
...mapGetters('credentials', ['allCredentialTypes']),
expressionDisplayValue(): string {
if (this.activeDrop || this.forceShowExpression) {
@ -499,7 +508,7 @@ export default mixins(
}
// Get the resolved parameter values of the current node
const currentNodeParameters = this.$store.getters['ndv/activeNode'].parameters;
const currentNodeParameters = this.ndvStore.activeNode?.parameters;
try {
const resolvedNodeParameters = this.resolveParameter(currentNodeParameters);
@ -514,7 +523,7 @@ export default mixins(
}
},
node (): INodeUi | null {
return this.$store.getters['ndv/activeNode'];
return this.ndvStore.activeNode;
},
displayTitle (): string {
const interpolation = { interpolate: { shortPath: this.shortPath } };
@ -742,12 +751,14 @@ export default mixins(
},
credentialSelected (updateInformation: INodeUpdatePropertiesInformation) {
// Update the values on the node
this.$store.commit('updateNodeProperties', updateInformation);
this.workflowsStore.updateNodeProperties(updateInformation);
const node = this.$store.getters.getNodeByName(updateInformation.name);
const node = this.workflowsStore.getNodeByName(updateInformation.name);
// Update the issues
this.updateNodeCredentialIssues(node);
if (node) {
// Update the issues
this.updateNodeCredentialIssues(node);
}
this.$externalHooks().run('nodeSettings.credentialSelected', { updateInformation });
},
@ -784,12 +795,12 @@ export default mixins(
// Get the resolved parameter values of the current node
try {
const currentNodeParameters = (this.$store.getters['ndv/activeNode'] as INodeUi).parameters;
const currentNodeParameters = (this.ndvStore.activeNode as INodeUi).parameters;
const resolvedNodeParameters = this.resolveParameter(currentNodeParameters) as INodeParameters;
const loadOptionsMethod = this.getArgument('loadOptionsMethod') as string | undefined;
const loadOptions = this.getArgument('loadOptions') as ILoadOptions | undefined;
const options = await this.$store.dispatch('nodeTypes/getNodeParameterOptions',
const options = await this.nodeTypesStore.getNodeParameterOptions(
{
nodeTypeAndVersion: {
name: this.node.type,
@ -827,8 +838,8 @@ export default mixins(
parameter_name: this.parameter.displayName,
parameter_field_type: this.parameter.type,
new_expression: !this.isValueExpression,
workflow_id: this.$store.getters.workflowId,
session_id: this.$store.getters['ndv/ndvSessionId'],
workflow_id: this.workflowsStore.workflowId,
session_id: this.ndvStore.sessionId,
source: this.eventSource || 'ndv',
});
}
@ -956,11 +967,11 @@ export default mixins(
if (this.parameter.name === 'operation' || this.parameter.name === 'mode') {
this.$telemetry.track('User set node operation or mode', {
workflow_id: this.$store.getters.workflowId,
workflow_id: this.workflowsStore.workflowId,
node_type: this.node && this.node.type,
resource: this.node && this.node.parameters.resource,
is_custom: value === CUSTOM_API_CALL_KEY,
session_id: this.$store.getters['ndv/ndvSessionId'],
session_id: this.ndvStore.sessionId,
parameter: this.parameter.name,
});
}

View file

@ -54,6 +54,8 @@ import Vue, { PropType } from 'vue';
import ParameterInputWrapper from './ParameterInputWrapper.vue';
import { isValueExpression } from './helpers';
import { INodeParameterResourceLocator, INodeProperties } from 'n8n-workflow';
import { mapStores } from 'pinia';
import { useWorkflowsStore } from '@/stores/workflows';
export default Vue.extend({
name: 'parameter-input-expanded',
@ -85,6 +87,9 @@ export default Vue.extend({
};
},
computed: {
...mapStores(
useWorkflowsStore,
),
showRequiredErrors(): boolean {
if (!this.$props.parameter.required) {
return false;
@ -136,7 +141,7 @@ export default Vue.extend({
this.$telemetry.track('User clicked credential modal docs link', {
docs_link: this.documentationUrl,
source: 'field',
workflow_id: this.$store.getters.workflowId,
workflow_id: this.workflowsStore.workflowId,
});
},
},

View file

@ -79,6 +79,8 @@ import { hasOnlyListMode } from '@/components/ResourceLocator/helpers';
import { INodeParameters, INodeProperties, INodePropertyMode } from 'n8n-workflow';
import { isResourceLocatorValue } from '@/typeGuards';
import { BaseTextKey } from "@/plugins/i18n";
import { mapStores } from 'pinia';
import { useNDVStore } from '@/stores/ndv';
export default mixins(
showMessage,
@ -135,8 +137,11 @@ export default mixins(
];
},
computed: {
...mapStores(
useNDVStore,
),
node (): INodeUi | null {
return this.$store.getters['ndv/activeNode'];
return this.ndvStore.activeNode;
},
hint (): string | null {
return this.$locale.nodeText().hint(this.parameter, this.path);
@ -154,10 +159,10 @@ export default mixins(
return this.isResourceLocator ? !hasOnlyListMode(this.parameter): true;
},
isInputDataEmpty (): boolean {
return this.$store.getters['ndv/getNDVDataIsEmpty']('input');
return this.ndvStore.isDNVDataEmpty('input');
},
displayMode(): IRunDataDisplayMode {
return this.$store.getters['ndv/inputPanelDisplayMode'];
return this.ndvStore.inputPanelDisplayMode;
},
showMappingTooltip (): boolean {
return this.focused && this.isInputTypeString && !this.isInputDataEmpty && window.localStorage.getItem(LOCAL_STORAGE_MAPPING_FLAG) !== 'true';
@ -167,13 +172,13 @@ export default mixins(
onFocus() {
this.focused = true;
if (!this.parameter.noDataExpression) {
this.$store.commit('ndv/setMappableNDVInputFocus', this.parameter.displayName);
this.ndvStore.setMappableNDVInputFocus(this.parameter.displayName);
}
},
onBlur() {
this.focused = false;
if (!this.parameter.noDataExpression) {
this.$store.commit('ndv/setMappableNDVInputFocus', '');
this.ndvStore.setMappableNDVInputFocus('');
}
},
onMenuExpanded(expanded: boolean) {
@ -250,7 +255,7 @@ export default mixins(
window.localStorage.setItem(LOCAL_STORAGE_MAPPING_FLAG, 'true');
}
this.$store.commit('ndv/setMappingTelemetry', {
this.ndvStore.setMappingTelemetry({
dest_node_type: this.node.type,
dest_parameter: this.path,
dest_parameter_mode: typeof prevValue === 'string' && prevValue.startsWith('=')? 'expression': 'fixed',

View file

@ -119,6 +119,9 @@ import { get, set } from 'lodash';
import mixins from 'vue-typed-mixins';
import {Component} from "vue";
import { mapState, mapStores } from 'pinia';
import { useNDVStore } from '@/stores/ndv';
import { useNodeTypesStore } from '@/stores/nodeTypes';
export default mixins(
workflowHelpers,
@ -141,6 +144,10 @@ export default mixins(
'isReadOnly',
],
computed: {
...mapStores(
useNodeTypesStore,
useNDVStore,
),
nodeTypeVersion(): number | null {
if (this.node) {
return this.node.typeVersion;
@ -159,8 +166,8 @@ export default mixins(
filteredParameterNames (): string[] {
return this.filteredParameters.map(parameter => parameter.name);
},
node (): INodeUi {
return this.$store.getters['ndv/activeNode'];
node (): INodeUi | null {
return this.ndvStore.activeNode;
},
indexToShowSlotAt (): number {
let index = 0;
@ -179,7 +186,7 @@ export default mixins(
methods: {
getCredentialsDependencies() {
const dependencies = new Set();
const nodeType = this.$store.getters['nodeTypes/getNodeType'](this.node.type, this.node.typeVersion) as INodeTypeDescription | undefined;
const nodeType = this.nodeTypesStore.getNodeType(this.node?.type || '', this.node?.typeVersion);
// Get names of all fields that credentials rendering depends on (using displayOptions > show)
if(nodeType && nodeType.credentials) {
@ -323,7 +330,7 @@ export default mixins(
if (!newValue.includes(parameter)) {
const parameterData = {
name: `${this.path}.${parameter}`,
node: this.$store.getters['ndv/activeNode'].name,
node: this.ndvStore.activeNode?.name || '',
value: undefined,
};
this.$emit('valueChanged', parameterData);

View file

@ -37,6 +37,8 @@ import { INodeProperties, INodePropertyMode, IRunData, isResourceLocatorValue, N
import { INodeUi, IUiState, IUpdateInformation, TargetItem } from '@/Interface';
import { workflowHelpers } from './mixins/workflowHelpers';
import { isValueExpression } from './helpers';
import { mapStores } from 'pinia';
import { useNDVStore } from '@/stores/ndv';
export default mixins(
showMessage,
@ -97,11 +99,14 @@ export default mixins(
},
},
computed: {
...mapStores(
useNDVStore,
),
isValueExpression () {
return isValueExpression(this.parameter, this.value);
},
activeNode(): INodeUi | null {
return this.$store.getters['ndv/activeNode'];
return this.ndvStore.activeNode;
},
selectedRLMode(): INodePropertyMode | undefined {
if (typeof this.value !== 'object' ||this.parameter.type !== 'resourceLocator' || !isResourceLocatorValue(this.value)) {
@ -126,17 +131,17 @@ export default mixins(
return this.hint;
},
targetItem(): TargetItem | null {
return this.$store.getters['ndv/hoveringItem'];
return this.ndvStore.hoveringItem;
},
expressionValueComputed (): string | null {
const inputNodeName: string | undefined = this.$store.getters['ndv/ndvInputNodeName'];
const inputNodeName: string | undefined = this.ndvStore.ndvInputNodeName;
const value = isResourceLocatorValue(this.value)? this.value.value: this.value;
if (this.activeNode === null || !this.isValueExpression || typeof value !== 'string') {
return null;
}
const inputRunIndex: number | undefined = this.$store.getters['ndv/ndvInputRunIndex'];
const inputBranchIndex: number | undefined = this.$store.getters['ndv/ndvInputBranchIndex'];
const inputRunIndex: number | undefined = this.ndvStore.ndvInputRunIndex;
const inputBranchIndex: number | undefined = this.ndvStore.ndvInputBranchIndex;
let computedValue: NodeParameterValue;
try {
@ -157,7 +162,7 @@ export default mixins(
},
expressionOutput(): string | null {
if (this.isValueExpression && this.expressionValueComputed) {
const inputData = this.$store.getters['ndv/ndvInputData'];
const inputData = this.ndvStore.ndvInputData;
if (!inputData || (inputData && inputData.length <= 1)) {
return this.expressionValueComputed;
}

View file

@ -17,7 +17,7 @@
>
<template v-slot:content>
<div v-if="submitted" :class="$style.submittedContainer">
<img :class="$style.demoImage" :src="baseUrl + 'suggestednodes.png'" />
<img :class="$style.demoImage" :src="rootStore.baseUrl + 'suggestednodes.png'" />
<n8n-text>{{ $locale.baseText('personalizationModal.lookOutForThingsMarked') }}</n8n-text>
</div>
<div :class="$style.container" v-else>
@ -50,7 +50,6 @@ import mixins from 'vue-typed-mixins';
const SURVEY_VERSION = 'v3';
import {
CODING_SKILL_KEY,
COMPANY_SIZE_100_499,
COMPANY_SIZE_1000_OR_MORE,
COMPANY_SIZE_20_OR_LESS,
@ -116,11 +115,15 @@ import {
import { workflowHelpers } from '@/components/mixins/workflowHelpers';
import { showMessage } from '@/components/mixins/showMessage';
import Modal from './Modal.vue';
import { IFormInputs, IPersonalizationLatestVersion } from '@/Interface';
import { IFormInputs, IPersonalizationLatestVersion, IPersonalizationSurveyAnswersV3, IUser } from '@/Interface';
import Vue from 'vue';
import { mapGetters } from 'vuex';
import { getAccountAge } from '@/modules/userHelpers';
import { GenericValue } from 'n8n-workflow';
import { mapStores } from 'pinia';
import { useUIStore } from '@/stores/ui';
import { useSettingsStore } from '@/stores/settings';
import { useRootStore } from '@/stores/n8nRootStore';
import { useUsersStore } from '@/stores/users';
export default mixins(showMessage, workflowHelpers).extend({
components: { Modal },
@ -138,15 +141,12 @@ export default mixins(showMessage, workflowHelpers).extend({
};
},
computed: {
...mapGetters({
baseUrl: 'getBaseUrl',
}),
...mapGetters('users', [
'currentUser',
]),
...mapGetters('settings', [
'isOnboardingCallPromptFeatureEnabled',
]),
...mapStores(
useRootStore,
useSettingsStore,
useUIStore,
useUsersStore,
),
survey() {
const survey: IFormInputs = [
{
@ -470,12 +470,12 @@ export default mixins(showMessage, workflowHelpers).extend({
...values,
version: SURVEY_VERSION,
personalization_survey_submitted_at: new Date().toISOString(),
personalization_survey_n8n_version: this.$store.getters.versionCli,
personalization_survey_n8n_version: this.rootStore.versionCli,
};
this.$externalHooks().run('personalizationModal.onSubmit', survey);
await this.$store.dispatch('users/submitPersonalizationSurvey', survey);
await this.usersStore.submitPersonalizationSurvey(survey as IPersonalizationSurveyAnswersV3);
if (Object.keys(values).length === 0) {
this.closeDialog();
@ -490,8 +490,8 @@ export default mixins(showMessage, workflowHelpers).extend({
this.$data.isSaving = false;
},
async fetchOnboardingPrompt() {
if (this.isOnboardingCallPromptFeatureEnabled && getAccountAge(this.currentUser) <= ONBOARDING_PROMPT_TIMEBOX) {
const onboardingResponse = await this.$store.dispatch('ui/getNextOnboardingPrompt');
if (this.settingsStore.onboardingCallPromptEnabled && getAccountAge(this.usersStore.currentUser || {} as IUser) <= ONBOARDING_PROMPT_TIMEBOX) {
const onboardingResponse = await this.uiStore.getNextOnboardingPrompt();
const promptTimeout = onboardingResponse.toast_sequence_number === 1 ? FIRST_ONBOARDING_PROMPT_TIMEOUT : 1000;
if (onboardingResponse.title && onboardingResponse.description) {
@ -509,7 +509,7 @@ export default mixins(showMessage, workflowHelpers).extend({
title: onboardingResponse.title,
description: onboardingResponse.description,
});
this.$store.commit('ui/openModal', ONBOARDING_CALL_SIGNUP_MODAL_KEY, {root: true});
this.uiStore.openModal(ONBOARDING_CALL_SIGNUP_MODAL_KEY);
},
});
}, promptTimeout);

View file

@ -1,6 +1,6 @@
<template>
<span>
<div class="push-connection-lost primary-color" v-if="!pushConnectionActive">
<div class="push-connection-lost primary-color" v-if="!rootStore.pushConnectionActive">
<n8n-tooltip placement="bottom-end" >
<div slot="content" v-html="$locale.baseText('pushConnectionTracker.cannotConnectToServer')"></div>
<span>
@ -13,13 +13,16 @@
</template>
<script lang="ts">
import { useRootStore } from "@/stores/n8nRootStore";
import { mapStores } from "pinia";
import Vue from "vue";
import { mapGetters } from "vuex";
export default Vue.extend({
name: "PushConnectionTracker",
computed: {
...mapGetters(["pushConnectionActive"]),
...mapStores(
useRootStore,
),
},
});
</script>

View file

@ -172,6 +172,12 @@ import { workflowHelpers } from '../mixins/workflowHelpers';
import { nodeHelpers } from '../mixins/nodeHelpers';
import { getAppNameFromNodeName } from '../helpers';
import { isResourceLocatorValue } from '@/typeGuards';
import { mapStores } from 'pinia';
import { useUIStore } from '@/stores/ui';
import { useWorkflowsStore } from '@/stores/workflows';
import { useRootStore } from '@/stores/n8nRootStore';
import { useNDVStore } from '@/stores/ndv';
import { useNodeTypesStore } from '@/stores/nodeTypes';
interface IResourceLocatorQuery {
results: INodeListSearchItems[];
@ -248,7 +254,6 @@ export default mixins(debounceHelper, workflowHelpers, nodeHelpers).extend({
},
data() {
return {
mainPanelMutationSubscription: () => {},
showResourceDropdown: false,
searchFilter: '',
cachedResponses: {} as { [key: string]: IResourceLocatorQuery },
@ -257,13 +262,20 @@ export default mixins(debounceHelper, workflowHelpers, nodeHelpers).extend({
};
},
computed: {
...mapStores(
useNodeTypesStore,
useNDVStore,
useRootStore,
useUIStore,
useWorkflowsStore,
),
appName(): string {
if (!this.node) {
return '';
}
const nodeType = this.$store.getters['nodeTypes/getNodeType'](this.node.type);
return getAppNameFromNodeName(nodeType.displayName);
const nodeType = this.nodeTypesStore.getNodeType(this.node.type);
return getAppNameFromNodeName(nodeType?.displayName || '');
},
selectedMode(): string {
if (typeof this.value !== 'object') { // legacy mode
@ -280,7 +292,7 @@ export default mixins(debounceHelper, workflowHelpers, nodeHelpers).extend({
return this.selectedMode === 'list';
},
hasCredential(): boolean {
const node = this.$store.getters['ndv/activeNode'] as INodeUi | null;
const node = this.ndvStore.activeNode;
if (!node) {
return false;
}
@ -425,24 +437,21 @@ export default mixins(debounceHelper, workflowHelpers, nodeHelpers).extend({
mounted() {
this.$on('refreshList', this.refreshList);
window.addEventListener('resize', this.setWidth);
this.mainPanelMutationSubscription = this.$store.subscribe(this.setWidthOnMainPanelResize);
useNDVStore().$subscribe((mutation, state) => {
// Update the width when main panel dimension change
this.setWidth();
});
setTimeout(() => {
this.setWidth();
}, 0);
},
beforeDestroy() {
// Unsubscribe
this.mainPanelMutationSubscription();
window.removeEventListener('resize', this.setWidth);
},
methods: {
setWidth() {
this.width = (this.$refs.container as HTMLElement).offsetWidth;
},
setWidthOnMainPanelResize(mutation: { type: string }) {
// Update the width when main panel dimension change
if(mutation.type === 'ndv/setMainPanelDimensions') this.setWidth();
},
getLinkAlt(entity: string) {
if (this.selectedMode === 'list' && entity) {
return this.$locale.baseText('resourceLocator.openSpecificResource', { interpolate: { entity, appName: this.appName } });
@ -480,7 +489,7 @@ export default mixins(debounceHelper, workflowHelpers, nodeHelpers).extend({
return parameter.typeOptions[argumentName];
},
openCredential(): void {
const node = this.$store.getters['ndv/activeNode'] as INodeUi | null;
const node = this.ndvStore.activeNode;
if (!node || !node.credentials) {
return;
}
@ -489,7 +498,7 @@ export default mixins(debounceHelper, workflowHelpers, nodeHelpers).extend({
return;
}
const id = node.credentials[credentialKey].id;
this.$store.dispatch('ui/openExistingCredential', { id });
this.uiStore.openExistingCredential(id);
},
findModeByName(name: string): INodePropertyMode | null {
if (this.parameter.modes) {
@ -533,8 +542,8 @@ export default mixins(debounceHelper, workflowHelpers, nodeHelpers).extend({
},
trackEvent(event: string, params?: {[key: string]: string}): void {
this.$telemetry.track(event, {
instance_id: this.$store.getters.instanceId,
workflow_id: this.$store.getters.workflowId,
instance_id: this.rootStore.instanceId,
workflow_id: this.workflowsStore.workflowId,
node_type: this.node && this.node.type,
resource: this.node && this.node.parameters && this.node.parameters.resource,
operation: this.node && this.node.parameters && this.node.parameters.operation,
@ -618,10 +627,7 @@ export default mixins(debounceHelper, workflowHelpers, nodeHelpers).extend({
...(paginationToken ? { paginationToken } : {}),
};
const response: INodeListSearchResult = await this.$store.dispatch(
'nodeTypes/getResourceLocatorResults',
requestParams,
);
const response = await this.nodeTypesStore.getResourceLocatorResults(requestParams);
this.setResponse(paramsKey, {
results: (cachedResponse ? cachedResponse.results : []).concat(response.results),

View file

@ -145,7 +145,7 @@
:value="editMode.value"
:options="{ scrollBeyondLastLine: false }"
type="json"
@input="$store.commit('ndv/setOutputPanelEditModeValue', $event)"
@input="ndvStore.setOutputPanelEditModeValue($event)"
/>
</div>
<div :class="$style['edit-mode-footer']">
@ -344,6 +344,7 @@ import {
IBinaryDisplayData,
IExecutionResponse,
INodeUi,
INodeUpdatePropertiesInformation,
IRunDataDisplayMode,
ITab,
} from '@/Interface';
@ -373,6 +374,10 @@ import { clearJsonKey, executionDataToJson, stringSizeInBytes } from './helpers'
import RunDataTable from './RunDataTable.vue';
import RunDataJson from '@/components/RunDataJson.vue';
import { isEmpty } from '@/utils';
import { useWorkflowsStore } from "@/stores/workflows";
import { mapStores } from "pinia";
import { useNDVStore } from "@/stores/ndv";
import { useNodeTypesStore } from "@/stores/nodeTypes";
export type EnterEditModeArgs = {
origin: 'editIconButton' | 'insertTestDataLink',
@ -475,8 +480,8 @@ export default mixins(
this.showPinDataDiscoveryTooltip(this.jsonData);
}
}
this.$store.commit('ndv/setNDVBranchIndex', {
pane: this.paneType,
this.ndvStore.setNDVBranchIndex({
pane: this.paneType as "input" | "output",
branchIndex: this.currentOutputIndex,
});
},
@ -486,8 +491,13 @@ export default mixins(
this.eventBus.$off('data-unpinning', this.onDataUnpinning);
},
computed: {
activeNode(): INodeUi {
return this.$store.getters['ndv/activeNode'];
...mapStores(
useNodeTypesStore,
useNDVStore,
useWorkflowsStore,
),
activeNode(): INodeUi | null {
return this.ndvStore.activeNode;
},
dataPinningDocsUrl(): string {
return DATA_PINNING_DOCS_URL;
@ -496,19 +506,19 @@ export default mixins(
return DATA_EDITING_DOCS_URL;
},
displayMode(): IRunDataDisplayMode {
return this.$store.getters['ndv/getPanelDisplayMode'](this.paneType);
return this.ndvStore.getPanelDisplayMode(this.paneType as "input" | "output");
},
node(): INodeUi | null {
return (this.nodeUi as INodeUi | null) || null;
},
nodeType (): INodeTypeDescription | null {
if (this.node) {
return this.$store.getters['nodeTypes/getNodeType'](this.node.type, this.node.typeVersion);
return this.nodeTypesStore.getNodeType(this.node.type, this.node.typeVersion);
}
return null;
},
isTriggerNode (): boolean {
return this.$store.getters['nodeTypes/isTriggerNode'](this.node.type);
return this.nodeTypesStore.isTriggerNode(this.node.type);
},
canPinData (): boolean {
return !this.isPaneTypeInput &&
@ -532,7 +542,7 @@ export default mixins(
return Boolean(!this.isExecuting && this.node && (this.workflowRunData && this.workflowRunData.hasOwnProperty(this.node.name) || this.hasPinData));
},
subworkflowExecutionError(): Error | null {
return this.$store.getters.subworkflowExecutionError;
return this.workflowsStore.subWorkflowExecutionError;
},
hasSubworkflowExecutionError(): boolean {
return Boolean(this.subworkflowExecutionError);
@ -541,7 +551,7 @@ export default mixins(
return Boolean(this.node && this.workflowRunData && this.workflowRunData[this.node.name] && this.workflowRunData[this.node.name][this.runIndex] && this.workflowRunData[this.node.name][this.runIndex].error);
},
workflowExecution (): IExecutionResponse | null {
return this.$store.getters.getWorkflowExecution;
return this.workflowsStore.getWorkflowExecution;
},
workflowRunData (): IRunData | null {
if (this.workflowExecution === null) {
@ -680,7 +690,7 @@ export default mixins(
editMode(): { enabled: boolean; value: string; } {
return this.isPaneTypeInput
? { enabled: false, value: '' }
: this.$store.getters['ndv/outputPanelEditMode'];
: this.ndvStore.outputPanelEditMode;
},
isPaneTypeInput(): boolean {
return this.paneType === 'input';
@ -700,9 +710,9 @@ export default mixins(
},
onClickDataPinningDocsLink() {
this.$telemetry.track('User clicked ndv link', {
workflow_id: this.$store.getters.workflowId,
workflow_id: this.workflowsStore.workflowId,
session_id: this.sessionId,
node_type: this.activeNode.type,
node_type: this.activeNode?.type,
pane: 'output',
type: 'data-pinning-docs',
});
@ -742,11 +752,11 @@ export default mixins(
? inputData
: TEST_PIN_DATA;
this.$store.commit('ndv/setOutputPanelEditModeEnabled', true);
this.$store.commit('ndv/setOutputPanelEditModeValue', JSON.stringify(data, null, 2));
this.ndvStore.setOutputPanelEditModeEnabled(true);
this.ndvStore.setOutputPanelEditModeValue(JSON.stringify(data, null, 2));
this.$telemetry.track('User opened ndv edit state', {
node_type: this.activeNode.type,
node_type: this.activeNode?.type,
click_type: origin === 'editIconButton' ? 'button' : 'link',
session_id: this.sessionId,
run_index: this.runIndex,
@ -756,8 +766,8 @@ export default mixins(
});
},
onClickCancelEdit() {
this.$store.commit('ndv/setOutputPanelEditModeEnabled', false);
this.$store.commit('ndv/setOutputPanelEditModeValue', '');
this.ndvStore.setOutputPanelEditModeEnabled(false);
this.ndvStore.setOutputPanelEditModeValue('');
this.onExitEditMode({ type: 'cancel' });
},
onClickSaveEdit() {
@ -775,8 +785,8 @@ export default mixins(
return;
}
this.$store.commit('ndv/setOutputPanelEditModeEnabled', false);
this.$store.commit('pinData', { node: this.node, data: clearJsonKey(value) });
this.ndvStore.setOutputPanelEditModeEnabled(false);
this.workflowsStore.pinData({ node: this.node, data: clearJsonKey(value) as INodeExecutionData[] });
this.onDataPinningSuccess({ source: 'save-edit' });
@ -784,7 +794,7 @@ export default mixins(
},
onExitEditMode({ type }: { type: 'save' | 'cancel' }) {
this.$telemetry.track('User closed ndv edit state', {
node_type: this.activeNode.type,
node_type: this.activeNode?.type,
session_id: this.sessionId,
run_index: this.runIndex,
view: this.displayMode,
@ -795,7 +805,7 @@ export default mixins(
{ source }: { source: 'banner-link' | 'pin-icon-click' | 'unpin-and-execute-modal' },
) {
this.$telemetry.track('User unpinned ndv data', {
node_type: this.activeNode.type,
node_type: this.activeNode?.type,
session_id: this.sessionId,
run_index: this.runIndex,
source,
@ -849,11 +859,11 @@ export default mixins(
if (this.hasPinData) {
this.onDataUnpinning({ source });
this.$store.commit('unpinData', { node: this.node });
this.workflowsStore.unpinData({ node: this.node });
return;
}
const data = executionDataToJson(this.rawInputData);
const data = executionDataToJson(this.rawInputData) as INodeExecutionData[];
if (!this.isValidPinDataSize(data)) {
this.onDataPinningError({ errorType: 'data-too-large', source: 'pin-icon-click' });
@ -862,7 +872,7 @@ export default mixins(
this.onDataPinningSuccess({ source: 'pin-icon-click' });
this.$store.commit('pinData', { node: this.node, data });
this.workflowsStore.pinData({ node: this.node, data });
if (this.maxRunIndex > 0) {
this.$showToast({
@ -898,7 +908,7 @@ export default mixins(
this.showData = true;
this.$telemetry.track('User clicked ndv button', {
node_type: this.activeNode.type,
workflow_id: this.$store.getters.workflowId,
workflow_id: this.workflowsStore.workflowId,
session_id: this.sessionId,
pane: this.paneType,
type: 'showTooMuchData',
@ -912,8 +922,8 @@ export default mixins(
},
onCurrentPageChange() {
this.$telemetry.track('User changed ndv page', {
node_type: this.activeNode.type,
workflow_id: this.$store.getters.workflowId,
node_type: this.activeNode?.type,
workflow_id: this.workflowsStore.workflowId,
session_id: this.sessionId,
pane: this.paneType,
page_selected: this.currentPage,
@ -929,8 +939,8 @@ export default mixins(
}
this.$telemetry.track('User changed ndv page size', {
node_type: this.activeNode.type,
workflow_id: this.$store.getters.workflowId,
node_type: this.activeNode?.type,
workflow_id: this.workflowsStore.workflowId,
session_id: this.sessionId,
pane: this.paneType,
page_selected: this.currentPage,
@ -940,7 +950,7 @@ export default mixins(
},
onDisplayModeChange(displayMode: IRunDataDisplayMode) {
const previous = this.displayMode;
this.$store.commit('ndv/setPanelDisplayMode', {pane: this.paneType, mode: displayMode});
this.ndvStore.setPanelDisplayMode({pane: this.paneType as "input" | "output", mode: displayMode});
const dataContainer = this.$refs.dataContainer;
if (dataContainer) {
@ -958,7 +968,7 @@ export default mixins(
previous_view: previous,
new_view: displayMode,
node_type: this.activeNode.type,
workflow_id: this.$store.getters.workflowId,
workflow_id: this.workflowsStore.workflowId,
session_id: this.sessionId,
pane: this.paneType,
});
@ -1012,10 +1022,10 @@ export default mixins(
this.refreshDataSize();
this.closeBinaryDataDisplay();
if (this.binaryData.length > 0) {
this.$store.commit('ndv/setPanelDisplayMode', {pane: this.paneType, mode: 'binary'});
this.ndvStore.setPanelDisplayMode({pane: this.paneType as "input" | "output", mode: 'binary'});
}
else if (this.displayMode === 'binary') {
this.$store.commit('ndv/setPanelDisplayMode', {pane: this.paneType, mode: 'table'});
this.ndvStore.setPanelDisplayMode({pane: this.paneType as "input" | "output", mode: 'table'});
}
},
closeBinaryDataDisplay () {
@ -1023,7 +1033,7 @@ export default mixins(
this.binaryDataDisplayData = null;
},
clearExecutionData () {
this.$store.commit('setWorkflowExecutionData', null);
this.workflowsStore.setWorkflowExecutionData(null);
this.updateNodesExecutionIssues();
},
isDownloadable (index: number, key: string): boolean {
@ -1093,15 +1103,15 @@ export default mixins(
name: this.node.name,
properties: {
disabled: !this.node.disabled,
},
};
} as IDataObject,
} as INodeUpdatePropertiesInformation;
this.$store.commit('updateNodeProperties', updateInformation);
this.workflowsStore.updateNodeProperties(updateInformation);
}
},
goToErroredNode() {
if (this.node) {
this.$store.commit('ndv/setActiveNodeName', this.node.name);
this.ndvStore.activeNodeName = this.node.name;
}
},
},
@ -1112,7 +1122,7 @@ export default mixins(
inputData:{
handler(data: INodeExecutionData[]) {
if(this.paneType && data){
this.$store.commit('ndv/setNDVPanelDataIsEmpty', { panel: this.paneType, isEmpty: data.every(item => isEmpty(item.json)) });
this.ndvStore.setNDVPanelDataIsEmpty({ panel: this.paneType as "input" | "output", isEmpty: data.every(item => isEmpty(item.json)) });
}
},
immediate: true,
@ -1135,8 +1145,8 @@ export default mixins(
}
},
currentOutputIndex(branchIndex: number) {
this.$store.commit('ndv/setNDVBranchIndex', {
pane: this.paneType,
this.ndvStore.setNDVBranchIndex({
pane: this.paneType as "input" | "output",
branchIndex,
});
},

View file

@ -66,6 +66,8 @@ import { convertPath, executionDataToJson, isString, isStringNumber } from "@/co
import { INodeUi } from "@/Interface";
import { shorten } from './helpers';
import { externalHooks } from "@/components/mixins/externalHooks";
import { mapStores } from "pinia";
import { useNDVStore } from "@/stores/ndv";
const runDataJsonActions = () => import('@/components/RunDataJsonActions.vue');
@ -137,6 +139,9 @@ export default mixins(externalHooks).extend({
}
},
computed: {
...mapStores(
useNDVStore,
),
jsonData(): IDataObject[] {
return executionDataToJson(this.inputData as INodeExecutionData[]);
},
@ -165,13 +170,13 @@ export default mixins(externalHooks).extend({
this.draggingPath = el.dataset.path;
}
this.$store.commit('ndv/resetMappingTelemetry');
this.ndvStore.resetMappingTelemetry();
},
onDragEnd(el: HTMLElement) {
this.draggingPath = null;
setTimeout(() => {
const mappingTelemetry = this.$store.getters['ndv/mappingTelemetry'];
const mappingTelemetry = this.ndvStore.mappingTelemetry;
const telemetryPayload = {
src_node_type: this.node.type,
src_field_name: el.dataset.name || '',

View file

@ -35,6 +35,9 @@ import { pinData } from "@/components/mixins/pinData";
import { nodeHelpers } from "@/components/mixins/nodeHelpers";
import { genericHelpers } from "@/components/mixins/genericHelpers";
import { clearJsonKey, convertPath, executionDataToJson } from "@/components/helpers";
import { mapStores } from "pinia";
import { useWorkflowsStore } from "@/stores/workflows";
import { useNDVStore } from "@/stores/ndv";
type JsonPathData = {
path: string;
@ -83,8 +86,12 @@ export default mixins(
},
},
computed: {
activeNode(): INodeUi {
return this.$store.getters['ndv/activeNode'];
...mapStores(
useNDVStore,
useWorkflowsStore,
),
activeNode(): INodeUi | null {
return this.ndvStore.activeNode;
},
normalisedJsonPath(): string {
const isNotSelected = this.selectedJsonPath === nonExistingJsonPath;
@ -189,7 +196,7 @@ export default mixins(
run_index: this.runIndex,
view: 'json',
copy_type: copyType,
workflow_id: this.$store.getters.workflowId,
workflow_id: this.workflowsStore.workflowId,
pane: this.paneType,
in_execution_log: this.isReadOnly,
});

View file

@ -152,14 +152,18 @@
</template>
<script lang="ts">
import { INodeUi, IRootState, ITableData, NDVState } from '@/Interface';
/* eslint-disable prefer-spread */
import { INodeUi, ITableData, NDVState } from '@/Interface';
import { getPairedItemId } from '@/pairedItemUtils';
import Vue, { PropType } from 'vue';
import mixins from 'vue-typed-mixins';
import { GenericValue, IDataObject, INodeExecutionData } from 'n8n-workflow';
import Draggable from '@/components/Draggable.vue';
import { shorten } from '@/components/helpers';
import { externalHooks } from '@/components/mixins/externalHooks';
import Draggable from './Draggable.vue';
import { shorten } from './helpers';
import { externalHooks } from './mixins/externalHooks';
import { mapStores } from 'pinia';
import { useWorkflowsStore } from '@/stores/workflows';
import { useNDVStore } from '@/stores/ndv';
const MAX_COLUMNS_LIMIT = 40;
@ -217,11 +221,15 @@ export default mixins(externalHooks).extend({
}
},
computed: {
...mapStores(
useNDVStore,
useWorkflowsStore,
),
hoveringItem(): NDVState['hoveringItem'] {
return this.$store.getters['ndv/hoveringItem'];
return this.ndvStore.hoveringItem;
},
pairedItemMappings(): IRootState['workflowExecutionPairedItemMappings'] {
return this.$store.getters['workflowExecutionPairedItemMappings'];
pairedItemMappings(): {[itemId: string]: Set<string>} {
return this.workflowsStore.workflowExecutionPairedItemMappings;
},
tableData(): ITableData {
return this.convertToTable(this.inputData);
@ -360,8 +368,7 @@ export default mixins(externalHooks).extend({
},
onDragStart() {
this.draggedColumn = true;
this.$store.commit('ndv/resetMappingTelemetry');
this.ndvStore.resetMappingTelemetry();
},
onCellDragStart(el: HTMLElement) {
if (el && el.dataset.value) {
@ -384,7 +391,7 @@ export default mixins(externalHooks).extend({
},
onDragEnd(column: string, src: string, depth = '0') {
setTimeout(() => {
const mappingTelemetry = this.$store.getters['ndv/mappingTelemetry'];
const mappingTelemetry = this.ndvStore.mappingTelemetry;
const telemetryPayload = {
src_node_type: this.node.type,
src_field_name: column,

View file

@ -12,7 +12,7 @@
<template #menuSuffix>
<div :class="$style.versionContainer">
<n8n-link @click="onVersionClick" size="small">
{{ $locale.baseText('settings.version') }} {{ versionCli }}
{{ $locale.baseText('settings.version') }} {{ rootStore.versionCli }}
</n8n-link>
</div>
</template>
@ -29,6 +29,10 @@ import { pushConnection } from "@/components/mixins/pushConnection";
import { IFakeDoor } from '@/Interface';
import { IMenuItem } from 'n8n-design-system';
import { BaseTextKey } from '@/plugins/i18n';
import { mapStores } from 'pinia';
import { useUIStore } from '@/stores/ui';
import { useSettingsStore } from '@/stores/settings';
import { useRootStore } from '@/stores/n8nRootStore';
export default mixins(
userHelpers,
@ -36,9 +40,13 @@ export default mixins(
).extend({
name: 'SettingsSidebar',
computed: {
...mapGetters('settings', ['versionCli']),
...mapStores(
useRootStore,
useSettingsStore,
useUIStore,
),
settingsFakeDoorFeatures(): IFakeDoor[] {
return this.$store.getters['ui/getFakeDoorByLocation']('settings');
return this.uiStore.getFakeDoorByLocation('settings');
},
sidebarMenuItems(): IMenuItem[] {
@ -113,13 +121,13 @@ export default mixins(
return this.canUserAccessRouteByName(VIEWS.API_SETTINGS);
},
onVersionClick() {
this.$store.dispatch('ui/openModal', ABOUT_MODAL_KEY);
this.uiStore.openModal(ABOUT_MODAL_KEY);
},
onReturn() {
this.$router.push({name: VIEWS.HOMEPAGE});
},
openUpdatesPanel() {
this.$store.dispatch('ui/openModal', VERSIONS_MODAL_KEY);
this.uiStore.openModal(VERSIONS_MODAL_KEY);
},
async handleSelect (key: string) {
switch (key) {

View file

@ -50,12 +50,18 @@ import { nodeBase } from '@/components/mixins/nodeBase';
import { nodeHelpers } from '@/components/mixins/nodeHelpers';
import { workflowHelpers } from '@/components/mixins/workflowHelpers';
import { getStyleTokenValue, isNumber, isString } from './helpers';
import { INodeUi, XYPosition } from '@/Interface';
import { INodeUi, INodeUpdatePropertiesInformation, IUpdateInformation, XYPosition } from '@/Interface';
import {
IDataObject,
INodeTypeDescription,
} from 'n8n-workflow';
import { QUICKSTART_NOTE_NAME } from '@/constants';
import { mapStores } from 'pinia';
import { useUIStore } from '@/stores/ui';
import { useWorkflowsStore } from '@/stores/workflows';
import { useNDVStore } from '@/stores/ndv';
import { useNodeTypesStore } from '@/stores/nodeTypes';
export default mixins(externalHooks, nodeBase, nodeHelpers, workflowHelpers).extend({
name: 'Sticky',
@ -68,6 +74,12 @@ export default mixins(externalHooks, nodeBase, nodeHelpers, workflowHelpers).ext
},
},
computed: {
...mapStores(
useNodeTypesStore,
useNDVStore,
useUIStore,
useWorkflowsStore,
),
defaultText (): string {
if (!this.nodeType) {
return '';
@ -78,13 +90,13 @@ export default mixins(externalHooks, nodeBase, nodeHelpers, workflowHelpers).ext
return content && isString(content.default) ? content.default : '';
},
isSelected (): boolean {
return this.$store.getters.getSelectedNodes.find((node: INodeUi) => node.name === this.data.name);
return this.uiStore.getSelectedNodes.find((node: INodeUi) => node.name === this.data.name) !== undefined;
},
nodeType (): INodeTypeDescription | null {
return this.data && this.$store.getters['nodeTypes/getNodeType'](this.data.type, this.data.typeVersion);
return this.data && this.nodeTypesStore.getNodeType(this.data.type, this.data.typeVersion);
},
node (): INodeUi | undefined { // same as this.data but reactive..
return this.$store.getters.nodesByName[this.name] as INodeUi | undefined;
node (): INodeUi | null { // same as this.data but reactive..
return this.workflowsStore.getNodeByName(this.name);
},
position (): XYPosition {
if (this.node) {
@ -124,7 +136,7 @@ export default mixins(externalHooks, nodeBase, nodeHelpers, workflowHelpers).ext
return !(this.hideActions || this.isReadOnly || this.workflowRunning || this.isResizing);
},
workflowRunning (): boolean {
return this.$store.getters.isActionActive('workflowRunning');
return this.uiStore.isActionActive('workflowRunning');
},
},
data () {
@ -142,10 +154,10 @@ export default mixins(externalHooks, nodeBase, nodeHelpers, workflowHelpers).ext
},
onEdit(edit: boolean) {
if (edit && !this.isActive && this.node) {
this.$store.commit('ndv/setActiveNodeName', this.node.name);
this.ndvStore.activeNodeName = this.node.name;
}
else if (this.isActive && !edit) {
this.$store.commit('ndv/setActiveNodeName', null);
this.ndvStore.activeNodeName = null;
}
},
onMarkdownClick ( link:HTMLAnchorElement, event: Event ) {
@ -191,12 +203,13 @@ export default mixins(externalHooks, nodeBase, nodeHelpers, workflowHelpers).ext
width: isNumber(params.width) ? params.width : this.node.parameters.width,
};
const updateInformation = {
const updateInformation: IUpdateInformation = {
key: this.node.id,
name: this.node.name,
value: nodeParameters,
};
this.$store.commit('setNodeParameters', updateInformation);
this.workflowsStore.setNodeParameters(updateInformation);
}
},
setPosition(position: XYPosition) {
@ -204,14 +217,14 @@ export default mixins(externalHooks, nodeBase, nodeHelpers, workflowHelpers).ext
return;
}
const updateInformation = {
const updateInformation: INodeUpdatePropertiesInformation = {
name: this.node.name,
properties: {
position,
position: { position },
},
};
this.$store.commit('updateNodeProperties', updateInformation);
this.workflowsStore.updateNodeProperties(updateInformation);
},
touchStart () {
if (this.isTouchDevice === true && this.isMacOs === false && this.isTouchActive === false) {

View file

@ -60,6 +60,8 @@ import { ITag } from "@/Interface";
import { MAX_TAG_NAME_LENGTH, TAGS_MANAGER_MODAL_KEY } from "@/constants";
import { showMessage } from "@/components/mixins/showMessage";
import { mapStores } from "pinia";
import { useUIStore } from "@/stores/ui";
const MANAGE_KEY = "__manage";
const CREATE_KEY = "__create";
@ -113,6 +115,7 @@ export default mixins(showMessage).extend({
this.$store.dispatch("tags/fetchAll");
},
computed: {
...mapStores(useUIStore),
...mapGetters("tags", ["allTags", "isLoading", "hasTags"]),
options(): ITag[] {
return this.allTags
@ -156,7 +159,7 @@ export default mixins(showMessage).extend({
);
if (ops === MANAGE_KEY) {
this.$data.filter = "";
this.$store.dispatch("ui/openModal", TAGS_MANAGER_MODAL_KEY);
this.uiStore.openModal(TAGS_MANAGER_MODAL_KEY);
} else if (ops === CREATE_KEY) {
this.onCreate();
} else {

View file

@ -32,6 +32,8 @@ import { ITag, ITagRow } from "@/Interface";
import TagsTableHeader from "@/components/TagsManager/TagsView/TagsTableHeader.vue";
import TagsTable from "@/components/TagsManager/TagsView/TagsTable.vue";
import { mapGetters } from 'vuex';
import { mapStores } from "pinia";
import { useUsersStore } from "@/stores/users";
const matches = (name: string, filter: string) => name.toLowerCase().trim().includes(filter.toLowerCase().trim());
@ -51,7 +53,7 @@ export default Vue.extend({
};
},
computed: {
...mapGetters('users', ['canUserDeleteTags']),
...mapStores(useUsersStore),
isCreateEnabled(): boolean {
return (this.$props.tags || []).length === 0 || this.$data.createEnabled;
},
@ -69,7 +71,7 @@ export default Vue.extend({
disable: disabled && tag.id !== this.deleteId && tag.id !== this.$data.updateId,
update: disabled && tag.id === this.$data.updateId,
delete: disabled && tag.id === this.$data.deleteId,
canDelete: this.canUserDeleteTags,
canDelete: this.usersStore.canUserDeleteTags,
}));
return this.isCreateEnabled

View file

@ -3,10 +3,12 @@
</template>
<script lang="ts">
import Vue from 'vue';
import { useRootStore } from '@/stores/n8nRootStore';
import { useSettingsStore } from '@/stores/settings';
import { useUsersStore } from '@/stores/users';
import { ITelemetrySettings } from 'n8n-workflow';
import { mapStores } from 'pinia';
import mixins from 'vue-typed-mixins';
import { mapGetters } from 'vuex';
import { externalHooks } from './mixins/externalHooks';
export default mixins(externalHooks).extend({
@ -17,12 +19,20 @@ export default mixins(externalHooks).extend({
};
},
computed: {
...mapGetters('settings', ['telemetry']),
...mapGetters('users', ['currentUserId']),
...mapGetters(['instanceId']),
...mapStores(
useRootStore,
useSettingsStore,
useUsersStore,
),
currentUserId(): string {
return this.usersStore.currentUserId || '';
},
isTelemetryEnabledOnRoute(): boolean {
return this.$route.meta && this.$route.meta.telemetry ? !this.$route.meta.telemetry.disabled: true;
},
telemetry(): ITelemetrySettings {
return this.settingsStore.telemetry;
},
},
mounted() {
this.init();
@ -40,15 +50,15 @@ export default mixins(externalHooks).extend({
this.$telemetry.init(
telemetrySettings,
{
instanceId: this.instanceId,
instanceId: this.rootStore.instanceId,
userId: this.currentUserId,
store: this.$store,
versionCli: this.$store.getters['settings/versionCli'],
versionCli: this.rootStore.versionCli,
},
);
this.$externalHooks().run('telemetry.currentUserIdChanged', {
instanceId: this.instanceId,
instanceId: this.rootStore.instanceId,
userId: this.currentUserId,
});
@ -60,9 +70,9 @@ export default mixins(externalHooks).extend({
this.init();
},
currentUserId(userId) {
this.$telemetry.identify(this.instanceId, userId);
this.$telemetry.identify(this.rootStore.instanceId, userId);
this.$externalHooks().run('telemetry.currentUserIdChanged', {
instanceId: this.instanceId,
instanceId: this.rootStore.instanceId,
userId,
});
},

View file

@ -20,7 +20,7 @@
</template-details-block>
<template-details-block
v-if="!loading && template.categories.length > 0"
v-if="!loading && template?.categories.length > 0"
:title="$locale.baseText('template.details.categories')"
>
<n8n-tags :tags="template.categories" @click="redirectToCategory" />
@ -52,6 +52,8 @@ import TemplateDetailsBlock from '@/components/TemplateDetailsBlock.vue';
import NodeIcon from '@/components/NodeIcon.vue';
import { abbreviateNumber, filterTemplateNodes } from '@/components/helpers';
import { ITemplatesNode, ITemplatesWorkflow, ITemplatesWorkflowFull } from '@/Interface';
import { mapStores } from 'pinia';
import { useTemplatesStore } from '@/stores/templates';
export default Vue.extend({
name: 'TemplateDetails',
props: {
@ -69,15 +71,20 @@ export default Vue.extend({
NodeIcon,
TemplateDetailsBlock,
},
computed: {
...mapStores(
useTemplatesStore,
),
},
methods: {
abbreviateNumber,
filterTemplateNodes,
redirectToCategory(id: string) {
this.$store.commit('templates/resetSessionId');
this.templatesStore.resetSessionId();
this.$router.push(`/templates?categories=${id}`);
},
redirectToSearchPage(node: ITemplatesNode) {
this.$store.commit('templates/resetSessionId');
this.templatesStore.resetSessionId();
this.$router.push(`/templates?search=${node.displayName}`);
},
},

View file

@ -9,6 +9,8 @@ import { format, LocaleFunc, register } from 'timeago.js';
import { convertToHumanReadableDate } from './helpers';
import Vue from 'vue';
import { mapGetters } from 'vuex';
import { mapStores } from 'pinia';
import { useRootStore } from '@/stores/n8nRootStore';
export default Vue.extend({
name: 'TimeAgo',
@ -48,7 +50,12 @@ export default Vue.extend({
},
},
computed: {
...mapGetters(['defaultLocale']),
...mapStores(
useRootStore,
),
defaultLocale(): string {
return this.rootStore.defaultLocale;
},
format(): string {
const text = format(this.date, this.defaultLocale);

View file

@ -108,6 +108,11 @@ import NodeIcon from './NodeIcon.vue';
import { copyPaste } from './mixins/copyPaste';
import { showMessage } from '@/components/mixins/showMessage';
import Vue from 'vue';
import { mapStores } from 'pinia';
import { useUIStore } from '@/stores/ui';
import { useWorkflowsStore } from '@/stores/workflows';
import { useNDVStore } from '@/stores/ndv';
import { useNodeTypesStore } from '@/stores/nodeTypes';
export default mixins(workflowHelpers, copyPaste, showMessage).extend({
name: 'TriggerPanel',
@ -125,12 +130,18 @@ export default mixins(workflowHelpers, copyPaste, showMessage).extend({
},
},
computed: {
...mapStores(
useNodeTypesStore,
useNDVStore,
useUIStore,
useWorkflowsStore,
),
node(): INodeUi | null {
return this.$store.getters.getNodeByName(this.nodeName);
return this.workflowsStore.getNodeByName(this.nodeName);
},
nodeType(): INodeTypeDescription | null {
if (this.node) {
return this.$store.getters['nodeTypes/getNodeType'](this.node.type, this.node.typeVersion);
return this.nodeTypesStore.getNodeType(this.node.type, this.node.typeVersion);
}
return null;
@ -195,8 +206,8 @@ export default mixins(workflowHelpers, copyPaste, showMessage).extend({
return Boolean(this.nodeType && this.nodeType.polling);
},
isListeningForEvents(): boolean {
const waitingOnWebhook = this.$store.getters.executionWaitingForWebhook as boolean;
const executedNode = this.$store.getters.executedNode as string | undefined;
const waitingOnWebhook = this.workflowsStore.executionWaitingForWebhook as boolean;
const executedNode = this.workflowsStore.executedNode as string | undefined;
return (
!!this.node &&
!this.node.disabled &&
@ -206,15 +217,15 @@ export default mixins(workflowHelpers, copyPaste, showMessage).extend({
);
},
workflowRunning(): boolean {
return this.$store.getters.isActionActive('workflowRunning');
return this.uiStore.isActionActive('workflowRunning');
},
isActivelyPolling(): boolean {
const triggeredNode = this.$store.getters.executedNode;
const triggeredNode = this.workflowsStore.executedNode;
return this.workflowRunning && this.isPollingNode && this.nodeName === triggeredNode;
},
isWorkflowActive(): boolean {
return this.$store.getters.isActive;
return this.workflowsStore.isWorkflowActive;
},
header(): string {
const serviceName = this.nodeType ? getTriggerNodeServiceName(this.nodeType) : '';
@ -369,15 +380,15 @@ export default mixins(workflowHelpers, copyPaste, showMessage).extend({
this.$emit('activate');
} else if (target.dataset.key === 'executions') {
this.$telemetry.track('User clicked ndv link', {
workflow_id: this.$store.getters.workflowId,
workflow_id: this.workflowsStore.workflowId,
session_id: this.sessionId,
pane: 'input',
type: 'open-executions-log',
});
this.$store.commit('ndv/setActiveNodeName', null);
this.$store.dispatch('ui/openModal', EXECUTIONS_MODAL_KEY);
this.ndvStore.activeNodeName = null;
this.uiStore.openModal(EXECUTIONS_MODAL_KEY);
} else if (target.dataset.key === 'settings') {
this.$store.dispatch('ui/openModal', WORKFLOW_SETTINGS_MODAL_KEY);
this.uiStore.openModal(WORKFLOW_SETTINGS_MODAL_KEY);
}
}
},

View file

@ -64,6 +64,9 @@ import ModalDrawer from './ModalDrawer.vue';
import mixins from 'vue-typed-mixins';
import { workflowHelpers } from '@/components/mixins/workflowHelpers';
import Vue from 'vue';
import { mapStores } from 'pinia';
import { useSettingsStore } from '@/stores/settings';
import { useRootStore } from '@/stores/n8nRootStore';
const DEFAULT_TITLE = `How likely are you to recommend n8n to a friend or colleague?`;
const GREAT_FEEDBACK_TITLE = `Great to hear! Can we reach out to see how we can make n8n even better for you?`;
@ -79,12 +82,16 @@ export default mixins(workflowHelpers).extend({
isActive(isActive) {
if (isActive) {
this.$telemetry.track('User shown value survey', {
instance_id: this.$store.getters.instanceId,
instance_id: this.rootStore.instanceId,
});
}
},
},
computed: {
...mapStores(
useRootStore,
useSettingsStore,
),
getTitle(): string {
if (this.form.value !== '') {
if (Number(this.form.value) > 7) {
@ -115,13 +122,13 @@ export default mixins(workflowHelpers).extend({
closeDialog(): void {
if (this.form.value === '') {
this.$telemetry.track('User responded value survey score', {
instance_id: this.$store.getters.instanceId,
instance_id: this.rootStore.instanceId,
nps: '',
});
}
if (this.form.value !== '' && this.form.email === '') {
this.$telemetry.track('User responded value survey email', {
instance_id: this.$store.getters.instanceId,
instance_id: this.rootStore.instanceId,
email: '',
});
}
@ -133,31 +140,25 @@ export default mixins(workflowHelpers).extend({
this.form.value = value;
this.showButtons = false;
const response: IN8nPromptResponse = await this.$store.dispatch(
'settings/submitValueSurvey',
{ value: this.form.value },
);
const response: IN8nPromptResponse | undefined = await this.settingsStore.submitValueSurvey({ value: this.form.value });
if (response.updated) {
if (response && response.updated) {
this.$telemetry.track('User responded value survey score', {
instance_id: this.$store.getters.instanceId,
instance_id: this.rootStore.instanceId,
nps: this.form.value,
});
}
},
async send() {
if (this.isEmailValid) {
const response: IN8nPromptResponse = await this.$store.dispatch(
'settings/submitValueSurvey',
{
email: this.form.email,
value: this.form.value,
},
);
const response: IN8nPromptResponse | undefined = await this.settingsStore.submitValueSurvey({
email: this.form.email,
value: this.form.value,
});
if (response.updated) {
if (response && response.updated) {
this.$telemetry.track('User responded value survey email', {
instance_id: this.$store.getters.instanceId,
instance_id: this.rootStore.instanceId,
email: this.form.email,
});
this.$showMessage({

View file

@ -40,6 +40,10 @@ import {
import { workflowHelpers } from '@/components/mixins/workflowHelpers';
import mixins from 'vue-typed-mixins';
import { mapStores } from 'pinia';
import { useWorkflowsStore } from '@/stores/workflows';
import { useRootStore } from '@/stores/n8nRootStore';
import { useNDVStore } from '@/stores/ndv';
// Node types that should not be displayed in variable selector
const SKIPPED_NODE_TYPES = [
@ -64,6 +68,11 @@ export default mixins(
};
},
computed: {
...mapStores(
useNDVStore,
useRootStore,
useWorkflowsStore,
),
extendAll (): boolean {
if (this.variableFilter) {
return true;
@ -406,7 +415,7 @@ export default mixins(
const runIndex = 0;
const returnData: IVariableSelectorOption[] = [];
const activeNode: INodeUi | null = this.$store.getters['ndv/activeNode'];
const activeNode: INodeUi | null = this.ndvStore.activeNode;
if (activeNode === null) {
return returnData;
@ -431,7 +440,7 @@ export default mixins(
$resumeWebhookUrl: PLACEHOLDER_FILLED_AT_EXECUTION_TIME,
};
const dataProxy = new WorkflowDataProxy(workflow, runExecutionData, runIndex, itemIndex, nodeName, connectionInputData, {}, 'manual', this.$store.getters.timezone, additionalKeys);
const dataProxy = new WorkflowDataProxy(workflow, runExecutionData, runIndex, itemIndex, nodeName, connectionInputData, {}, 'manual', this.rootStore.timezone, additionalKeys);
const proxy = dataProxy.getDataProxy();
// @ts-ignore
@ -486,15 +495,15 @@ export default mixins(
getFilterResults (filterText: string, itemIndex: number): IVariableSelectorOption[] {
const inputName = 'main';
const activeNode: INodeUi | null = this.$store.getters['ndv/activeNode'];
const activeNode: INodeUi | null = this.ndvStore.activeNode;
if (activeNode === null) {
return [];
}
const executionData = this.$store.getters.getWorkflowExecution as IExecutionResponse | null;
const executionData = this.workflowsStore.getWorkflowExecution;
let parentNode = this.workflow.getParentNodes(activeNode.name, inputName, 1);
let runData = this.$store.getters.getWorkflowRunData as IRunData | null;
let runData = this.workflowsStore.getWorkflowRunData;
if (runData === null) {
runData = {};
@ -543,7 +552,7 @@ export default mixins(
},
];
parentNode.forEach((parentNodeName) => {
const pinData = this.$store.getters['pinDataByNodeName'](parentNodeName);
const pinData = this.workflowsStore.pinDataByNodeName(parentNodeName);
if (pinData) {
const output = this.getNodePinDataOutput(parentNodeName, pinData, filterText, true);
@ -677,7 +686,7 @@ export default mixins(
if (upstreamNodes.includes(nodeName)) {
// If the node is an upstream node add also the output data which can be referenced
const pinData = this.$store.getters['pinDataByNodeName'](nodeName);
const pinData = this.workflowsStore.pinDataByNodeName(nodeName);
tempOutputData = pinData
? this.getNodePinDataOutput(nodeName, pinData, filterText)
: this.getNodeRunDataOutput(nodeName, runData, filterText, itemIndex);

View file

@ -35,10 +35,10 @@
import { showMessage } from '@/components/mixins/showMessage';
import { workflowActivate } from '@/components/mixins/workflowActivate';
import { useUIStore } from '@/stores/ui';
import { useWorkflowsStore } from '@/stores/workflows';
import { mapStores } from 'pinia';
import mixins from 'vue-typed-mixins';
import { mapGetters } from "vuex";
import { getActivatableTriggerNodes } from './helpers';
export default mixins(
@ -53,14 +53,18 @@ export default mixins(
'workflowId',
],
computed: {
...mapGetters({
dirtyState: "getStateIsDirty",
}),
...mapStores(
useUIStore,
useWorkflowsStore,
),
getStateIsDirty (): boolean {
return this.uiStore.stateIsDirty;
},
nodesIssuesExist (): boolean {
return this.$store.getters.nodesIssuesExist;
return this.workflowsStore.nodesIssuesExist;
},
isWorkflowActive (): boolean {
const activeWorkflows = this.$store.getters.getActiveWorkflows;
const activeWorkflows = this.workflowsStore.activeWorkflows;
return activeWorkflows.includes(this.workflowId);
},
couldNotBeStarted (): boolean {
@ -73,7 +77,7 @@ export default mixins(
return '#13ce66';
},
isCurrentWorkflow(): boolean {
return this.$store.getters['workflowId'] === this.workflowId;
return this.workflowsStore.workflowId === this.workflowId;
},
disabled(): boolean {
const isNewWorkflow = !this.workflowId;
@ -84,7 +88,7 @@ export default mixins(
return false;
},
containsTrigger(): boolean {
const foundTriggers = getActivatableTriggerNodes(this.$store.getters.workflowTriggerNodes);
const foundTriggers = getActivatableTriggerNodes(this.workflowsStore.workflowTriggerNodes);
return foundTriggers.length > 0;
},
},

View file

@ -12,7 +12,7 @@
<n8n-text color="text-light" size="small">
<span v-show="data">{{$locale.baseText('workflows.item.updated')}} <time-ago :date="data.updatedAt" /> | </span>
<span v-show="data" class="mr-2xs">{{$locale.baseText('workflows.item.created')}} {{ formattedCreatedAtDate }} </span>
<span v-if="areTagsEnabled && data.tags && data.tags.length > 0" v-show="data">
<span v-if="settingsStore.areTagsEnabled && data.tags && data.tags.length > 0" v-show="data">
<n8n-tags
:tags="data.tags"
:truncateAt="3"
@ -62,6 +62,11 @@ import dateformat from "dateformat";
import { restApi } from '@/components/mixins/restApi';
import WorkflowActivator from '@/components/WorkflowActivator.vue';
import Vue from "vue";
import { mapStores } from 'pinia';
import { useUIStore } from '@/stores/ui';
import { useSettingsStore } from '@/stores/settings';
import { useUsersStore } from '@/stores/users';
import { useWorkflowsStore } from '@/stores/workflows';
export const WORKFLOW_LIST_ITEM_ACTIONS = {
OPEN: 'open',
@ -103,11 +108,14 @@ export default mixins(
},
},
computed: {
...mapStores(
useSettingsStore,
useUIStore,
useUsersStore,
useWorkflowsStore,
),
currentUser (): IUser {
return this.$store.getters['users/currentUser'];
},
areTagsEnabled(): boolean {
return this.$store.getters['settings/areTagsEnabled'];
return this.usersStore.currentUser || {} as IUser;
},
credentialPermissions(): IPermissions {
return getWorkflowPermissions(this.currentUser, this.data, this.$store);
@ -162,7 +170,7 @@ export default mixins(
if (action === WORKFLOW_LIST_ITEM_ACTIONS.OPEN) {
await this.onClick();
} else if (action === WORKFLOW_LIST_ITEM_ACTIONS.DUPLICATE) {
await this.$store.dispatch('ui/openModalWithData', {
this.uiStore.openModalWithData({
name: DUPLICATE_MODAL_KEY,
data: {
id: this.data.id,
@ -188,7 +196,7 @@ export default mixins(
try {
await this.restApi().deleteWorkflow(this.data.id);
this.$store.commit('deleteWorkflow', this.data.id);
this.workflowsStore.deleteWorkflow(this.data.id);
} catch (error) {
this.$showError(
error,

View file

@ -220,10 +220,12 @@ import { restApi } from '@/components/mixins/restApi';
import { genericHelpers } from '@/components/mixins/genericHelpers';
import { showMessage } from '@/components/mixins/showMessage';
import {
IN8nUISettings,
ITimeoutHMS,
IWorkflowDataUpdate,
IWorkflowSettings,
IWorkflowShortResponse,
WorkflowCallerPolicyDefaultOption,
} from '@/Interface';
import Modal from './Modal.vue';
import { PLACEHOLDER_EMPTY_WORKFLOW_ID, WORKFLOW_SETTINGS_MODAL_KEY } from '../constants';
@ -232,6 +234,10 @@ import mixins from 'vue-typed-mixins';
import { mapGetters } from "vuex";
import { deepCopy } from "n8n-workflow";
import { mapStores } from 'pinia';
import { useWorkflowsStore } from '@/stores/workflows';
import { useSettingsStore } from '@/stores/settings';
import { useRootStore } from '@/stores/n8nRootStore';
export default mixins(
externalHooks,
@ -274,8 +280,8 @@ export default mixins(
timezones: [] as Array<{ key: string, value: string }>,
workflowSettings: {} as IWorkflowSettings,
workflows: [] as IWorkflowShortResponse[],
executionTimeout: this.$store.getters.executionTimeout,
maxExecutionTimeout: this.$store.getters.maxExecutionTimeout,
executionTimeout: 0,
maxExecutionTimeout: 0,
timeoutHMS: { hours: 0, minutes: 0, seconds: 0 } as ITimeoutHMS,
modalBus: new Vue(),
WORKFLOW_SETTINGS_MODAL_KEY,
@ -283,13 +289,25 @@ export default mixins(
},
computed: {
...mapGetters(['workflowName', 'workflowId']),
...mapStores(
useRootStore,
useSettingsStore,
useWorkflowsStore,
),
workflowName(): string {
return this.workflowsStore.workflowName;
},
workflowId(): string {
return this.workflowsStore.workflowId;
},
isWorkflowSharingEnabled(): boolean {
return this.$store.getters['settings/isWorkflowSharingEnabled'];
return this.settingsStore.isWorkflowSharingEnabled;
},
},
async mounted () {
this.executionTimeout = this.rootStore.executionTimeout;
this.maxExecutionTimeout = this.rootStore.maxExecutionTimeout;
if (!this.workflowId || this.workflowId === PLACEHOLDER_EMPTY_WORKFLOW_ID) {
this.$showMessage({
title: 'No workflow active',
@ -301,11 +319,11 @@ export default mixins(
return;
}
this.defaultValues.saveDataErrorExecution = this.$store.getters.saveDataErrorExecution;
this.defaultValues.saveDataSuccessExecution = this.$store.getters.saveDataSuccessExecution;
this.defaultValues.saveManualExecutions = this.$store.getters.saveManualExecutions;
this.defaultValues.timezone = this.$store.getters.timezone;
this.defaultValues.workflowCallerPolicy = this.$store.getters['settings/workflowCallerPolicyDefaultOption'];
this.defaultValues.saveDataErrorExecution = this.settingsStore.saveDataErrorExecution;
this.defaultValues.saveDataSuccessExecution = this.settingsStore.saveDataSuccessExecution;
this.defaultValues.saveManualExecutions = this.settingsStore.saveManualExecutions;
this.defaultValues.timezone = this.rootStore.timezone;
this.defaultValues.workflowCallerPolicy = this.settingsStore.workflowCallerPolicyDefaultOption;
this.isLoading = true;
const promises = [];
@ -323,7 +341,7 @@ export default mixins(
this.$showError(error, 'Problem loading settings', 'The following error occurred loading the data:');
}
const workflowSettings = deepCopy(this.$store.getters.workflowSettings);
const workflowSettings = deepCopy(this.workflowsStore.workflowSettings) as IWorkflowSettings;
if (workflowSettings.timezone === undefined) {
workflowSettings.timezone = 'DEFAULT';
@ -338,16 +356,16 @@ export default mixins(
workflowSettings.saveExecutionProgress = 'DEFAULT';
}
if (workflowSettings.saveManualExecutions === undefined) {
workflowSettings.saveManualExecutions = 'DEFAULT';
workflowSettings.saveManualExecutions = this.defaultValues.saveManualExecutions;
}
if (workflowSettings.callerPolicy === undefined) {
workflowSettings.callerPolicy = this.defaultValues.workflowCallerPolicy;
workflowSettings.callerPolicy = this.defaultValues.workflowCallerPolicy as WorkflowCallerPolicyDefaultOption;
}
if (workflowSettings.executionTimeout === undefined) {
workflowSettings.executionTimeout = this.$store.getters.executionTimeout;
workflowSettings.executionTimeout = this.rootStore.executionTimeout;
}
if (workflowSettings.maxExecutionTimeout === undefined) {
workflowSettings.maxExecutionTimeout = this.$store.getters.maxExecutionTimeout;
workflowSettings.maxExecutionTimeout = this.rootStore.maxExecutionTimeout;
}
Vue.set(this, 'workflowSettings', workflowSettings);
@ -355,7 +373,7 @@ export default mixins(
this.isLoading = false;
this.$externalHooks().run('workflowSettings.dialogVisibleChanged', { dialogVisible: true });
this.$telemetry.track('User opened workflow settings', { workflow_id: this.$store.getters.workflowId });
this.$telemetry.track('User opened workflow settings', { workflow_id: this.workflowsStore.workflowId });
},
methods: {
onCallerIdsInput(str: string) {
@ -589,11 +607,11 @@ export default mixins(
delete data.settings!.maxExecutionTimeout;
this.isLoading = true;
data.hash = this.$store.getters.workflowHash;
data.hash = this.workflowsStore.workflowHash;
try {
const workflow = await this.restApi().updateWorkflow(this.$route.params.name, data);
this.$store.commit('setWorkflowHash', workflow.hash);
this.workflowsStore.setWorkflowHash(workflow.hash || '');
} catch (error) {
this.$showError(
error,
@ -611,9 +629,9 @@ export default mixins(
}
}
const oldSettings = deepCopy(this.$store.getters.workflowSettings);
const oldSettings = deepCopy(this.workflowsStore.workflowSettings);
this.$store.commit('setWorkflowSettings', localWorkflowSettings);
this.workflowsStore.setWorkflowSettings(localWorkflowSettings);
this.isLoading = false;
@ -625,7 +643,7 @@ export default mixins(
this.closeDialog();
this.$externalHooks().run('workflowSettings.saveSettings', { oldSettings });
this.$telemetry.track('User updated workflow settings', { workflow_id: this.$store.getters.workflowId });
this.$telemetry.track('User updated workflow settings', { workflow_id: this.workflowsStore.workflowId });
},
toggleTimeout() {
this.workflowSettings.executionTimeout = this.workflowSettings.executionTimeout === -1 ? 0 : -1;

View file

@ -32,7 +32,7 @@
/>
<n8n-user-select
:users="ownedByUsers"
:currentUserId="currentUser.id"
:currentUserId="usersStore.currentUser.id"
:value="value.ownedBy"
size="small"
@input="setKeyValue('ownedBy', $event)"
@ -48,7 +48,7 @@
/>
<n8n-user-select
:users="sharedWithUsers"
:currentUserId="currentUser.id"
:currentUserId="usersStore.currentUser.id"
:value="value.sharedWith"
size="small"
@input="setKeyValue('sharedWith', $event)"
@ -66,8 +66,9 @@
<script lang="ts">
import Vue, { PropType } from 'vue';
import {EnterpriseEditionFeature} from "@/constants";
import {IResource} from "@/components/layouts/ResourcesListLayout.vue";
import {IUser} from "@/Interface";
import { mapStores } from 'pinia';
import { useUsersStore } from '@/stores/users';
export type IResourceFiltersType = Record<string, boolean | string | string[]>;
@ -95,17 +96,12 @@ export default Vue.extend({
};
},
computed: {
currentUser(): IUser {
return this.$store.getters['users/currentUser'];
},
allUsers(): IUser[] {
return this.$store.getters['users/allUsers'];
},
...mapStores(useUsersStore),
ownedByUsers(): IUser[] {
return this.allUsers.map((user) => user.id === this.value.sharedWith ? { ...user, disabled: true } : user);
return this.usersStore.allUsers.map((user) => user.id === this.value.sharedWith ? { ...user, disabled: true } : user);
},
sharedWithUsers(): IUser[] {
return this.allUsers.map((user) => user.id === this.value.ownedBy ? { ...user, disabled: true } : user);
return this.usersStore.allUsers.map((user) => user.id === this.value.ownedBy ? { ...user, disabled: true } : user);
},
filtersLength(): number {
let length = 0;

View file

@ -1,5 +1,5 @@
<template>
<div :class="[$style.wrapper, !sidebarMenuCollapsed && $style.expandedSidebar]">
<div :class="[$style.wrapper, !this.uiStore.sidebarMenuCollapsed && $style.expandedSidebar]">
<div :class="$style.container">
<aside :class="$style.aside" v-if="$slots.aside">
<slot name="aside" />
@ -12,6 +12,8 @@
</template>
<script lang="ts">
import { useUIStore } from '@/stores/ui';
import { mapStores } from 'pinia';
import Vue from 'vue';
export default Vue.extend({
@ -22,9 +24,7 @@ export default Vue.extend({
};
},
computed: {
sidebarMenuCollapsed(): boolean {
return this.$store.getters['ui/sidebarMenuCollapsed'];
},
...mapStores(useUIStore),
},
});
</script>

Some files were not shown because too many files have changed in this diff Show more