feat(editor): Show avatars for users currently working on the same workflow (#7763)

This PR introduces the following changes:
- New Vue stores: `collaborationStore` and `pushConnectionStore`
- Front-end push connection handling overhaul: Keep only a singe
connection open and handle it from the new store
- Add user avatars in the editor header when there are multiple users
working on the same workflow
- Sending a heartbeat event to back-end service periodically to confirm
user is still active

- Back-end overhauls (authored by @tomi):
  - Implementing a cleanup procedure that removes inactive users
  - Refactoring collaboration service current implementation

---------

Co-authored-by: Tomi Turtiainen <10324676+tomi@users.noreply.github.com>
This commit is contained in:
Milorad FIlipović
2023-11-23 10:14:34 +01:00
committed by GitHub
parent 99a9ea497a
commit 77bc8ecd4b
18 changed files with 654 additions and 148 deletions

View File

@@ -230,6 +230,7 @@ import {
AI_NODE_CREATOR_VIEW,
DRAG_EVENT_DATA_KEY,
UPDATE_WEBHOOK_ID_NODE_TYPES,
TIME,
} from '@/constants';
import { copyPaste } from '@/mixins/copyPaste';
import { externalHooks } from '@/mixins/externalHooks';
@@ -329,6 +330,7 @@ import {
useUIStore,
useHistoryStore,
useExternalSecretsStore,
useCollaborationStore,
} from '@/stores';
import * as NodeViewUtils from '@/utils/nodeViewUtils';
import { getAccountAge, getConnectionInfo, getNodeViewTab } from '@/utils';
@@ -523,15 +525,18 @@ export default defineComponent({
},
);
} else {
this.collaborationStore.notifyWorkflowClosed(this.currentWorkflow);
next();
}
} else if (confirmModal === MODAL_CANCEL) {
this.collaborationStore.notifyWorkflowClosed(this.currentWorkflow);
this.workflowsStore.setWorkflowId(PLACEHOLDER_EMPTY_WORKFLOW_ID);
this.resetWorkspace();
this.uiStore.stateIsDirty = false;
next();
}
} else {
this.collaborationStore.notifyWorkflowClosed(this.currentWorkflow);
next();
}
},
@@ -554,6 +559,7 @@ export default defineComponent({
useWorkflowsEEStore,
useHistoryStore,
useExternalSecretsStore,
useCollaborationStore,
),
nativelyNumberSuffixedDefaults(): string[] {
return this.nodeTypesStore.nativelyNumberSuffixedDefaults;
@@ -717,6 +723,7 @@ export default defineComponent({
suspendRecordingDetachedConnections: false,
NODE_CREATOR_OPEN_SOURCES,
eventsAttached: false,
unloadTimeout: undefined as undefined | ReturnType<typeof setTimeout>,
};
},
methods: {
@@ -1064,6 +1071,7 @@ export default defineComponent({
this.workflowsStore.activeWorkflowExecution = selectedExecution;
}
this.stopLoading();
this.collaborationStore.notifyWorkflowOpened(workflow.id);
},
touchTap(e: MouseEvent | TouchEvent) {
if (this.isTouchDevice) {
@@ -3047,20 +3055,30 @@ export default defineComponent({
this.eventsAttached = false;
},
onBeforeUnload(e) {
onBeforeUnload(e: BeforeUnloadEvent) {
if (this.isDemo || window.preventNodeViewBeforeUnload) {
return;
} else if (this.uiStore.stateIsDirty) {
const confirmationMessage = this.$locale.baseText(
'nodeView.itLooksLikeYouHaveBeenEditingSomething',
);
(e || window.event).returnValue = confirmationMessage; //Gecko + IE
return confirmationMessage; //Gecko + Webkit, Safari, Chrome etc.
// A bit hacky solution to detecting users leaving the page after prompt:
// 1. Notify that workflow is closed straight away
this.collaborationStore.notifyWorkflowClosed(this.workflowsStore.workflowId);
// 2. If user decided to stay on the page we notify that the workflow is opened again
this.unloadTimeout = setTimeout(() => {
this.collaborationStore.notifyWorkflowOpened(this.workflowsStore.workflowId);
}, 5 * TIME.SECOND);
e.returnValue = true; //Gecko + IE
return true; //Gecko + Webkit, Safari, Chrome etc.
} else {
this.startLoading(this.$locale.baseText('nodeView.redirecting'));
this.collaborationStore.notifyWorkflowClosed(this.workflowsStore.workflowId);
return;
}
},
onUnload() {
// This will fire if users decides to leave the page after prompted
// Clear the interval to prevent the notification from being sent
clearTimeout(this.unloadTimeout);
},
async newWorkflow(): Promise<void> {
this.startLoading();
this.resetWorkspace();
@@ -3159,6 +3177,7 @@ export default defineComponent({
document.addEventListener('keyup', this.keyUp);
window.addEventListener('beforeunload', this.onBeforeUnload);
window.addEventListener('unload', this.onUnload);
},
getOutputEndpointUUID(
nodeName: string,