Collapse SSE feature flags into single enable_sse_updates

Replace enable_sse_history_updates and enable_sse_entry_point_updates
with a single enable_sse_updates. The two flags shared the same SSE
connection plumbing (events/stream + dispatcher), and only the
HistoryAuditMonitor lifecycle has real per-process cost — entry-point
dispatch is event-driven and effectively free. One knob admins can flip.

Updates schema, sample config, server (app, configuration manager,
history_audit_monitor, sse_monitor), client stores and their tests,
integration tests, and the regenerated admin docs.
This commit is contained in:
mvdbeek
2026-04-28 19:15:24 +02:00
parent 5def1f8cb5
commit bbb4cdc5f1
12 changed files with 91 additions and 75 deletions
+1 -1
View File
@@ -66,7 +66,7 @@ export const useEntryPointStore = defineStore("entryPointStore", () => {
const configStore = useConfigStore();
const decide = () => {
if (configStore.config?.enable_sse_entry_point_updates) {
if (configStore.config?.enable_sse_updates) {
// Baseline fetch + SSE. Reconnect-refetch closes the "user
// navigated away and missed events" window.
fetchEntryPoints().catch((err) => console.warn("Initial entry-point load failed", err));
+3 -3
View File
@@ -41,7 +41,7 @@ function registerDefaultHandlers({ enableSse }: { enableSse: boolean }) {
server.use(
http.get("/api/configuration", ({ response }) => {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
return response(200).json({ enable_sse_history_updates: enableSse } as any);
return response(200).json({ enable_sse_updates: enableSse } as any);
}),
);
}
@@ -73,7 +73,7 @@ describe("historyStore — config-driven SSE vs polling", () => {
vi.useRealTimers();
});
describe("when enable_sse_history_updates is true (SSE scenario)", () => {
describe("when enable_sse_updates is true (SSE scenario)", () => {
beforeEach(() => {
registerDefaultHandlers({ enableSse: true });
});
@@ -148,7 +148,7 @@ describe("historyStore — config-driven SSE vs polling", () => {
});
});
describe("when enable_sse_history_updates is false (polling scenario)", () => {
describe("when enable_sse_updates is false (polling scenario)", () => {
beforeEach(() => {
registerDefaultHandlers({ enableSse: false });
});
+2 -2
View File
@@ -423,7 +423,7 @@ export const useHistoryStore = defineStore("historyStore", () => {
}
// Choose between SSE and polling based on the server config flag
// `enable_sse_history_updates`. SSE success at the socket level is not a
// `enable_sse_updates`. SSE success at the socket level is not a
// reliable proxy: the `/api/events/stream` endpoint accepts connections
// even when the HistoryAuditMonitor is disabled, so relying on the
// EventSource `connected` state would silently stop polling without any
@@ -443,7 +443,7 @@ export const useHistoryStore = defineStore("historyStore", () => {
const configStore = useConfigStore();
const decide = () => {
if (configStore.config?.enable_sse_history_updates) {
if (configStore.config?.enable_sse_updates) {
// SSE delivers incremental updates only; the store still needs
// a baseline fetch so the history panel isn't empty until the
// first change arrives.
+38 -25
View File
@@ -281,6 +281,23 @@
:Type: int
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
``kombu_sqla_transport_cleanup_interval``
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
:Description:
Time (in seconds) between attempts to delete fully-consumed rows
from the Kombu SQLAlchemy transport tables (``kombu_message``).
Only relevant when ``amqp_internal_connection`` uses a
``sqlalchemy+*`` scheme (the default with an on-disk
control.sqlite); the SQLAlchemy transport has no built-in TTL, so
without this task the tables grow unbounded. The task no-ops on
non-SQLAlchemy brokers (RabbitMQ/Redis honor per-message
expiration natively). Set to 0 to disable the cleanup task.
:Default: ``900``
:Type: int
~~~~~~~~~~~~~
``file_path``
~~~~~~~~~~~~~
@@ -5816,30 +5833,23 @@
:Type: str
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
``enable_sse_history_updates``
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
~~~~~~~~~~~~~~~~~~~~~~
``enable_sse_updates``
~~~~~~~~~~~~~~~~~~~~~~
:Description:
Enables real-time history update notifications via Server-Sent
Events (SSE). When enabled, a background monitor watches for
history changes (via PostgreSQL LISTEN/NOTIFY or audit table
polling as a fallback for SQLite) and pushes update signals to
connected browsers, replacing aggressive 3-second polling.
:Default: ``false``
:Type: bool
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
``enable_sse_entry_point_updates``
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
:Description:
Enables real-time interactive-tool entry-point update
notifications via Server-Sent Events. When enabled, the client
subscribes to entry_point_update SSE events and refetches the
entry-point list on each event, replacing the 10-second polling
loop. When disabled, polling remains the source of updates.
Enables real-time updates via Server-Sent Events (SSE), replacing
the history (3 s), entry-point (10 s) and notification (30 s)
polling loops with push events delivered over a single
``/api/events/stream`` connection per browser tab. A background
monitor watches for history changes (via PostgreSQL LISTEN/NOTIFY,
or audit-table polling as a fallback for SQLite); entry-point
changes are dispatched directly from the code paths that mutate
them; in-app notifications and broadcasts are pushed when
``enable_notification_system`` is also true. When disabled,
polling remains the source of updates for all three. See the admin
guide "Server-Sent Events for real-time updates" for the full
architecture, monitoring guidance and proxy configuration.
:Default: ``false``
:Type: bool
@@ -5851,9 +5861,9 @@
:Description:
The interval in seconds between history audit table polls when
using the polling fallback (SQLite or when PostgreSQL
LISTEN/NOTIFY is unavailable). Only used when
enable_sse_history_updates is true. Lower values mean faster
updates but more database queries. Recommended range: 1-5 seconds.
LISTEN/NOTIFY is unavailable). Only used when enable_sse_updates
is true. Lower values mean faster updates but more database
queries. Recommended range: 1-5 seconds.
:Default: ``2``
:Type: int
@@ -5869,6 +5879,9 @@
finished, etc.
The system allows notification scheduling and expiration, and
users can opt-out of specific notification categories or channels.
Delivery is push-based via Server-Sent Events when
``enable_sse_updates`` is also true, and falls back to 30-second
polling against ``/api/notifications/status`` otherwise.
Admins can schedule and broadcast notifications that will be
visible to all users, including special server-wide announcements
such as scheduled maintenance, high load warnings, and event
+2 -2
View File
@@ -1018,7 +1018,7 @@ class UniverseApplication(StructuredApp, GalaxyManagerApplication, InstallationT
# daemon wins that election when present, otherwise one webapp picks it
# up. start/stop are driven by heartbeat role transitions rather than
# postfork, so the monitor cleanly migrates when the leader dies.
if self.config.enable_sse_history_updates:
if self.config.enable_sse_updates:
monitor = self._register_singleton(HistoryAuditMonitor)
self.database_heartbeat.add_audit_monitor_change_callback(monitor.on_role_change)
@@ -1057,7 +1057,7 @@ class UniverseApplication(StructuredApp, GalaxyManagerApplication, InstallationT
self.database_heartbeat.shutdown()
def _shutdown_history_audit_monitor(self):
if not self.config.enable_sse_history_updates:
if not self.config.enable_sse_updates:
return
self[HistoryAuditMonitor].shutdown()
+21 -18
View File
@@ -512,8 +512,8 @@ galaxy:
# history_audit database table. Set to 0 to disable pruning.
#history_audit_table_prune_interval: 3600
# Time (in seconds) between attempts to delete fully-consumed rows from
# the Kombu SQLAlchemy transport tables (``kombu_message``). Only
# Time (in seconds) between attempts to delete fully-consumed rows
# from the Kombu SQLAlchemy transport tables (``kombu_message``). Only
# relevant when ``amqp_internal_connection`` uses a ``sqlalchemy+*``
# scheme (the default with an on-disk control.sqlite); the SQLAlchemy
# transport has no built-in TTL, so without this task the tables grow
@@ -3144,25 +3144,25 @@ galaxy:
# Message to display on the export citations tool page
#citations_export_message_html: When writing up your analysis, remember to include all references that should be cited in order to completely describe your work. Also, please remember to <a href="https://galaxyproject.org/citing-galaxy">cite Galaxy</a>.
# Enables real-time history update notifications via Server-Sent
# Events (SSE). When enabled, a background monitor watches for history
# changes (via PostgreSQL LISTEN/NOTIFY or audit table polling as a
# fallback for SQLite) and pushes update signals to connected
# browsers, replacing aggressive 3-second polling.
#enable_sse_history_updates: false
# Enables real-time interactive-tool entry-point update notifications
# via Server-Sent Events. When enabled, the client subscribes to
# entry_point_update SSE events and refetches the entry-point list on
# each event, replacing the 10-second polling loop. When disabled,
# polling remains the source of updates.
#enable_sse_entry_point_updates: false
# Enables real-time updates via Server-Sent Events (SSE), replacing
# the history (3 s), entry-point (10 s) and notification (30 s)
# polling loops with push events delivered over a single
# ``/api/events/stream`` connection per browser tab. A background
# monitor watches for history changes (via PostgreSQL LISTEN/NOTIFY,
# or audit-table polling as a fallback for SQLite); entry-point
# changes are dispatched directly from the code paths that mutate
# them; in-app notifications and broadcasts are pushed when
# ``enable_notification_system`` is also true. When disabled, polling
# remains the source of updates for all three. See the admin guide
# "Server-Sent Events for real-time updates" for the full
# architecture, monitoring guidance and proxy configuration.
#enable_sse_updates: false
# The interval in seconds between history audit table polls when using
# the polling fallback (SQLite or when PostgreSQL LISTEN/NOTIFY is
# unavailable). Only used when enable_sse_history_updates is true.
# Lower values mean faster updates but more database queries.
# Recommended range: 1-5 seconds.
# unavailable). Only used when enable_sse_updates is true. Lower
# values mean faster updates but more database queries. Recommended
# range: 1-5 seconds.
#history_audit_monitor_poll_interval: 2
# Enables the Notification System integrated in Galaxy.
@@ -3171,6 +3171,9 @@ galaxy:
# etc.
# The system allows notification scheduling and expiration, and users
# can opt-out of specific notification categories or channels.
# Delivery is push-based via Server-Sent Events when
# ``enable_sse_updates`` is also true, and falls back to 30-second
# polling against ``/api/notifications/status`` otherwise.
# Admins can schedule and broadcast notifications that will be visible
# to all users, including special server-wide announcements such as
# scheduled maintenance, high load warnings, and event announcements,
+17 -16
View File
@@ -4313,25 +4313,22 @@ mapping:
desc: |
Message to display on the export citations tool page
enable_sse_history_updates:
enable_sse_updates:
type: bool
default: false
required: false
desc: |
Enables real-time history update notifications via Server-Sent Events (SSE).
When enabled, a background monitor watches for history changes (via PostgreSQL
LISTEN/NOTIFY or audit table polling as a fallback for SQLite) and pushes update
signals to connected browsers, replacing aggressive 3-second polling.
enable_sse_entry_point_updates:
type: bool
default: false
required: false
desc: |
Enables real-time interactive-tool entry-point update notifications via
Server-Sent Events. When enabled, the client subscribes to entry_point_update
SSE events and refetches the entry-point list on each event, replacing the
10-second polling loop. When disabled, polling remains the source of updates.
Enables real-time updates via Server-Sent Events (SSE), replacing the
history (3 s), entry-point (10 s) and notification (30 s) polling loops
with push events delivered over a single ``/api/events/stream``
connection per browser tab. A background monitor watches for history
changes (via PostgreSQL LISTEN/NOTIFY, or audit-table polling as a
fallback for SQLite); entry-point changes are dispatched directly from
the code paths that mutate them; in-app notifications and broadcasts
are pushed when ``enable_notification_system`` is also true.
When disabled, polling remains the source of updates for all three.
See the admin guide "Server-Sent Events for real-time updates" for the
full architecture, monitoring guidance and proxy configuration.
history_audit_monitor_poll_interval:
type: int
@@ -4340,7 +4337,7 @@ mapping:
desc: |
The interval in seconds between history audit table polls when using the polling
fallback (SQLite or when PostgreSQL LISTEN/NOTIFY is unavailable). Only used when
enable_sse_history_updates is true. Lower values mean faster updates but more
enable_sse_updates is true. Lower values mean faster updates but more
database queries. Recommended range: 1-5 seconds.
enable_notification_system:
@@ -4354,6 +4351,10 @@ mapping:
The system allows notification scheduling and expiration, and users can opt-out of specific notification categories or channels.
Delivery is push-based via Server-Sent Events when ``enable_sse_updates``
is also true, and falls back to 30-second polling against
``/api/notifications/status`` otherwise.
Admins can schedule and broadcast notifications that will be visible to all users, including special server-wide announcements such as scheduled maintenance, high load warnings, and event announcements, to name a few examples.
enable_mcp_server:
+1 -2
View File
@@ -229,8 +229,7 @@ class ConfigSerializer(base.ModelSerializer):
"tool_training_recommendations_link": _use_config,
"tool_training_recommendations_api_url": _use_config,
"enable_notification_system": _use_config,
"enable_sse_history_updates": _use_config,
"enable_sse_entry_point_updates": _use_config,
"enable_sse_updates": _use_config,
"instance_resource_url": _use_config,
"instance_access_url": _use_config,
"organization_name": _use_config,
+1 -1
View File
@@ -4,7 +4,7 @@ Detects history changes via PostgreSQL LISTEN/NOTIFY (instant) or by polling
the history_audit table (SQLite fallback). Dispatches SSE events to connected
users via Kombu control tasks.
Only active when ``enable_sse_history_updates`` is True in the Galaxy config.
Only active when ``enable_sse_updates`` is True in the Galaxy config.
"""
import logging
+2 -2
View File
@@ -56,13 +56,13 @@ def main() -> int:
heartbeat = DatabaseHeartbeat(application_stack=app.application_stack)
monitor = None
if app.config.enable_sse_history_updates:
if app.config.enable_sse_updates:
from galaxy.managers.history_audit_monitor import HistoryAuditMonitor
monitor = app[HistoryAuditMonitor]
heartbeat.add_audit_monitor_change_callback(monitor.on_role_change)
else:
log.warning("enable_sse_history_updates is False — galaxy-sse-monitor will idle with no producers")
log.warning("enable_sse_updates is False — galaxy-sse-monitor will idle with no producers")
heartbeat.start()
+1 -1
View File
@@ -19,7 +19,7 @@ class TestHistorySSEIntegration(IntegrationTestCase):
def handle_galaxy_config_kwds(cls, config):
super().handle_galaxy_config_kwds(config)
config["enable_celery_tasks"] = False
config["enable_sse_history_updates"] = True
config["enable_sse_updates"] = True
def setUp(self):
super().setUp()
@@ -36,10 +36,10 @@ class TestEntryPointSSESeleniumIntegration(SeleniumIntegrationTestCase):
config["enable_celery_tasks"] = False
# App.vue only calls entryPointStore.startWatchingEntryPoints() when
# interactivetools_enable is True, and the store only opens an SSE
# connection when enable_sse_entry_point_updates is True. Without both,
# connection when enable_sse_updates is True. Without both,
# __galaxy_sse_connected never becomes true and the gate below times out.
config["interactivetools_enable"] = True
config["enable_sse_entry_point_updates"] = True
config["enable_sse_updates"] = True
def _wait_for_sse_connected(self) -> None:
"""Block until the frontend confirms the SSE pipeline is live."""