From 3bbd378e4f42eaf867328ea5aefb7d585d87641f Mon Sep 17 00:00:00 2001 From: Milorad Filipovic Date: Tue, 25 Oct 2022 10:34:26 +0200 Subject: [PATCH 01/38] =?UTF-8?q?=E2=9C=A8=20Added=20pinia=20support.=20Mi?= =?UTF-8?q?grated=20community=20nodes=20module.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- packages/editor-ui/package.json | 2 + packages/editor-ui/src/api/communityNodes.ts | 2 +- .../CommunityPackageInstallModal.vue | 9 +- .../CommunityPackageManageConfirmModal.vue | 9 +- packages/editor-ui/src/main.ts | 6 ++ packages/editor-ui/src/store.ts | 2 - .../editor-ui/src/stores/communityNodes.ts | 86 +++++++++++++++++++ packages/editor-ui/src/stores/n8nRootStore.ts | 31 +++++++ .../src/views/SettingsCommunityNodesView.vue | 21 +++-- 9 files changed, 151 insertions(+), 17 deletions(-) create mode 100644 packages/editor-ui/src/stores/communityNodes.ts create mode 100644 packages/editor-ui/src/stores/n8nRootStore.ts diff --git a/packages/editor-ui/package.json b/packages/editor-ui/package.json index 978084000d18f..2ceec6dba8033 100644 --- a/packages/editor-ui/package.json +++ b/packages/editor-ui/package.json @@ -37,6 +37,7 @@ "@fortawesome/free-regular-svg-icons": "^6.1.1", "@fortawesome/free-solid-svg-icons": "^5.15.3", "@fortawesome/vue-fontawesome": "^2.0.2", + "@vue/composition-api": "^1.7.0", "axios": "^0.21.1", "dateformat": "^3.0.3", "esprima": "^4.0.1", @@ -57,6 +58,7 @@ "n8n-design-system": "~0.39.0", "n8n-workflow": "~0.121.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", diff --git a/packages/editor-ui/src/api/communityNodes.ts b/packages/editor-ui/src/api/communityNodes.ts index 4fe841053dfec..3e2d116e01550 100644 --- a/packages/editor-ui/src/api/communityNodes.ts +++ b/packages/editor-ui/src/api/communityNodes.ts @@ -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 { +export async function updatePackage(context: IRestApiContext, name: string): Promise { return await makeRestApiRequest(context, 'PATCH', '/nodes', { name }); } diff --git a/packages/editor-ui/src/components/CommunityPackageInstallModal.vue b/packages/editor-ui/src/components/CommunityPackageInstallModal.vue index 6d974571189ce..bcc6fa7d1fc4a 100644 --- a/packages/editor-ui/src/components/CommunityPackageInstallModal.vue +++ b/packages/editor-ui/src/components/CommunityPackageInstallModal.vue @@ -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({ diff --git a/packages/editor-ui/src/components/CommunityPackageManageConfirmModal.vue b/packages/editor-ui/src/components/CommunityPackageManageConfirmModal.vue index dad324648d4e5..b910b6d647c76 100644 --- a/packages/editor-ui/src/components/CommunityPackageManageConfirmModal.vue +++ b/packages/editor-ui/src/components/CommunityPackageManageConfirmModal.vue @@ -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', { diff --git a/packages/editor-ui/src/main.ts b/packages/editor-ui/src/main.ts index de091b8dcba8d..013b9ecd97301 100644 --- a/packages/editor-ui/src/main.ts +++ b/packages/editor-ui/src/main.ts @@ -22,6 +22,8 @@ import { runExternalHook } from './components/mixins/externalHooks'; import { TelemetryPlugin } from './plugins/telemetry'; import { I18nPlugin, i18nInstance } from './plugins/i18n'; +import { createPinia, PiniaVuePlugin } from 'pinia'; + import { store } from './store'; Vue.config.productionTip = false; @@ -31,11 +33,15 @@ router.afterEach((to, from) => { Vue.use(TelemetryPlugin); Vue.use((vue) => I18nPlugin(vue, store)); +Vue.use(PiniaVuePlugin); + +const pinia = createPinia(); new Vue({ i18n: i18nInstance, router, store, + pinia, render: h => h(App), }).$mount('#app'); diff --git a/packages/editor-ui/src/store.ts b/packages/editor-ui/src/store.ts index 4926e5fcc58f9..b709e5f639029 100644 --- a/packages/editor-ui/src/store.ts +++ b/packages/editor-ui/src/store.ts @@ -48,7 +48,6 @@ import versions from './modules/versions'; import templates from './modules/templates'; import {stringSizeInBytes} from "@/components/helpers"; import {dataPinningEventBus} from "@/event-bus/data-pinning-event-bus"; -import communityNodes from './modules/communityNodes'; import nodeCreator from './modules/nodeCreator'; import { isJsonKeyObject } from './utils'; import {getActiveWorkflows, getWorkflows} from "@/api/workflows"; @@ -120,7 +119,6 @@ const modules = { versions, users, ui, - communityNodes, nodeCreator, ndv, }; diff --git a/packages/editor-ui/src/stores/communityNodes.ts b/packages/editor-ui/src/stores/communityNodes.ts new file mode 100644 index 0000000000000..2994a30a453c7 --- /dev/null +++ b/packages/editor-ui/src/stores/communityNodes.ts @@ -0,0 +1,86 @@ +import { getInstalledCommunityNodes, installNewPackage, uninstallPackage, updatePackage } from "@/api/communityNodes"; +import { getAvailableCommunityPackageCount } from "@/api/settings"; +import { defineStore } from "pinia"; +import { useRootStore } from "./n8nRootStore"; +import { PublicInstalledPackage } from 'n8n-workflow'; +import Vue from "vue"; +import { ICommunityPackageMap } from "@/Interface"; + +const LOADER_DELAY = 300; + +export interface ICommunityNodesState { + availablePackageCount: number; + installedPackages: { [name: string]: PublicInstalledPackage }; +} + +export const useCommunityNodesStore = defineStore('communityNodes', { + state: (): ICommunityNodesState => ({ + // -1 means that package count has not been fetched yet + availablePackageCount: -1, + installedPackages: {}, + }), + getters: { + getInstalledPackages: (state) => { + return Object.values(state.installedPackages).sort((a, b) => a.packageName.localeCompare(b.packageName)); + }, + getInstalledPackageByName: (state) => { + return (name: string): PublicInstalledPackage => state.installedPackages[name]; + }, + }, + actions: { + async fetchAvailableCommunityPackageCount(): Promise { + if (this.availablePackageCount === -1) { + this.availablePackageCount = await getAvailableCommunityPackageCount(); + } + }, + async fetchInstalledPackages(): Promise { + const rootStore = useRootStore(); + const installedPackages = await getInstalledCommunityNodes(rootStore.getRestApiContext); + this.setInstalledPackages(installedPackages); + const timeout = installedPackages.length > 0 ? 0: LOADER_DELAY; + setTimeout(() => { + return; + }, timeout); + }, + async installPackage(packageName: string): Promise { + try { + const rootStore = useRootStore(); + await installNewPackage(rootStore.getRestApiContext, packageName); + await this.fetchInstalledPackages(); + } catch (error) { + throw (error); + } + }, + async uninstallPackage(packageName: string): Promise { + try { + const rootStore = useRootStore(); + await uninstallPackage(rootStore.getRestApiContext, packageName); + this.removePackageByName(packageName); + } catch (error) { + throw (error); + } + }, + async updatePackage(packageName: string): Promise { + try { + const rootStore = useRootStore(); + const packageToUpdate: PublicInstalledPackage = this.getInstalledPackageByName(packageName); + const updatedPackage: PublicInstalledPackage = await updatePackage(rootStore.getRestApiContext, packageToUpdate.packageName); + this.updatePackageObject(updatedPackage); + } catch (error) { + throw (error); + } + }, + setInstalledPackages(packages: PublicInstalledPackage[]) { + this.installedPackages = packages.reduce((packageMap: ICommunityPackageMap, pack: PublicInstalledPackage) => { + packageMap[pack.packageName] = pack; + return packageMap; + }, {}); + }, + removePackageByName(name: string): void { + Vue.delete(this.installedPackages, name); + }, + updatePackageObject(newPackage: PublicInstalledPackage) { + this.installedPackages[newPackage.packageName] = newPackage; + }, + }, +}); diff --git a/packages/editor-ui/src/stores/n8nRootStore.ts b/packages/editor-ui/src/stores/n8nRootStore.ts new file mode 100644 index 0000000000000..d4a8348ede075 --- /dev/null +++ b/packages/editor-ui/src/stores/n8nRootStore.ts @@ -0,0 +1,31 @@ +import { IRestApiContext } from '@/Interface'; +import { defineStore } from 'pinia'; + +// TODO: Use imported interface when done +interface RootState { + sessionId: string; + baseUrl: string; +} + +export const useRootStore = defineStore('root', { + state: (): RootState => ({ + sessionId: Math.random().toString(36).substring(2, 15), + // @ts-ignore + baseUrl: import.meta.env.VUE_APP_URL_BASE_API ? import.meta.env.VUE_APP_URL_BASE_API : (window.BASE_PATH === '/%BASE_PATH%/' ? '/' : window.BASE_PATH), + }), + getters: { + getRestApiContext(state): IRestApiContext { + let endpoint = 'rest'; + if (import.meta.env.VUE_APP_ENDPOINT_REST) { + endpoint = process.env.VUE_APP_ENDPOINT_REST; + } + return { + baseUrl: `${state.baseUrl}${endpoint}`, + sessionId: state.sessionId, + }; + }, + }, + actions: { + + }, +}); diff --git a/packages/editor-ui/src/views/SettingsCommunityNodesView.vue b/packages/editor-ui/src/views/SettingsCommunityNodesView.vue index 11282934827f7..a5acadd1dfca7 100644 --- a/packages/editor-ui/src/views/SettingsCommunityNodesView.vue +++ b/packages/editor-ui/src/views/SettingsCommunityNodesView.vue @@ -4,7 +4,7 @@
{{ $locale.baseText('settings.communityNodes') }}
@@ -72,6 +72,9 @@ import { } from '../constants'; import { PublicInstalledPackage } from 'n8n-workflow'; +import { useCommunityNodesStore } from '@/stores/communityNodes'; +import { mapStores } from 'pinia'; + const PACKAGE_COUNT_THRESHOLD = 31; export default mixins( @@ -90,9 +93,9 @@ export default mixins( async mounted() { try { this.$data.loading = true; - await this.$store.dispatch('communityNodes/fetchInstalledPackages'); + await this.communityNodesStore.fetchInstalledPackages(); - const installedPackages: PublicInstalledPackage[] = this.getInstalledPackages; + const installedPackages: PublicInstalledPackage[] = this.communityNodesStore.getInstalledPackages; const packagesToUpdate: PublicInstalledPackage[] = installedPackages.filter(p => p.updateAvailable ); this.$telemetry.track('user viewed cnr settings page', { num_of_packages_installed: installedPackages.length, @@ -123,16 +126,16 @@ export default mixins( this.$data.loading = false; } try { - await this.$store.dispatch('communityNodes/fetchAvailableCommunityPackageCount'); + await this.communityNodesStore.fetchAvailableCommunityPackageCount(); } finally { this.$data.loading = false; } }, computed: { + ...mapStores(useCommunityNodesStore), ...mapGetters('settings', ['isNpmAvailable', 'isQueueModeEnabled']), - ...mapGetters('communityNodes', ['getInstalledPackages']), getEmptyStateDescription() { - const packageCount = this.$store.getters['communityNodes/availablePackageCount']; + const packageCount = this.communityNodesStore.availablePackageCount; return packageCount < PACKAGE_COUNT_THRESHOLD ? this.$locale.baseText('settings.communityNodes.empty.description.no-packages', { interpolate: { @@ -192,7 +195,7 @@ export default mixins( }, methods: { openInstallModal(event: MouseEvent) { - const telemetryPayload = { is_empty_state: this.getInstalledPackages.length === 0 }; + const telemetryPayload = { is_empty_state: this.communityNodesStore.getInstalledPackages.length === 0 }; this.$telemetry.track('user clicked cnr install button', telemetryPayload); this.$externalHooks().run('settingsCommunityNodesView.openInstallModal', telemetryPayload); this.$store.dispatch('ui/openModal', COMMUNITY_PACKAGE_INSTALL_MODAL_KEY); From 9395b2372ec68baa14879e0e1de37758f75ab931 Mon Sep 17 00:00:00 2001 From: Milorad Filipovic Date: Tue, 25 Oct 2022 13:18:06 +0200 Subject: [PATCH 02/38] =?UTF-8?q?=E2=9C=A8=20Added=20ui=20pinia=20store,?= =?UTF-8?q?=20moved=20some=20data=20from=20root=20store=20to=20it,=20updat?= =?UTF-8?q?ed=20modals=20to=20work=20with=20pinia=20stores?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- packages/editor-ui/src/Interface.ts | 102 ++++- .../editor-ui/src/api/workflow-webhooks.ts | 2 +- .../src/components/CommunityPackageCard.vue | 9 +- packages/editor-ui/src/components/Modal.vue | 65 ++- .../editor-ui/src/components/ModalDrawer.vue | 22 +- .../editor-ui/src/components/ModalRoot.vue | 38 +- packages/editor-ui/src/constants.ts | 6 + .../editor-ui/src/modules/communityNodes.ts | 87 ---- .../editor-ui/src/stores/communityNodes.ts | 18 +- packages/editor-ui/src/stores/n8nRootStore.ts | 53 ++- packages/editor-ui/src/stores/ui.ts | 411 ++++++++++++++++++ .../src/views/SettingsCommunityNodesView.vue | 15 +- 12 files changed, 635 insertions(+), 193 deletions(-) delete mode 100644 packages/editor-ui/src/modules/communityNodes.ts create mode 100644 packages/editor-ui/src/stores/ui.ts diff --git a/packages/editor-ui/src/Interface.ts b/packages/editor-ui/src/Interface.ts index f9baa2be45b11..971fe769d398e 100644 --- a/packages/editor-ui/src/Interface.ts +++ b/packages/editor-ui/src/Interface.ts @@ -1,3 +1,4 @@ +import { IMenuItem } from 'n8n-design-system'; import { GenericValue, IConnections, @@ -851,6 +852,53 @@ export interface INodeMetadata { parametersLastUpdatedAt?: number; } +// TODO: +// - Make this the only root state once migration is done +// - Remove commented out props +export interface rootStatePinia { + // --> UI store + // activeExecutions: IExecutionsCurrentSummaryExtended[]; + // activeWorkflows: string[]; + // activeActions: string[]; + // activeCredentialType: string | null; + // activeNode: string | null; + baseUrl: string; + defaultLocale: string; + endpointWebhook: string; + endpointWebhookTest: string; + executionId: string | null; + executingNode: string | null; + executionWaitingForWebhook: boolean; + pushConnectionActive: boolean; + saveDataErrorExecution: string; + saveDataSuccessExecution: string; + saveManualExecutions: boolean; + timezone: string; + // stateIsDirty: boolean; // --> UI Store + executionTimeout: number; + maxExecutionTimeout: number; + versionCli: string; + oauthCallbackUrls: object; + n8nMetadata: object; + // workflowExecutionData: IExecutionResponse | null; // --> WF store + // lastSelectedNode: string | null; // --> UI Store + // lastSelectedNodeOutputIndex: number | null; // --> UI Store + // nodeViewOffsetPosition: XYPosition; // --> UI Store + // nodeViewMoveInProgress: boolean; // --> UI Store + // selectedNodes: INodeUi[]; // --> UI Store + sessionId: string; + urlBaseWebhook: string; + // workflow: IWorkflowDb; // --> WF Store + // sidebarMenuItems: IMenuItem[]; // --> UI Store + instanceId: string; + nodeMetadata: nodeMetadataMap; + isNpmAvailable: boolean; +} + +export interface nodeMetadataMap { + [nodeName: string]: INodeMetadata; +} + export interface IRootState { activeExecutions: IExecutionsCurrentSummaryExtended[]; activeWorkflows: string[]; @@ -888,12 +936,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; } @@ -983,6 +1031,52 @@ export interface IUiState { fakeDoorFeatures: IFakeDoor[]; } +export interface uiState { + activeExecutions: IExecutionsCurrentSummaryExtended[]; + activeWorkflows: string[]; + activeActions: string[]; + activeCredentialType: string | null; + activeNode: string | null; + sidebarMenuCollapsed: boolean; + modalStack: string[]; + modals: { + [key: string]: IModalState; + }; + isPageLoading: boolean; + currentView: string; + ndv: { + sessionId: string; + input: { + displayMode: IRunDataDisplayMode; + }; + output: { + displayMode: IRunDataDisplayMode; + editMode: { + enabled: boolean; + value: string; + }; + }; + focusedMappableInput: string; + mappingTelemetry: {[key: string]: string | number | boolean}; + }; + 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[]; +} + export type ILogLevel = 'info' | 'debug' | 'warn' | 'error' | 'verbose'; export type IFakeDoor = { @@ -1066,9 +1160,9 @@ export interface IWorkflowsMap { export interface IWorkflowsState {} -export interface ICommunityNodesState { +export interface communityNodesState { availablePackageCount: number; - installedPackages: ICommunityPackageMap; + installedPackages: communityPackageMap; } export interface IRestApiContext { diff --git a/packages/editor-ui/src/api/workflow-webhooks.ts b/packages/editor-ui/src/api/workflow-webhooks.ts index 04176dcf5baf7..88c0e6edf620c 100644 --- a/packages/editor-ui/src/api/workflow-webhooks.ts +++ b/packages/editor-ui/src/api/workflow-webhooks.ts @@ -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 { +export async function submitEmailOnSignup(instanceId: string, currentUer: IUser, email: string | undefined, agree: boolean): Promise { return await post( N8N_API_BASE_URL, CONTACT_EMAIL_SUBMISSION_ENDPOINT, diff --git a/packages/editor-ui/src/components/CommunityPackageCard.vue b/packages/editor-ui/src/components/CommunityPackageCard.vue index 2ff205016bbf8..6a4ca080e826c 100644 --- a/packages/editor-ui/src/components/CommunityPackageCard.vue +++ b/packages/editor-ui/src/components/CommunityPackageCard.vue @@ -55,7 +55,9 @@ diff --git a/packages/editor-ui/src/components/ModalDrawer.vue b/packages/editor-ui/src/components/ModalDrawer.vue index 3854b72f9515b..af031bf15bc96 100644 --- a/packages/editor-ui/src/components/ModalDrawer.vue +++ b/packages/editor-ui/src/components/ModalDrawer.vue @@ -1,7 +1,7 @@ diff --git a/packages/editor-ui/src/components/mixins/mouseSelect.ts b/packages/editor-ui/src/components/mixins/mouseSelect.ts index dcee43e1179fc..75586b1d5fa9c 100644 --- a/packages/editor-ui/src/components/mixins/mouseSelect.ts +++ b/packages/editor-ui/src/components/mixins/mouseSelect.ts @@ -5,6 +5,8 @@ import mixins from 'vue-typed-mixins'; import { deviceSupportHelpers } from '@/components/mixins/deviceSupportHelpers'; import { getMousePosition, getRelativePosition, HEADER_HEIGHT, SIDEBAR_WIDTH, SIDEBAR_WIDTH_EXPANDED } from '@/views/canvasHelpers'; import { VIEWS } from '@/constants'; +import { mapStores } from 'pinia'; +import { useUIStore } from '@/stores/ui'; export const mouseSelect = mixins( deviceSupportHelpers, @@ -19,6 +21,7 @@ export const mouseSelect = mixins( this.createSelectBox(); }, computed: { + ...mapStores(useUIStore), isDemo (): boolean { return this.$route.name === VIEWS.DEMO; }, @@ -49,10 +52,10 @@ export const mouseSelect = mixins( }, getMousePositionWithinNodeView (event: MouseEvent | TouchEvent): XYPosition { const [x, y] = getMousePosition(event); - const sidebarOffset = this.isDemo ? 0 : this.$store.getters['ui/sidebarMenuCollapsed'] ? SIDEBAR_WIDTH : SIDEBAR_WIDTH_EXPANDED; + const sidebarOffset = this.isDemo ? 0 : this.uiStore.sidebarMenuCollapsed ? SIDEBAR_WIDTH : SIDEBAR_WIDTH_EXPANDED; const headerOffset = this.isDemo ? 0 : HEADER_HEIGHT; // @ts-ignore - return getRelativePosition(x - sidebarOffset, y - headerOffset, this.nodeViewScale, this.$store.getters.getNodeViewOffsetPosition); + return getRelativePosition(x - sidebarOffset, y - headerOffset, this.nodeViewScale, this.uiStore.nodeViewOffsetPosition); }, showSelectBox (event: MouseEvent) { const [x, y] = this.getMousePositionWithinNodeView(event); @@ -125,7 +128,7 @@ export const mouseSelect = mixins( return; } - if (this.$store.getters.isActionActive('dragActive')) { + if (this.uiStore.isActionActive('dragActive')) { // If a node does currently get dragged we do not activate the selection return; } @@ -161,7 +164,7 @@ export const mouseSelect = mixins( }); if (selectedNodes.length === 1) { - this.$store.commit('setLastSelectedNode', selectedNodes[0].name); + this.uiStore.lastSelectedNode = selectedNodes[0].name; } this.hideSelectBox(); @@ -178,21 +181,21 @@ export const mouseSelect = mixins( this.updateSelectBox(e); }, nodeDeselected (node: INodeUi) { - this.$store.commit('removeNodeFromSelection', node); + this.uiStore.removeNodeFromSelection(node); // @ts-ignore this.instance.removeFromDragSelection(node.id); }, nodeSelected (node: INodeUi) { - this.$store.commit('addSelectedNode', node); + this.uiStore.addSelectedNode(node); // @ts-ignore this.instance.addToDragSelection(node.id); }, deselectAllNodes () { // @ts-ignore this.instance.clearDragSelection(); - this.$store.commit('resetSelectedNodes'); - this.$store.commit('setLastSelectedNode', null); - this.$store.commit('setLastSelectedNodeOutputIndex', null); + this.uiStore.resetSelectedNodes(); + this.uiStore.lastSelectedNode = null; + this.uiStore.lastSelectedNodeOutputIndex = null; // @ts-ignore this.lastSelectedConnection = null; // @ts-ignore diff --git a/packages/editor-ui/src/components/mixins/moveNodeWorkflow.ts b/packages/editor-ui/src/components/mixins/moveNodeWorkflow.ts index dacf5884fdf84..0884215d94049 100644 --- a/packages/editor-ui/src/components/mixins/moveNodeWorkflow.ts +++ b/packages/editor-ui/src/components/mixins/moveNodeWorkflow.ts @@ -3,6 +3,8 @@ import mixins from 'vue-typed-mixins'; import normalizeWheel from 'normalize-wheel'; import { deviceSupportHelpers } from '@/components/mixins/deviceSupportHelpers'; import { getMousePosition } from '@/views/canvasHelpers'; +import { mapStores } from 'pinia'; +import { useUIStore } from '@/stores/ui'; export const moveNodeWorkflow = mixins( deviceSupportHelpers, @@ -12,16 +14,18 @@ export const moveNodeWorkflow = mixins( moveLastPosition: [0, 0], }; }, - + computed: { + ...mapStores(useUIStore), + }, methods: { moveWorkflow (e: MouseEvent) { - const offsetPosition = this.$store.getters.getNodeViewOffsetPosition; + const offsetPosition = this.uiStore.nodeViewOffsetPosition; const [x, y] = getMousePosition(e); const nodeViewOffsetPositionX = offsetPosition[0] + (x - this.moveLastPosition[0]); const nodeViewOffsetPositionY = offsetPosition[1] + (y - this.moveLastPosition[1]); - this.$store.commit('setNodeViewOffsetPosition', {newOffset: [nodeViewOffsetPositionX, nodeViewOffsetPositionY]}); + this.uiStore.nodeViewOffsetPosition = [nodeViewOffsetPositionX, nodeViewOffsetPositionY]; // Update the last position this.moveLastPosition[0] = x; @@ -34,12 +38,12 @@ export const moveNodeWorkflow = mixins( return; } - if (this.$store.getters.isActionActive('dragActive')) { + if (this.uiStore.isActionActive('dragActive')) { // If a node does currently get dragged we do not activate the selection return; } - this.$store.commit('setNodeViewMoveInProgress', true); + this.uiStore.nodeViewMoveInProgress = true; const [x, y] = getMousePosition(e); @@ -50,7 +54,7 @@ export const moveNodeWorkflow = mixins( this.$el.addEventListener('mousemove', this.mouseMoveNodeWorkflow); }, mouseUpMoveWorkflow (e: MouseEvent) { - if (this.$store.getters.isNodeViewMoveInProgress === false) { + if (this.uiStore.nodeViewMoveInProgress === false) { // If it is not active return directly. // Else normal node dragging will not work. return; @@ -59,7 +63,7 @@ export const moveNodeWorkflow = mixins( // @ts-ignore this.$el.removeEventListener('mousemove', this.mouseMoveNodeWorkflow); - this.$store.commit('setNodeViewMoveInProgress', false); + this.uiStore.nodeViewMoveInProgress = false; // Nothing else to do. Simply leave the node view at the current offset }, @@ -69,7 +73,7 @@ export const moveNodeWorkflow = mixins( return; } - if (this.$store.getters.isActionActive('dragActive')) { + if (this.uiStore.isActionActive('dragActive')) { return; } @@ -86,10 +90,10 @@ export const moveNodeWorkflow = mixins( }, wheelMoveWorkflow (e: WheelEvent) { const normalized = normalizeWheel(e); - const offsetPosition = this.$store.getters.getNodeViewOffsetPosition; + const offsetPosition = this.uiStore.nodeViewOffsetPosition; const nodeViewOffsetPositionX = offsetPosition[0] - (e.shiftKey ? normalized.pixelY : normalized.pixelX); const nodeViewOffsetPositionY = offsetPosition[1] - (e.shiftKey ? normalized.pixelX : normalized.pixelY); - this.$store.commit('setNodeViewOffsetPosition', {newOffset: [nodeViewOffsetPositionX, nodeViewOffsetPositionY]}); + this.uiStore.nodeViewOffsetPosition = [nodeViewOffsetPositionX, nodeViewOffsetPositionY]; }, }, }); diff --git a/packages/editor-ui/src/components/mixins/newVersions.ts b/packages/editor-ui/src/components/mixins/newVersions.ts index dbb721a6320e5..5df15a668a184 100644 --- a/packages/editor-ui/src/components/mixins/newVersions.ts +++ b/packages/editor-ui/src/components/mixins/newVersions.ts @@ -4,10 +4,15 @@ import { IVersion, } from '../../Interface'; import { VERSIONS_MODAL_KEY } from '@/constants'; +import { mapStores } from 'pinia'; +import { useUIStore } from '@/stores/ui'; export const newVersions = mixins( showMessage, ).extend({ + computed: { + ...mapStores(useUIStore), + }, methods: { async checkForNewVersions() { const enabled = this.$store.getters['versions/areNotificationsEnabled']; @@ -31,7 +36,7 @@ export const newVersions = mixins( title: 'Critical update available', message, onClick: () => { - this.$store.dispatch('ui/openModal', VERSIONS_MODAL_KEY); + this.uiStore.openModal(VERSIONS_MODAL_KEY); }, closeOnClick: true, customClass: 'clickable', diff --git a/packages/editor-ui/src/components/mixins/nodeBase.ts b/packages/editor-ui/src/components/mixins/nodeBase.ts index 22ffb78511cc1..6fdc443833b31 100644 --- a/packages/editor-ui/src/components/mixins/nodeBase.ts +++ b/packages/editor-ui/src/components/mixins/nodeBase.ts @@ -11,6 +11,8 @@ import { INodeTypeDescription, } from 'n8n-workflow'; import { getStyleTokenValue } from '../helpers'; +import { mapStores } from 'pinia'; +import { useUIStore } from '@/stores/ui'; export const nodeBase = mixins( deviceSupportHelpers, @@ -22,6 +24,7 @@ export const nodeBase = mixins( } }, computed: { + ...mapStores(useUIStore), data (): INodeUi { return this.$store.getters.getNodeByName(this.name); }, @@ -234,7 +237,7 @@ export const nodeBase = mixins( // @ts-ignore this.dragging = true; - const isSelected = this.$store.getters.isNodeSelected(this.data.name); + const isSelected = this.uiStore.isNodeSelected(this.data.name); const nodeName = this.data.name; if (this.data.type === STICKY_NODE_TYPE && !isSelected) { setTimeout(() => { @@ -247,17 +250,17 @@ export const nodeBase = mixins( // undefined. So check if the currently dragged node is selected and if not clear // the drag-selection. this.instance.clearDragSelection(); - this.$store.commit('resetSelectedNodes'); + this.uiStore.resetSelectedNodes(); } - this.$store.commit('addActiveAction', 'dragActive'); + this.uiStore.addActiveAction('dragActive'); return true; }, stop: (params: { e: MouseEvent }) => { // @ts-ignore this.dragging = false; - if (this.$store.getters.isActionActive('dragActive')) { - const moveNodes = this.$store.getters.getSelectedNodes.slice(); + if (this.uiStore.isActionActive('dragActive')) { + const moveNodes = this.uiStore.getSelectedNodes.slice(); const selectedNodeNames = moveNodes.map((node: INodeUi) => node.name); if (!selectedNodeNames.includes(this.data.name)) { // If the current node is not in selected add it to the nodes which @@ -311,8 +314,8 @@ export const nodeBase = mixins( }, touchEnd(e: MouseEvent) { if (this.isTouchDevice) { - if (this.$store.getters.isActionActive('dragActive')) { - this.$store.commit('removeActiveAction', 'dragActive'); + if (this.uiStore.isActionActive('dragActive')) { + this.uiStore.removeActiveAction('dragActive'); } } }, @@ -326,14 +329,14 @@ export const nodeBase = mixins( } if (!this.isTouchDevice) { - if (this.$store.getters.isActionActive('dragActive')) { - this.$store.commit('removeActiveAction', 'dragActive'); + if (this.uiStore.isActionActive('dragActive')) { + this.uiStore.removeActiveAction('dragActive'); } else { if (!this.isCtrlKeyPressed(e)) { this.$emit('deselectAllNodes'); } - if (this.$store.getters.isNodeSelected(this.data.name)) { + if (this.uiStore.isNodeSelected(this.data.name)) { this.$emit('deselectNode', this.name); } else { this.$emit('nodeSelected', this.name); diff --git a/packages/editor-ui/src/components/mixins/pushConnection.ts b/packages/editor-ui/src/components/mixins/pushConnection.ts index 866a387673748..6bf1cda408224 100644 --- a/packages/editor-ui/src/components/mixins/pushConnection.ts +++ b/packages/editor-ui/src/components/mixins/pushConnection.ts @@ -22,6 +22,8 @@ import mixins from 'vue-typed-mixins'; import { WORKFLOW_SETTINGS_MODAL_KEY } from '@/constants'; import { getTriggerNodeServiceName } from '../helpers'; import { codeNodeEditorEventBus } from '@/event-bus/code-node-editor-event-bus'; +import { mapStores } from 'pinia'; +import { useUIStore } from '@/stores/ui'; export const pushConnection = mixins( externalHooks, @@ -40,6 +42,7 @@ export const pushConnection = mixins( }; }, computed: { + ...mapStores(useUIStore), sessionId (): string { return this.$store.getters.sessionId; }, @@ -178,7 +181,7 @@ export const pushConnection = mixins( } if (receivedData.type === 'nodeExecuteAfter' || receivedData.type === 'nodeExecuteBefore') { - if (this.$store.getters.isActionActive('workflowRunning') === false) { + if (!this.uiStore.isActionActive('workflowRunning')) { // No workflow is running so ignore the messages return false; } @@ -197,9 +200,9 @@ export const pushConnection = mixins( // The workflow finished executing const pushData = receivedData.data; - this.$store.commit('finishActiveExecution', pushData); + this.uiStore.finishActiveExecution(pushData); - if (this.$store.getters.isActionActive('workflowRunning') === false) { + if (!this.uiStore.isActionActive('workflowRunning')) { // No workflow is running so ignore the messages return false; } @@ -239,7 +242,7 @@ export const pushConnection = mixins( if (!isSavingExecutions) { this.$root.$emit('registerGlobalLinkAction', 'open-settings', async () => { if (this.$store.getters.isNewWorkflow) await this.saveAsNewWorkflow(); - this.$store.dispatch('ui/openModal', WORKFLOW_SETTINGS_MODAL_KEY); + this.uiStore.openModal(WORKFLOW_SETTINGS_MODAL_KEY); }); action = 'Turn on saving manual executions and run again to see what happened after this node.'; @@ -365,7 +368,7 @@ export const pushConnection = mixins( this.$store.commit('setExecutingNode', null); this.$store.commit('setWorkflowExecutionData', runDataExecuted); - this.$store.commit('removeActiveAction', 'workflowRunning'); + this.uiStore.removeActiveAction('workflowRunning'); // Set the node execution issues on all the nodes which produced an error so that // it can be displayed in the node-view @@ -397,7 +400,7 @@ export const pushConnection = mixins( workflowName: pushData.workflowName, }; - this.$store.commit('addActiveExecution', executionData); + this.uiStore.addActiveExecution(executionData); } else if (receivedData.type === 'nodeExecuteAfter') { // A node finished to execute. Add its data const pushData = receivedData.data; @@ -412,7 +415,7 @@ export const pushConnection = mixins( if (pushData.workflowId === this.$store.getters.workflowId) { this.$store.commit('setExecutionWaitingForWebhook', false); - this.$store.commit('removeActiveAction', 'workflowRunning'); + this.uiStore.removeActiveAction('workflowRunning'); } } else if (receivedData.type === 'testWebhookReceived') { // A test-webhook did get called diff --git a/packages/editor-ui/src/components/mixins/workflowActivate.ts b/packages/editor-ui/src/components/mixins/workflowActivate.ts index 783be2a84fc3d..bdb10d541eca6 100644 --- a/packages/editor-ui/src/components/mixins/workflowActivate.ts +++ b/packages/editor-ui/src/components/mixins/workflowActivate.ts @@ -5,6 +5,8 @@ import { showMessage } from '@/components/mixins/showMessage'; import mixins from 'vue-typed-mixins'; import { LOCAL_STORAGE_ACTIVATION_FLAG, PLACEHOLDER_EMPTY_WORKFLOW_ID, WORKFLOW_ACTIVE_MODAL_KEY } from '@/constants'; +import { mapStores } from 'pinia'; +import { useUIStore } from '@/stores/ui'; export const workflowActivate = mixins( externalHooks, @@ -17,6 +19,9 @@ export const workflowActivate = mixins( updatingWorkflowActivation: false, }; }, + computed: { + ...mapStores(useUIStore), + }, methods: { async activateCurrentWorkflow(telemetrySource?: string) { const workflowId = this.$store.getters.workflowId; @@ -37,7 +42,7 @@ export const workflowActivate = mixins( } const isCurrentWorkflow = currWorkflowId === this.$store.getters['workflowId']; - const activeWorkflows = this.$store.getters.getActiveWorkflows; + const activeWorkflows = this.uiStore.activeWorkflows; const isWorkflowActive = activeWorkflows.includes(currWorkflowId); const telemetryPayload = { @@ -93,7 +98,7 @@ export const workflowActivate = mixins( if (isCurrentWorkflow) { if (newActiveState && window.localStorage.getItem(LOCAL_STORAGE_ACTIVATION_FLAG) !== 'true') { - this.$store.dispatch('ui/openModal', WORKFLOW_ACTIVE_MODAL_KEY); + this.uiStore.openModal(WORKFLOW_ACTIVE_MODAL_KEY); } else { this.$store.dispatch('settings/fetchPromptsData'); diff --git a/packages/editor-ui/src/components/mixins/workflowHelpers.ts b/packages/editor-ui/src/components/mixins/workflowHelpers.ts index 15094047aacb5..665d75d75575f 100644 --- a/packages/editor-ui/src/components/mixins/workflowHelpers.ts +++ b/packages/editor-ui/src/components/mixins/workflowHelpers.ts @@ -57,6 +57,8 @@ import { isEqual } from 'lodash'; import mixins from 'vue-typed-mixins'; import { v4 as uuid } from 'uuid'; import { getSourceItems } from '@/pairedItemUtils'; +import { mapStores } from 'pinia'; +import { useUIStore } from '@/stores/ui'; let cachedWorkflowKey: string | null = ''; let cachedWorkflow: Workflow | null = null; @@ -68,6 +70,9 @@ export const workflowHelpers = mixins( showMessage, ) .extend({ + computed: { + ...mapStores(useUIStore), + }, methods: { executeData(parentNode: string[], currentNode: string, inputName: string, runIndex: number): IExecuteData { const executeData = { @@ -674,9 +679,9 @@ export const workflowHelpers = mixins( } if (workflow.active) { - this.$store.commit('setWorkflowActive', workflowId); + this.uiStore.setWorkflowActive(workflowId); } else { - this.$store.commit('setWorkflowInactive', workflowId); + this.uiStore.setWorkflowInactive(workflowId); } }, @@ -688,7 +693,7 @@ export const workflowHelpers = mixins( // Workflow exists already so update it try { - this.$store.commit('addActiveAction', 'workflowSaving'); + this.uiStore.addActiveAction('workflowSaving'); const workflowDataRequest: IWorkflowDataUpdate = await this.getWorkflowDataToSave(); @@ -713,12 +718,12 @@ export const workflowHelpers = mixins( } this.$store.commit('setStateDirty', false); - this.$store.commit('removeActiveAction', 'workflowSaving'); + this.uiStore.removeActiveAction('workflowSaving'); this.$externalHooks().run('workflow.afterUpdate', { workflowData }); return true; } catch (error) { - this.$store.commit('removeActiveAction', 'workflowSaving'); + this.uiStore.removeActiveAction('workflowSaving'); this.$showMessage({ title: this.$locale.baseText('workflowHelpers.showMessage.title'), @@ -732,7 +737,7 @@ export const workflowHelpers = mixins( async saveAsNewWorkflow ({ name, tags, resetWebhookUrls, resetNodeIds, openInNewWindow, data }: {name?: string, tags?: string[], resetWebhookUrls?: boolean, openInNewWindow?: boolean, resetNodeIds?: boolean, data?: IWorkflowDataUpdate} = {}, redirect = true): Promise { try { - this.$store.commit('addActiveAction', 'workflowSaving'); + this.uiStore.addActiveAction('workflowSaving'); const workflowDataRequest: IWorkflowDataUpdate = data || await this.getWorkflowDataToSave(); // make sure that the new ones are not active @@ -771,7 +776,7 @@ export const workflowHelpers = mixins( if (openInNewWindow) { const routeData = this.$router.resolve({name: VIEWS.WORKFLOW, params: {name: workflowData.id}}); window.open(routeData.href, '_blank'); - this.$store.commit('removeActiveAction', 'workflowSaving'); + this.uiStore.removeActiveAction('workflowSaving'); return true; } @@ -809,13 +814,13 @@ export const workflowHelpers = mixins( }); } - this.$store.commit('removeActiveAction', 'workflowSaving'); + this.uiStore.removeActiveAction('workflowSaving'); this.$store.commit('setStateDirty', false); this.$externalHooks().run('workflow.afterUpdate', { workflowData }); return true; } catch (e) { - this.$store.commit('removeActiveAction', 'workflowSaving'); + this.uiStore.removeActiveAction('workflowSaving'); this.$showMessage({ title: this.$locale.baseText('workflowHelpers.showMessage.title'), diff --git a/packages/editor-ui/src/components/mixins/workflowRun.ts b/packages/editor-ui/src/components/mixins/workflowRun.ts index 35c7317af2a36..64c882957a9db 100644 --- a/packages/editor-ui/src/components/mixins/workflowRun.ts +++ b/packages/editor-ui/src/components/mixins/workflowRun.ts @@ -19,6 +19,8 @@ import { showMessage } from '@/components/mixins/showMessage'; import mixins from 'vue-typed-mixins'; import { titleChange } from './titleChange'; +import { mapStores } from 'pinia'; +import { useUIStore } from '@/stores/ui'; export const workflowRun = mixins( externalHooks, @@ -27,6 +29,9 @@ export const workflowRun = mixins( showMessage, titleChange, ).extend({ + computed: { + ...mapStores(useUIStore), + }, methods: { // Starts to executes a workflow on server. async runWorkflowApi (runData: IStartRunData): Promise { @@ -40,14 +45,14 @@ export const workflowRun = mixins( this.$store.commit('setSubworkflowExecutionError', null); - this.$store.commit('addActiveAction', 'workflowRunning'); + this.uiStore.addActiveAction('workflowRunning'); let response: IExecutionPushResponse; try { response = await this.restApi().runWorkflow(runData); } catch (error) { - this.$store.commit('removeActiveAction', 'workflowRunning'); + this.uiStore.removeActiveAction('workflowRunning'); throw error; } @@ -64,7 +69,7 @@ export const workflowRun = mixins( async runWorkflow (nodeName?: string, source?: string): Promise { const workflow = this.getCurrentWorkflow(); - if (this.$store.getters.isActionActive('workflowRunning') === true) { + if (this.uiStore.isActionActive('workflowRunning')) { return; } diff --git a/packages/editor-ui/src/modules/settings.ts b/packages/editor-ui/src/modules/settings.ts index c3af2c413c9f2..8d78c63af9a13 100644 --- a/packages/editor-ui/src/modules/settings.ts +++ b/packages/editor-ui/src/modules/settings.ts @@ -13,6 +13,7 @@ import {CONTACT_PROMPT_MODAL_KEY, EnterpriseEditionFeature, VALUE_SURVEY_MODAL_K import { ITelemetrySettings } from 'n8n-workflow'; import { testHealthEndpoint } from '@/api/templates'; import {createApiKey, deleteApiKey, getApiKey } from "@/api/api-keys"; +import { useUIStore } from '@/stores/ui'; const module: Module = { namespaced: true, @@ -171,14 +172,15 @@ const module: Module = { } try { + const uiStore = useUIStore(); const instanceId = context.state.settings.instanceId; const userId = context.rootGetters['users/currentUserId']; const promptsData: IN8nPrompts = await getPromptsData(instanceId, userId); if (promptsData && promptsData.showContactPrompt) { - context.commit('ui/openModal', CONTACT_PROMPT_MODAL_KEY, {root: true}); + uiStore.openModal(CONTACT_PROMPT_MODAL_KEY); } else if (promptsData && promptsData.showValueSurvey) { - context.commit('ui/openModal', VALUE_SURVEY_MODAL_KEY, {root: true}); + uiStore.openModal(VALUE_SURVEY_MODAL_KEY); } context.commit('setPromptsData', promptsData); diff --git a/packages/editor-ui/src/modules/users.ts b/packages/editor-ui/src/modules/users.ts index d1faa6f290458..90b82ef4c8e65 100644 --- a/packages/editor-ui/src/modules/users.ts +++ b/packages/editor-ui/src/modules/users.ts @@ -30,6 +30,7 @@ import { IUsersState, } from '../Interface'; import { getPersonalizedNodeTypes, isAuthorized, PERMISSIONS, ROLE } from './userHelpers'; +import { useUIStore } from '@/stores/ui'; const isDefaultUser = (user: IUserResponse | null) => Boolean(user && user.isPending && user.globalRole && user.globalRole.name === ROLE.Owner); @@ -212,7 +213,8 @@ const module: Module = { const surveyEnabled = context.rootGetters['settings/isPersonalizationSurveyEnabled'] as boolean; const currentUser = context.getters.currentUser as IUser | null; if (surveyEnabled && currentUser && !currentUser.personalizationAnswers) { - context.dispatch('ui/openModal', PERSONALIZATION_MODAL_KEY, {root: true}); + const uiStore = useUIStore(); + uiStore.openModal(PERSONALIZATION_MODAL_KEY); } }, async skipOwnerSetup(context: ActionContext) { diff --git a/packages/editor-ui/src/plugins/i18n/index.ts b/packages/editor-ui/src/plugins/i18n/index.ts index 6f3507baf8c05..6131aaaf52e95 100644 --- a/packages/editor-ui/src/plugins/i18n/index.ts +++ b/packages/editor-ui/src/plugins/i18n/index.ts @@ -15,6 +15,7 @@ import { } from 'n8n-design-system'; import englishBaseText from './locales/en.json'; +import { useUIStore } from "@/stores/ui"; Vue.use(VueI18n); locale.use('en'); @@ -95,7 +96,8 @@ export class I18nClass { * Namespace for methods to render text in the credentials details modal. */ credText () { - const credentialType = this.$store.getters.activeCredentialType; + const uiStore = useUIStore(); + const credentialType = uiStore.activeCredentialType; const credentialPrefix = `n8n-nodes-base.credentials.${credentialType}`; const context = this; diff --git a/packages/editor-ui/src/store.ts b/packages/editor-ui/src/store.ts index b709e5f639029..accb10bbd1550 100644 --- a/packages/editor-ui/src/store.ts +++ b/packages/editor-ui/src/store.ts @@ -129,54 +129,54 @@ export const store = new Vuex.Store({ state, mutations: { // Active Actions - addActiveAction(state, action: string) { - if (!state.activeActions.includes(action)) { - state.activeActions.push(action); - } - }, - - removeActiveAction(state, action: string) { - const actionIndex = state.activeActions.indexOf(action); - if (actionIndex !== -1) { - state.activeActions.splice(actionIndex, 1); - } - }, + // addActiveAction(state, action: string) { + // if (!state.activeActions.includes(action)) { + // state.activeActions.push(action); + // } + // }, + + // removeActiveAction(state, action: string) { + // const actionIndex = state.activeActions.indexOf(action); + // if (actionIndex !== -1) { + // state.activeActions.splice(actionIndex, 1); + // } + // }, // Active Executions - addActiveExecution(state, newActiveExecution: IExecutionsCurrentSummaryExtended) { - // Check if the execution exists already - const activeExecution = state.activeExecutions.find(execution => { - return execution.id === newActiveExecution.id; - }); - - if (activeExecution !== undefined) { - // Exists already so no need to add it again - if (activeExecution.workflowName === undefined) { - activeExecution.workflowName = newActiveExecution.workflowName; - } - return; - } - - state.activeExecutions.unshift(newActiveExecution); - }, - finishActiveExecution(state, finishedActiveExecution: IPushDataExecutionFinished) { - // Find the execution to set to finished - const activeExecution = state.activeExecutions.find(execution => { - return execution.id === finishedActiveExecution.executionId; - }); - - if (activeExecution === undefined) { - // The execution could not be found - return; - } - - if (finishedActiveExecution.executionId !== undefined) { - Vue.set(activeExecution, 'id', finishedActiveExecution.executionId); - } - - Vue.set(activeExecution, 'finished', finishedActiveExecution.data.finished); - Vue.set(activeExecution, 'stoppedAt', finishedActiveExecution.data.stoppedAt); - }, + // addActiveExecution(state, newActiveExecution: IExecutionsCurrentSummaryExtended) { + // // Check if the execution exists already + // const activeExecution = state.activeExecutions.find(execution => { + // return execution.id === newActiveExecution.id; + // }); + + // if (activeExecution !== undefined) { + // // Exists already so no need to add it again + // if (activeExecution.workflowName === undefined) { + // activeExecution.workflowName = newActiveExecution.workflowName; + // } + // return; + // } + + // state.activeExecutions.unshift(newActiveExecution); + // }, + // finishActiveExecution(state, finishedActiveExecution: IPushDataExecutionFinished) { + // // Find the execution to set to finished + // const activeExecution = state.activeExecutions.find(execution => { + // return execution.id === finishedActiveExecution.executionId; + // }); + + // if (activeExecution === undefined) { + // // The execution could not be found + // return; + // } + + // if (finishedActiveExecution.executionId !== undefined) { + // Vue.set(activeExecution, 'id', finishedActiveExecution.executionId); + // } + + // Vue.set(activeExecution, 'finished', finishedActiveExecution.data.finished); + // Vue.set(activeExecution, 'stoppedAt', finishedActiveExecution.data.stoppedAt); + // }, setSubworkflowExecutionError(state, subworkflowExecutionError: Error | null) { state.subworkflowExecutionError = subworkflowExecutionError; }, @@ -204,9 +204,9 @@ export const store = new Vuex.Store({ }, // Active Workflows - setActiveWorkflows(state, newActiveWorkflows: string[]) { - state.activeWorkflows = newActiveWorkflows; - }, + // setActiveWorkflows(state, newActiveWorkflows: string[]) { + // state.activeWorkflows = newActiveWorkflows; + // }, setWorkflowActive(state, workflowId: string) { state.stateIsDirty = false; const index = state.activeWorkflows.indexOf(workflowId); @@ -235,21 +235,21 @@ export const store = new Vuex.Store({ }, // Selected Nodes - addSelectedNode(state, node: INodeUi) { - state.selectedNodes.push(node); - }, - removeNodeFromSelection(state, node: INodeUi) { - let index; - for (index in state.selectedNodes) { - if (state.selectedNodes[index].name === node.name) { - state.selectedNodes.splice(parseInt(index, 10), 1); - break; - } - } - }, - resetSelectedNodes(state) { - Vue.set(state, 'selectedNodes', []); - }, + // addSelectedNode(state, node: INodeUi) { + // state.selectedNodes.push(node); + // }, + // removeNodeFromSelection(state, node: INodeUi) { + // let index; + // for (index in state.selectedNodes) { + // if (state.selectedNodes[index].name === node.name) { + // state.selectedNodes.splice(parseInt(index, 10), 1); + // break; + // } + // } + // }, + // resetSelectedNodes(state) { + // Vue.set(state, 'selectedNodes', []); + // }, // Pin data pinData(state, payload: { node: INodeUi, data: INodeExecutionData[] }) { @@ -395,6 +395,7 @@ export const store = new Vuex.Store({ } }, + // THIS GOES TO WORKFLOW OR NDV STORE... renameNodeSelectedAndExecution(state, nameData) { state.stateIsDirty = true; // If node has any WorkflowResultData rename also that one that the data @@ -580,12 +581,12 @@ export const store = new Vuex.Store({ }, // Node-View - setNodeViewMoveInProgress(state, value: boolean) { - state.nodeViewMoveInProgress = value; - }, - setNodeViewOffsetPosition(state, data) { - state.nodeViewOffsetPosition = data.newOffset; - }, + // setNodeViewMoveInProgress(state, value: boolean) { + // state.nodeViewMoveInProgress = value; + // }, + // setNodeViewOffsetPosition(state, data) { + // state.nodeViewOffsetPosition = data.newOffset; + // }, // Active Execution setExecutingNode(state, executingNode: string) { @@ -655,9 +656,9 @@ export const store = new Vuex.Store({ setIsNpmAvailable(state, isNpmAvailable: boolean) { Vue.set(state, 'isNpmAvailable', isNpmAvailable); }, - setActiveCredentialType(state, activeCredentialType: string) { - state.activeCredentialType = activeCredentialType; - }, + // setActiveCredentialType(state, activeCredentialType: string) { + // state.activeCredentialType = activeCredentialType; + // }, setLastSelectedNode(state, nodeName: string) { state.lastSelectedNode = nodeName; }, @@ -752,17 +753,17 @@ export const store = new Vuex.Store({ executedNode: (state): string | undefined => { return state.workflowExecutionData ? state.workflowExecutionData.executedNode : undefined; }, - activeCredentialType: (state): string | null => { - return state.activeCredentialType; - }, + // activeCredentialType: (state): string | null => { + // return state.activeCredentialType; + // }, subworkflowExecutionError: (state): Error | null => { return state.subworkflowExecutionError; }, - isActionActive: (state) => (action: string): boolean => { - return state.activeActions.includes(action); - }, + // isActionActive: (state) => (action: string): boolean => { + // return state.activeActions.includes(action); + // }, isNewWorkflow: (state) => { return state.workflow.id === PLACEHOLDER_EMPTY_WORKFLOW_ID; @@ -864,9 +865,9 @@ export const store = new Vuex.Store({ }, // Active Workflows - getActiveWorkflows: (state): string[] => { - return state.activeWorkflows; - }, + // getActiveWorkflows: (state): string[] => { + // return state.activeWorkflows; + // }, workflowTriggerNodes: (state, getters) => { return state.workflow.nodes.filter(node => { @@ -875,35 +876,35 @@ export const store = new Vuex.Store({ }); }, - getNodeViewOffsetPosition: (state): XYPosition => { - return state.nodeViewOffsetPosition; - }, - isNodeViewMoveInProgress: (state): boolean => { - return state.nodeViewMoveInProgress; - }, + // getNodeViewOffsetPosition: (state): XYPosition => { + // return state.nodeViewOffsetPosition; + // }, + // isNodeViewMoveInProgress: (state): boolean => { + // return state.nodeViewMoveInProgress; + // }, // Selected Nodes - getSelectedNodes: (state): INodeUi[] => { - const seen = new Set(); - return state.selectedNodes.filter((node: INodeUi) => { - // dedupe for instances when same node is selected in different ways - if (!seen.has(node.id)) { - seen.add(node.id); - return true; - } - - return false; - }); - }, - isNodeSelected: (state) => (nodeName: string): boolean => { - let index; - for (index in state.selectedNodes) { - if (state.selectedNodes[index].name === nodeName) { - return true; - } - } - return false; - }, + // getSelectedNodes: (state): INodeUi[] => { + // const seen = new Set(); + // return state.selectedNodes.filter((node: INodeUi) => { + // // dedupe for instances when same node is selected in different ways + // if (!seen.has(node.id)) { + // seen.add(node.id); + // return true; + // } + + // return false; + // }); + // }, + // isNodeSelected: (state) => (nodeName: string): boolean => { + // let index; + // for (index in state.selectedNodes) { + // if (state.selectedNodes[index].name === nodeName) { + // return true; + // } + // } + // return false; + // }, isActive: (state): boolean => { return state.workflow.active; @@ -1052,11 +1053,11 @@ export const store = new Vuex.Store({ return workflows; }, - fetchActiveWorkflows: async (context: ActionContext): Promise => { - const activeWorkflows = await getActiveWorkflows(context.rootGetters.getRestApiContext); - context.commit('setActiveWorkflows', activeWorkflows); + // fetchActiveWorkflows: async (context: ActionContext): Promise => { + // const activeWorkflows = await getActiveWorkflows(context.rootGetters.getRestApiContext); + // context.commit('setActiveWorkflows', activeWorkflows); - return activeWorkflows; - }, + // return activeWorkflows; + // }, }, }); diff --git a/packages/editor-ui/src/stores/ui.ts b/packages/editor-ui/src/stores/ui.ts index 4afc0ac0bab6e..925a9ccf12057 100644 --- a/packages/editor-ui/src/stores/ui.ts +++ b/packages/editor-ui/src/stores/ui.ts @@ -29,11 +29,12 @@ import { WORKFLOW_SETTINGS_MODAL_KEY, } from "@/constants"; import { + curlToJSONResponse, IExecutionsCurrentSummaryExtended, IFakeDoorLocation, IMenuItem, INodeUi, - IOnboardingCallPromptResponse, + IOnboardingCallPrompt, IPushDataExecutionFinished, IRunDataDisplayMode, IUser, @@ -43,13 +44,15 @@ import { import Vue from "vue"; import { defineStore } from "pinia"; import { useRootStore } from "./n8nRootStore"; +import { getCurlToJson } from "@/api/curlHelper"; +import { getActiveWorkflows } from "@/api/workflows"; export const useUIStore = defineStore(STORES.UI, { state: (): uiState => ({ + // TODO: Maybe move workflows and executions to workflow store activeExecutions: [], activeWorkflows: [], activeActions: [], - activeNode: null, activeCredentialType: null, modals: { [ABOUT_MODAL_KEY]: { @@ -121,21 +124,6 @@ export const useUIStore = defineStore(STORES.UI, { sidebarMenuCollapsed: true, isPageLoading: true, currentView: '', - ndv: { - sessionId: '', - input: { - displayMode: 'table', - }, - output: { - displayMode: 'table', - editMode: { - enabled: false, - value: '', - }, - }, - focusedMappableInput: '', - mappingTelemetry: {}, - }, mainPanelPosition: 0.5, fakeDoorFeatures: [ { @@ -175,6 +163,7 @@ export const useUIStore = defineStore(STORES.UI, { stickyPosition: null, }, stateIsDirty: false, + // TODO: Those two are waiting for Workflow store... lastSelectedNode: null, lastSelectedNodeOutputIndex: null, nodeViewOffsetPosition: [0, 0], @@ -183,7 +172,13 @@ export const useUIStore = defineStore(STORES.UI, { sidebarMenuItems: [], }), getters: { - areExpressionsDisabled(state: uiState): boolean { + getCurlCommand: (state: uiState): string|undefined => { + return state.modals[IMPORT_CURL_MODAL_KEY].curlCommand; + }, + getHttpNodeParameters: (state: uiState): string|undefined => { + return state.modals[IMPORT_CURL_MODAL_KEY].httpNodeParameters; + }, + areExpressionsDisabled: (state: uiState): boolean => { return state.currentView === VIEWS.DEMO; }, isVersionsOpen: (state: uiState): boolean => { @@ -204,15 +199,40 @@ export const useUIStore = defineStore(STORES.UI, { getModalData: (state: uiState) => { return (name: string) => state.modals[name].data; }, - getPanelDisplayMode: (state: uiState) => { - return (panel: 'input' | 'output') => state.ndv[panel].displayMode; - }, + // getPanelDisplayMode: (state: uiState) => { + // return (panel: 'input' | 'output') => state.ndv[panel].displayMode; + // }, getFakeDoorByLocation: (state: uiState) => (location: IFakeDoorLocation) => { return state.fakeDoorFeatures.filter(fakeDoor => fakeDoor.uiLocations.includes(location)); }, getFakeDoorById: (state: uiState) => (id: string) => { return state.fakeDoorFeatures.find(fakeDoor => fakeDoor.id.toString() === id); }, + isNodeView: (state: uiState) => [VIEWS.NEW_WORKFLOW.toString(), VIEWS.WORKFLOW.toString(), VIEWS.EXECUTION.toString()].includes(state.currentView), + // getNDVDataIsEmpty: (state: uiState) => (panel: 'input' | 'output'): boolean => state.ndv[panel].data.isEmpty, + isActionActive: (state: uiState) => (action: string) => { + return state.activeActions.includes(action); + }, + getSelectedNodes: (state: uiState) => { + const seen = new Set(); + return state.selectedNodes.filter((node: INodeUi) => { + // dedupe for instances when same node is selected in different ways + if (!seen.has(node.id)) { + seen.add(node.id); + return true; + } + return false; + }); + }, + isNodeSelected: (state: uiState) => (nodeName: string): boolean => { + let index; + for (index in state.selectedNodes) { + if (state.selectedNodes[index].name === nodeName) { + return true; + } + } + return false; + }, }, actions: { setMode(name: string, mode: string): void { @@ -221,10 +241,17 @@ export const useUIStore = defineStore(STORES.UI, { setActiveId(name: string, id: string): void { Vue.set(this.modals[name], 'activeId', id); }, + setModalData (payload: { name: string, data: Record }) { + Vue.set(this.modals[payload.name], 'data', payload.data); + }, openModal(name: string): void { Vue.set(this.modals[name], 'open', true); this.modalStack = [name].concat(this.modalStack); }, + openModalWithData (payload: { name: string, data: Record }): void { + this.setModalData(payload); + this.openModal(payload.name); + }, closeModal(name: string): void { Vue.set(this.modals[name], 'open', false); this.modalStack = this.modalStack.filter((openModalName: string) => { @@ -239,24 +266,24 @@ export const useUIStore = defineStore(STORES.UI, { }); this.modalStack = []; }, - setNDVSessionId(): void { - Vue.set(this.ndv, 'sessionId', `ndv-${Math.random().toString(36).slice(-8)}`); - }, - resetNDVSessionId(): void { - Vue.set(this.ndv, 'sessionId', ''); - }, - setPanelDisplayMode(pane: 'input' | 'output', mode: IRunDataDisplayMode): void { - Vue.set(this.ndv[pane], 'displayMode', mode); - }, - setOutputPanelEditModeEnabled(isEnabled: boolean): void { - Vue.set(this.ndv.output.editMode, 'enabled', isEnabled); - }, - setOutputPanelEditModeValue(value: string): void { - Vue.set(this.ndv.output.editMode, 'value', value); - }, - setMappableNDVInputFocus(paramName: string): void { - Vue.set(this.ndv, 'focusedMappableInput', paramName); - }, + // setNDVSessionId(): void { + // Vue.set(this.ndv, 'sessionId', `ndv-${Math.random().toString(36).slice(-8)}`); + // }, + // resetNDVSessionId(): void { + // Vue.set(this.ndv, 'sessionId', ''); + // }, + // setPanelDisplayMode(pane: 'input' | 'output', mode: IRunDataDisplayMode): void { + // Vue.set(this.ndv[pane], 'displayMode', mode); + // }, + // setOutputPanelEditModeEnabled(isEnabled: boolean): void { + // Vue.set(this.ndv.output.editMode, 'enabled', isEnabled); + // }, + // setOutputPanelEditModeValue(value: string): void { + // Vue.set(this.ndv.output.editMode, 'value', value); + // }, + // setMappableNDVInputFocus(paramName: string): void { + // Vue.set(this.ndv, 'focusedMappableInput', paramName); + // }, draggableStartDragging(type: string, data: string): void { this.draggable = { isDragging: true, @@ -281,12 +308,12 @@ export const useUIStore = defineStore(STORES.UI, { setDraggableCanDrop(canDrop: boolean): void { Vue.set(this.draggable, 'canDrop', canDrop); }, - setMappingTelemetry(telemetry: {[key: string]: string | number | boolean}): void { - this.ndv.mappingTelemetry = { ...this.ndv.mappingTelemetry, ...telemetry }; - }, - resetMappingTelemetry(): void { - this.ndv.mappingTelemetry = {}; - }, + // setMappingTelemetry(telemetry: {[key: string]: string | number | boolean}): void { + // this.ndv.mappingTelemetry = { ...this.ndv.mappingTelemetry, ...telemetry }; + // }, + // resetMappingTelemetry(): void { + // this.ndv.mappingTelemetry = {}; + // }, openDeleteUserModal(id: string): void { this.setActiveId(DELETE_USER_MODAL_KEY, id); this.openModal(DELETE_USER_MODAL_KEY); @@ -296,12 +323,12 @@ export const useUIStore = defineStore(STORES.UI, { this.setMode(CREDENTIAL_EDIT_MODAL_KEY, 'edit'); this.openModal(CREDENTIAL_EDIT_MODAL_KEY); }, - openNewCredential(id: string): void { - this.setActiveId(CREDENTIAL_EDIT_MODAL_KEY, id); + openNewCredential(type: string): void { + this.setActiveId(CREDENTIAL_EDIT_MODAL_KEY, type); this.setMode(CREDENTIAL_EDIT_MODAL_KEY, 'new'); this.openModal(CREDENTIAL_EDIT_MODAL_KEY); }, - async getNextOnboardingPrompt(): Promise { + async getNextOnboardingPrompt(): Promise { const rootStore = useRootStore(); const instanceId = rootStore.instanceId; // TODO: current USER @@ -407,5 +434,24 @@ export const useUIStore = defineStore(STORES.UI, { const updated = this.sidebarMenuItems.concat(menuItems); Vue.set(this, 'sidebarMenuItems', updated); }, + setCurlCommand (payload: { name: string, command: string }): void { + Vue.set(this.modals[payload.name], 'curlCommand', payload.command); + }, + setHttpNodeParameters (payload: { name: string, parameters: string }): void { + Vue.set(this.modals[payload.name], 'httpNodeParameters', payload.parameters); + }, + toggleSidebarMenuCollapse (): void { + this.sidebarMenuCollapsed = !this.sidebarMenuCollapsed; + }, + async getCurlToJson (curlCommand: string): Promise { + const rootStore = useRootStore(); + return await getCurlToJson(rootStore.getRestApiContext, curlCommand); + }, + async fetchActiveWorkflows (): Promise { + const rootStore = useRootStore(); + const activeWorkflows = await getActiveWorkflows(rootStore.getRestApiContext); + this.activeWorkflows = activeWorkflows; + return activeWorkflows; + }, }, }); diff --git a/packages/editor-ui/src/views/CredentialsView.vue b/packages/editor-ui/src/views/CredentialsView.vue index c4d6e1f87d4a5..758dfe340a241 100644 --- a/packages/editor-ui/src/views/CredentialsView.vue +++ b/packages/editor-ui/src/views/CredentialsView.vue @@ -59,6 +59,8 @@ import ResourceOwnershipSelect from "@/components/forms/ResourceOwnershipSelect. import ResourceFiltersDropdown from "@/components/forms/ResourceFiltersDropdown.vue"; import {CREDENTIAL_SELECT_MODAL_KEY} from '@/constants'; import Vue from "vue"; +import { mapStores } from 'pinia'; +import { useUIStore } from '@/stores/ui'; type IResourcesListLayoutInstance = Vue & { sendFiltersTelemetry: (source: string) => void }; @@ -88,6 +90,7 @@ export default mixins( }; }, computed: { + ...mapStores(useUIStore), currentUser(): IUser { return this.$store.getters['users/currentUser']; }, @@ -106,7 +109,7 @@ export default mixins( }, methods: { addCredential() { - this.$store.dispatch('ui/openModal', CREDENTIAL_SELECT_MODAL_KEY); + this.uiStore.openModal(CREDENTIAL_SELECT_MODAL_KEY); this.$telemetry.track('User clicked add cred button', { source: 'Creds list', diff --git a/packages/editor-ui/src/views/NodeView.vue b/packages/editor-ui/src/views/NodeView.vue index a3646c0028bf4..dc8a12c4d3934 100644 --- a/packages/editor-ui/src/views/NodeView.vue +++ b/packages/editor-ui/src/views/NodeView.vue @@ -72,7 +72,7 @@ @addNode="onAddNode" />
+ :class="{ 'zoom-menu': true, 'regular-zoom-menu': !isDemo, 'demo-zoom-menu': isDemo, expanded: !uiStore.sidebarMenuCollapsed }"> node.disabled); @@ -412,7 +412,7 @@ export default mixins( return !this.containsTrigger || this.allTriggersDisabled; }, getNodeViewOffsetPosition(): XYPosition { - return this.$store.getters.getNodeViewOffsetPosition; + return this.uiStore.nodeViewOffsetPosition; }, }, data() { @@ -1018,14 +1018,14 @@ export default mixins( if (this.editAllowedCheck() === false) { return; } - this.disableNodes(this.$store.getters.getSelectedNodes); + this.disableNodes(this.uiStore.getSelectedNodes); }, deleteSelectedNodes() { // Copy "selectedNodes" as the nodes get deleted out of selection // when they get deleted and if we would use original it would mess // with the index and would so not delete all nodes - const nodesToDelete: string[] = this.$store.getters.getSelectedNodes.map((node: INodeUi) => { + const nodesToDelete: string[] = this.uiStore.getSelectedNodes.map((node: INodeUi) => { return node.name; }); nodesToDelete.forEach((nodeName: string) => { @@ -1135,21 +1135,21 @@ export default mixins( const { scale, offset } = CanvasHelpers.scaleReset({ scale: this.nodeViewScale, offset: this.getNodeViewOffsetPosition }); this.setZoomLevel(scale); - this.$store.commit('setNodeViewOffsetPosition', { newOffset: offset }); + this.uiStore.nodeViewOffsetPosition = offset; }, zoomIn() { const { scale, offset: [xOffset, yOffset] } = CanvasHelpers.scaleBigger({ scale: this.nodeViewScale, offset: this.getNodeViewOffsetPosition }); this.setZoomLevel(scale); - this.$store.commit('setNodeViewOffsetPosition', { newOffset: [xOffset, yOffset] }); + this.uiStore.nodeViewOffsetPosition = [xOffset, yOffset]; }, zoomOut() { const { scale, offset: [xOffset, yOffset] } = CanvasHelpers.scaleSmaller({ scale: this.nodeViewScale, offset: this.getNodeViewOffsetPosition }); this.setZoomLevel(scale); - this.$store.commit('setNodeViewOffsetPosition', { newOffset: [xOffset, yOffset] }); + this.uiStore.nodeViewOffsetPosition = [xOffset, yOffset]; }, setZoomLevel(zoomLevel: number) { @@ -1212,7 +1212,7 @@ export default mixins( const { zoomLevel, offset } = CanvasHelpers.getZoomToFit(nodes); this.setZoomLevel(zoomLevel); - this.$store.commit('setNodeViewOffsetPosition', { newOffset: offset }); + this.uiStore.nodeViewOffsetPosition = offset; }, async stopExecution() { @@ -1244,11 +1244,11 @@ export default mixins( executionId, retryOf: execution.retryOf, } as IPushDataExecutionFinished; - this.$store.commit('finishActiveExecution', pushData); + this.uiStore.finishActiveExecution(pushData); this.$titleSet(execution.workflowData.name, 'IDLE'); this.$store.commit('setExecutingNode', null); this.$store.commit('setWorkflowExecutionData', executedData); - this.$store.commit('removeActiveAction', 'workflowRunning'); + this.uiStore.removeActiveAction('workflowRunning'); this.$showMessage({ title: this.$locale.baseText('nodeView.showMessage.stopExecutionCatch.title'), message: this.$locale.baseText('nodeView.showMessage.stopExecutionCatch.message'), @@ -1504,8 +1504,8 @@ export default mixins( this.nodeSelected(node); } - this.$store.commit('setLastSelectedNode', node.name); - this.$store.commit('setLastSelectedNodeOutputIndex', null); + this.uiStore.lastSelectedNode = node.name; + this.uiStore.lastSelectedNodeOutputIndex = null; this.lastSelectedConnection = null; this.newNodeInsertPosition = null; @@ -1795,8 +1795,8 @@ export default mixins( return; } - this.$store.commit('setLastSelectedNode', sourceNode.name); - this.$store.commit('setLastSelectedNodeOutputIndex', info.index); + this.uiStore.lastSelectedNode = sourceNode.name; + this.uiStore.lastSelectedNodeOutputIndex = info.index; this.newNodeInsertPosition = null; if (info.connection) { @@ -2614,8 +2614,7 @@ export default mixins( } // Remove node from selected index if found in it - this.$store.commit('removeNodeFromSelection', node); - + this.uiStore.removeNodeFromSelection(node); }, 0); // allow other events to finish like drag stop }, valueChanged(parameterData: IUpdateInformation) { @@ -2983,7 +2982,7 @@ export default mixins( let nodeData; const exportNodeNames: string[] = []; - for (const node of this.$store.getters.getSelectedNodes) { + for (const node of this.uiStore.getSelectedNodes) { try { nodeData = this.getNodeDataToSave(node); exportNodeNames.push(node.name); @@ -3067,18 +3066,17 @@ export default mixins( this.$store.commit('setActiveExecutionId', null); this.$store.commit('setExecutingNode', null); - this.$store.commit('removeActiveAction', 'workflowRunning'); + this.uiStore.removeActiveAction('workflowRunning'); this.$store.commit('setExecutionWaitingForWebhook', false); - this.$store.commit('resetSelectedNodes'); - - this.$store.commit('setNodeViewOffsetPosition', { newOffset: [0, 0], setStateDirty: false }); + this.uiStore.resetSelectedNodes(); + this.uiStore.nodeViewOffsetPosition = [0, 0]; return Promise.resolve(); }, async loadActiveWorkflows(): Promise { const activeWorkflows = await this.restApi().getActiveWorkflows(); - this.$store.commit('setActiveWorkflows', activeWorkflows); + this.uiStore.activeWorkflows = activeWorkflows; }, async loadNodeTypes(): Promise { await this.$store.dispatch('nodeTypes/getNodeTypes'); @@ -3251,7 +3249,7 @@ export default mixins( this.isOnboardingCallPromptFeatureEnabled && getAccountAge(this.currentUser) <= ONBOARDING_PROMPT_TIMEBOX ) { - const onboardingResponse = await this.$store.dispatch('ui/getNextOnboardingPrompt'); + const onboardingResponse = await this.uiStore.getNextOnboardingPrompt(); const promptTimeout = onboardingResponse.toast_sequence_number === 1 ? FIRST_ONBOARDING_PROMPT_TIMEOUT : 1000; if (onboardingResponse.title && onboardingResponse.description) { @@ -3269,7 +3267,7 @@ export default mixins( 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); diff --git a/packages/editor-ui/src/views/SettingsFakeDoorView.vue b/packages/editor-ui/src/views/SettingsFakeDoorView.vue index ccd5cb1929bce..5ab16b3b24b25 100644 --- a/packages/editor-ui/src/views/SettingsFakeDoorView.vue +++ b/packages/editor-ui/src/views/SettingsFakeDoorView.vue @@ -9,6 +9,8 @@ import { IFakeDoor } from '@/Interface'; import Vue from 'vue'; import SettingsView from './SettingsView.vue'; import FeatureComingSoon from '../components/FeatureComingSoon.vue'; +import { mapStores } from 'pinia'; +import { useUIStore } from '@/stores/ui'; export default Vue.extend({ name: 'SettingsFakeDoorView', @@ -23,13 +25,16 @@ export default Vue.extend({ }, }, computed: { - featureInfo(): IFakeDoor { - return this.$store.getters['ui/getFakeDoorFeatures'][this.featureId] as IFakeDoor; + ...mapStores(useUIStore), + featureInfo(): IFakeDoor|undefined { + return this.uiStore.getFakeDoorById(this.featureId); }, }, methods: { openLinkPage() { - window.open(this.featureInfo.linkURL, '_blank'); + if (this.featureInfo) { + window.open(this.featureInfo.linkURL, '_blank'); + } }, }, }); diff --git a/packages/editor-ui/src/views/SettingsPersonalView.vue b/packages/editor-ui/src/views/SettingsPersonalView.vue index 4cfe58322c3b0..cbac2b81a0bef 100644 --- a/packages/editor-ui/src/views/SettingsPersonalView.vue +++ b/packages/editor-ui/src/views/SettingsPersonalView.vue @@ -46,6 +46,8 @@ import { showMessage } from '@/components/mixins/showMessage'; import { CHANGE_PASSWORD_MODAL_KEY } from '@/constants'; import { IFormInputs, IUser } from '@/Interface'; +import { useUIStore } from '@/stores/ui'; +import { mapStores } from 'pinia'; import Vue from 'vue'; import mixins from 'vue-typed-mixins'; @@ -105,6 +107,7 @@ export default mixins( ]; }, computed: { + ...mapStores(useUIStore), currentUser() { return this.$store.getters['users/currentUser'] as IUser; }, @@ -142,7 +145,7 @@ export default mixins( this.formBus.$emit('submit'); }, openPasswordModal() { - this.$store.dispatch('ui/openModal', CHANGE_PASSWORD_MODAL_KEY); + this.uiStore.openModal(CHANGE_PASSWORD_MODAL_KEY); }, }, }); diff --git a/packages/editor-ui/src/views/SettingsUsersView.vue b/packages/editor-ui/src/views/SettingsUsersView.vue index eae2091933d1e..16e52e15be321 100644 --- a/packages/editor-ui/src/views/SettingsUsersView.vue +++ b/packages/editor-ui/src/views/SettingsUsersView.vue @@ -49,6 +49,8 @@ import PageAlert from '../components/PageAlert.vue'; import { IUser } from '@/Interface'; import mixins from 'vue-typed-mixins'; import { showMessage } from '@/components/mixins/showMessage'; +import { mapStores } from 'pinia'; +import { useUIStore } from '@/stores/ui'; export default mixins(showMessage).extend({ name: 'SettingsUsersView', @@ -62,6 +64,7 @@ export default mixins(showMessage).extend({ } }, computed: { + ...mapStores(useUIStore), ...mapGetters('users', ['allUsers', 'currentUserId', 'showUMSetupWarning']), ...mapGetters('settings', ['isSmtpSetup']), }, @@ -70,13 +73,13 @@ export default mixins(showMessage).extend({ this.$router.push({name: VIEWS.SETUP}); }, onInvite() { - this.$store.dispatch('ui/openModal', INVITE_USER_MODAL_KEY); + this.uiStore.openModal(INVITE_USER_MODAL_KEY); }, async onDelete(userId: string) { const getUserById = this.$store.getters['users/getUserById']; const user = getUserById(userId) as IUser | null; if (user) { - this.$store.dispatch('ui/openDeleteUserModal', { id: userId }); + this.uiStore.openDeleteUserModal(userId); } }, async onReinvite(userId: string) { diff --git a/packages/editor-ui/src/views/SetupView.vue b/packages/editor-ui/src/views/SetupView.vue index ab508f52dbcd1..fb813a8ec57bb 100644 --- a/packages/editor-ui/src/views/SetupView.vue +++ b/packages/editor-ui/src/views/SetupView.vue @@ -15,6 +15,8 @@ import mixins from 'vue-typed-mixins'; import { IFormBoxConfig } from '@/Interface'; import { VIEWS } from '@/constants'; import { restApi } from '@/components/mixins/restApi'; +import { mapStores } from 'pinia'; +import { useUIStore } from '@/stores/ui'; export default mixins( @@ -96,6 +98,9 @@ export default mixins( credentialsCount: 0, }; }, + computed: { + ...mapStores(useUIStore), + }, methods: { async getAllCredentials() { const credentials = await this.$store.dispatch('credentials/fetchAllCredentials'); @@ -150,7 +155,7 @@ export default mixins( if (values.agree === true) { try { - await this.$store.dispatch('ui/submitContactEmail', { email: values.email, agree: values.agree }); + await this.uiStore.submitContactEmail(values.email.toString(), values.agree); } catch { } } diff --git a/packages/editor-ui/src/views/SignupView.vue b/packages/editor-ui/src/views/SignupView.vue index 9553a2b566183..4b8e5a067ce4a 100644 --- a/packages/editor-ui/src/views/SignupView.vue +++ b/packages/editor-ui/src/views/SignupView.vue @@ -14,6 +14,9 @@ import { showMessage } from '@/components/mixins/showMessage'; import mixins from 'vue-typed-mixins'; import { IFormBoxConfig } from '@/Interface'; import { VIEWS } from '@/constants'; +import { mapStores } from 'pinia'; +import { useUIStore } from '@/stores/ui'; +import { value } from 'jsonpath'; export default mixins( showMessage, @@ -90,6 +93,7 @@ export default mixins( } }, computed: { + ...mapStores(useUIStore), inviteMessage(): null | string { if (!this.inviter) { return null; @@ -111,7 +115,7 @@ export default mixins( if (values.agree === true) { try { - await this.$store.dispatch('ui/submitContactEmail', { email: values.email, agree: values.agree }); + await this.uiStore.submitContactEmail(values.email.toString(), values.agree); } catch { } } diff --git a/packages/editor-ui/src/views/WorkflowsView.vue b/packages/editor-ui/src/views/WorkflowsView.vue index f5e1f330031c0..170666da988a1 100644 --- a/packages/editor-ui/src/views/WorkflowsView.vue +++ b/packages/editor-ui/src/views/WorkflowsView.vue @@ -73,6 +73,8 @@ import {VIEWS} from '@/constants'; import Vue from "vue"; import {ITag, IUser, IWorkflowDb} from "@/Interface"; import TagsDropdown from "@/components/TagsDropdown.vue"; +import { mapStores } from 'pinia'; +import { useUIStore } from '@/stores/ui'; type IResourcesListLayoutInstance = Vue & { sendFiltersTelemetry: (source: string) => void }; @@ -101,6 +103,7 @@ export default mixins( }; }, computed: { + ...mapStores(useUIStore), currentUser(): IUser { return this.$store.getters['users/currentUser']; }, @@ -127,7 +130,8 @@ export default mixins( return await Promise.all([ this.$store.dispatch('fetchAllWorkflows'), - this.$store.dispatch('fetchActiveWorkflows'), + // this.$store.dispatch('fetchActiveWorkflows'), + this.uiStore.fetchActiveWorkflows(), ]); }, onClickTag(tagId: string, event: PointerEvent) { From 360545d76c81474132a5c5985d881cfd2acc8b40 Mon Sep 17 00:00:00 2001 From: Milorad Filipovic Date: Wed, 26 Oct 2022 13:43:12 +0200 Subject: [PATCH 04/38] =?UTF-8?q?=E2=9C=A8=20Migrated=20`settings`=20store?= =?UTF-8?q?=20to=20pinia?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- packages/editor-ui/src/App.vue | 21 +- packages/editor-ui/src/Interface.ts | 5 +- packages/editor-ui/src/api/settings.ts | 4 +- .../editor-ui/src/components/AboutModal.vue | 12 +- .../src/components/ContactPromptModal.vue | 20 +- .../CredentialEdit/CredentialEdit.vue | 7 +- .../components/DuplicateWorkflowDialog.vue | 8 +- .../src/components/EnterpriseEdition.ee.vue | 5 +- .../src/components/FeatureComingSoon.vue | 12 +- .../components/MainHeader/WorkflowDetails.vue | 11 +- .../editor-ui/src/components/MainSidebar.vue | 16 +- .../src/components/PersonalizationModal.vue | 19 +- .../src/components/SettingsSidebar.vue | 9 +- .../editor-ui/src/components/Telemetry.vue | 21 +- .../editor-ui/src/components/ValueSurvey.vue | 10 +- .../editor-ui/src/components/WorkflowCard.vue | 11 +- .../layouts/ResourcesListLayout.vue | 5 +- .../src/components/mixins/nodeHelpers.ts | 5 +- .../src/components/mixins/workflowActivate.ts | 8 +- packages/editor-ui/src/constants.ts | 1 + packages/editor-ui/src/modules/settings.ts | 230 ------------------ packages/editor-ui/src/modules/templates.ts | 35 ++- packages/editor-ui/src/modules/users.ts | 10 +- packages/editor-ui/src/permissions.ts | 6 +- .../editor-ui/src/plugins/telemetry/index.ts | 5 +- packages/editor-ui/src/router.ts | 13 +- packages/editor-ui/src/store.ts | 2 - packages/editor-ui/src/stores/settings.ts | 208 ++++++++++++++++ packages/editor-ui/src/stores/ui.ts | 1 - .../src/views/ForgotMyPasswordView.vue | 6 +- packages/editor-ui/src/views/NodeView.vue | 19 +- .../editor-ui/src/views/SettingsApiView.vue | 19 +- .../src/views/SettingsCommunityNodesView.vue | 23 +- .../editor-ui/src/views/SettingsUsersView.vue | 13 +- packages/editor-ui/src/views/SetupView.vue | 8 +- .../src/views/TemplatesSearchView.vue | 6 +- .../editor-ui/src/views/WorkflowsView.vue | 13 +- 37 files changed, 438 insertions(+), 389 deletions(-) delete mode 100644 packages/editor-ui/src/modules/settings.ts create mode 100644 packages/editor-ui/src/stores/settings.ts diff --git a/packages/editor-ui/src/App.vue b/packages/editor-ui/src/App.vue index eba095754b223..d870618a5e282 100644 --- a/packages/editor-ui/src/App.vue +++ b/packages/editor-ui/src/App.vue @@ -40,6 +40,7 @@ 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'; export default mixins( showMessage, @@ -54,9 +55,11 @@ export default mixins( Modals, }, computed: { - ...mapGetters('settings', ['isHiringBannerEnabled', 'isTemplatesEnabled', 'isTemplatesEndpointReachable', 'isUserManagementEnabled', 'showSetupPage']), ...mapGetters('users', ['currentUser']), - ...mapStores(useUIStore), + ...mapStores( + useUIStore, + useSettingsStore, + ), defaultLocale (): string { return this.$store.getters.defaultLocale; }, @@ -69,7 +72,7 @@ export default mixins( methods: { async initSettings(): Promise { try { - await this.$store.dispatch('settings/getSettings'); + await this.settingsStore.getSettings(); } catch (e) { this.$showToast({ title: this.$locale.baseText('startupError'), @@ -87,17 +90,16 @@ export default mixins( } catch (e) {} }, async initTemplates(): Promise { - if (!this.isTemplatesEnabled) { + if (!this.settingsStore.isTemplatesEnabled) { return; } - try { - await this.$store.dispatch('settings/testTemplatesEndpoint'); + 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 } }, @@ -118,7 +120,7 @@ export default mixins( }, 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; } @@ -169,7 +171,8 @@ export default mixins( this.loading = false; this.trackPage(); - this.$externalHooks().run('app.mount'); + // 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'); diff --git a/packages/editor-ui/src/Interface.ts b/packages/editor-ui/src/Interface.ts index 2f3d4a6f1b4c7..3add6bd0ffe01 100644 --- a/packages/editor-ui/src/Interface.ts +++ b/packages/editor-ui/src/Interface.ts @@ -880,7 +880,9 @@ export interface rootStatePinia { maxExecutionTimeout: number; versionCli: string; oauthCallbackUrls: object; - n8nMetadata: object; + n8nMetadata: { + [key: string]: string | number | undefined; + }; // workflowExecutionData: IExecutionResponse | null; // --> WF store // lastSelectedNode: string | null; // --> UI Store // lastSelectedNodeOutputIndex: number | null; // --> UI Store @@ -889,6 +891,7 @@ export interface rootStatePinia { // selectedNodes: INodeUi[]; // --> UI Store sessionId: string; urlBaseWebhook: string; + urlBaseEditor: string; // workflow: IWorkflowDb; // --> WF Store // sidebarMenuItems: IMenuItem[]; // --> UI Store instanceId: string; diff --git a/packages/editor-ui/src/api/settings.ts b/packages/editor-ui/src/api/settings.ts index 0165a5356a72b..e353ff888779d 100644 --- a/packages/editor-ui/src/api/settings.ts +++ b/packages/editor-ui/src/api/settings.ts @@ -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'; @@ -14,7 +14,7 @@ export async function submitContactInfo(instanceId: string, userId: string, emai 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 { +export async function submitValueSurvey(instanceId: string, userId: string, params: IN8nValueSurveyData): Promise { return await post(N8N_IO_BASE_URL, '/value-survey', params, {'n8n-instance-id': instanceId, 'n8n-user-id': userId}); } diff --git a/packages/editor-ui/src/components/AboutModal.vue b/packages/editor-ui/src/components/AboutModal.vue index 8c57926878e74..7fa1e5e99b95f 100644 --- a/packages/editor-ui/src/components/AboutModal.vue +++ b/packages/editor-ui/src/components/AboutModal.vue @@ -13,7 +13,7 @@ {{ $locale.baseText('about.n8nVersion') }} - {{ versionCli }} + {{ settingsStore.versionCli }} @@ -39,7 +39,7 @@ {{ $locale.baseText('about.instanceID') }} - {{ instanceId }} + {{ settingsStore.settings.instanceId }}
@@ -55,9 +55,10 @@ From cd6467dfb78ac1c7d14b66c61ee95fb27bc6e846 Mon Sep 17 00:00:00 2001 From: Milorad Filipovic Date: Thu, 27 Oct 2022 09:47:07 +0200 Subject: [PATCH 07/38] =?UTF-8?q?=E2=9A=A1=20Fixing=20errors=20after=20syn?= =?UTF-8?q?c=20with=20master?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- packages/editor-ui/src/components/ActivationModal.vue | 1 - packages/editor-ui/src/components/mixins/mouseSelect.ts | 1 - 2 files changed, 2 deletions(-) diff --git a/packages/editor-ui/src/components/ActivationModal.vue b/packages/editor-ui/src/components/ActivationModal.vue index 5074a9883720d..23b40b9d8b597 100644 --- a/packages/editor-ui/src/components/ActivationModal.vue +++ b/packages/editor-ui/src/components/ActivationModal.vue @@ -61,7 +61,6 @@ export default Vue.extend({ methods: { async showExecutionsList () { const activeExecution = this.$store.getters['workflows/getActiveWorkflowExecution']; - // TODO: UPDATE AFTER CONFLICTS ARE RESOLVED: const currentWorkflow = this.$store.getters.workflowId; if (activeExecution) { diff --git a/packages/editor-ui/src/components/mixins/mouseSelect.ts b/packages/editor-ui/src/components/mixins/mouseSelect.ts index 5d56d94ddd435..c10316c003961 100644 --- a/packages/editor-ui/src/components/mixins/mouseSelect.ts +++ b/packages/editor-ui/src/components/mixins/mouseSelect.ts @@ -54,7 +54,6 @@ export const mouseSelect = mixins( const [x, y] = getMousePosition(event); const sidebarOffset = this.isDemo ? 0 : this.uiStore.sidebarMenuCollapsed ? SIDEBAR_WIDTH : SIDEBAR_WIDTH_EXPANDED; const headerOffset = this.isDemo ? 0 : HEADER_HEIGHT; - const sidebarOffset = this.isDemo ? 0 : this.$store.getters['ui/sidebarMenuCollapsed'] ? SIDEBAR_WIDTH : SIDEBAR_WIDTH_EXPANDED; // @ts-ignore return getRelativePosition(x - sidebarOffset, y - headerOffset, this.nodeViewScale, this.uiStore.nodeViewOffsetPosition); }, From 2cd5495ad6b14190d9ddb018b0dad7b4e14a62d8 Mon Sep 17 00:00:00 2001 From: Milorad Filipovic Date: Thu, 27 Oct 2022 09:51:47 +0200 Subject: [PATCH 08/38] =?UTF-8?q?=E2=9A=A1=20One=20more=20error=20after=20?= =?UTF-8?q?merge?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- packages/editor-ui/src/components/ImportParameter.vue | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/packages/editor-ui/src/components/ImportParameter.vue b/packages/editor-ui/src/components/ImportParameter.vue index 1e6d391f3fff9..5dee146874e8b 100644 --- a/packages/editor-ui/src/components/ImportParameter.vue +++ b/packages/editor-ui/src/components/ImportParameter.vue @@ -19,14 +19,15 @@ import { showMessage } from './mixins/showMessage'; export default mixins(showMessage).extend({ name: 'import-parameter', - computed: { - ...mapStores(useUIStore), props: { isReadOnly: { type: Boolean, default: false, }, }, + computed: { + ...mapStores(useUIStore), + }, methods: { onImportCurlClicked() { this.uiStore.openModal(IMPORT_CURL_MODAL_KEY); From 363988dfec0243be4fed4cf20a372d47945be671 Mon Sep 17 00:00:00 2001 From: Milorad Filipovic Date: Thu, 27 Oct 2022 18:43:38 +0200 Subject: [PATCH 09/38] =?UTF-8?q?=E2=9A=A1=20Created=20`workflows`=20pinia?= =?UTF-8?q?=20store.=20Moved=20large=20part=20of=20root=20store=20to=20it.?= =?UTF-8?q?=20Started=20updating=20references.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- packages/editor-ui/src/Interface.ts | 32 +- .../src/components/ExecutionsList.vue | 27 +- .../ExecutionsView/ExecutionsLandingPage.vue | 13 +- .../ExecutionsView/ExecutionsView.vue | 79 +- .../src/components/MainHeader/MainHeader.vue | 13 +- .../editor-ui/src/components/MainSidebar.vue | 24 +- packages/editor-ui/src/components/Node.vue | 28 +- .../src/components/mixins/mouseSelect.ts | 8 +- .../src/components/mixins/nodeBase.ts | 14 +- .../src/components/mixins/nodeHelpers.ts | 8 +- .../src/components/mixins/pinData.ts | 7 +- .../src/components/mixins/workflowHelpers.ts | 97 +- packages/editor-ui/src/constants.ts | 5 +- packages/editor-ui/src/modules/ndv.ts | 7 +- packages/editor-ui/src/store.ts | 1099 ++++++++--------- .../editor-ui/src/stores/communityNodes.ts | 8 +- packages/editor-ui/src/stores/n8nRootStore.ts | 5 - packages/editor-ui/src/stores/settings.ts | 98 +- packages/editor-ui/src/stores/ui.ts | 142 +-- packages/editor-ui/src/stores/workflows.ts | 762 ++++++++++++ packages/editor-ui/src/views/NodeView.vue | 449 +++---- 21 files changed, 1860 insertions(+), 1065 deletions(-) create mode 100644 packages/editor-ui/src/stores/workflows.ts diff --git a/packages/editor-ui/src/Interface.ts b/packages/editor-ui/src/Interface.ts index 5391968718ed6..17b03d0ba5dc5 100644 --- a/packages/editor-ui/src/Interface.ts +++ b/packages/editor-ui/src/Interface.ts @@ -886,6 +886,23 @@ export interface INodeMetadata { parametersLastUpdatedAt?: number; } +export interface workflowsState { + activeExecutions: IExecutionsCurrentSummaryExtended[]; + activeWorkflows: string[]; + activeWorkflowExecution: IExecutionsSummary | null; + currentWorkflowExecutions: IExecutionsSummary[]; + executionId: string | null; + executingNode: string | null; + executionWaitingForWebhook: boolean; + finishedExecutionsCount: number; + nodeMetadata: nodeMetadataMap; + subworkflowExecutionError: Error | null; + workflow: IWorkflowDb; + workflowExecutionData: IExecutionResponse | null; + workflowExecutionPairedItemMappings: {[itemId: string]: Set}; + workflowsById: IWorkflowsMap; +} + // TODO: // - Make this the only root state once migration is done // - Remove commented out props @@ -900,9 +917,9 @@ export interface rootStatePinia { defaultLocale: string; endpointWebhook: string; endpointWebhookTest: string; - executionId: string | null; - executingNode: string | null; - executionWaitingForWebhook: boolean; + // executionId: string | null; ---> WF STORE + // executingNode: string | null; ---> WF STORE + // executionWaitingForWebhook: boolean; ---> WF STORE pushConnectionActive: boolean; saveDataErrorExecution: string; saveDataSuccessExecution: string; @@ -928,7 +945,7 @@ export interface rootStatePinia { // workflow: IWorkflowDb; // --> WF Store // sidebarMenuItems: IMenuItem[]; // --> UI Store instanceId: string; - nodeMetadata: nodeMetadataMap; + // nodeMetadata: nodeMetadataMap;// --> WF STORE isNpmAvailable: boolean; } @@ -1073,8 +1090,6 @@ export interface IUiState { } export interface uiState { - activeExecutions: IExecutionsCurrentSummaryExtended[]; - activeWorkflows: string[]; activeActions: string[]; activeCredentialType: string | null; // activeNode: string | null; --> NDV STORE @@ -1116,6 +1131,9 @@ export interface uiState { nodeViewMoveInProgress: boolean; selectedNodes: INodeUi[]; sidebarMenuItems: IMenuItem[]; + nodeViewInitialized: boolean; + addFirstStepOnLoad: boolean; + executionSidebarAutoRefresh: boolean; } export type ILogLevel = 'info' | 'debug' | 'warn' | 'error' | 'verbose'; @@ -1204,8 +1222,6 @@ export interface IWorkflowsState { [name: string]: IWorkflowDb; } -export interface IWorkflowsState {} - export interface communityNodesState { availablePackageCount: number; installedPackages: communityPackageMap; diff --git a/packages/editor-ui/src/components/ExecutionsList.vue b/packages/editor-ui/src/components/ExecutionsList.vue index ed2762e5596b1..9062a7a4be3bd 100644 --- a/packages/editor-ui/src/components/ExecutionsList.vue +++ b/packages/editor-ui/src/components/ExecutionsList.vue @@ -205,6 +205,7 @@ import { import mixins from 'vue-typed-mixins'; import { mapStores } from 'pinia'; import { useUIStore } from '@/stores/ui'; +import { useWorkflowsStore } from '@/stores/workflows'; export default mixins( externalHooks, @@ -251,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) { @@ -260,7 +261,10 @@ export default mixins( } }, computed: { - ...mapStores(useUIStore), + ...mapStores( + useUIStore, + useWorkflowsStore, + ), statuses () { return [ { @@ -411,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 }, @@ -471,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', }); diff --git a/packages/editor-ui/src/components/ExecutionsView/ExecutionsLandingPage.vue b/packages/editor-ui/src/components/ExecutionsView/ExecutionsLandingPage.vue index 192bb6493c021..f7b127e6ceeff 100644 --- a/packages/editor-ui/src/components/ExecutionsView/ExecutionsLandingPage.vue +++ b/packages/editor-ui/src/components/ExecutionsView/ExecutionsLandingPage.vue @@ -27,7 +27,8 @@ diff --git a/packages/editor-ui/src/components/NodeCredentials.vue b/packages/editor-ui/src/components/NodeCredentials.vue index b25451b72be60..262a18cb08e30 100644 --- a/packages/editor-ui/src/components/NodeCredentials.vue +++ b/packages/editor-ui/src/components/NodeCredentials.vue @@ -85,6 +85,7 @@ import {getCredentialPermissions} from "@/permissions"; import { mapStores } from 'pinia'; import { useUIStore } from '@/stores/ui'; import { useUsersStore } from '@/stores/users'; +import { useWorkflowsStore } from '@/stores/workflows'; export default mixins( genericHelpers, @@ -110,6 +111,7 @@ export default mixins( ...mapStores( useUIStore, useUsersStore, + useWorkflowsStore, ), ...mapGetters('credentials', { allCredentialsByType: 'allCredentialsByType', @@ -231,7 +233,7 @@ export default mixins( if (credentialId === this.NEW_CREDENTIALS_TEXT) { this.listenForNewCredentials(credentialType); this.uiStore.openNewCredential(credentialType); - this.$telemetry.track('User opened Credential modal', { credential_type: credentialType, source: 'node', new_credential: true, workflow_id: this.$store.getters.workflowId }); + this.$telemetry.track('User opened Credential modal', { credential_type: credentialType, source: 'node', new_credential: true, workflow_id: this.workflowsStore.workflowId }); return; } @@ -241,7 +243,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, }, ); @@ -254,7 +256,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, @@ -328,7 +330,7 @@ export default mixins( const { id } = this.node.credentials[credentialType]; 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); }, diff --git a/packages/editor-ui/src/components/NodeDetailsView.vue b/packages/editor-ui/src/components/NodeDetailsView.vue index 518d547078c5a..60681e56bce49 100644 --- a/packages/editor-ui/src/components/NodeDetailsView.vue +++ b/packages/editor-ui/src/components/NodeDetailsView.vue @@ -138,6 +138,8 @@ 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'; export default mixins( externalHooks, @@ -189,7 +191,9 @@ export default mixins( dataPinningEventBus.$off('data-pinning-discovery'); }, computed: { - ...mapGetters(['executionWaitingForWebhook']), + ...mapStores( + useWorkflowsStore, + ), sessionId(): string { return this.$store.getters['ndv/ndvSessionId']; }, @@ -202,7 +206,7 @@ export default mixins( !!this.activeNodeType && !this.activeNodeType.group.includes('trigger') && this.workflowRunning && - this.executionWaitingForWebhook + this.workflowsStore.executionWaitingForWebhook ); }, activeNode(): INodeUi | null { @@ -213,7 +217,7 @@ export default mixins( }, inputNode(): INodeUi | null { if (this.inputNodeName) { - return this.$store.getters.getNodeByName(this.inputNodeName); + return this.workflowsStore.getNodeByName(this.inputNodeName); } return null; @@ -262,7 +266,7 @@ export default mixins( ); }, workflowExecution(): IExecutionResponse | null { - return this.$store.getters.getWorkflowExecution; + return this.workflowsStore.getWorkflowExecution; }, workflowRunData(): IRunData | null { if (this.workflowExecution === null) { @@ -366,13 +370,13 @@ 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, @@ -383,7 +387,7 @@ export default mixins( selected_view_outputs: this.$store.getters['ndv/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, @@ -463,7 +467,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', @@ -485,7 +489,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; }, @@ -559,7 +563,9 @@ 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); @@ -569,7 +575,7 @@ export default mixins( 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); @@ -602,7 +608,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 : '', }); diff --git a/packages/editor-ui/src/components/NodeExecuteButton.vue b/packages/editor-ui/src/components/NodeExecuteButton.vue index 1e1f2e374a736..213a51954e03d 100644 --- a/packages/editor-ui/src/components/NodeExecuteButton.vue +++ b/packages/editor-ui/src/components/NodeExecuteButton.vue @@ -23,6 +23,8 @@ 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'; export default mixins( workflowRun, @@ -54,8 +56,11 @@ export default mixins( }, }, computed: { - node (): INodeUi { - return this.$store.getters.getNodeByName(this.nodeName); + ...mapStores( + useWorkflowsStore, + ), + node (): INodeUi | null { + return this.workflowsStore.getNodeByName(this.nodeName); }, nodeType (): INodeTypeDescription | null { if (this.node) { @@ -64,8 +69,8 @@ export default mixins( 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 { @@ -87,8 +92,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 && @@ -151,7 +156,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, @@ -177,14 +182,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); diff --git a/packages/editor-ui/src/components/NodeSettings.vue b/packages/editor-ui/src/components/NodeSettings.vue index 6437dbf294cfb..6608c2c14d108 100644 --- a/packages/editor-ui/src/components/NodeSettings.vue +++ b/packages/editor-ui/src/components/NodeSettings.vue @@ -151,6 +151,7 @@ import NodeExecuteButton from './NodeExecuteButton.vue'; import { isCommunityPackageName } from './helpers'; import { mapStores } from 'pinia'; import { useUIStore } from '@/stores/ui'; +import { useWorkflowsStore } from '@/stores/workflows'; export default mixins(externalHooks, nodeHelpers).extend({ name: 'NodeSettings', @@ -164,7 +165,10 @@ export default mixins(externalHooks, nodeHelpers).extend({ NodeExecuteButton, }, computed: { - ...mapStores(useUIStore), + ...mapStores( + useUIStore, + useWorkflowsStore, + ), isCurlImportModalOpen() { return this.uiStore.isModalOpen(IMPORT_CURL_MODAL_KEY); }, @@ -445,12 +449,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 }); }, @@ -474,7 +480,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 @@ -570,9 +581,9 @@ export default mixins(externalHooks, nodeHelpers).extend({ const updateInformation = { name: node.name, value: nodeParameters, - }; + } as IUpdateInformation; - this.$store.commit('setNodeParameters', updateInformation); + this.workflowsStore.setNodeParameters(updateInformation); this.updateNodeParameterIssues(node, nodeType); this.updateNodeCredentialIssues(node); @@ -648,9 +659,9 @@ export default mixins(externalHooks, nodeHelpers).extend({ const updateInformation = { name: node.name, value: nodeParameters, - }; + } as IUpdateInformation; - this.$store.commit('setNodeParameters', updateInformation); + this.workflowsStore.setNodeParameters(updateInformation); this.$externalHooks().run('nodeSettings.valueChanged', { parameterPath, @@ -672,8 +683,9 @@ export default mixins(externalHooks, nodeHelpers).extend({ name: node.name, key: parameterData.name, value: newValue, - }; - this.$store.commit('setNodeValue', updateInformation); + } as IUpdateInformation; + + this.workflowsStore.setNodeValue(updateInformation); } }, /** diff --git a/packages/editor-ui/src/components/NodeSettingsTabs.vue b/packages/editor-ui/src/components/NodeSettingsTabs.vue index 83eda6fd64b48..49a0c999b12cb 100644 --- a/packages/editor-ui/src/components/NodeSettingsTabs.vue +++ b/packages/editor-ui/src/components/NodeSettingsTabs.vue @@ -6,7 +6,9 @@ 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 { useWorkflowsStore } from '@/stores/workflows'; import { INodeTypeDescription } from 'n8n-workflow'; +import { mapStores } from 'pinia'; import mixins from 'vue-typed-mixins'; import { isCommunityPackageName } from './helpers'; @@ -26,6 +28,9 @@ export default mixins( }, }, computed: { + ...mapStores( + useWorkflowsStore, + ), activeNode(): INodeUi { return this.$store.getters['ndv/activeNode']; }, @@ -113,7 +118,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 +126,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') { diff --git a/packages/editor-ui/src/components/OutputPanel.vue b/packages/editor-ui/src/components/OutputPanel.vue index 4dc7cc2e69331..98d63a79e0a34 100644 --- a/packages/editor-ui/src/components/OutputPanel.vue +++ b/packages/editor-ui/src/components/OutputPanel.vue @@ -87,6 +87,7 @@ 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'; type RunDataRef = Vue & { enterEditMode: (args: EnterEditModeArgs) => void }; @@ -113,7 +114,10 @@ export default mixins( }, }, computed: { - ...mapStores(useUIStore), + ...mapStores( + useUIStore, + useWorkflowsStore, + ), node(): INodeUi { return this.$store.getters['ndv/activeNode']; }, @@ -133,14 +137,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.uiStore.isActionActive('workflowRunning'); }, workflowExecution(): IExecutionResponse | null { - return this.$store.getters.getWorkflowExecution; + return this.workflowsStore.getWorkflowExecution; }, workflowRunData(): IRunData | null { if (this.workflowExecution === null) { @@ -153,7 +157,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), @@ -197,7 +201,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; } @@ -219,7 +223,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', @@ -237,7 +241,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', diff --git a/packages/editor-ui/src/components/ParameterInput.vue b/packages/editor-ui/src/components/ParameterInput.vue index 27f9a92b6dd32..b392eb0eb7e70 100644 --- a/packages/editor-ui/src/components/ParameterInput.vue +++ b/packages/editor-ui/src/components/ParameterInput.vue @@ -339,6 +339,8 @@ 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'; export default mixins( externalHooks, @@ -472,6 +474,9 @@ export default mixins( }, }, computed: { + ...mapStores( + useWorkflowsStore, + ), ...mapGetters('credentials', ['allCredentialTypes']), expressionDisplayValue(): string { if (this.activeDrop || this.forceShowExpression) { @@ -742,12 +747,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 }); }, @@ -827,7 +834,7 @@ export default mixins( parameter_name: this.parameter.displayName, parameter_field_type: this.parameter.type, new_expression: !this.isValueExpression, - workflow_id: this.$store.getters.workflowId, + workflow_id: this.workflowsStore.workflowId, session_id: this.$store.getters['ndv/ndvSessionId'], source: this.eventSource || 'ndv', }); @@ -956,7 +963,7 @@ 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, diff --git a/packages/editor-ui/src/components/ParameterInputExpanded.vue b/packages/editor-ui/src/components/ParameterInputExpanded.vue index 64462d315350a..aa4f5902d71d1 100644 --- a/packages/editor-ui/src/components/ParameterInputExpanded.vue +++ b/packages/editor-ui/src/components/ParameterInputExpanded.vue @@ -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, }); }, }, diff --git a/packages/editor-ui/src/components/ResourceLocator/ResourceLocator.vue b/packages/editor-ui/src/components/ResourceLocator/ResourceLocator.vue index eded3b46553ab..bccc70f53f1d1 100644 --- a/packages/editor-ui/src/components/ResourceLocator/ResourceLocator.vue +++ b/packages/editor-ui/src/components/ResourceLocator/ResourceLocator.vue @@ -174,6 +174,8 @@ 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'; interface IResourceLocatorQuery { results: INodeListSearchItems[]; @@ -259,7 +261,11 @@ export default mixins(debounceHelper, workflowHelpers, nodeHelpers).extend({ }; }, computed: { - ...mapStores(useUIStore), + ...mapStores( + useRootStore, + useUIStore, + useWorkflowsStore, + ), appName(): string { if (!this.node) { return ''; @@ -536,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, diff --git a/packages/editor-ui/src/components/RunData.vue b/packages/editor-ui/src/components/RunData.vue index 292df910eda49..67cf6038cf3af 100644 --- a/packages/editor-ui/src/components/RunData.vue +++ b/packages/editor-ui/src/components/RunData.vue @@ -343,6 +343,7 @@ import { IBinaryDisplayData, IExecutionResponse, INodeUi, + INodeUpdatePropertiesInformation, IRunDataDisplayMode, ITab, } from '@/Interface'; @@ -372,6 +373,8 @@ 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"; export type EnterEditModeArgs = { origin: 'editIconButton' | 'insertTestDataLink', @@ -481,6 +484,9 @@ export default mixins( this.eventBus.$off('data-unpinning', this.onDataUnpinning); }, computed: { + ...mapStores( + useWorkflowsStore, + ), activeNode(): INodeUi { return this.$store.getters['ndv/activeNode']; }, @@ -527,7 +533,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); @@ -536,7 +542,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) { @@ -695,7 +701,7 @@ 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, pane: 'output', @@ -771,7 +777,7 @@ export default mixins( } this.$store.commit('ndv/setOutputPanelEditModeEnabled', false); - this.$store.commit('pinData', { node: this.node, data: clearJsonKey(value) }); + this.workflowsStore.pinData({ node: this.node, data: clearJsonKey(value) as INodeExecutionData[] }); this.onDataPinningSuccess({ source: 'save-edit' }); @@ -844,11 +850,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' }); @@ -857,7 +863,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({ @@ -893,7 +899,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', @@ -908,7 +914,7 @@ export default mixins( onCurrentPageChange() { this.$telemetry.track('User changed ndv page', { node_type: this.activeNode.type, - workflow_id: this.$store.getters.workflowId, + workflow_id: this.workflowsStore.workflowId, session_id: this.sessionId, pane: this.paneType, page_selected: this.currentPage, @@ -925,7 +931,7 @@ export default mixins( this.$telemetry.track('User changed ndv page size', { node_type: this.activeNode.type, - workflow_id: this.$store.getters.workflowId, + workflow_id: this.workflowsStore.workflowId, session_id: this.sessionId, pane: this.paneType, page_selected: this.currentPage, @@ -953,7 +959,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, }); @@ -1018,7 +1024,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 { @@ -1088,10 +1094,10 @@ 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() { diff --git a/packages/editor-ui/src/components/RunDataJsonActions.vue b/packages/editor-ui/src/components/RunDataJsonActions.vue index 1cd7ca27d9c98..f9ad5ef78e87c 100644 --- a/packages/editor-ui/src/components/RunDataJsonActions.vue +++ b/packages/editor-ui/src/components/RunDataJsonActions.vue @@ -35,6 +35,8 @@ 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"; type JsonPathData = { path: string; @@ -83,6 +85,9 @@ export default mixins( }, }, computed: { + ...mapStores( + useWorkflowsStore, + ), activeNode(): INodeUi { return this.$store.getters['ndv/activeNode']; }, @@ -189,7 +194,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, }); diff --git a/packages/editor-ui/src/components/RunDataTable.vue b/packages/editor-ui/src/components/RunDataTable.vue index 36c74bdd63101..2f08fb4429251 100644 --- a/packages/editor-ui/src/components/RunDataTable.vue +++ b/packages/editor-ui/src/components/RunDataTable.vue @@ -148,6 +148,8 @@ import { GenericValue, IDataObject, INodeExecutionData } from 'n8n-workflow'; import Draggable from './Draggable.vue'; import { shorten } from './helpers'; import { externalHooks } from './mixins/externalHooks'; +import { mapStores } from 'pinia'; +import { useWorkflowsStore } from '@/stores/workflows'; export default mixins(externalHooks).extend({ name: 'run-data-table', @@ -202,11 +204,14 @@ export default mixins(externalHooks).extend({ } }, computed: { + ...mapStores( + useWorkflowsStore, + ), hoveringItem(): NDVState['ndv']['hoveringItem'] { return this.$store.getters['ndv/hoveringItem']; }, - pairedItemMappings(): IRootState['workflowExecutionPairedItemMappings'] { - return this.$store.getters['workflowExecutionPairedItemMappings']; + pairedItemMappings(): {[itemId: string]: Set} { + return this.workflowsStore.workflowExecutionPairedItemMappings; }, tableData(): ITableData { return this.convertToTable(this.inputData); diff --git a/packages/editor-ui/src/components/Sticky.vue b/packages/editor-ui/src/components/Sticky.vue index d848bee360dd0..486fed26d1511 100644 --- a/packages/editor-ui/src/components/Sticky.vue +++ b/packages/editor-ui/src/components/Sticky.vue @@ -50,14 +50,16 @@ 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'; export default mixins(externalHooks, nodeBase, nodeHelpers, workflowHelpers).extend({ name: 'Sticky', @@ -70,7 +72,10 @@ export default mixins(externalHooks, nodeBase, nodeHelpers, workflowHelpers).ext }, }, computed: { - ...mapStores(useUIStore), + ...mapStores( + useUIStore, + useWorkflowsStore, + ), defaultText (): string { if (!this.nodeType) { return ''; @@ -86,8 +91,8 @@ export default mixins(externalHooks, nodeBase, nodeHelpers, workflowHelpers).ext nodeType (): INodeTypeDescription | null { return this.data && this.$store.getters['nodeTypes/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) { @@ -195,11 +200,12 @@ export default mixins(externalHooks, nodeBase, nodeHelpers, workflowHelpers).ext }; const updateInformation = { + key: this.node.id, name: this.node.name, value: nodeParameters, - }; + } as IUpdateInformation; - this.$store.commit('setNodeParameters', updateInformation); + this.workflowsStore.setNodeParameters(updateInformation); } }, setPosition(position: XYPosition) { @@ -211,10 +217,10 @@ export default mixins(externalHooks, nodeBase, nodeHelpers, workflowHelpers).ext name: this.node.name, properties: { position, - }, - }; + } as IDataObject, + } as INodeUpdatePropertiesInformation; - this.$store.commit('updateNodeProperties', updateInformation); + this.workflowsStore.updateNodeProperties(updateInformation); }, touchStart () { if (this.isTouchDevice === true && this.isMacOs === false && this.isTouchActive === false) { diff --git a/packages/editor-ui/src/components/TriggerPanel.vue b/packages/editor-ui/src/components/TriggerPanel.vue index 5757801557703..728143b7d84d2 100644 --- a/packages/editor-ui/src/components/TriggerPanel.vue +++ b/packages/editor-ui/src/components/TriggerPanel.vue @@ -105,6 +105,7 @@ import { showMessage } from '@/components/mixins/showMessage'; import Vue from 'vue'; import { mapStores } from 'pinia'; import { useUIStore } from '@/stores/ui'; +import { useWorkflowsStore } from '@/stores/workflows'; export default mixins(workflowHelpers, copyPaste, showMessage).extend({ name: 'TriggerPanel', @@ -122,9 +123,12 @@ export default mixins(workflowHelpers, copyPaste, showMessage).extend({ }, }, computed: { - ...mapStores(useUIStore), + ...mapStores( + useUIStore, + useWorkflowsStore, + ), node(): INodeUi | null { - return this.$store.getters.getNodeByName(this.nodeName); + return this.workflowsStore.getNodeByName(this.nodeName); }, nodeType(): INodeTypeDescription | null { if (this.node) { @@ -193,8 +197,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 && @@ -207,12 +211,12 @@ export default mixins(workflowHelpers, copyPaste, showMessage).extend({ 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.isActive; }, header(): string { const serviceName = this.nodeType ? getTriggerNodeServiceName(this.nodeType) : ''; @@ -367,7 +371,7 @@ 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', diff --git a/packages/editor-ui/src/components/VariableSelector.vue b/packages/editor-ui/src/components/VariableSelector.vue index 10cc87dd5e62d..d030455ec1cdf 100644 --- a/packages/editor-ui/src/components/VariableSelector.vue +++ b/packages/editor-ui/src/components/VariableSelector.vue @@ -40,6 +40,9 @@ 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'; // Node types that should not be displayed in variable selector const SKIPPED_NODE_TYPES = [ @@ -64,6 +67,10 @@ export default mixins( }; }, computed: { + ...mapStores( + useRootStore, + useWorkflowsStore, + ), extendAll (): boolean { if (this.variableFilter) { return true; @@ -431,7 +438,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 @@ -492,9 +499,9 @@ export default mixins( 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 +550,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 +684,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); diff --git a/packages/editor-ui/src/components/WorkflowActivator.vue b/packages/editor-ui/src/components/WorkflowActivator.vue index 1fdff23d59b58..1e19cecf3b9fe 100644 --- a/packages/editor-ui/src/components/WorkflowActivator.vue +++ b/packages/editor-ui/src/components/WorkflowActivator.vue @@ -36,11 +36,9 @@ 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( @@ -55,15 +53,18 @@ export default mixins( 'workflowId', ], computed: { - ...mapStores(useUIStore), - ...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.uiStore.activeWorkflows; + const activeWorkflows = this.workflowsStore.activeWorkflows; return activeWorkflows.includes(this.workflowId); }, couldNotBeStarted (): boolean { @@ -76,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; @@ -87,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; }, }, diff --git a/packages/editor-ui/src/components/WorkflowCard.vue b/packages/editor-ui/src/components/WorkflowCard.vue index b2a9e50dd5632..8e0f7c80634ed 100644 --- a/packages/editor-ui/src/components/WorkflowCard.vue +++ b/packages/editor-ui/src/components/WorkflowCard.vue @@ -66,6 +66,7 @@ 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', @@ -111,6 +112,7 @@ export default mixins( useSettingsStore, useUIStore, useUsersStore, + useWorkflowsStore, ), currentUser (): IUser { return this.usersStore.currentUser || {} as IUser; @@ -194,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, diff --git a/packages/editor-ui/src/components/WorkflowSettings.vue b/packages/editor-ui/src/components/WorkflowSettings.vue index 8db2a0238171e..14fbb45dd4ced 100644 --- a/packages/editor-ui/src/components/WorkflowSettings.vue +++ b/packages/editor-ui/src/components/WorkflowSettings.vue @@ -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.rootStore.saveDataErrorExecution; + this.defaultValues.saveDataSuccessExecution = this.rootStore.saveDataSuccessExecution; + this.defaultValues.saveManualExecutions = this.rootStore.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.settingsStore.settings) 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) { @@ -609,9 +627,10 @@ export default mixins( } } - const oldSettings = deepCopy(this.$store.getters.workflowSettings); + const oldSettings = deepCopy(this.settingsStore.settings); - this.$store.commit('setWorkflowSettings', localWorkflowSettings); + // TODO: Check if this is correct + this.settingsStore.setSettings(localWorkflowSettings as unknown as IN8nUISettings); this.isLoading = false; @@ -623,7 +642,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; diff --git a/packages/editor-ui/src/components/mixins/executionsHelpers.ts b/packages/editor-ui/src/components/mixins/executionsHelpers.ts index 7591fda98bc4c..70aaa5e5a05a4 100644 --- a/packages/editor-ui/src/components/mixins/executionsHelpers.ts +++ b/packages/editor-ui/src/components/mixins/executionsHelpers.ts @@ -1,5 +1,7 @@ import { IExecutionsSummary } from "@/Interface"; +import { useWorkflowsStore } from "@/stores/workflows"; import dateFormat from "dateformat"; +import { mapStores } from "pinia"; import mixins from "vue-typed-mixins"; import { genericHelpers } from "./genericHelpers"; @@ -12,20 +14,23 @@ export interface IExecutionUIData { export const executionHelpers = mixins(genericHelpers).extend({ computed: { + ...mapStores( + useWorkflowsStore, + ), executionId(): string { return this.$route.params.executionId; }, 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; }, executions(): IExecutionsSummary[] { - return this.$store.getters['workflows/currentWorkflowExecutions']; + return this.workflowsStore.currentWorkflowExecutions; }, - activeExecution(): IExecutionsSummary { - return this.$store.getters['workflows/getActiveWorkflowExecution']; + activeExecution(): IExecutionsSummary | null { + return this.workflowsStore.activeWorkflowExecution; }, }, methods: { diff --git a/packages/editor-ui/src/components/mixins/nodeHelpers.ts b/packages/editor-ui/src/components/mixins/nodeHelpers.ts index 8c127739013df..80a1a4f80b829 100644 --- a/packages/editor-ui/src/components/mixins/nodeHelpers.ts +++ b/packages/editor-ui/src/components/mixins/nodeHelpers.ts @@ -22,11 +22,13 @@ import { ITaskDataConnections, INode, INodePropertyOptions, + IDataObject, } from 'n8n-workflow'; import { ICredentialsResponse, INodeUi, + INodeUpdatePropertiesInformation, IUser, } from '@/Interface'; @@ -85,7 +87,7 @@ export const nodeHelpers = mixins( // Returns all the issues of the node getNodeIssues (nodeType: INodeTypeDescription | null, node: INodeUi, ignoreIssues?: string[]): INodeIssues | null { - const pinDataNodeNames = Object.keys(this.$store.getters.pinData || {}); + const pinDataNodeNames = Object.keys(this.workflowsStore.getPinData || {}); let nodeIssues: INodeIssues | null = null; ignoreIssues = ignoreIssues || []; @@ -168,10 +170,10 @@ export const nodeHelpers = mixins( // Updates the execution issues. updateNodesExecutionIssues () { - const nodes = this.$store.getters.allNodes; + const nodes = this.workflowsStore.allNodes; for (const node of nodes) { - this.$store.commit('setNodeIssue', { + this.workflowsStore.setNodeIssue({ node: node.name, type: 'execution', value: this.hasNodeExecutionIssues(node) ? true : null, @@ -188,11 +190,11 @@ export const nodeHelpers = mixins( newIssues = fullNodeIssues.credentials!; } - this.$store.commit('setNodeIssue', { + this.workflowsStore.setNodeIssue({ node: node.name, type: 'credentials', value: newIssues, - } as INodeIssueData); + }); }, // Updates the parameter-issues of the node @@ -214,11 +216,11 @@ export const nodeHelpers = mixins( newIssues = fullNodeIssues.parameters!; } - this.$store.commit('setNodeIssue', { + this.workflowsStore.setNodeIssue({ node: node.name, type: 'parameters', value: newIssues, - } as INodeIssueData); + }); }, // Returns all the credential-issues of the node @@ -369,13 +371,13 @@ export const nodeHelpers = mixins( // Updates the node credential issues updateNodesCredentialsIssues () { - const nodes = this.$store.getters.allNodes; + const nodes = this.workflowsStore.allNodes; let issues: INodeIssues | null; for (const node of nodes) { issues = this.getNodeCredentialIssues(node); - this.$store.commit('setNodeIssue', { + this.workflowsStore.setNodeIssue({ node: node.name, type: 'credentials', value: issues === null ? null : issues.credentials, @@ -388,10 +390,10 @@ export const nodeHelpers = mixins( return []; } - if (this.$store.getters.getWorkflowExecution === null) { + if (this.workflowsStore.getWorkflowExecution === null) { return []; } - const executionData: IRunExecutionData = this.$store.getters.getWorkflowExecution.data; + const executionData = this.workflowsStore.getWorkflowExecution.data; if (!executionData || !executionData.resultData) { // unknown status return []; } @@ -448,13 +450,13 @@ export const nodeHelpers = mixins( name: node.name, properties: { disabled: !node.disabled, - }, - }; + } as IDataObject, + } as INodeUpdatePropertiesInformation; - this.$telemetry.track('User set node enabled status', { node_type: node.type, is_enabled: node.disabled, workflow_id: this.$store.getters.workflowId }); + this.$telemetry.track('User set node enabled status', { node_type: node.type, is_enabled: node.disabled, workflow_id: this.workflowsStore.workflowId }); - this.$store.commit('updateNodeProperties', updateInformation); - this.$store.commit('clearNodeExecutionData', node.name); + this.workflowsStore.updateNodeProperties(updateInformation); + this.workflowsStore.clearNodeExecutionData(node.name); this.updateNodeParameterIssues(node); this.updateNodeCredentialIssues(node); } diff --git a/packages/editor-ui/src/components/mixins/pushConnection.ts b/packages/editor-ui/src/components/mixins/pushConnection.ts index 77ff3f69a1834..7b5f270c6090a 100644 --- a/packages/editor-ui/src/components/mixins/pushConnection.ts +++ b/packages/editor-ui/src/components/mixins/pushConnection.ts @@ -1,4 +1,5 @@ import { + IExecutionResponse, IExecutionsCurrentSummaryExtended, IPushData, } from '../../Interface'; @@ -24,6 +25,7 @@ import { getTriggerNodeServiceName } from '../helpers'; import { codeNodeEditorEventBus } from '@/event-bus/code-node-editor-event-bus'; import { mapStores } from 'pinia'; import { useUIStore } from '@/stores/ui'; +import { useWorkflowsStore } from '@/stores/workflows'; export const pushConnection = mixins( externalHooks, @@ -42,9 +44,12 @@ export const pushConnection = mixins( }; }, computed: { - ...mapStores(useUIStore), + ...mapStores( + useUIStore, + useWorkflowsStore, + ), sessionId (): string { - return this.$store.getters.sessionId; + return this.rootStore.sessionId; }, }, methods: { @@ -66,13 +71,13 @@ export const pushConnection = mixins( // always removed that we do not end up with multiple ones this.pushDisconnect(); - const connectionUrl = `${this.$store.getters.getRestUrl}/push?sessionId=${this.sessionId}`; + const connectionUrl = `${this.rootStore.getRestUrl}/push?sessionId=${this.sessionId}`; this.eventSource = new EventSource(connectionUrl, { withCredentials: true }); this.eventSource.addEventListener('message', this.pushMessageReceived, false); this.eventSource.addEventListener('open', () => { - this.$store.commit('setPushConnectionActive', true); + this.rootStore.pushConnectionActive = true; if (this.reconnectTimeout !== null) { clearTimeout(this.reconnectTimeout); this.reconnectTimeout = null; @@ -87,7 +92,7 @@ export const pushConnection = mixins( this.reconnectTimeout = null; } - this.$store.commit('setPushConnectionActive', false); + this.rootStore.pushConnectionActive = false; this.pushAutomaticReconnect(); }, false); }, @@ -100,7 +105,7 @@ export const pushConnection = mixins( this.eventSource.close(); this.eventSource = null; - this.$store.commit('setPushConnectionActive', false); + this.rootStore.pushConnectionActive = false; } }, @@ -186,7 +191,7 @@ export const pushConnection = mixins( return false; } const pushData = receivedData.data; - if (this.$store.getters.activeExecutionId !== pushData.executionId) { + if (this.workflowsStore.executionId !== pushData.executionId) { // The data is not for the currently active execution or // we do not have the execution id yet. if (isRetry !== true) { @@ -207,7 +212,7 @@ export const pushConnection = mixins( return false; } - if (this.$store.getters.activeExecutionId !== pushData.executionId) { + if (this.workflowsStore.executionId !== pushData.executionId) { // The workflow which did finish execution did either not get started // by this session or we do not have the execution id yet. if (isRetry !== true) { @@ -230,18 +235,16 @@ export const pushConnection = mixins( const workflow = this.getCurrentWorkflow(); if (runDataExecuted.waitTill !== undefined) { - const { - activeExecutionId, - workflowSettings, - saveManualExecutions, - } = this.$store.getters; + const activeExecutionId = this.workflowsStore.executionId; + const workflowSettings = this.workflowsStore.workflowSettings; + const saveManualExecutions = this.rootStore.saveManualExecutions; const isSavingExecutions= workflowSettings.saveManualExecutions === undefined ? saveManualExecutions : workflowSettings.saveManualExecutions; let action; if (!isSavingExecutions) { this.$root.$emit('registerGlobalLinkAction', 'open-settings', async () => { - if (this.$store.getters.isNewWorkflow) await this.saveAsNewWorkflow(); + if (this.workflowsStore.isNewWorkflow) await this.saveAsNewWorkflow(); this.uiStore.openModal(WORKFLOW_SETTINGS_MODAL_KEY); }); @@ -275,7 +278,7 @@ export const pushConnection = mixins( error_title: error.message, error_type: error.context.type, node_graph_string: JSON.stringify(TelemetryHelpers.generateNodesGraph(workflowData as IWorkflowBase, this.getNodeTypes()).nodeGraph), - workflow_id: this.$store.getters.workflowId, + workflow_id: this.workflowsStore.workflowId, }; if (error.context.nodeCause && ['no pairing info', 'invalid pairing info'].includes(error.context.type as string)) { @@ -298,7 +301,7 @@ export const pushConnection = mixins( if (runDataExecuted.data.resultData.error?.name === 'SubworkflowOperationError') { const error = runDataExecuted.data.resultData.error as SubworkflowOperationError; - this.$store.commit('setSubworkflowExecutionError', error); + this.workflowsStore.subworkflowExecutionError = error; this.$showMessage({ title: error.message, @@ -327,9 +330,9 @@ export const pushConnection = mixins( // Workflow did execute without a problem this.$titleSet(workflow.name as string, 'IDLE'); - const execution = this.$store.getters.getWorkflowExecution; + const execution = this.workflowsStore.getWorkflowExecution; if (execution && execution.executedNode) { - const node = this.$store.getters.getNodeByName(execution.executedNode); + const node = this.workflowsStore.getNodeByName(execution.executedNode); const nodeType = node && this.$store.getters['nodeTypes/getNodeType'](node.type, node.typeVersion); const nodeOutput = execution && execution.executedNode && execution.data && execution.data.resultData && execution.data.resultData.runData && execution.data.resultData.runData[execution.executedNode]; if (node && nodeType && !nodeOutput) { @@ -364,10 +367,12 @@ export const pushConnection = mixins( // It does not push the runData as it got already pushed with each // node that did finish. For that reason copy in here the data // which we already have. - runDataExecuted.data.resultData.runData = this.$store.getters.getWorkflowRunData; + if (this.workflowsStore.getWorkflowRunData) { + runDataExecuted.data.resultData.runData = this.workflowsStore.getWorkflowRunData; + } - this.$store.commit('setExecutingNode', null); - this.$store.commit('setWorkflowExecutionData', runDataExecuted); + this.workflowsStore.executingNode = null; + this.workflowsStore.setWorkflowExecutionData(runDataExecuted as IExecutionResponse); this.uiStore.removeActiveAction('workflowRunning'); // Set the node execution issues on all the nodes which produced an error so that @@ -400,30 +405,30 @@ export const pushConnection = mixins( workflowName: pushData.workflowName, }; - this.uiStore.addActiveExecution(executionData); + this.workflowsStore.addActiveExecution(executionData); } else if (receivedData.type === 'nodeExecuteAfter') { // A node finished to execute. Add its data const pushData = receivedData.data; - this.$store.commit('addNodeExecutionData', pushData); + this.workflowsStore.addNodeExecutionData(pushData); } else if (receivedData.type === 'nodeExecuteBefore') { // A node started to be executed. Set it as executing. const pushData = receivedData.data; - this.$store.commit('setExecutingNode', pushData.nodeName); + this.workflowsStore.executingNode = pushData.nodeName; } else if (receivedData.type === 'testWebhookDeleted') { // A test-webhook was deleted const pushData = receivedData.data; - if (pushData.workflowId === this.$store.getters.workflowId) { - this.$store.commit('setExecutionWaitingForWebhook', false); + if (pushData.workflowId === this.workflowsStore.workflowId) { + this.workflowsStore.executionWaitingForWebhook = false; this.uiStore.removeActiveAction('workflowRunning'); } } else if (receivedData.type === 'testWebhookReceived') { // A test-webhook did get called const pushData = receivedData.data; - if (pushData.workflowId === this.$store.getters.workflowId) { - this.$store.commit('setExecutionWaitingForWebhook', false); - this.$store.commit('setActiveExecutionId', pushData.executionId); + if (pushData.workflowId === this.workflowsStore.workflowId) { + this.workflowsStore.executionWaitingForWebhook = false; + this.workflowsStore.executionId = pushData.executionId; } this.processWaitingPushMessages(); diff --git a/packages/editor-ui/src/components/mixins/showMessage.ts b/packages/editor-ui/src/components/mixins/showMessage.ts index 473fc85550f02..b76e0c88b0a4b 100644 --- a/packages/editor-ui/src/components/mixins/showMessage.ts +++ b/packages/editor-ui/src/components/mixins/showMessage.ts @@ -7,10 +7,17 @@ import { IRunExecutionData } from 'n8n-workflow'; import type { ElMessageBoxOptions } from 'element-ui/types/message-box'; import type { ElMessageComponent, ElMessageOptions, MessageType } from 'element-ui/types/message'; import { sanitizeHtml } from '@/utils'; +import { mapStores } from 'pinia'; +import { useWorkflowsStore } from '@/stores/workflows'; let stickyNotificationQueue: ElNotificationComponent[] = []; export const showMessage = mixins(externalHooks).extend({ + computed: { + ...mapStores( + useWorkflowsStore, + ), + }, methods: { $showMessage( messageData: Omit & { message?: string }, @@ -34,7 +41,7 @@ export const showMessage = mixins(externalHooks).extend({ error_title: messageData.title, error_message: messageData.message, caused_by_credential: this.causedByCredential(messageData.message), - workflow_id: this.$store.getters.workflowId, + workflow_id: this.workflowsStore.workflowId, }); } @@ -134,7 +141,7 @@ export const showMessage = mixins(externalHooks).extend({ error_description: message, error_message: error.message, caused_by_credential: this.causedByCredential(error.message), - workflow_id: this.$store.getters.workflowId, + workflow_id: this.workflowsStore.workflowId, }); }, diff --git a/packages/editor-ui/src/components/mixins/workflowActivate.ts b/packages/editor-ui/src/components/mixins/workflowActivate.ts index bfc605217c8a2..5133aaee5a478 100644 --- a/packages/editor-ui/src/components/mixins/workflowActivate.ts +++ b/packages/editor-ui/src/components/mixins/workflowActivate.ts @@ -8,6 +8,7 @@ import { LOCAL_STORAGE_ACTIVATION_FLAG, PLACEHOLDER_EMPTY_WORKFLOW_ID, WORKFLOW_ import { mapStores } from 'pinia'; import { useUIStore } from '@/stores/ui'; import { useSettingsStore } from '@/stores/settings'; +import { useWorkflowsStore } from '@/stores/workflows'; export const workflowActivate = mixins( externalHooks, @@ -24,16 +25,17 @@ export const workflowActivate = mixins( ...mapStores( useSettingsStore, useUIStore, + useWorkflowsStore, ), }, methods: { async activateCurrentWorkflow(telemetrySource?: string) { - const workflowId = this.$store.getters.workflowId; + const workflowId = this.workflowsStore.workflowId; return this.updateWorkflowActivation(workflowId, true, telemetrySource); }, async updateWorkflowActivation(workflowId: string | undefined, newActiveState: boolean, telemetrySource?: string) { this.updatingWorkflowActivation = true; - const nodesIssuesExist = this.$store.getters.nodesIssuesExist as boolean; + const nodesIssuesExist = this.workflowsStore.nodesIssuesExist as boolean; let currWorkflowId: string | undefined = workflowId; if (!currWorkflowId || currWorkflowId === PLACEHOLDER_EMPTY_WORKFLOW_ID) { @@ -42,11 +44,11 @@ export const workflowActivate = mixins( this.updatingWorkflowActivation = false; return; } - currWorkflowId = this.$store.getters.workflowId as string; + currWorkflowId = this.workflowsStore.workflowId as string; } - const isCurrentWorkflow = currWorkflowId === this.$store.getters['workflowId']; + const isCurrentWorkflow = currWorkflowId === this.workflowsStore.workflowId; - const activeWorkflows = this.uiStore.activeWorkflows; + const activeWorkflows = this.workflowsStore.activeWorkflows; const isWorkflowActive = activeWorkflows.includes(currWorkflowId); const telemetryPayload = { diff --git a/packages/editor-ui/src/components/mixins/workflowRun.ts b/packages/editor-ui/src/components/mixins/workflowRun.ts index be46e106ae8c5..9af469becb387 100644 --- a/packages/editor-ui/src/components/mixins/workflowRun.ts +++ b/packages/editor-ui/src/components/mixins/workflowRun.ts @@ -21,6 +21,8 @@ import mixins from 'vue-typed-mixins'; import { titleChange } from './titleChange'; import { mapStores } from 'pinia'; import { useUIStore } from '@/stores/ui'; +import { useWorkflowsStore } from '@/stores/workflows'; +import { useRootStore } from '@/stores/n8nRootStore'; export const workflowRun = mixins( externalHooks, @@ -30,12 +32,16 @@ export const workflowRun = mixins( titleChange, ).extend({ computed: { - ...mapStores(useUIStore), + ...mapStores( + useRootStore, + useUIStore, + useWorkflowsStore, + ), }, methods: { // Starts to executes a workflow on server. async runWorkflowApi (runData: IStartRunData): Promise { - if (this.$store.getters.pushConnectionActive === false) { + if (this.rootStore.pushConnectionActive === false) { // Do not start if the connection to server is not active // because then it can not receive the data as it executes. throw new Error( @@ -43,7 +49,7 @@ export const workflowRun = mixins( ); } - this.$store.commit('setSubworkflowExecutionError', null); + this.workflowsStore.subworkflowExecutionError = null; this.uiStore.addActiveAction('workflowRunning'); @@ -57,11 +63,11 @@ export const workflowRun = mixins( } if (response.executionId !== undefined) { - this.$store.commit('setActiveExecutionId', response.executionId); + this.workflowsStore.executionId = response.executionId; } if (response.waitingForWebhook === true) { - this.$store.commit('setExecutionWaitingForWebhook', true); + this.workflowsStore.executionWaitingForWebhook = true; } return response; @@ -79,7 +85,7 @@ export const workflowRun = mixins( try { // Check first if the workflow has any issues before execute it - const issuesExist = this.$store.getters.nodesIssuesExist; + const issuesExist = this.workflowsStore.nodesIssuesExist; if (issuesExist === true) { // If issues exist get all of the issues of all nodes const workflowIssues = this.checkReadyForExecution(workflow, nodeName); @@ -94,7 +100,7 @@ export const workflowRun = mixins( for (const nodeName of Object.keys(workflowIssues)) { nodeIssues = NodeHelpers.nodeIssuesToString(workflowIssues[nodeName]); let issueNodeType = 'UNKNOWN'; - const issueNode = this.$store.getters.getNodeByName(nodeName); + const issueNode = this.workflowsStore.getNodeByName(nodeName); if (issueNode) { issueNodeType = issueNode.type; @@ -143,7 +149,7 @@ export const workflowRun = mixins( directParentNodes = workflow.getParentNodes(nodeName, 'main', 1); } - const runData = this.$store.getters.getWorkflowRunData; + const runData = this.workflowsStore.getWorkflowRunData; let newRunData: IRunData | undefined; @@ -186,8 +192,8 @@ export const workflowRun = mixins( startNodes.push(nodeName); } - const isNewWorkflow = this.$store.getters.isNewWorkflow; - const hasWebhookNode = this.$store.getters.currentWorkflowHasWebhookNode; + const isNewWorkflow = this.workflowsStore.isNewWorkflow; + const hasWebhookNode = this.workflowsStore.currentWorkflowHasWebhookNode; if (isNewWorkflow && hasWebhookNode) { await this.saveCurrentWorkflow(); } @@ -224,7 +230,7 @@ export const workflowRun = mixins( }, } as IRunExecutionData, workflowData: { - id: this.$store.getters.workflowId, + id: this.workflowsStore.workflowId, name: workflowData.name!, active: workflowData.active!, createdAt: 0, @@ -232,7 +238,7 @@ export const workflowRun = mixins( ...workflowData, }, }; - this.$store.commit('setWorkflowExecutionData', executionData); + this.workflowsStore.setWorkflowExecutionData(executionData); this.updateNodesExecutionIssues(); const runWorkflowApiResponse = await this.runWorkflowApi(startRunData); diff --git a/packages/editor-ui/src/modules/tags.ts b/packages/editor-ui/src/modules/tags.ts index 52063607c6375..5b1ae3fbe2b9b 100644 --- a/packages/editor-ui/src/modules/tags.ts +++ b/packages/editor-ui/src/modules/tags.ts @@ -6,6 +6,7 @@ import { } from '../Interface'; import { createTag, deleteTag, getTags, updateTag } from '../api/tags'; import Vue from 'vue'; +import { useWorkflowsStore } from '@/stores/workflows'; const module: Module = { namespaced: true, @@ -94,7 +95,8 @@ const module: Module = { if (deleted) { context.commit('deleteTag', id); - context.commit('removeWorkflowTagId', id, {root: true}); + const workflowsStore = useWorkflowsStore(); + workflowsStore.removeWorkflowTagId(id); } return deleted; diff --git a/packages/editor-ui/src/modules/templates.ts b/packages/editor-ui/src/modules/templates.ts index 8871fb033cd34..9f39deb346634 100644 --- a/packages/editor-ui/src/modules/templates.ts +++ b/packages/editor-ui/src/modules/templates.ts @@ -15,6 +15,7 @@ import { import Vue from 'vue'; import { useRootStore } from '@/stores/n8nRootStore'; import { useSettingsStore } from '@/stores/settings'; +import { useWorkflowsStore } from '@/stores/workflows'; const TEMPLATES_PAGE_SIZE = 10; @@ -188,7 +189,6 @@ const module: Module = { ...response.workflow, full: true, }; - context.commit('addWorkflows', [template]); return template; }, diff --git a/packages/editor-ui/src/stores/settings.ts b/packages/editor-ui/src/stores/settings.ts index 46a540d9d0f79..4abecc5ac0e99 100644 --- a/packages/editor-ui/src/stores/settings.ts +++ b/packages/editor-ui/src/stores/settings.ts @@ -2,7 +2,7 @@ import { createApiKey, deleteApiKey, getApiKey } from "@/api/api-keys"; import { getPromptsData, getSettings, submitContactInfo, submitValueSurvey } from "@/api/settings"; import { testHealthEndpoint } from "@/api/templates"; import { CONTACT_PROMPT_MODAL_KEY, EnterpriseEditionFeature, STORES, VALUE_SURVEY_MODAL_KEY } from "@/constants"; -import { ILogLevel, IN8nPromptResponse, IN8nPrompts, IN8nUISettings, IN8nValueSurveyData, ISettingsState } from "@/Interface"; +import { ILogLevel, IN8nPromptResponse, IN8nPrompts, IN8nUISettings, IN8nValueSurveyData, ISettingsState, WorkflowCallerPolicyDefaultOption } from "@/Interface"; import { ITelemetrySettings } from "n8n-workflow"; import { defineStore } from "pinia"; import Vue from "vue"; @@ -103,6 +103,12 @@ export const useSettingsStore = defineStore(STORES.SETTINGS, { isQueueModeEnabled(): boolean { return this.settings.executionMode === 'queue'; }, + isWorkflowSharingEnabled(): boolean { + return this.settings.isWorkflowSharingEnabled; + }, + workflowCallerPolicyDefaultOption(): WorkflowCallerPolicyDefaultOption { + return this.settings.workflowCallerPolicyDefaultOption; + }, }, actions: { setSettings(settings: IN8nUISettings): void { diff --git a/packages/editor-ui/src/stores/workflows.ts b/packages/editor-ui/src/stores/workflows.ts index e76df933bfd8b..4d9278640d167 100644 --- a/packages/editor-ui/src/stores/workflows.ts +++ b/packages/editor-ui/src/stores/workflows.ts @@ -40,56 +40,22 @@ export const useWorkflowsStore = defineStore(STORES.WORKFLOWS, { nodeMetadata: {}, }), getters: { - // ------------------------------------------------------------------------- - // TODO: Rearrange this (adn actions) so order makes sense - // ------------------------------------------------------------------------- - - - // currentWorkflowExecutions(): IExecutionsSummary[] { - // return this.currentWorkflowExecutions; - // }, - // getActiveWorkflowExecution(state: IWorkflowsState): IExecutionsSummary|null { - // return this.activeWorkflowExecution; - // }, - // getTotalFinishedExecutionsCount() : number { - // return this.finishedExecutionsCount; - // }, - // workflowExecutionPairedItemMappings() : {[itemId: string]: Set} { - // return this.workflowExecutionPairedItemMappings; - // }, - // subworkflowExecutionError() : Error | null { - // return this.subworkflowExecutionError; - // }, - // getActiveExecutions(): IExecutionsCurrentSummaryExtended[] { - // return this.activeExecutions; - // }, - // getActiveWorkflows(s) : string[] { - // return this.activeWorkflows; - // }, - // executingNode(): string | null { - // return this.executingNode; - // }, - // activeExecutionId() : string | null { - // return this.executionId; - // }, - // executionWaitingForWebhook(): boolean { - // return this.executionWaitingForWebhook; - // }, - // currentWorkflowExecutions(): IExecutionsSummary[] { - // return this.currentWorkflowExecutions; - // }, - // getActiveWorkflowExecution(): IExecutionsSummary|null { - // return this.activeWorkflowExecution; - // }, - // setCurrentWorkflowExecutions (, executions: IExecutionsSummary[]) { - // this.currentWorkflowExecutions = executions; - // }, - // setActiveWorkflowExecution (executionData: IExecutionsSummary): void { - // this.activeWorkflowExecution = executionData; - // }, - // setTotalFinishedExecutionsCount (count: number): void { - // this.finishedExecutionsCount = count; - // }, + // Workflow getters + workflowName(): string { + return this.workflow.name; + }, + workflowId(): string { + return this.workflow.id; + }, + workflowSettings() : IWorkflowSettings { + if (this.workflow.settings === undefined) { + return {}; + } + return this.workflow.settings; + }, + workflowTags() : string[] { + return this.workflow.tags as string[]; + }, allWorkflows() : IWorkflowDb[] { return Object.values(this.workflowsById) .sort((a, b) => a.name.localeCompare(b.name)); @@ -100,6 +66,37 @@ export const useWorkflowsStore = defineStore(STORES.WORKFLOWS, { isActive(): boolean { return this.workflow.active; }, + workflowTriggerNodes() : INodeUi[] { + return this.workflow.nodes.filter((node: INodeUi) => { + // TODO: Waiting for nodeType store migration... + // const nodeType = getters['nodeTypes/getNodeType'](node.type, node.typeVersion); + // return nodeType && nodeType.group.includes('trigger'); + }); + }, + currentWorkflowHasWebhookNode(): boolean { + return !!this.workflow.nodes.find((node: INodeUi) => !!node.webhookId); + }, + getWorkflowRunData(): IRunData | null { + if (!this.workflowExecutionData || !this.workflowExecutionData.data || !this.workflowExecutionData.data.resultData) { + return null; + } + return this.workflowExecutionData.data.resultData.runData; + }, + getWorkflowResultDataByNodeName() { + return (nodeName: string): ITaskData[] | null => { + const workflowRunData = this.getWorkflowRunData;; + + if (workflowRunData === null) { + return null; + } + if (!workflowRunData.hasOwnProperty(nodeName)) { + return null; + } + return workflowRunData[nodeName]; + }; + }, + + // Node getters allConnections() : IConnections { return this.workflow.connections; }, @@ -135,62 +132,6 @@ export const useWorkflowsStore = defineStore(STORES.WORKFLOWS, { } return false; }, - workflowTriggerNodes() : INodeUi[] { - return this.workflow.nodes.filter((node: INodeUi) => { - // TODO: Waiting for nodeType store migration... - // const nodeType = getters['nodeTypes/getNodeType'](node.type, node.typeVersion); - // return nodeType && nodeType.group.includes('trigger'); - }); - }, - currentWorkflowHasWebhookNode(): boolean { - return !!this.workflow.nodes.find((node: INodeUi) => !!node.webhookId); - }, - getExecutionDataById() { - return (id: string) => this.currentWorkflowExecutions.find(execution => execution.id === id); - }, - getAllLoadedFinishedExecutions() : IExecutionsSummary[] { - return this.currentWorkflowExecutions.filter(ex => ex.finished === true || ex.stoppedAt !== undefined); - }, - workflowName(): string { - return this.workflow.name; - }, - workflowId(): string { - return this.workflow.id; - }, - workflowSettings() : IWorkflowSettings { - if (this.workflow.settings === undefined) { - return {}; - } - return this.workflow.settings; - }, - workflowTags() : string[] { - return this.workflow.tags as string[]; - }, - getWorkflowExecution() : IExecutionResponse | null { - return this.workflowExecutionData; - }, - getWorkflowRunData() : IRunData | null { - if (!this.workflowExecutionData || !this.workflowExecutionData.data || !this.workflowExecutionData.data.resultData) { - return null; - } - return this.workflowExecutionData.data.resultData.runData; - }, - getWorkflowResultDataByNodeName() { - return (nodeName: string): ITaskData[] | null => { - const workflowRunData = this.getWorkflowRunData;; - - if (workflowRunData === null) { - return null; - } - if (!workflowRunData.hasOwnProperty(nodeName)) { - return null; - } - return workflowRunData[nodeName]; - }; - }, - getTotalFinishedExecutionsCount() : number { - return this.finishedExecutionsCount; - }, getPinData(): IPinData | undefined { return this.workflow.pinData; }, @@ -206,59 +147,97 @@ export const useWorkflowsStore = defineStore(STORES.WORKFLOWS, { return acc; }, 0); }, + executedNode(): string | undefined { + return this.workflowExecutionData ? this.workflowExecutionData.executedNode : undefined; + }, + getParametersLastUpdate(): ((name: string) => number | undefined) { + return (nodeName: string) => this.nodeMetadata[nodeName] && this.nodeMetadata[nodeName].parametersLastUpdatedAt; + }, + + // Executions getters + getExecutionDataById(): (id: string) => IExecutionsSummary | undefined { + return (id: string): IExecutionsSummary | undefined => this.currentWorkflowExecutions.find(execution => execution.id === id); + }, + getAllLoadedFinishedExecutions(): IExecutionsSummary[] { + return this.currentWorkflowExecutions.filter(ex => ex.finished === true || ex.stoppedAt !== undefined); + }, + getWorkflowExecution(): IExecutionResponse | null { + return this.workflowExecutionData; + }, + getTotalFinishedExecutionsCount(): number { + return this.finishedExecutionsCount; + }, }, actions: { - // setSubworkflowExecutionError(subworkflowExecutionError: Error | null) { - // this.subworkflowExecutionError = subworkflowExecutionError; - // }, - // setActiveWorkflows(newActiveWorkflows: string[]) : void { - // this.activeWorkflows = newActiveWorkflows; - // }, - // setExecutingNode(executingNode: string) { - // this.executingNode = executingNode; - // }, - // setExecutionWaitingForWebhook(newWaiting: boolean) { - // this.executionWaitingForWebhook = newWaiting; - // }, - // setActiveExecutionId(executionId: string | null) { - // this.executionId = executionId; - // }, - addActiveExecution(newActiveExecution: IExecutionsCurrentSummaryExtended) : void { - // Check if the execution exists already - const activeExecution = this.activeExecutions.find(execution => { - return execution.id === newActiveExecution.id; - }); - if (activeExecution !== undefined) { - // Exists already so no need to add it again - if (activeExecution.workflowName === undefined) { - activeExecution.workflowName = newActiveExecution.workflowName; - } - return; - } - this.activeExecutions.unshift(newActiveExecution); + // Workflow actions + + async fetchAllWorkflows(): Promise { + const rootStore = useRootStore(); + const workflows = await getWorkflows(rootStore.getRestApiContext); + this.setWorkflows(workflows); + return workflows; }, - finishActiveExecution(finishedActiveExecution: IPushDataExecutionFinished) : void { - // Find the execution to set to finished - const activeExecution = this.activeExecutions.find(execution => { - return execution.id === finishedActiveExecution.executionId; - }); - if (activeExecution === undefined) { - // The execution could not be found - return; + async getNewWorkflowData(name?: string): Promise { + let workflowData = { + name: '', + onboardingFlowEnabled: false, + }; + try { + const rootStore = useRootStore(); + workflowData = await getNewWorkflow(rootStore.getRestApiContext, name); } - - if (finishedActiveExecution.executionId !== undefined) { - Vue.set(activeExecution, 'id', finishedActiveExecution.executionId); + catch (e) { + // in case of error, default to original name + workflowData.name = name || DEFAULT_NEW_WORKFLOW_NAME; } - Vue.set(activeExecution, 'finished', finishedActiveExecution.data.finished); - Vue.set(activeExecution, 'stoppedAt', finishedActiveExecution.data.stoppedAt); + this.setWorkflowName({ newName: workflowData.name, setStateDirty: false }); + return workflowData; }, - setActiveExecutions(newActiveExecutions: IExecutionsCurrentSummaryExtended[]) : void { - Vue.set(this, 'activeExecutions', newActiveExecutions); + + setWorkflowId (id: string): void { + this.workflow.id = id === 'new' ? PLACEHOLDER_EMPTY_WORKFLOW_ID : id; + }, + + setWorkflowName(data: { newName: string, setStateDirty: boolean }): void { + if (data.setStateDirty === true) { + const uiStore = useUIStore(); + uiStore.stateIsDirty = true; + } + this.workflow.name = data.newName; + }, + + // replace invalid credentials in workflow + replaceInvalidWorkflowCredentials(data: {credentials: INodeCredentialsDetails, invalid: INodeCredentialsDetails, type: string}): void { + this.workflow.nodes.forEach((node : INodeUi) => { + const nodeCredentials: INodeCredentials | undefined = (node as unknown as INode).credentials; + + if (!nodeCredentials || !nodeCredentials[data.type]) { + return; + } + + const nodeCredentialDetails: INodeCredentialsDetails | string = nodeCredentials[data.type]; + + if (typeof nodeCredentialDetails === 'string' && nodeCredentialDetails === data.invalid.name) { + (node.credentials as INodeCredentials)[data.type] = data.credentials; + return; + } + + if (nodeCredentialDetails.id === null) { + if (nodeCredentialDetails.name === data.invalid.name) { + (node.credentials as INodeCredentials)[data.type] = data.credentials; + } + return; + } + + if (nodeCredentialDetails.id === data.invalid.id) { + (node.credentials as INodeCredentials)[data.type] = data.credentials; + } + }); }, + setWorkflows(workflows: IWorkflowDb[]) : void { this.workflowsById = workflows.reduce((acc, workflow: IWorkflowDb) => { if (workflow.id) { @@ -268,13 +247,16 @@ export const useWorkflowsStore = defineStore(STORES.WORKFLOWS, { return acc; }, {}); }, + deleteWorkflow(id: string) : void { const { [id]: deletedWorkflow, ...workflows } = this.workflowsById; this.workflowsById = workflows; }, + addWorkflow(workflow: IWorkflowDb) : void { Vue.set(this.workflowsById, workflow.id, workflow); }, + setWorkflowActive(workflowId: string): void { const uiStore = useUIStore(); uiStore.stateIsDirty = false; @@ -283,21 +265,132 @@ export const useWorkflowsStore = defineStore(STORES.WORKFLOWS, { this.activeWorkflows.push(workflowId); } }, + setWorkflowInactive(workflowId: string): void { const index = this.activeWorkflows.indexOf(workflowId); if (index !== -1) { this.activeWorkflows.splice(index, 1); } }, - async fetchActiveWorkflows (): Promise { + + async fetchActiveWorkflows(): Promise { const rootStore = useRootStore(); const activeWorkflows = await getActiveWorkflows(rootStore.getRestApiContext); this.activeWorkflows = activeWorkflows; return activeWorkflows; }, + setActive(newActive: boolean) : void { this.workflow.active = newActive; }, + + async getDuplicateCurrentWorkflowName(currentWorkflowName: string): Promise { + if (currentWorkflowName && (currentWorkflowName.length + DUPLICATE_POSTFFIX.length) >= MAX_WORKFLOW_NAME_LENGTH) { + return currentWorkflowName; + } + + let newName = `${currentWorkflowName}${DUPLICATE_POSTFFIX}`; + try { + const rootStore = useRootStore(); + const newWorkflow = await getNewWorkflow(rootStore.getRestApiContext, newName ); + newName = newWorkflow.name; + } + catch (e) { + } + return newName; + }, + + + // Node actions + + + setWorkflowExecutionData(workflowResultData: IExecutionResponse | null): void { + this.workflowExecutionData = workflowResultData; + this.workflowExecutionPairedItemMappings = getPairedItemsMapping(this.workflowExecutionData); + }, + + setWorkflowSettings(workflowSettings: IWorkflowSettings): void { + Vue.set(this.workflow, 'settings', workflowSettings); + }, + + setWorkflowPinData(pinData: IPinData): void { + Vue.set(this.workflow, 'pinData', pinData || {}); + dataPinningEventBus.$emit('pin-data', pinData || {}); + }, + + setWorkflowTagIds(tags: string[]): void { + Vue.set(this.workflow, 'tags', tags); + }, + + addWorkflowTagIds(tags: string[]): void { + Vue.set(this.workflow, 'tags', [...new Set([...(this.workflow.tags || []), ...tags])]); + }, + + removeWorkflowTagId(tagId: string): void { + const tags = this.workflow.tags as string[]; + const updated = tags.filter((id: string) => id !== tagId); + Vue.set(this.workflow, 'tags', updated); + }, + + setWorkflow(workflow: IWorkflowDb): void { + Vue.set(this, 'workflow', workflow); + + if (!this.workflow.hasOwnProperty('active')) { + Vue.set(this.workflow, 'active', false); + } + if (!this.workflow.hasOwnProperty('connections')) { + Vue.set(this.workflow, 'connections', {}); + } + if (!this.workflow.hasOwnProperty('createdAt')) { + Vue.set(this.workflow, 'createdAt', -1); + } + if (!this.workflow.hasOwnProperty('updatedAt')) { + Vue.set(this.workflow, 'updatedAt', -1); + } + if (!this.workflow.hasOwnProperty('id')) { + Vue.set(this.workflow, 'id', PLACEHOLDER_EMPTY_WORKFLOW_ID); + } + if (!this.workflow.hasOwnProperty('nodes')) { + Vue.set(this.workflow, 'nodes', []); + } + if (!this.workflow.hasOwnProperty('settings')) { + Vue.set(this.workflow, 'settings', {}); + } + }, + + pinData(payload: { node: INodeUi, data: INodeExecutionData[] }): void { + if (!this.workflow.pinData) { + Vue.set(this.workflow, 'pinData', {}); + } + + if (!Array.isArray(payload.data)) { + payload.data = [payload.data]; + } + + const storedPinData = payload.data.map(item => isJsonKeyObject(item) ? item : { json: item }); + + Vue.set(this.workflow.pinData!, payload.node.name, storedPinData); + + const uiStore = useUIStore(); + uiStore.stateIsDirty = true; + + dataPinningEventBus.$emit('pin-data', { [payload.node.name]: storedPinData }); + }, + + unpinData(payload: { node: INodeUi }): void { + if (!this.workflow.pinData) { + Vue.set(this.workflow, 'pinData', {}); + } + + Vue.set(this.workflow.pinData!, payload.node.name, undefined); + delete this.workflow.pinData![payload.node.name]; + + const uiStore = useUIStore(); + uiStore.stateIsDirty = true; + + dataPinningEventBus.$emit('unpin-data', { [payload.node.name]: undefined }); + }, + addConnection(data: { connection: IConnection[], setStateDirty: boolean }): void { if (data.connection.length !== 2) { // All connections need two entries @@ -344,6 +437,7 @@ export const useWorkflowsStore = defineStore(STORES.WORKFLOWS, { this.workflow.connections[sourceData.node][sourceData.type][sourceData.index].push(destinationData); } }, + removeConnection(data: { connection: IConnection[] }): void { const sourceData = data.connection[0]; const destinationData = data.connection[1]; @@ -368,6 +462,7 @@ export const useWorkflowsStore = defineStore(STORES.WORKFLOWS, { } } }, + removeAllConnections(data: { setStateDirty: boolean }): void { if (data && data.setStateDirty === true) { const uiStore = useUIStore(); @@ -375,6 +470,7 @@ export const useWorkflowsStore = defineStore(STORES.WORKFLOWS, { } this.workflow.connections = {}; }, + removeAllNodeConnection(node: INodeUi): void { const uiStore = useUIStore(); uiStore.stateIsDirty = true; @@ -404,6 +500,7 @@ export const useWorkflowsStore = defineStore(STORES.WORKFLOWS, { } } }, + renameNodeSelectedAndExecution(nameData: { old: any, new: any }): void { const uiStore = useUIStore(); uiStore.stateIsDirty = true; @@ -429,12 +526,14 @@ export const useWorkflowsStore = defineStore(STORES.WORKFLOWS, { this.workflowExecutionPairedItemMappings = getPairedItemsMapping(this.workflowExecutionData); }, + resetAllNodesIssues(): boolean { this.workflow.nodes.forEach((node) => { node.issues = undefined; }); return true; }, + setNodeIssue(nodeIssueData: INodeIssueData): boolean { const node = this.workflow.nodes.find(node => { return node.name === nodeIssueData.node; @@ -460,44 +559,7 @@ export const useWorkflowsStore = defineStore(STORES.WORKFLOWS, { } return true; }, - setWorkflowId (id: string): void { - this.workflow.id = id === 'new' ? PLACEHOLDER_EMPTY_WORKFLOW_ID : id; - }, - setWorkflowName(data: { newName: string, setStateDirty: boolean }): void { - if (data.setStateDirty === true) { - const uiStore = useUIStore(); - uiStore.stateIsDirty = true; - } - this.workflow.name = data.newName; - }, - // replace invalid credentials in workflow - replaceInvalidWorkflowCredentials(data: {credentials: INodeCredentialsDetails, invalid: INodeCredentialsDetails, type: string}): void { - this.workflow.nodes.forEach((node : INodeUi) => { - const nodeCredentials: INodeCredentials | undefined = (node as unknown as INode).credentials; - - if (!nodeCredentials || !nodeCredentials[data.type]) { - return; - } - - const nodeCredentialDetails: INodeCredentialsDetails | string = nodeCredentials[data.type]; - - if (typeof nodeCredentialDetails === 'string' && nodeCredentialDetails === data.invalid.name) { - (node.credentials as INodeCredentials)[data.type] = data.credentials; - return; - } - - if (nodeCredentialDetails.id === null) { - if (nodeCredentialDetails.name === data.invalid.name) { - (node.credentials as INodeCredentials)[data.type] = data.credentials; - } - return; - } - if (nodeCredentialDetails.id === data.invalid.id) { - (node.credentials as INodeCredentials)[data.type] = data.credentials; - } - }); - }, addNode(nodeData: INodeUi): void { if (!nodeData.hasOwnProperty('name')) { // All nodes have to have a name @@ -506,6 +568,7 @@ export const useWorkflowsStore = defineStore(STORES.WORKFLOWS, { } this.workflow.nodes.push(nodeData); }, + removeNode(node: INodeUi): void { Vue.delete(this.nodeMetadata, node.name); @@ -522,6 +585,7 @@ export const useWorkflowsStore = defineStore(STORES.WORKFLOWS, { } } }, + removeAllNodes(data: { setStateDirty: boolean, removePinData: boolean }): void { if (data.setStateDirty === true) { const uiStore = useUIStore(); @@ -535,6 +599,7 @@ export const useWorkflowsStore = defineStore(STORES.WORKFLOWS, { this.workflow.nodes.splice(0, this.workflow.nodes.length); this.nodeMetadata = {}; }, + updateNodeProperties(updateInformation: INodeUpdatePropertiesInformation): void { // Find the node that should be updated const node = this.workflow.nodes.find(node => { @@ -549,6 +614,7 @@ export const useWorkflowsStore = defineStore(STORES.WORKFLOWS, { } } }, + setNodeValue(updateInformation: IUpdateInformation): void { // Find the node that should be updated const node = this.workflow.nodes.find(node => { @@ -563,6 +629,7 @@ export const useWorkflowsStore = defineStore(STORES.WORKFLOWS, { uiStore.stateIsDirty = true; Vue.set(node, updateInformation.key, updateInformation.value); }, + setNodeParameters(updateInformation: IUpdateInformation): void { // Find the node that should be updated const node = this.workflow.nodes.find(node => { @@ -582,10 +649,7 @@ export const useWorkflowsStore = defineStore(STORES.WORKFLOWS, { } Vue.set(this.nodeMetadata[node.name], 'parametersLastUpdatedAt', Date.now()); }, - setWorkflowExecutionData(workflowResultData: IExecutionResponse | null): void { - this.workflowExecutionData = workflowResultData; - this.workflowExecutionPairedItemMappings = getPairedItemsMapping(this.workflowExecutionData); - }, + addNodeExecutionData(pushData: IPushDataNodeExecuteAfter): void { if (this.workflowExecutionData === null || !this.workflowExecutionData.data) { throw new Error('The "workflowExecutionData" is not initialized!'); @@ -602,84 +666,12 @@ export const useWorkflowsStore = defineStore(STORES.WORKFLOWS, { } Vue.delete(this.workflowExecutionData.data.resultData.runData, nodeName); }, - setWorkflowSettings(workflowSettings: IWorkflowSettings): void { - Vue.set(this.workflow, 'settings', workflowSettings); - }, - setWorkflowPinData(pinData: IPinData): void { - Vue.set(this.workflow, 'pinData', pinData || {}); - dataPinningEventBus.$emit('pin-data', pinData || {}); - }, - setWorkflowTagIds(tags: string[]): void { - Vue.set(this.workflow, 'tags', tags); - }, - addWorkflowTagIds(tags: string[]): void { - Vue.set(this.workflow, 'tags', [...new Set([...(this.workflow.tags || []), ...tags])]); - }, - removeWorkflowTagId(tagId: string): void { - const tags = this.workflow.tags as string[]; - const updated = tags.filter((id: string) => id !== tagId); - Vue.set(this.workflow, 'tags', updated); - }, - setWorkflow(workflow: IWorkflowDb): void { - Vue.set(this, 'workflow', workflow); - - if (!this.workflow.hasOwnProperty('active')) { - Vue.set(this.workflow, 'active', false); - } - if (!this.workflow.hasOwnProperty('connections')) { - Vue.set(this.workflow, 'connections', {}); - } - if (!this.workflow.hasOwnProperty('createdAt')) { - Vue.set(this.workflow, 'createdAt', -1); - } - if (!this.workflow.hasOwnProperty('updatedAt')) { - Vue.set(this.workflow, 'updatedAt', -1); - } - if (!this.workflow.hasOwnProperty('id')) { - Vue.set(this.workflow, 'id', PLACEHOLDER_EMPTY_WORKFLOW_ID); - } - if (!this.workflow.hasOwnProperty('nodes')) { - Vue.set(this.workflow, 'nodes', []); - } - if (!this.workflow.hasOwnProperty('settings')) { - Vue.set(this.workflow, 'settings', {}); - } - }, - pinData(payload: { node: INodeUi, data: INodeExecutionData[] }): void { - if (!this.workflow.pinData) { - Vue.set(this.workflow, 'pinData', {}); - } - if (!Array.isArray(payload.data)) { - payload.data = [payload.data]; - } - - const storedPinData = payload.data.map(item => isJsonKeyObject(item) ? item : { json: item }); - - Vue.set(this.workflow.pinData!, payload.node.name, storedPinData); - - const uiStore = useUIStore(); - uiStore.stateIsDirty = true; - - dataPinningEventBus.$emit('pin-data', { [payload.node.name]: storedPinData }); - }, - unpinData(payload: { node: INodeUi }): void { - if (!this.workflow.pinData) { - Vue.set(this.workflow, 'pinData', {}); - } - - Vue.set(this.workflow.pinData!, payload.node.name, undefined); - delete this.workflow.pinData![payload.node.name]; - - const uiStore = useUIStore(); - uiStore.stateIsDirty = true; - - dataPinningEventBus.$emit('unpin-data', { [payload.node.name]: undefined }); - }, pinDataByNodeName(nodeName: string): INodeExecutionData[] | undefined { if (!this.workflow.pinData || !this.workflow.pinData[nodeName]) return undefined; - return this.workflow.pinData[nodeName].map((item: INodeExecutionData) => item.json); + return this.workflow.pinData[nodeName].map(item => item.json) as INodeExecutionData[]; }, + activeNode(): INodeUi | null { // kept here for FE hooks // TODO: Update once hooks and NDV store are updated @@ -687,47 +679,49 @@ export const useWorkflowsStore = defineStore(STORES.WORKFLOWS, { // return ndvStore.activeNode; return null; }, - async fetchAllWorkflows(): Promise { - const rootStore = useRootStore(); - const workflows = await getWorkflows(rootStore.getRestApiContext); - this.setWorkflows(workflows); - return workflows; - }, - // ------------------------------------------------ - async getNewWorkflowData(name?: string): Promise { - let workflowData = { - name: '', - onboardingFlowEnabled: false, - }; - try { - const rootStore = useRootStore(); - workflowData = await getNewWorkflow(rootStore.getRestApiContext, name); - } - catch (e) { - // in case of error, default to original name - workflowData.name = name || DEFAULT_NEW_WORKFLOW_NAME; - } - this.setWorkflowName({ newName: workflowData.name, setStateDirty: false }); - return workflowData; - }, + // Executions actions - async getDuplicateCurrentWorkflowName(currentWorkflowName: string): Promise { - if (currentWorkflowName && (currentWorkflowName.length + DUPLICATE_POSTFFIX.length) >= MAX_WORKFLOW_NAME_LENGTH) { - return currentWorkflowName; + + addActiveExecution(newActiveExecution: IExecutionsCurrentSummaryExtended) : void { + // Check if the execution exists already + const activeExecution = this.activeExecutions.find(execution => { + return execution.id === newActiveExecution.id; + }); + + if (activeExecution !== undefined) { + // Exists already so no need to add it again + if (activeExecution.workflowName === undefined) { + activeExecution.workflowName = newActiveExecution.workflowName; + } + return; } + this.activeExecutions.unshift(newActiveExecution); + }, + finishActiveExecution(finishedActiveExecution: IPushDataExecutionFinished) : void { + // Find the execution to set to finished + const activeExecution = this.activeExecutions.find(execution => { + return execution.id === finishedActiveExecution.executionId; + }); - let newName = `${currentWorkflowName}${DUPLICATE_POSTFFIX}`; - try { - const rootStore = useRootStore(); - const newWorkflow = await getNewWorkflow(rootStore.getRestApiContext, newName ); - newName = newWorkflow.name; + if (activeExecution === undefined) { + // The execution could not be found + return; } - catch (e) { + + if (finishedActiveExecution.executionId !== undefined) { + Vue.set(activeExecution, 'id', finishedActiveExecution.executionId); } - return newName; + + Vue.set(activeExecution, 'finished', finishedActiveExecution.data.finished); + Vue.set(activeExecution, 'stoppedAt', finishedActiveExecution.data.stoppedAt); }, + + setActiveExecutions(newActiveExecutions: IExecutionsCurrentSummaryExtended[]) : void { + Vue.set(this, 'activeExecutions', newActiveExecutions); + }, + async loadCurrentWorkflowExecutions (filter: { finished: boolean, status: string }): Promise { let activeExecutions = []; let finishedExecutions = []; diff --git a/packages/editor-ui/src/views/NodeView.vue b/packages/editor-ui/src/views/NodeView.vue index cebcab9e7b8af..3deacd8958016 100644 --- a/packages/editor-ui/src/views/NodeView.vue +++ b/packages/editor-ui/src/views/NodeView.vue @@ -3140,7 +3140,7 @@ export default mixins( }, async loadActiveWorkflows(): Promise { const activeWorkflows = await this.restApi().getActiveWorkflows(); - this.uiStore.activeWorkflows = activeWorkflows; + this.workflowsStore.activeWorkflows = activeWorkflows; }, async loadNodeTypes(): Promise { await this.$store.dispatch('nodeTypes/getNodeTypes'); diff --git a/packages/editor-ui/src/views/WorkflowsView.vue b/packages/editor-ui/src/views/WorkflowsView.vue index 9170ed8bcb1d6..822157c0ab3e5 100644 --- a/packages/editor-ui/src/views/WorkflowsView.vue +++ b/packages/editor-ui/src/views/WorkflowsView.vue @@ -77,6 +77,7 @@ import { mapStores } from 'pinia'; import { useUIStore } from '@/stores/ui'; import { useSettingsStore } from '@/stores/settings'; import { useUsersStore } from '@/stores/users'; +import { useWorkflowsStore } from '@/stores/workflows'; type IResourcesListLayoutInstance = Vue & { sendFiltersTelemetry: (source: string) => void }; @@ -109,17 +110,18 @@ export default mixins( useSettingsStore, useUIStore, useUsersStore, + useWorkflowsStore, ), currentUser(): IUser { return this.usersStore.currentUser || {} as IUser; }, allWorkflows(): IWorkflowDb[] { - return this.$store.getters['allWorkflows']; + return this.workflowsStore.allWorkflows; }, }, methods: { addWorkflow() { - this.$store.commit('ui/setNodeViewInitialized', false); + this.uiStore.nodeViewInitialized = false; this.$router.push({ name: VIEWS.NEW_WORKFLOW }); this.$telemetry.track('User clicked add workflow button', { @@ -133,8 +135,8 @@ export default mixins( this.usersStore.fetchUsers(); // Can be loaded in the background, used for filtering return await Promise.all([ - this.$store.dispatch('fetchAllWorkflows'), - this.uiStore.fetchActiveWorkflows(), + this.workflowsStore.fetchAllWorkflows(), + this.workflowsStore.fetchActiveWorkflows(), ]); }, onClickTag(tagId: string, event: PointerEvent) { From d591bcdd70087705d54690f8933d42fc7f0cbc20 Mon Sep 17 00:00:00 2001 From: Milorad Filipovic Date: Fri, 28 Oct 2022 14:46:30 +0200 Subject: [PATCH 11/38] =?UTF-8?q?=E2=9A=A1=20Renaming=20some=20getters=20a?= =?UTF-8?q?nd=20actions=20to=20make=20more=20sense?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- packages/editor-ui/src/Interface.ts | 4 ++-- .../src/components/MainHeader/WorkflowDetails.vue | 2 +- packages/editor-ui/src/components/OutputPanel.vue | 2 +- packages/editor-ui/src/components/RunData.vue | 2 +- packages/editor-ui/src/components/TriggerPanel.vue | 2 +- .../src/components/mixins/pushConnection.ts | 12 ++++++------ .../src/components/mixins/workflowHelpers.ts | 4 ++-- packages/editor-ui/src/stores/workflows.ts | 6 +++--- packages/editor-ui/src/views/NodeView.vue | 4 ++-- 9 files changed, 19 insertions(+), 19 deletions(-) diff --git a/packages/editor-ui/src/Interface.ts b/packages/editor-ui/src/Interface.ts index 17b03d0ba5dc5..65a143a6ed506 100644 --- a/packages/editor-ui/src/Interface.ts +++ b/packages/editor-ui/src/Interface.ts @@ -891,12 +891,12 @@ export interface workflowsState { activeWorkflows: string[]; activeWorkflowExecution: IExecutionsSummary | null; currentWorkflowExecutions: IExecutionsSummary[]; - executionId: string | null; + activeExecutionId: string | null; executingNode: string | null; executionWaitingForWebhook: boolean; finishedExecutionsCount: number; nodeMetadata: nodeMetadataMap; - subworkflowExecutionError: Error | null; + subWorkflowExecutionError: Error | null; workflow: IWorkflowDb; workflowExecutionData: IExecutionResponse | null; workflowExecutionPairedItemMappings: {[itemId: string]: Set}; diff --git a/packages/editor-ui/src/components/MainHeader/WorkflowDetails.vue b/packages/editor-ui/src/components/MainHeader/WorkflowDetails.vue index 0589cd679a774..e36adebc5f2ff 100644 --- a/packages/editor-ui/src/components/MainHeader/WorkflowDetails.vue +++ b/packages/editor-ui/src/components/MainHeader/WorkflowDetails.vue @@ -150,7 +150,7 @@ export default mixins(workflowHelpers, titleChange).extend({ useWorkflowsStore, ), isWorkflowActive(): boolean { - return this.workflowsStore.isActive; + return this.workflowsStore.isWorkflowActive; }, workflowName(): string { return this.workflowsStore.workflowName; diff --git a/packages/editor-ui/src/components/OutputPanel.vue b/packages/editor-ui/src/components/OutputPanel.vue index 98d63a79e0a34..526d9ec5b9a9b 100644 --- a/packages/editor-ui/src/components/OutputPanel.vue +++ b/packages/editor-ui/src/components/OutputPanel.vue @@ -157,7 +157,7 @@ export default mixins( return executionData.resultData.runData; }, hasNodeRun(): boolean { - if (this.workflowsStore.subworkflowExecutionError) return true; + if (this.workflowsStore.subWorkflowExecutionError) return true; return Boolean( this.node && this.workflowRunData && this.workflowRunData.hasOwnProperty(this.node.name), diff --git a/packages/editor-ui/src/components/RunData.vue b/packages/editor-ui/src/components/RunData.vue index 67cf6038cf3af..5bd4c2444f0c1 100644 --- a/packages/editor-ui/src/components/RunData.vue +++ b/packages/editor-ui/src/components/RunData.vue @@ -533,7 +533,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.workflowsStore.subworkflowExecutionError; + return this.workflowsStore.subWorkflowExecutionError; }, hasSubworkflowExecutionError(): boolean { return Boolean(this.subworkflowExecutionError); diff --git a/packages/editor-ui/src/components/TriggerPanel.vue b/packages/editor-ui/src/components/TriggerPanel.vue index 728143b7d84d2..30fccfbdc9e8c 100644 --- a/packages/editor-ui/src/components/TriggerPanel.vue +++ b/packages/editor-ui/src/components/TriggerPanel.vue @@ -216,7 +216,7 @@ export default mixins(workflowHelpers, copyPaste, showMessage).extend({ return this.workflowRunning && this.isPollingNode && this.nodeName === triggeredNode; }, isWorkflowActive(): boolean { - return this.workflowsStore.isActive; + return this.workflowsStore.isWorkflowActive; }, header(): string { const serviceName = this.nodeType ? getTriggerNodeServiceName(this.nodeType) : ''; diff --git a/packages/editor-ui/src/components/mixins/pushConnection.ts b/packages/editor-ui/src/components/mixins/pushConnection.ts index 7b5f270c6090a..b02e39a674cf1 100644 --- a/packages/editor-ui/src/components/mixins/pushConnection.ts +++ b/packages/editor-ui/src/components/mixins/pushConnection.ts @@ -191,7 +191,7 @@ export const pushConnection = mixins( return false; } const pushData = receivedData.data; - if (this.workflowsStore.executionId !== pushData.executionId) { + if (this.workflowsStore.activeExecutionId !== pushData.executionId) { // The data is not for the currently active execution or // we do not have the execution id yet. if (isRetry !== true) { @@ -205,14 +205,14 @@ export const pushConnection = mixins( // The workflow finished executing const pushData = receivedData.data; - this.uiStore.finishActiveExecution(pushData); + this.workflowsStore.finishActiveExecution(pushData); if (!this.uiStore.isActionActive('workflowRunning')) { // No workflow is running so ignore the messages return false; } - if (this.workflowsStore.executionId !== pushData.executionId) { + if (this.workflowsStore.activeExecutionId !== pushData.executionId) { // The workflow which did finish execution did either not get started // by this session or we do not have the execution id yet. if (isRetry !== true) { @@ -235,7 +235,7 @@ export const pushConnection = mixins( const workflow = this.getCurrentWorkflow(); if (runDataExecuted.waitTill !== undefined) { - const activeExecutionId = this.workflowsStore.executionId; + const activeExecutionId = this.workflowsStore.activeExecutionId; const workflowSettings = this.workflowsStore.workflowSettings; const saveManualExecutions = this.rootStore.saveManualExecutions; @@ -301,7 +301,7 @@ export const pushConnection = mixins( if (runDataExecuted.data.resultData.error?.name === 'SubworkflowOperationError') { const error = runDataExecuted.data.resultData.error as SubworkflowOperationError; - this.workflowsStore.subworkflowExecutionError = error; + this.workflowsStore.subWorkflowExecutionError = error; this.$showMessage({ title: error.message, @@ -428,7 +428,7 @@ export const pushConnection = mixins( if (pushData.workflowId === this.workflowsStore.workflowId) { this.workflowsStore.executionWaitingForWebhook = false; - this.workflowsStore.executionId = pushData.executionId; + this.workflowsStore.activeExecutionId = pushData.executionId; } this.processWaitingPushMessages(); diff --git a/packages/editor-ui/src/components/mixins/workflowHelpers.ts b/packages/editor-ui/src/components/mixins/workflowHelpers.ts index 4039713541a26..e15c54619a5ae 100644 --- a/packages/editor-ui/src/components/mixins/workflowHelpers.ts +++ b/packages/editor-ui/src/components/mixins/workflowHelpers.ts @@ -407,9 +407,9 @@ export const workflowHelpers = mixins( const data: IWorkflowData = { name: this.workflowsStore.workflowName, nodes, - pinData: this.workflowsStore.pinData, + pinData: this.workflowsStore.getPinData, connections: workflowConnections, - active: this.workflowsStore.isActive, + active: this.workflowsStore.isWorkflowActive, settings: this.workflowsStore.workflowSettings, tags: this.workflowsStore.workflowTags, }; diff --git a/packages/editor-ui/src/stores/workflows.ts b/packages/editor-ui/src/stores/workflows.ts index 4d9278640d167..990e3a6a385d4 100644 --- a/packages/editor-ui/src/stores/workflows.ts +++ b/packages/editor-ui/src/stores/workflows.ts @@ -33,8 +33,8 @@ export const useWorkflowsStore = defineStore(STORES.WORKFLOWS, { workflowExecutionData: null, workflowExecutionPairedItemMappings: {}, workflowsById: {}, - subworkflowExecutionError: null, - executionId: null, + subWorkflowExecutionError: null, + activeExecutionId: null, executingNode: null, executionWaitingForWebhook: false, nodeMetadata: {}, @@ -63,7 +63,7 @@ export const useWorkflowsStore = defineStore(STORES.WORKFLOWS, { isNewWorkflow() : boolean { return this.workflow.id === PLACEHOLDER_EMPTY_WORKFLOW_ID; }, - isActive(): boolean { + isWorkflowActive(): boolean { return this.workflow.active; }, workflowTriggerNodes() : INodeUi[] { diff --git a/packages/editor-ui/src/views/NodeView.vue b/packages/editor-ui/src/views/NodeView.vue index 3deacd8958016..ecdaa56b45e7d 100644 --- a/packages/editor-ui/src/views/NodeView.vue +++ b/packages/editor-ui/src/views/NodeView.vue @@ -1279,7 +1279,7 @@ export default mixins( }, async stopExecution() { - const executionId = this.workflowsStore.executionId; + const executionId = this.workflowsStore.activeExecutionId; if (executionId === null) { return; } @@ -3128,7 +3128,7 @@ export default mixins( this.workflowsStore.setWorkflowSettings({}); this.workflowsStore.setWorkflowTagIds([]); - this.workflowsStore.executionId = null; + this.workflowsStore.activeExecutionId = null; this.workflowsStore.executingNode = null; this.workflowsStore.executionWaitingForWebhook = false; this.uiStore.removeActiveAction('workflowRunning'); From c31ada66b1fe925727b6d1cacfec1438c17dfc9b Mon Sep 17 00:00:00 2001 From: Milorad Filipovic Date: Fri, 28 Oct 2022 16:45:00 +0200 Subject: [PATCH 12/38] =?UTF-8?q?=E2=9C=A8=20Finished=20migrating=20the=20?= =?UTF-8?q?root=20store=20to=20pinia?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- packages/editor-ui/src/App.vue | 4 +- packages/editor-ui/src/Interface.ts | 25 +----- .../editor-ui/src/components/AboutModal.vue | 6 +- .../CodeNodeEditor/CodeNodeEditor.vue | 7 +- .../src/components/ContactPromptModal.vue | 10 ++- .../components/CredentialEdit/OauthButton.vue | 7 +- .../src/components/CredentialIcon.vue | 7 +- .../ExecutionsInfoAccordion.vue | 8 +- .../src/components/FeatureComingSoon.vue | 2 +- .../src/components/HoverableNodeIcon.vue | 7 +- packages/editor-ui/src/components/Logo.vue | 7 +- .../editor-ui/src/components/NodeIcon.vue | 7 +- .../src/components/PersonalizationModal.vue | 2 +- .../src/components/PushConnectionTracker.vue | 9 ++- .../src/components/SettingsSidebar.vue | 4 +- .../editor-ui/src/components/Telemetry.vue | 2 +- packages/editor-ui/src/components/TimeAgo.vue | 9 ++- .../editor-ui/src/components/ValueSurvey.vue | 29 +++---- .../src/components/WorkflowSettings.vue | 6 +- .../src/components/mixins/restApi.ts | 9 ++- packages/editor-ui/src/modules/versions.ts | 10 ++- packages/editor-ui/src/store.ts | 28 +++---- packages/editor-ui/src/stores/n8nRootStore.ts | 79 ++++++++++++++++--- packages/editor-ui/src/stores/settings.ts | 44 +++++++---- packages/editor-ui/src/views/NodeView.vue | 2 +- 25 files changed, 218 insertions(+), 112 deletions(-) diff --git a/packages/editor-ui/src/App.vue b/packages/editor-ui/src/App.vue index 8db3707ceffc2..4ccfaada17a46 100644 --- a/packages/editor-ui/src/App.vue +++ b/packages/editor-ui/src/App.vue @@ -43,6 +43,7 @@ import { mapStores } from 'pinia'; import { useUIStore } from './stores/ui'; import { useSettingsStore } from './stores/settings'; import { useUsersStore } from './stores/users'; +import { useRootStore } from './stores/n8nRootStore'; export default mixins( showMessage, @@ -58,12 +59,13 @@ export default mixins( }, computed: { ...mapStores( + useRootStore, useSettingsStore, useUIStore, useUsersStore, ), defaultLocale (): string { - return this.$store.getters.defaultLocale; + return this.rootStore.defaultLocale; }, }, data() { diff --git a/packages/editor-ui/src/Interface.ts b/packages/editor-ui/src/Interface.ts index 65a143a6ed506..ff78538997ccf 100644 --- a/packages/editor-ui/src/Interface.ts +++ b/packages/editor-ui/src/Interface.ts @@ -907,25 +907,12 @@ export interface workflowsState { // - Make this the only root state once migration is done // - Remove commented out props export interface rootStatePinia { - // --> UI store - // activeExecutions: IExecutionsCurrentSummaryExtended[]; - // activeWorkflows: string[]; - // activeActions: string[]; - // activeCredentialType: string | null; - // activeNode: string | null; ---> NDV STORE baseUrl: string; defaultLocale: string; endpointWebhook: string; endpointWebhookTest: string; - // executionId: string | null; ---> WF STORE - // executingNode: string | null; ---> WF STORE - // executionWaitingForWebhook: boolean; ---> WF STORE pushConnectionActive: boolean; - saveDataErrorExecution: string; - saveDataSuccessExecution: string; - saveManualExecutions: boolean; timezone: string; - // stateIsDirty: boolean; // --> UI Store executionTimeout: number; maxExecutionTimeout: number; versionCli: string; @@ -933,19 +920,10 @@ export interface rootStatePinia { n8nMetadata: { [key: string]: string | number | undefined; }; - // workflowExecutionData: IExecutionResponse | null; // --> WF store - // lastSelectedNode: string | null; // --> UI Store - // lastSelectedNodeOutputIndex: number | null; // --> UI Store - // nodeViewOffsetPosition: XYPosition; // --> UI Store - // nodeViewMoveInProgress: boolean; // --> UI Store - // selectedNodes: INodeUi[]; // --> UI Store sessionId: string; urlBaseWebhook: string; urlBaseEditor: string; - // workflow: IWorkflowDb; // --> WF Store - // sidebarMenuItems: IMenuItem[]; // --> UI Store instanceId: string; - // nodeMetadata: nodeMetadataMap;// --> WF STORE isNpmAvailable: boolean; } @@ -1172,6 +1150,9 @@ export interface ISettingsState { path: string; }; onboardingCallPromptEnabled: boolean; + saveDataErrorExecution: string; + saveDataSuccessExecution: string; + saveManualExecutions: boolean; } export interface INodeTypesState { diff --git a/packages/editor-ui/src/components/AboutModal.vue b/packages/editor-ui/src/components/AboutModal.vue index 7fa1e5e99b95f..211f4d6010d2c 100644 --- a/packages/editor-ui/src/components/AboutModal.vue +++ b/packages/editor-ui/src/components/AboutModal.vue @@ -13,7 +13,7 @@ {{ $locale.baseText('about.n8nVersion') }} - {{ settingsStore.versionCli }} + {{ rootStore.versionCli }} @@ -39,7 +39,7 @@ {{ $locale.baseText('about.instanceID') }} - {{ settingsStore.settings.instanceId }} + {{ rootStore.instanceId }}
@@ -59,6 +59,7 @@ 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', @@ -73,6 +74,7 @@ export default Vue.extend({ }, computed: { ...mapStores( + useRootStore, useSettingsStore, ), }, diff --git a/packages/editor-ui/src/components/CodeNodeEditor/CodeNodeEditor.vue b/packages/editor-ui/src/components/CodeNodeEditor/CodeNodeEditor.vue index 9dc2a6050ea77..d014afb2ab00a 100644 --- a/packages/editor-ui/src/components/CodeNodeEditor/CodeNodeEditor.vue +++ b/packages/editor-ui/src/components/CodeNodeEditor/CodeNodeEditor.vue @@ -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', diff --git a/packages/editor-ui/src/components/ContactPromptModal.vue b/packages/editor-ui/src/components/ContactPromptModal.vue index 1d8e2caa573c0..208694187c3b9 100644 --- a/packages/editor-ui/src/components/ContactPromptModal.vue +++ b/packages/editor-ui/src/components/ContactPromptModal.vue @@ -42,6 +42,7 @@ 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 }, @@ -54,7 +55,10 @@ export default mixins(workflowHelpers).extend({ }; }, computed: { - ...mapStores(useSettingsStore), + ...mapStores( + useRootStore, + useSettingsStore, + ), title(): string { if (this.settingsStore.promptsData && this.settingsStore.promptsData.title) { return this.settingsStore.promptsData.title; @@ -77,7 +81,7 @@ 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, }); } @@ -88,7 +92,7 @@ export default mixins(workflowHelpers).extend({ 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({ diff --git a/packages/editor-ui/src/components/CredentialEdit/OauthButton.vue b/packages/editor-ui/src/components/CredentialEdit/OauthButton.vue index aa0b81b514f79..e64137caf6e15 100644 --- a/packages/editor-ui/src/components/CredentialEdit/OauthButton.vue +++ b/packages/editor-ui/src/components/CredentialEdit/OauthButton.vue @@ -17,6 +17,8 @@ diff --git a/packages/editor-ui/src/components/SettingsSidebar.vue b/packages/editor-ui/src/components/SettingsSidebar.vue index b534414abd8a3..d8354c9bca5ea 100644 --- a/packages/editor-ui/src/components/SettingsSidebar.vue +++ b/packages/editor-ui/src/components/SettingsSidebar.vue @@ -12,7 +12,7 @@ @@ -32,6 +32,7 @@ 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, @@ -40,6 +41,7 @@ export default mixins( name: 'SettingsSidebar', computed: { ...mapStores( + useRootStore, useSettingsStore, useUIStore, ), diff --git a/packages/editor-ui/src/components/Telemetry.vue b/packages/editor-ui/src/components/Telemetry.vue index 939f3cce0706b..19a8ce9c07db7 100644 --- a/packages/editor-ui/src/components/Telemetry.vue +++ b/packages/editor-ui/src/components/Telemetry.vue @@ -53,7 +53,7 @@ export default mixins(externalHooks).extend({ instanceId: this.rootStore.instanceId, userId: this.currentUserId, store: this.$store, - versionCli: this.settingsStore.versionCli, + versionCli: this.rootStore.versionCli, }, ); diff --git a/packages/editor-ui/src/components/TimeAgo.vue b/packages/editor-ui/src/components/TimeAgo.vue index 1ed601ab9ebac..6373d08d61944 100644 --- a/packages/editor-ui/src/components/TimeAgo.vue +++ b/packages/editor-ui/src/components/TimeAgo.vue @@ -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); diff --git a/packages/editor-ui/src/components/ValueSurvey.vue b/packages/editor-ui/src/components/ValueSurvey.vue index 6b450f7c02431..f940a9a0608b5 100644 --- a/packages/editor-ui/src/components/ValueSurvey.vue +++ b/packages/editor-ui/src/components/ValueSurvey.vue @@ -66,6 +66,7 @@ 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?`; @@ -81,13 +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(useSettingsStore), + ...mapStores( + useRootStore, + useSettingsStore, + ), getTitle(): string { if (this.form.value !== '') { if (Number(this.form.value) > 7) { @@ -118,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: '', }); } @@ -140,24 +144,21 @@ export default mixins(workflowHelpers).extend({ 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({ diff --git a/packages/editor-ui/src/components/WorkflowSettings.vue b/packages/editor-ui/src/components/WorkflowSettings.vue index 14fbb45dd4ced..c208ba5a99ed4 100644 --- a/packages/editor-ui/src/components/WorkflowSettings.vue +++ b/packages/editor-ui/src/components/WorkflowSettings.vue @@ -319,9 +319,9 @@ export default mixins( return; } - this.defaultValues.saveDataErrorExecution = this.rootStore.saveDataErrorExecution; - this.defaultValues.saveDataSuccessExecution = this.rootStore.saveDataSuccessExecution; - this.defaultValues.saveManualExecutions = this.rootStore.saveManualExecutions; + 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; diff --git a/packages/editor-ui/src/components/mixins/restApi.ts b/packages/editor-ui/src/components/mixins/restApi.ts index ecf3c48f22e3a..cf3ee357f5995 100644 --- a/packages/editor-ui/src/components/mixins/restApi.ts +++ b/packages/editor-ui/src/components/mixins/restApi.ts @@ -28,6 +28,8 @@ import { INodeTypeNameVersion, } from 'n8n-workflow'; import { makeRestApiRequest } from '@/api/helpers'; +import { mapStores } from 'pinia'; +import { useRootStore } from '@/stores/n8nRootStore'; /** * Unflattens the Execution data. @@ -52,12 +54,17 @@ function unflattenExecutionData (fullExecutionData: IExecutionFlattedResponse): } export const restApi = Vue.extend({ + computed: { + ...mapStores( + useRootStore, + ), + }, methods: { restApi (): IRestApi { const self = this; return { async makeRestApiRequest (method: Method, endpoint: string, data?: IDataObject): Promise { // tslint:disable-line:no-any - return makeRestApiRequest(self.$store.getters.getRestApiContext, method, endpoint, data); + return makeRestApiRequest(self.rootStore.getRestApiContext, method, endpoint, data); }, getActiveWorkflows: (): Promise => { return self.restApi().makeRestApiRequest('GET', `/active`); diff --git a/packages/editor-ui/src/modules/versions.ts b/packages/editor-ui/src/modules/versions.ts index 56d28c6411591..e992644bc2727 100644 --- a/packages/editor-ui/src/modules/versions.ts +++ b/packages/editor-ui/src/modules/versions.ts @@ -1,4 +1,5 @@ import { getNextVersions } from '@/api/versions'; +import { useRootStore } from '@/stores/n8nRootStore'; import { ActionContext, Module } from 'vuex'; import { IRootState, @@ -40,7 +41,7 @@ const module: Module = { state.currentVersion = versions.find((version) => version.name === currentVersion); }, setVersionNotificationSettings(state: IVersionsState, settings: {enabled: true, endpoint: string, infoUrl: string}) { - state.versionNotificationSettings = settings; + state.versionNotificationSettings = settings; }, }, actions: { @@ -48,8 +49,9 @@ const module: Module = { try { const { enabled, endpoint } = context.state.versionNotificationSettings; if (enabled && endpoint) { - const currentVersion = context.rootState.versionCli; - const instanceId = context.rootState.instanceId; + const rootStore = useRootStore(); + const currentVersion = rootStore.versionCli; + const instanceId = rootStore.instanceId; const versions = await getNextVersions(endpoint, currentVersion, instanceId); context.commit('setVersions', {versions, currentVersion}); } @@ -59,4 +61,4 @@ const module: Module = { }, }; -export default module; \ No newline at end of file +export default module; diff --git a/packages/editor-ui/src/store.ts b/packages/editor-ui/src/store.ts index 302f9c72bc70b..4fb73f7bfc7a1 100644 --- a/packages/editor-ui/src/store.ts +++ b/packages/editor-ui/src/store.ts @@ -226,9 +226,9 @@ export const store = new Vuex.Store({ // }, // Set state condition dirty or not // ** Dirty: if current workflow state has been synchronized with database AKA has it been saved - setStateDirty(state, dirty: boolean) { - state.stateIsDirty = dirty; - }, + // setStateDirty(state, dirty: boolean) { + // state.stateIsDirty = dirty; + // }, // Selected Nodes // addSelectedNode(state, node: INodeUi) { @@ -654,13 +654,13 @@ export const store = new Vuex.Store({ // setActiveCredentialType(state, activeCredentialType: string) { // state.activeCredentialType = activeCredentialType; // }, - setLastSelectedNode(state, nodeName: string) { - state.lastSelectedNode = nodeName; - }, + // setLastSelectedNode(state, nodeName: string) { + // state.lastSelectedNode = nodeName; + // }, - setLastSelectedNodeOutputIndex(state, outputIndex: number | null) { - state.lastSelectedNodeOutputIndex = outputIndex; - }, + // setLastSelectedNodeOutputIndex(state, outputIndex: number | null) { + // state.lastSelectedNodeOutputIndex = outputIndex; + // }, // setWorkflowExecutionData(state, workflowResultData: IExecutionResponse | null) { // state.workflowExecutionData = workflowResultData; @@ -806,10 +806,6 @@ export const store = new Vuex.Store({ return `${state.urlBaseEditor}${state.endpointWebhookTest}`; }, - getStateIsDirty: (state): boolean => { - return state.stateIsDirty; - }, - instanceId: (state): string => { return state.instanceId; }, @@ -1037,9 +1033,9 @@ export const store = new Vuex.Store({ // return workflowRunData[nodeName]; // }, - sidebarMenuItems: (state): IMenuItem[] => { - return state.sidebarMenuItems; - }, + // sidebarMenuItems: (state): IMenuItem[] => { + // return state.sidebarMenuItems; + // }, }, // actions: { // // fetchAllWorkflows: async (context: ActionContext): Promise => { diff --git a/packages/editor-ui/src/stores/n8nRootStore.ts b/packages/editor-ui/src/stores/n8nRootStore.ts index 69d3307bbb7e2..2944f815a6ea9 100644 --- a/packages/editor-ui/src/stores/n8nRootStore.ts +++ b/packages/editor-ui/src/stores/n8nRootStore.ts @@ -1,5 +1,7 @@ import { IRestApiContext, rootStatePinia } from '@/Interface'; +import { IDataObject } from 'n8n-workflow'; import { defineStore } from 'pinia'; +import Vue from 'vue'; export const useRootStore = defineStore('root', { state: (): rootStatePinia => ({ @@ -9,9 +11,6 @@ export const useRootStore = defineStore('root', { endpointWebhook: 'webhook', endpointWebhookTest: 'webhook-test', pushConnectionActive: true, - saveDataErrorExecution: 'all', - saveDataSuccessExecution: 'all', - saveManualExecutions: false, timezone: 'America/New_York', executionTimeout: -1, maxExecutionTimeout: Number.MAX_SAFE_INTEGER, @@ -20,13 +19,34 @@ export const useRootStore = defineStore('root', { n8nMetadata: {}, sessionId: Math.random().toString(36).substring(2, 15), urlBaseWebhook: 'http://localhost:5678/', + urlBaseEditor: 'http://localhost:5678', isNpmAvailable: false, instanceId: '', }), getters: { - getParametersLastUpdated: (state: rootStatePinia): ((name: string) => number | undefined) => { - return (nodeName: string) => state.nodeMetadata[nodeName] && state.nodeMetadata[nodeName].parametersLastUpdatedAt; + // TODO: Waiting for nodetypes store + + /** + * Getter for node default names ending with a number: `'S3'`, `'Magento 2'`, etc. + */ + // nativelyNumberSuffixedDefaults: (_, getters): string[] => { + // const { 'nodeTypes/allNodeTypes': allNodeTypes } = getters as { + // ['nodeTypes/allNodeTypes']: Array; + // }; + + // return allNodeTypes.reduce((acc, cur) => { + // if (/\d$/.test(cur.defaults.name)) acc.push(cur.defaults.name); + // return acc; + // }, []); + // }, + getWebhookUrl(): string { + return `${this.urlBaseWebhook}${this.endpointWebhook}`; + }, + + getWebhookTestUrl(): string { + return `${this.urlBaseEditor}${this.endpointWebhookTest}`; }, + getRestUrl: (state: rootStatePinia): string => { let endpoint = 'rest'; if (import.meta.env.VUE_APP_ENDPOINT_REST) { @@ -34,6 +54,7 @@ export const useRootStore = defineStore('root', { } return `${state.baseUrl}${endpoint}`; }, + getRestApiContext: (state: rootStatePinia): IRestApiContext => { let endpoint = 'rest'; if (import.meta.env.VUE_APP_ENDPOINT_REST) { @@ -44,14 +65,48 @@ export const useRootStore = defineStore('root', { sessionId: state.sessionId, }; }, - getWebhookUrl: (state: rootStatePinia): string => { - return `${state.urlBaseWebhook}${state.endpointWebhook}`; - }, - getWebhookTestUrl: (state: rootStatePinia): string => { - return `${state.urlBaseWebhook}${state.endpointWebhookTest}`; - }, }, actions: { - + setUrlBaseWebhook(urlBaseWebhook: string) { + const url = urlBaseWebhook.endsWith('/') ? urlBaseWebhook : `${urlBaseWebhook}/`; + Vue.set(this, 'urlBaseWebhook', url); + }, + setUrlBaseEditor(urlBaseEditor: string) { + const url = urlBaseEditor.endsWith('/') ? urlBaseEditor : `${urlBaseEditor}/`; + Vue.set(this, 'urlBaseEditor', url); + }, + setEndpointWebhook(endpointWebhook: string) { + Vue.set(this, 'endpointWebhook', endpointWebhook); + }, + setEndpointWebhookTest(endpointWebhookTest: string) { + Vue.set(this, 'endpointWebhookTest', endpointWebhookTest); + }, + setTimezone(timezone: string) { + Vue.set(this, 'timezone', timezone); + }, + setExecutionTimeout(executionTimeout: number) { + Vue.set(this, 'executionTimeout', executionTimeout); + }, + setMaxExecutionTimeout(maxExecutionTimeout: number) { + Vue.set(this, 'maxExecutionTimeout', maxExecutionTimeout); + }, + setVersionCli(version: string) { + Vue.set(this, 'versionCli', version); + }, + setInstanceId(instanceId: string) { + Vue.set(this, 'instanceId', instanceId); + }, + setOauthCallbackUrls(urls: IDataObject) { + Vue.set(this, 'oauthCallbackUrls', urls); + }, + setN8nMetadata(metadata: IDataObject) { + Vue.set(this, 'n8nMetadata', metadata); + }, + setDefaultLocale(locale: string) { + Vue.set(this, 'defaultLocale', locale); + }, + setIsNpmAvailable(isNpmAvailable: boolean) { + Vue.set(this, 'isNpmAvailable', isNpmAvailable); + }, }, }); diff --git a/packages/editor-ui/src/stores/settings.ts b/packages/editor-ui/src/stores/settings.ts index 4abecc5ac0e99..855d6faf60497 100644 --- a/packages/editor-ui/src/stores/settings.ts +++ b/packages/editor-ui/src/stores/settings.ts @@ -26,6 +26,9 @@ export const useSettingsStore = defineStore(STORES.SETTINGS, { path: '/', }, onboardingCallPromptEnabled: false, + saveDataErrorExecution: 'all', + saveDataSuccessExecution: 'all', + saveManualExecutions: false, }), getters: { isEnterpriseFeatureEnabled() { @@ -129,23 +132,23 @@ export const useSettingsStore = defineStore(STORES.SETTINGS, { this.settings.communityNodesEnabled = settings.communityNodesEnabled; // TODO: This will need to be updated on interface level once vuex store in removed this.setAllowedModules(settings.allowedModules as { builtIn?: string, external?: string }); + this.setSaveDataErrorExecution(settings.saveDataErrorExecution); + this.setSaveDataSuccessExecution(settings.saveDataSuccessExecution); + this.setSaveManualExecutions(settings.saveManualExecutions); - rootStore.urlBaseWebhook = settings.urlBaseWebhook; - rootStore.urlBaseEditor = settings.urlBaseEditor; - rootStore.endpointWebhook = settings.endpointWebhook; - rootStore.endpointWebhookTest = settings.endpointWebhookTest; - rootStore.saveDataErrorExecution = settings.saveDataErrorExecution; - rootStore.saveDataSuccessExecution = settings.saveDataSuccessExecution; - rootStore.saveManualExecutions = settings.saveManualExecutions; - rootStore.timezone = settings.timezone; - rootStore.executionTimeout = settings.executionTimeout; - rootStore.maxExecutionTimeout = settings.maxExecutionTimeout; - rootStore.versionCli = settings.versionCli; - rootStore.instanceId = settings.instanceId; - rootStore.oauthCallbackUrls = settings.oauthCallbackUrls; - rootStore.n8nMetadata = settings.n8nMetadata || {}; - rootStore.defaultLocale = settings.defaultLocale; - rootStore.isNpmAvailable = settings.isNpmAvailable; + rootStore.setUrlBaseWebhook(settings.urlBaseWebhook); + rootStore.setUrlBaseEditor(settings.urlBaseEditor); + rootStore.setEndpointWebhook(settings.endpointWebhook); + rootStore.setEndpointWebhookTest(settings.endpointWebhookTest); + rootStore.setTimezone(settings.timezone); + rootStore.setExecutionTimeout(settings.executionTimeout); + rootStore.setMaxExecutionTimeout(settings.maxExecutionTimeout); + rootStore.setVersionCli(settings.versionCli); + rootStore.setInstanceId(settings.instanceId); + rootStore.setOauthCallbackUrls(settings.oauthCallbackUrls); + rootStore.setN8nMetadata(settings.n8nMetadata || {}); + rootStore.setDefaultLocale(settings.defaultLocale); + rootStore.setIsNpmAvailable(settings.isNpmAvailable); // TODO: context.commit('versions/setVersionNotificationSettings', settings.versionNotifications, {root: true}); }, stopShowingSetupPage(): void { @@ -217,5 +220,14 @@ export const useSettingsStore = defineStore(STORES.SETTINGS, { const rootStore = useRootStore(); await deleteApiKey(rootStore.getRestApiContext); }, + setSaveDataErrorExecution(newValue: string) { + Vue.set(this, 'saveDataErrorExecution', newValue); + }, + setSaveDataSuccessExecution(newValue: string) { + Vue.set(this, 'saveDataSuccessExecution', newValue); + }, + setSaveManualExecutions(saveManualExecutions: boolean) { + Vue.set(this, 'saveManualExecutions', saveManualExecutions); + }, }, }); diff --git a/packages/editor-ui/src/views/NodeView.vue b/packages/editor-ui/src/views/NodeView.vue index ecdaa56b45e7d..aad0f27b793fd 100644 --- a/packages/editor-ui/src/views/NodeView.vue +++ b/packages/editor-ui/src/views/NodeView.vue @@ -333,7 +333,7 @@ export default mixins( return; } - const result = this.uiStore.nodeViewInitialized; + const result = this.uiStore.stateIsDirty; if (result) { const confirmModal = await this.confirmModal( this.$locale.baseText('generic.unsavedWork.confirmMessage.message'), From 81777b8626d796b8680603fc7887c19037d2e8c9 Mon Sep 17 00:00:00 2001 From: Milorad Filipovic Date: Fri, 28 Oct 2022 18:57:55 +0200 Subject: [PATCH 13/38] =?UTF-8?q?=E2=9C=A8=20Migrated=20ndv=20store=20to?= =?UTF-8?q?=20pinia?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- packages/editor-ui/src/Interface.ts | 1 - .../editor-ui/src/components/CodeEdit.vue | 4 +- .../completions/jsonField.completions.ts | 12 +- .../src/components/CollectionParameter.vue | 9 +- .../CredentialEdit/CredentialConfig.vue | 4 +- .../CredentialEdit/CredentialEdit.vue | 10 +- .../editor-ui/src/components/Draggable.vue | 13 +- .../src/components/DraggableTarget.vue | 15 +- .../src/components/Error/NodeErrorView.vue | 7 +- .../src/components/ExpressionEdit.vue | 10 +- .../src/components/ImportCurlModal.vue | 10 +- .../editor-ui/src/components/InputPanel.vue | 6 +- .../src/components/MainHeader/MainHeader.vue | 8 +- .../src/components/NDVDraggablePanels.vue | 25 ++- packages/editor-ui/src/components/Node.vue | 4 +- .../src/components/NodeDetailsView.vue | 39 ++-- .../src/components/NodeExecuteButton.vue | 5 +- .../editor-ui/src/components/NodeSettings.vue | 8 +- .../src/components/NodeSettingsTabs.vue | 6 +- .../editor-ui/src/components/OutputPanel.vue | 6 +- .../src/components/ParameterInput.vue | 12 +- .../src/components/ParameterInputFull.vue | 17 +- .../src/components/ParameterInputList.vue | 11 +- .../src/components/ParameterInputWrapper.vue | 17 +- .../ResourceLocator/ResourceLocator.vue | 9 +- packages/editor-ui/src/components/RunData.vue | 52 ++--- .../editor-ui/src/components/RunDataJson.vue | 9 +- .../src/components/RunDataJsonActions.vue | 6 +- .../editor-ui/src/components/RunDataTable.vue | 11 +- packages/editor-ui/src/components/Sticky.vue | 6 +- .../editor-ui/src/components/TriggerPanel.vue | 4 +- .../src/components/VariableSelector.vue | 6 +- .../src/components/mixins/workflowHelpers.ts | 6 +- packages/editor-ui/src/constants.ts | 1 + packages/editor-ui/src/plugins/i18n/index.ts | 13 +- packages/editor-ui/src/store.ts | 10 +- packages/editor-ui/src/stores/n8nRootStore.ts | 60 +++--- packages/editor-ui/src/stores/ndv.ts | 180 ++++++++++++++++++ packages/editor-ui/src/stores/workflows.ts | 5 +- packages/editor-ui/src/views/NodeView.vue | 16 +- 40 files changed, 468 insertions(+), 185 deletions(-) create mode 100644 packages/editor-ui/src/stores/ndv.ts diff --git a/packages/editor-ui/src/Interface.ts b/packages/editor-ui/src/Interface.ts index ff78538997ccf..2c0109dcf792a 100644 --- a/packages/editor-ui/src/Interface.ts +++ b/packages/editor-ui/src/Interface.ts @@ -930,7 +930,6 @@ export interface rootStatePinia { export interface nodeMetadataMap { [nodeName: string]: INodeMetadata; } - export interface IRootState { activeExecutions: IExecutionsCurrentSummaryExtended[]; activeWorkflows: string[]; diff --git a/packages/editor-ui/src/components/CodeEdit.vue b/packages/editor-ui/src/components/CodeEdit.vue index ec84c0360a2d0..62fee3168bf3a 100644 --- a/packages/editor-ui/src/components/CodeEdit.vue +++ b/packages/editor-ui/src/components/CodeEdit.vue @@ -35,6 +35,7 @@ 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, @@ -47,6 +48,7 @@ export default mixins( props: ['codeAutocomplete', 'parameter', 'path', 'type', 'value', 'readonly'], computed: { ...mapStores( + useNDVStore, useRootStore, useWorkflowsStore, ), @@ -61,7 +63,7 @@ export default mixins( 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, diff --git a/packages/editor-ui/src/components/CodeNodeEditor/completions/jsonField.completions.ts b/packages/editor-ui/src/components/CodeNodeEditor/completions/jsonField.completions.ts index 3ac803ce092d0..0ecbd2151a2ad 100644 --- a/packages/editor-ui/src/components/CodeNodeEditor/completions/jsonField.completions.ts +++ b/packages/editor-ui/src/components/CodeNodeEditor/completions/jsonField.completions.ts @@ -5,10 +5,12 @@ 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, ), }, @@ -213,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; } diff --git a/packages/editor-ui/src/components/CollectionParameter.vue b/packages/editor-ui/src/components/CollectionParameter.vue index 1832e2f0f03e8..ebc87dcfcf74d 100644 --- a/packages/editor-ui/src/components/CollectionParameter.vue +++ b/packages/editor-ui/src/components/CollectionParameter.vue @@ -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 { diff --git a/packages/editor-ui/src/components/CredentialEdit/CredentialConfig.vue b/packages/editor-ui/src/components/CredentialEdit/CredentialConfig.vue index fc224bea60c17..70da5ade5bd1f 100644 --- a/packages/editor-ui/src/components/CredentialEdit/CredentialConfig.vue +++ b/packages/editor-ui/src/components/CredentialEdit/CredentialConfig.vue @@ -106,6 +106,7 @@ 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', @@ -181,6 +182,7 @@ export default mixins(restApi).extend({ }, computed: { ...mapStores( + useNDVStore, useRootStore, useUIStore, useWorkflowsStore, @@ -204,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) { diff --git a/packages/editor-ui/src/components/CredentialEdit/CredentialEdit.vue b/packages/editor-ui/src/components/CredentialEdit/CredentialEdit.vue index f78a88f478f86..87690a47e7106 100644 --- a/packages/editor-ui/src/components/CredentialEdit/CredentialEdit.vue +++ b/packages/editor-ui/src/components/CredentialEdit/CredentialEdit.vue @@ -149,6 +149,7 @@ 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; @@ -240,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(() => { @@ -258,6 +259,7 @@ export default mixins(showMessage, nodeHelpers).extend({ }, computed: { ...mapStores( + useNDVStore, useSettingsStore, useUIStore, useUsersStore, @@ -573,7 +575,7 @@ 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, @@ -789,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 !== '') { diff --git a/packages/editor-ui/src/components/Draggable.vue b/packages/editor-ui/src/components/Draggable.vue index da82788e3fc95..33405f7ca050b 100644 --- a/packages/editor-ui/src/components/Draggable.vue +++ b/packages/editor-ui/src/components/Draggable.vue @@ -21,6 +21,8 @@