diff --git a/packages/editor-ui/src/api/eventbus.ee.ts b/packages/editor-ui/src/api/eventbus.ee.ts new file mode 100644 index 0000000000000..1b00f84630064 --- /dev/null +++ b/packages/editor-ui/src/api/eventbus.ee.ts @@ -0,0 +1,54 @@ +import { IRestApiContext } from '@/Interface'; +import { makeRestApiRequest } from '@/utils'; +import { IDataObject, MessageEventBusDestinationOptions } from 'n8n-workflow'; + +export async function saveDestinationToDb( + context: IRestApiContext, + destination: MessageEventBusDestinationOptions, + subscribedEvents: string[] = [], +) { + if (destination.id) { + const data: IDataObject = { + ...destination, + subscribedEvents, + }; + return makeRestApiRequest(context, 'POST', '/eventbus/destination', data); + } +} + +export async function deleteDestinationFromDb(context: IRestApiContext, destinationId: string) { + return makeRestApiRequest(context, 'DELETE', `/eventbus/destination?id=${destinationId}`); +} + +export async function sendTestMessageToDestination( + context: IRestApiContext, + destination: MessageEventBusDestinationOptions, +) { + if (destination.id) { + const data: IDataObject = { + ...destination, + }; + return makeRestApiRequest(context, 'GET', '/eventbus/testmessage', data); + } +} + +export async function getEventNamesFromBackend(context: IRestApiContext): Promise { + return makeRestApiRequest(context, 'GET', '/eventbus/eventnames'); +} + +export async function getDestinationsFromBackend( + context: IRestApiContext, +): Promise { + return makeRestApiRequest(context, 'GET', '/eventbus/destination'); +} + +export async function getExecutionEvents(context: IRestApiContext, executionId: string) { + return makeRestApiRequest(context, 'GET', `/eventbus/execution/${executionId}`); +} + +export async function recoverExecutionDataFromEvents( + context: IRestApiContext, + executionId: string, +) { + return makeRestApiRequest(context, 'GET', `/eventbus/execution-recover/${executionId}`); +} diff --git a/packages/editor-ui/src/components/SettingsLogStreaming/EventDestinationCard.ee.vue b/packages/editor-ui/src/components/SettingsLogStreaming/EventDestinationCard.ee.vue index 94c8c00a40476..b6823fa63accf 100644 --- a/packages/editor-ui/src/components/SettingsLogStreaming/EventDestinationCard.ee.vue +++ b/packages/editor-ui/src/components/SettingsLogStreaming/EventDestinationCard.ee.vue @@ -51,7 +51,6 @@ import mixins from 'vue-typed-mixins'; import { EnterpriseEditionFeature } from '@/constants'; import { showMessage } from '@/mixins/showMessage'; import { useLogStreamingStore } from '../../stores/logStreamingStore'; -import { restApi } from '@/mixins/restApi'; import Vue from 'vue'; import { mapStores } from 'pinia'; import { @@ -59,7 +58,6 @@ import { defaultMessageEventBusDestinationOptions, MessageEventBusDestinationOptions, } from 'n8n-workflow'; -import { saveDestinationToDb } from './Helpers.ee'; import { BaseTextKey } from '../../plugins/i18n'; export const DESTINATION_LIST_ITEM_ACTIONS = { @@ -67,7 +65,7 @@ export const DESTINATION_LIST_ITEM_ACTIONS = { DELETE: 'delete', }; -export default mixins(showMessage, restApi).extend({ +export default mixins(showMessage).extend({ data() { return { EnterpriseEditionFeature, @@ -142,7 +140,7 @@ export default mixins(showMessage, restApi).extend({ this.saveDestination(); }, async saveDestination() { - await saveDestinationToDb(this.restApi(), this.nodeParameters); + await this.logStreamingStore.saveDestination(this.nodeParameters); }, async onAction(action: string) { if (action === DESTINATION_LIST_ITEM_ACTIONS.OPEN) { diff --git a/packages/editor-ui/src/components/SettingsLogStreaming/EventDestinationSettingsModal.ee.vue b/packages/editor-ui/src/components/SettingsLogStreaming/EventDestinationSettingsModal.ee.vue index 3e66cb512f7ad..0b793d1d813d0 100644 --- a/packages/editor-ui/src/components/SettingsLogStreaming/EventDestinationSettingsModal.ee.vue +++ b/packages/editor-ui/src/components/SettingsLogStreaming/EventDestinationSettingsModal.ee.vue @@ -178,7 +178,6 @@ import mixins from 'vue-typed-mixins'; import { useLogStreamingStore } from '../../stores/logStreamingStore'; import { useNDVStore } from '../../stores/ndv'; import { useWorkflowsStore } from '../../stores/workflows'; -import { restApi } from '../../mixins/restApi'; import ParameterInputList from '@/components/ParameterInputList.vue'; import NodeCredentials from '@/components/NodeCredentials.vue'; import { IMenuItem, INodeUi, ITab, IUpdateInformation } from '../../Interface'; @@ -200,7 +199,7 @@ import Modal from '@/components/Modal.vue'; import { showMessage } from '@/mixins/showMessage'; import { useUIStore } from '../../stores/ui'; import { useUsersStore } from '../../stores/users'; -import { destinationToFakeINodeUi, saveDestinationToDb, sendTestMessage } from './Helpers.ee'; +import { destinationToFakeINodeUi } from './Helpers.ee'; import { webhookModalDescription, sentryModalDescription, @@ -212,7 +211,7 @@ import SaveButton from '../SaveButton.vue'; import EventSelection from '@/components/SettingsLogStreaming/EventSelection.ee.vue'; import { Checkbox } from 'element-ui'; -export default mixins(showMessage, restApi).extend({ +export default mixins(showMessage).extend({ name: 'event-destination-settings-modal', props: { modalName: String, @@ -427,12 +426,14 @@ export default mixins(showMessage, restApi).extend({ this.nodeParameters = deepCopy(nodeParameters); this.workflowsStore.updateNodeProperties({ name: this.node.name, - properties: { parameters: this.nodeParameters as unknown as IDataObject }, + properties: { parameters: this.nodeParameters as unknown as IDataObject, position: [0, 0] }, }); - this.logStreamingStore.updateDestination(this.nodeParameters); + if (this.hasOnceBeenSaved) { + this.logStreamingStore.updateDestination(this.nodeParameters); + } }, async sendTestEvent() { - this.testMessageResult = await sendTestMessage(this.restApi(), this.nodeParameters); + this.testMessageResult = await this.logStreamingStore.sendTestMessage(this.nodeParameters); this.testMessageSent = true; }, async removeThis() { @@ -467,12 +468,14 @@ export default mixins(showMessage, restApi).extend({ if (this.unchanged || !this.destination.id) { return; } - await saveDestinationToDb(this.restApi(), this.nodeParameters); - this.hasOnceBeenSaved = true; - this.testMessageSent = false; - this.unchanged = true; - this.$props.eventBus.$emit('destinationWasSaved', this.destination.id); - this.uiStore.stateIsDirty = false; + const saveResult = await this.logStreamingStore.saveDestination(this.nodeParameters); + if (saveResult === true) { + this.hasOnceBeenSaved = true; + this.testMessageSent = false; + this.unchanged = true; + this.$props.eventBus.$emit('destinationWasSaved', this.destination.id); + this.uiStore.stateIsDirty = false; + } }, }, }); diff --git a/packages/editor-ui/src/components/SettingsLogStreaming/Helpers.ee.ts b/packages/editor-ui/src/components/SettingsLogStreaming/Helpers.ee.ts index 26b8bd5bcedf5..1f367ed9c1971 100644 --- a/packages/editor-ui/src/components/SettingsLogStreaming/Helpers.ee.ts +++ b/packages/editor-ui/src/components/SettingsLogStreaming/Helpers.ee.ts @@ -1,6 +1,5 @@ import { INodeCredentials, INodeParameters, MessageEventBusDestinationOptions } from 'n8n-workflow'; -import { INodeUi, IRestApi } from '../../Interface'; -import { useLogStreamingStore } from '../../stores/logStreamingStore'; +import { INodeUi } from '../../Interface'; export function destinationToFakeINodeUi( destination: MessageEventBusDestinationOptions, @@ -20,39 +19,3 @@ export function destinationToFakeINodeUi( }, } as INodeUi; } - -export async function saveDestinationToDb( - restApi: IRestApi, - destination: MessageEventBusDestinationOptions, -) { - const logStreamingStore = useLogStreamingStore(); - if (destination.id) { - const data: MessageEventBusDestinationOptions = { - ...destination, - subscribedEvents: logStreamingStore.getSelectedEvents(destination.id), - }; - try { - await restApi.makeRestApiRequest('POST', '/eventbus/destination', data); - } catch (error) { - console.log(error); - } - logStreamingStore.updateDestination(destination); - } -} - -export async function sendTestMessage( - restApi: IRestApi, - destination: MessageEventBusDestinationOptions, -) { - if (destination.id) { - try { - const sendResult = await restApi.makeRestApiRequest('GET', '/eventbus/testmessage', { - id: destination.id, - }); - return sendResult; - } catch (error) { - console.log(error); - } - return false; - } -} diff --git a/packages/editor-ui/src/stores/logStreamingStore.ts b/packages/editor-ui/src/stores/logStreamingStore.ts index 6e157716d2983..3c1794cbe53a2 100644 --- a/packages/editor-ui/src/stores/logStreamingStore.ts +++ b/packages/editor-ui/src/stores/logStreamingStore.ts @@ -1,5 +1,13 @@ import { deepCopy, MessageEventBusDestinationOptions } from 'n8n-workflow'; import { defineStore } from 'pinia'; +import { + deleteDestinationFromDb, + getDestinationsFromBackend, + getEventNamesFromBackend, + saveDestinationToDb, + sendTestMessageToDestination, +} from '../api/eventbus.ee'; +import { useRootStore } from './n8nRootStore'; export interface EventSelectionItem { selected: boolean; @@ -8,18 +16,19 @@ export interface EventSelectionItem { label: string; } -export interface EventSelectionGroup extends EventSelectionItem { +interface EventSelectionGroup extends EventSelectionItem { children: EventSelectionItem[]; } -export interface TreeAndSelectionStoreItem { +interface DestinationStoreItem { destination: MessageEventBusDestinationOptions; selectedEvents: Set; eventGroups: EventSelectionGroup[]; + isNew: boolean; } export interface DestinationSettingsStore { - [key: string]: TreeAndSelectionStoreItem; + [key: string]: DestinationStoreItem; } export const useLogStreamingStore = defineStore('logStreaming', { @@ -51,13 +60,15 @@ export const useLogStreamingStore = defineStore('logStreaming', { return destinations; }, updateDestination(destination: MessageEventBusDestinationOptions) { - this.$patch((state) => { - if (destination.id && destination.id in this.items) { - state.items[destination.id].destination = destination; - } - // to trigger refresh - state.items = deepCopy(state.items); - }); + if (destination.id && destination.id in this.items) { + this.$patch((state) => { + if (destination.id && destination.id in this.items) { + state.items[destination.id].destination = destination; + } + // to trigger refresh + state.items = deepCopy(state.items); + }); + } }, removeDestination(destinationId: string) { if (!destinationId) return; @@ -159,7 +170,8 @@ export const useLogStreamingStore = defineStore('logStreaming', { destination, selectedEvents: new Set(), eventGroups: [], - } as TreeAndSelectionStoreItem; + isNew: false, + } as DestinationStoreItem; } this.items[destination.id]?.selectedEvents?.clear(); if (destination.subscribedEvents) { @@ -173,6 +185,44 @@ export const useLogStreamingStore = defineStore('logStreaming', { ); } }, + async saveDestination(destination: MessageEventBusDestinationOptions): Promise { + if (destination.id) { + const rootStore = useRootStore(); + const selectedEvents = this.getSelectedEvents(destination.id); + try { + await saveDestinationToDb(rootStore.getRestApiContext, destination, selectedEvents); + this.updateDestination(destination); + return true; + } catch (e) { + return false; + } + } + return false; + }, + async sendTestMessage(destination: MessageEventBusDestinationOptions) { + if (destination.id) { + const rootStore = useRootStore(); + const testResult = await sendTestMessageToDestination( + rootStore.getRestApiContext, + destination, + ); + return testResult; + } + return false; + }, + async fetchEventNames(): Promise { + const rootStore = useRootStore(); + return getEventNamesFromBackend(rootStore.getRestApiContext); + }, + async fetchDestinations(): Promise { + const rootStore = useRootStore(); + return getDestinationsFromBackend(rootStore.getRestApiContext); + }, + async deleteDestination(destinationId: string) { + const rootStore = useRootStore(); + await deleteDestinationFromDb(rootStore.getRestApiContext, destinationId); + this.removeDestination(destinationId); + }, }, }); diff --git a/packages/editor-ui/src/views/SettingsLogStreamingView.vue b/packages/editor-ui/src/views/SettingsLogStreamingView.vue index eb8f6adaa2c81..eb70ebc10d133 100644 --- a/packages/editor-ui/src/views/SettingsLogStreamingView.vue +++ b/packages/editor-ui/src/views/SettingsLogStreamingView.vue @@ -90,7 +90,6 @@ import { useSettingsStore } from '../stores/settings'; import { useUIStore } from '../stores/ui'; import { LOG_STREAM_MODAL_KEY, EnterpriseEditionFeature } from '../constants'; import Vue from 'vue'; -import { restApi } from '../mixins/restApi'; import { deepCopy, defaultMessageEventBusDestinationOptions, @@ -99,7 +98,7 @@ import { import PageViewLayout from '@/components/layouts/PageViewLayout.vue'; import EventDestinationCard from '@/components/SettingsLogStreaming/EventDestinationCard.ee.vue'; -export default mixins(restApi).extend({ +export default mixins().extend({ name: 'SettingsLogStreamingView', props: {}, components: { @@ -125,7 +124,7 @@ export default mixins(restApi).extend({ this.uiStore.nodeViewInitialized = false; // fetch Destination data from the backend - await this.getDestinationDataFromREST(); + await this.getDestinationDataFromBackend(); // since we are not really integrated into the hooks, we listen to the store and refresh the destinations this.logStreamingStore.$onAction(({ name, after }) => { @@ -174,18 +173,18 @@ export default mixins(restApi).extend({ }, }, methods: { - async getDestinationDataFromREST(): Promise { + async getDestinationDataFromBackend(): Promise { this.logStreamingStore.clearEventNames(); this.logStreamingStore.clearDestinationItemTrees(); this.allDestinations = []; - const eventNamesData = await this.restApi().makeRestApiRequest('get', '/eventbus/eventnames'); + const eventNamesData = await this.logStreamingStore.fetchEventNames(); if (eventNamesData) { for (const eventName of eventNamesData) { this.logStreamingStore.addEventName(eventName); } } const destinationData: MessageEventBusDestinationOptions[] = - await this.restApi().makeRestApiRequest('get', '/eventbus/destination'); + await this.logStreamingStore.fetchDestinations(); if (destinationData) { for (const destination of destinationData) { this.logStreamingStore.addDestination(destination); @@ -218,11 +217,7 @@ export default mixins(restApi).extend({ }, async onRemove(destinationId?: string) { if (!destinationId) return; - await this.restApi().makeRestApiRequest( - 'DELETE', - `/eventbus/destination?id=${destinationId}`, - ); - this.logStreamingStore.removeDestination(destinationId); + await this.logStreamingStore.deleteDestination(destinationId); const foundNode = this.workflowsStore.getNodeByName(destinationId); if (foundNode) { this.workflowsStore.removeNode(foundNode);