poll the backend for active users instead of keeping the connection open so it does not hang on a process w/ burnettk

This commit is contained in:
jasquat 2023-05-04 15:44:52 -04:00
parent c5d7a87e61
commit d6724087f6
3 changed files with 42 additions and 58 deletions

View File

@ -1,11 +1,10 @@
import json import json
import time import time
from typing import Generator
import flask.wrappers import flask.wrappers
from flask import current_app
from flask import g 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 flask.wrappers import Response
from spiffworkflow_backend.models.active_user import ActiveUserModel 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.add(active_user)
db.session.commit() db.session.commit()
return Response( active_user.last_seen_in_seconds = round(time.time())
stream_with_context(_active_user_updates(last_visited_identifier, active_user=active_user)), db.session.add(active_user)
mimetype="text/event-stream", db.session.commit()
headers={"X-Accel-Buffering": "no"},
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()
) )
return make_response(jsonify(active_users), 200)
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)
def active_user_unregister(last_visited_identifier: str) -> flask.wrappers.Response: def active_user_unregister(last_visited_identifier: str) -> flask.wrappers.Response:

View File

@ -247,15 +247,21 @@ export const splitProcessModelId = (processModelId: string) => {
export const refreshAtInterval = ( export const refreshAtInterval = (
interval: number, interval: number,
timeout: number, timeout: number,
func: Function periodicFunction: Function,
cleanupFunction?: Function
) => { ) => {
const intervalRef = setInterval(() => func(), interval * 1000); const intervalRef = setInterval(() => periodicFunction(), interval * 1000);
const timeoutRef = setTimeout( const timeoutRef = setTimeout(() => {
() => clearInterval(intervalRef), clearInterval(intervalRef);
timeout * 1000 if (cleanupFunction) {
); cleanupFunction();
}
}, timeout * 1000);
return () => { return () => {
clearInterval(intervalRef); clearInterval(intervalRef);
if (cleanupFunction) {
cleanupFunction();
}
clearTimeout(timeoutRef); clearTimeout(timeoutRef);
}; };
}; };

View File

@ -25,9 +25,7 @@ import Col from 'react-bootstrap/Col';
import Editor, { DiffEditor } from '@monaco-editor/react'; import Editor, { DiffEditor } from '@monaco-editor/react';
import MDEditor from '@uiw/react-md-editor'; import MDEditor from '@uiw/react-md-editor';
import { fetchEventSource } from '@microsoft/fetch-event-source'; import HttpService from '../services/HttpService';
import { BACKEND_BASE_URL } from '../config';
import HttpService, { getBasicHeaders } from '../services/HttpService';
import ReactDiagramEditor from '../components/ReactDiagramEditor'; import ReactDiagramEditor from '../components/ReactDiagramEditor';
import ProcessBreadcrumb from '../components/ProcessBreadcrumb'; import ProcessBreadcrumb from '../components/ProcessBreadcrumb';
import useAPIError from '../hooks/UseApiError'; import useAPIError from '../hooks/UseApiError';
@ -35,6 +33,8 @@ import {
makeid, makeid,
modifyProcessIdentifierForPathParam, modifyProcessIdentifierForPathParam,
encodeBase64, encodeBase64,
refreshAtInterval,
REFRESH_TIMEOUT_SECONDS,
} from '../helpers'; } from '../helpers';
import { import {
CarbonComboBoxProcessSelection, CarbonComboBoxProcessSelection,
@ -134,6 +134,12 @@ export default function ProcessModelEditDiagram() {
const lastVisitedIdentifier = encodeBase64(window.location.pathname); const lastVisitedIdentifier = encodeBase64(window.location.pathname);
useEffect(() => { 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. // Grab all available process models in case we need to search for them.
// Taken from the Process Group List // Taken from the Process Group List
const processResults = (result: any) => { const processResults = (result: any) => {
@ -155,30 +161,14 @@ export default function ProcessModelEditDiagram() {
successCallback: setActiveUsers, successCallback: setActiveUsers,
}); });
}; };
fetchEventSource( updateActiveUsers();
`${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;
},
}
);
// FIXME: this is not getting called when navigating away from this page. return refreshAtInterval(
// we do not know why yet. 15,
return unregisterUser; REFRESH_TIMEOUT_SECONDS,
updateActiveUsers,
unregisterUser
);
// eslint-disable-next-line react-hooks/exhaustive-deps // eslint-disable-next-line react-hooks/exhaustive-deps
}, []); // it is critical to only run this once. }, []); // it is critical to only run this once.