From d6724087f69841e85f81d2fcfa33af1c45a28522 Mon Sep 17 00:00:00 2001 From: jasquat Date: Thu, 4 May 2023 15:44:52 -0400 Subject: [PATCH] poll the backend for active users instead of keeping the connection open so it does not hang on a process w/ burnettk --- .../routes/active_users_controller.py | 40 +++++++----------- spiffworkflow-frontend/src/helpers.tsx | 18 +++++--- .../src/routes/ProcessModelEditDiagram.tsx | 42 +++++++------------ 3 files changed, 42 insertions(+), 58 deletions(-) diff --git a/spiffworkflow-backend/src/spiffworkflow_backend/routes/active_users_controller.py b/spiffworkflow-backend/src/spiffworkflow_backend/routes/active_users_controller.py index 73f88102..0350ba20 100644 --- a/spiffworkflow-backend/src/spiffworkflow_backend/routes/active_users_controller.py +++ b/spiffworkflow-backend/src/spiffworkflow_backend/routes/active_users_controller.py @@ -1,11 +1,10 @@ import json import time -from typing import Generator import flask.wrappers -from flask import current_app from flask import g -from flask import stream_with_context +from flask import jsonify +from flask import make_response from flask.wrappers import Response from spiffworkflow_backend.models.active_user import ActiveUserModel @@ -24,30 +23,19 @@ def active_user_updates(last_visited_identifier: str) -> Response: db.session.add(active_user) db.session.commit() - return Response( - stream_with_context(_active_user_updates(last_visited_identifier, active_user=active_user)), - mimetype="text/event-stream", - headers={"X-Accel-Buffering": "no"}, + active_user.last_seen_in_seconds = round(time.time()) + db.session.add(active_user) + db.session.commit() + + cutoff_time_in_seconds = time.time() - 30 + active_users = ( + UserModel.query.join(ActiveUserModel) + .filter(ActiveUserModel.last_visited_identifier == last_visited_identifier) + .filter(ActiveUserModel.last_seen_in_seconds > cutoff_time_in_seconds) + .filter(UserModel.id != g.user.id) + .all() ) - - -def _active_user_updates(last_visited_identifier: str, active_user: ActiveUserModel) -> Generator[str, None, None]: - while True: - active_user.last_seen_in_seconds = round(time.time()) - db.session.add(active_user) - db.session.commit() - - cutoff_time_in_seconds = time.time() - 15 - active_users = ( - UserModel.query.join(ActiveUserModel) - .filter(ActiveUserModel.last_visited_identifier == last_visited_identifier) - .filter(ActiveUserModel.last_seen_in_seconds > cutoff_time_in_seconds) - .filter(UserModel.id != g.user.id) - .all() - ) - yield f"data: {current_app.json.dumps(active_users)} \n\n" - - time.sleep(5) + return make_response(jsonify(active_users), 200) def active_user_unregister(last_visited_identifier: str) -> flask.wrappers.Response: diff --git a/spiffworkflow-frontend/src/helpers.tsx b/spiffworkflow-frontend/src/helpers.tsx index 08aff8cf..c816302c 100644 --- a/spiffworkflow-frontend/src/helpers.tsx +++ b/spiffworkflow-frontend/src/helpers.tsx @@ -247,15 +247,21 @@ export const splitProcessModelId = (processModelId: string) => { export const refreshAtInterval = ( interval: number, timeout: number, - func: Function + periodicFunction: Function, + cleanupFunction?: Function ) => { - const intervalRef = setInterval(() => func(), interval * 1000); - const timeoutRef = setTimeout( - () => clearInterval(intervalRef), - timeout * 1000 - ); + const intervalRef = setInterval(() => periodicFunction(), interval * 1000); + const timeoutRef = setTimeout(() => { + clearInterval(intervalRef); + if (cleanupFunction) { + cleanupFunction(); + } + }, timeout * 1000); return () => { clearInterval(intervalRef); + if (cleanupFunction) { + cleanupFunction(); + } clearTimeout(timeoutRef); }; }; diff --git a/spiffworkflow-frontend/src/routes/ProcessModelEditDiagram.tsx b/spiffworkflow-frontend/src/routes/ProcessModelEditDiagram.tsx index 9ea22a5c..152fc179 100644 --- a/spiffworkflow-frontend/src/routes/ProcessModelEditDiagram.tsx +++ b/spiffworkflow-frontend/src/routes/ProcessModelEditDiagram.tsx @@ -25,9 +25,7 @@ import Col from 'react-bootstrap/Col'; import Editor, { DiffEditor } from '@monaco-editor/react'; import MDEditor from '@uiw/react-md-editor'; -import { fetchEventSource } from '@microsoft/fetch-event-source'; -import { BACKEND_BASE_URL } from '../config'; -import HttpService, { getBasicHeaders } from '../services/HttpService'; +import HttpService from '../services/HttpService'; import ReactDiagramEditor from '../components/ReactDiagramEditor'; import ProcessBreadcrumb from '../components/ProcessBreadcrumb'; import useAPIError from '../hooks/UseApiError'; @@ -35,6 +33,8 @@ import { makeid, modifyProcessIdentifierForPathParam, encodeBase64, + refreshAtInterval, + REFRESH_TIMEOUT_SECONDS, } from '../helpers'; import { CarbonComboBoxProcessSelection, @@ -134,6 +134,12 @@ export default function ProcessModelEditDiagram() { const lastVisitedIdentifier = encodeBase64(window.location.pathname); useEffect(() => { + const updateActiveUsers = () => { + HttpService.makeCallToBackend({ + path: `/active-users/updates/${lastVisitedIdentifier}`, + successCallback: setActiveUsers, + }); + }; // Grab all available process models in case we need to search for them. // Taken from the Process Group List const processResults = (result: any) => { @@ -155,30 +161,14 @@ export default function ProcessModelEditDiagram() { successCallback: setActiveUsers, }); }; - fetchEventSource( - `${BACKEND_BASE_URL}/active-users/updates/${lastVisitedIdentifier}`, - { - headers: getBasicHeaders(), - onmessage(ev) { - const retValue = JSON.parse(ev.data); - if ('error_code' in retValue) { - addError(retValue); - } else { - setActiveUsers(retValue); - } - }, - onclose() { - unregisterUser(); - }, - onerror(err: any) { - throw err; - }, - } - ); + updateActiveUsers(); - // FIXME: this is not getting called when navigating away from this page. - // we do not know why yet. - return unregisterUser; + return refreshAtInterval( + 15, + REFRESH_TIMEOUT_SECONDS, + updateActiveUsers, + unregisterUser + ); // eslint-disable-next-line react-hooks/exhaustive-deps }, []); // it is critical to only run this once.