(null);
@@ -312,8 +323,7 @@ export default function LiveActivity({ origin }: { origin: ActivitySearch }) {
{live.frozen && (
- Display frozen — new queries keep buffering ({live.liveCount} in buffer, newest {RING_CAPACITY}{" "}
- kept).
+ Display frozen — new queries keep buffering ({live.liveCount} in buffer, newest {capacity} kept).
)}
@@ -413,7 +423,7 @@ export default function LiveActivity({ origin }: { origin: ActivitySearch }) {
Showing {live.rows.length} {live.rows.length === 1 ? "query" : "queries"} (newest first, last{" "}
- {RING_CAPACITY} kept).
+ {capacity} kept).
>
)}
diff --git a/admin/src/features/activity/useLiveQueries.ts b/admin/src/features/activity/useLiveQueries.ts
index 06466a2..28a9486 100644
--- a/admin/src/features/activity/useLiveQueries.ts
+++ b/admin/src/features/activity/useLiveQueries.ts
@@ -24,6 +24,12 @@ export interface LiveQueriesOptions {
fetchSince?: (since: number) => Promise;
/** Cheap session-gated GET fired once on entering capped, to distinguish an expired session from a real cap. */
probeSession?: () => Promise;
+ /**
+ * Ring size. Injectable so a test can provoke an eviction with a handful of
+ * rows instead of pushing 500 frames through React state; the app never
+ * passes it, and the operator never sees it.
+ */
+ capacity?: number;
}
// A transient drop is invisible to EventSource beyond a bare `error` event;
@@ -34,7 +40,10 @@ export interface LiveQueriesOptions {
export const CAP_ERROR_THRESHOLD = 3;
const defaultEventSource: EventSourceFactory = (url) => new EventSource(url);
-const defaultFetchSince = (since: number): Promise => api.getQueries({ since, limit: RING_CAPACITY });
+const defaultFetchSince =
+ (capacity: number) =>
+ (since: number): Promise =>
+ api.getQueries({ since, limit: capacity });
const defaultProbeSession = (): Promise => api.getPause();
function isUnauthorized(error: unknown): boolean {
@@ -79,7 +88,8 @@ export function useLiveQueries(options?: LiveQueriesOptions): LiveQueries {
errorsRef.current = 0;
setStatus("connecting");
const opts = optionsRef.current;
- const fetchSince = opts?.fetchSince ?? defaultFetchSince;
+ const capacity = opts?.capacity ?? RING_CAPACITY;
+ const fetchSince = opts?.fetchSince ?? defaultFetchSince(capacity);
const probeSession = opts?.probeSession ?? defaultProbeSession;
const es = (opts?.createEventSource ?? defaultEventSource)(opts?.url ?? api.liveQueriesUrl);
esRef.current = es;
@@ -94,7 +104,7 @@ export function useLiveQueries(options?: LiveQueriesOptions): LiveQueries {
fetchSince(since).then(
(page) => {
if (esRef.current !== es) return;
- const merged = mergeGap(bufferRef.current, page.queries, () => ++keyRef.current);
+ const merged = mergeGap(bufferRef.current, page.queries, () => ++keyRef.current, capacity);
bufferRef.current = merged.rows;
setRows(merged.rows);
setMissed(merged.missed);
@@ -122,11 +132,11 @@ export function useLiveQueries(options?: LiveQueriesOptions): LiveQueries {
return;
}
lastSeenTsRef.current = payload.request.time;
- bufferRef.current = pushRow(bufferRef.current, {
- kind: "streamed",
- event: payload,
- key: ++keyRef.current,
- });
+ bufferRef.current = pushRow(
+ bufferRef.current,
+ { kind: "streamed", event: payload, key: ++keyRef.current },
+ capacity,
+ );
setRows(bufferRef.current);
});