Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

add SSEs for Field Report updates, listen for them from frontend #1523

Merged
merged 1 commit into from
Jan 16, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ Each month below should look like the following, using the same ordering for the

- Introduced "striking" of report entries. This allows a user to hide an outdated/inaccurate entry, such that it doesn't appear by default on the Incident or Field Report page. https://github.com/burningmantech/ranger-ims-server/issues/249
- Added help modals, toggled by pressing "?", which show keyboard shortcuts for the current page. https://github.com/burningmantech/ranger-ims-server/issues/1482
- Started publishing Field Report entity updates to the web clients (via server-sent events), and started automatically background-updating the Field Reports (table) and Field Report pages on updates. https://github.com/burningmantech/ranger-ims-server/issues/1498

### Fixed

Expand Down
22 changes: 21 additions & 1 deletion src/ims/application/_eventsource.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@
from zope.interface import implementer

from ims.ext.json_ext import jsonTextFromObject
from ims.model import Incident
from ims.model import FieldReport, Incident


__all__ = ("DataStoreEventSourceLogObserver",)
Expand Down Expand Up @@ -136,7 +136,27 @@ def _transmogrify(self, loggerEvent: Mapping[str, Any]) -> Event | None:
"event_id": eventName,
"incident_number": incidentNumber,
}
elif eventClass is FieldReport:
fieldReport = loggerEvent.get("fieldReport", None)

if fieldReport is None:
fieldReportNumber = loggerEvent.get("fieldReportNumber", None)
eventName = loggerEvent.get("eventID", "")
else:
fieldReportNumber = fieldReport.number
eventName = fieldReport.eventID

if fieldReportNumber is None:
self._log.critical(
"Unable to determine field report number from store event: {event}",
event=loggerEvent,
)
return None

message = {
"event_id": eventName,
"field_report_number": fieldReportNumber,
}
else:
self._log.critical(
"Unknown data store event class {eventClass} sent event: {event}",
Expand Down
15 changes: 15 additions & 0 deletions src/ims/element/static/field_report.js
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,21 @@ function initFieldReportPage() {
disableEditing();
loadAndDisplayFieldReport(loadedFieldReport);

// Updates...it's fine to ignore the returned promise here
requestEventSourceLock();

const fieldReportChannel = new BroadcastChannel(fieldReportChannelName);
fieldReportChannel.onmessage = function (e) {
const number = e.data["field_report_number"];
const event = e.data["event_id"]
const updateAll = e.data["update_all"];

if (updateAll || (event === eventID && number === fieldReportNumber)) {
console.log("Got field report update: " + number);
loadAndDisplayFieldReport();
}
}

// Keyboard shortcuts
document.addEventListener("keydown", function(e) {
// No shortcuts when an input field is active
Expand Down
13 changes: 13 additions & 0 deletions src/ims/element/static/field_reports.js
Original file line number Diff line number Diff line change
Expand Up @@ -85,12 +85,25 @@ function initFieldReportsTable() {
requestEventSourceLock();
const fieldReportChannel = new BroadcastChannel(fieldReportChannelName);
fieldReportChannel.onmessage = function (e) {
if (e.data["update_all"]) {
console.log("Reloading the whole table to be cautious, as an SSE was missed")
fieldReportsTable.ajax.reload(clearErrorMessage);
return;
}

const number = e.data["field_report_number"];
const event = e.data["event_id"]
if (event !== eventID) {
return;
}
console.log("Got field report update: " + number);
// TODO(issue/1498): this reloads the entire Field Report table on any
// update to any Field Report. That's not ideal. The thing of which
// to be mindful when GETting a particular single Field Report is that
// limited access users will receive errors when they try to access
// Field Reports for which they're not authorized, and those errors
// show up in the browser console. I'd like to find a way to avoid
// bringing those errors into the console constantly.
fieldReportsTable.ajax.reload(clearErrorMessage);
}
}
Expand Down
7 changes: 4 additions & 3 deletions src/ims/element/static/ims.js
Original file line number Diff line number Diff line change
Expand Up @@ -1080,9 +1080,10 @@ function subscribeToUpdates(closed) {
send.postMessage(JSON.parse(e.data));
}, true);

// TODO: this will never receive any events currently, since the server isn't configured to
// fire events for FieldReports. See
// https://github.com/burningmantech/ranger-ims-server/blob/954498eb125bb9a83d2b922361abef4935f228ba/src/ims/application/_eventsource.py#L113-L135
// TODO(issue/1498): SSEs are now firing for Field Report updates, but we need
// to find an appropriate way for the various pages to handle these updates
// (i.e. without excessive volume of API calls or "unauthorized" errors from
// users with limited access).
eventSource.addEventListener("FieldReport", function(e) {
const send = new BroadcastChannel(fieldReportChannelName);
localStorage.setItem(lastSseIDKey, e.lastEventId);
Expand Down
9 changes: 7 additions & 2 deletions src/ims/element/static/incident.js
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,11 @@ function initIncidentPage() {
loadAndDisplayFieldReports();
}
}
// TODO(issue/1498): this page doesn't currently listen for Field Report
// updates, but it probably should. Those updates could be used to add
// to the merged report entries or to the list of Field Reports available
// to be attached. We just want to be careful not to reload all the Field
// Reports on any update, lest we introduce heightened latency.

// Keyboard shortcuts
document.addEventListener("keydown", function(e) {
Expand Down Expand Up @@ -270,7 +275,7 @@ function localLoadPersonnel() {
// Load incident types
//

var incidentTypes = null;
let incidentTypes = null;


function loadIncidentTypesAndCache(success) {
Expand Down Expand Up @@ -722,7 +727,7 @@ function drawMergedReportEntries() {
}
}

entries.sort(compareReportEntries)
entries.sort(compareReportEntries);

drawReportEntries(entries);
}
Expand Down
30 changes: 25 additions & 5 deletions src/ims/store/_db.py
Original file line number Diff line number Diff line change
Expand Up @@ -881,6 +881,19 @@ def _notifyIncidentUpdate(
incidentNumber=incidentNumber,
)

def _notifyFieldReportUpdate(
self,
eventID: str,
fieldReportNumber: int,
) -> None:
# This will trigger the DataStoreEventSourceLogObserver
self._log.info(
"Firing field report update event for {eventID}#{fieldReportNumber}",
storeWriteClass=FieldReport,
eventID=eventID,
fieldReportNumber=fieldReportNumber,
)

def _createAndAttachReportEntriesToIncident(
self,
eventID: str,
Expand Down Expand Up @@ -1673,7 +1686,6 @@ def _createAndAttachReportEntriesToFieldReport(
self._log.info(
"Attached report entries to field report "
"{eventID}#{fieldReportNumber}: {reportEntries}",
storeWriteClass=FieldReport,
eventID=eventID,
fieldReportNumber=fieldReportNumber,
reportEntries=reportEntries,
Expand Down Expand Up @@ -1749,10 +1761,13 @@ def createFieldReport(

self._log.info(
"Created field report: {fieldReport}",
storeWriteClass=FieldReport,
fieldReport=fieldReport,
)

self._notifyFieldReportUpdate(
eventID=fieldReport.eventID, fieldReportNumber=fieldReport.number
)

return fieldReport

async def createFieldReport(
Expand Down Expand Up @@ -1828,6 +1843,10 @@ def setFieldReportAttribute(txn: Transaction) -> None:
author=author,
)

self._notifyFieldReportUpdate(
eventID=eventID, fieldReportNumber=fieldReportNumber
)

async def setFieldReport_summary(
self,
eventID: str,
Expand Down Expand Up @@ -1888,6 +1907,9 @@ def addReportEntriesToFieldReport(txn: Transaction) -> None:
error=e,
)
raise
self._notifyFieldReportUpdate(
eventID=eventID, fieldReportNumber=fieldReportNumber
)

###
# Incident to Field Report Relationships
Expand Down Expand Up @@ -2031,9 +2053,7 @@ def setStricken(txn: Transaction) -> None:
error=e,
)
raise
# We still need a notify function like this
# We should also notify the linked incident, if any
# self._notifyFieldReportUpdate(eventID, fieldReportNumber)
self._notifyFieldReportUpdate(eventID, fieldReportNumber)


@frozen(kw_only=True)
Expand Down
Loading