docs: create-and-check-documentation

This commit is contained in:
Arthur van Assenbergh
2025-11-26 14:41:18 +01:00
committed by Gerla, J. (Justin)
parent f87c7fed03
commit 10a2c0c3cd
18 changed files with 625 additions and 97 deletions

View File

@@ -3,6 +3,19 @@ import {useCallback, useEffect, useRef, useState} from "react";
import {applyPriorityPredicates, type PriorityFilterPredicate} from "../../utils/priorityFiltering.ts";
import {cell, type Cell} from "../../utils/cellStore.ts";
/**
* Represents a single log record emitted by the backend logging system.
*
* @property name - The name of the logger or source (e.g., `"agent.core"`).
* @property message - The message content of the log record.
* @property levelname - The human-readable severity level (e.g., `"INFO"`, `"ERROR"`).
* @property levelno - The numeric severity value corresponding to `levelname`.
* @property created - The UNIX timestamp (in seconds) when this record was created.
* @property relativeCreated - The time (in milliseconds) since the logging system started.
* @property reference - (Optional) A reference identifier linking related log messages.
* @property firstCreated - Timestamp of the first log in this reference group.
* @property firstRelativeCreated - Relative timestamp of the first log in this reference group.
*/
export type LogRecord = {
name: string;
message: string;
@@ -15,29 +28,68 @@ export type LogRecord = {
firstRelativeCreated: number;
};
// eslint-disable-next-line @typescript-eslint/no-explicit-any
export type LogFilterPredicate = PriorityFilterPredicate<LogRecord> & { value: any };
/**
* A log filter predicate with priority support, used to determine whether
* a log record should be displayed.
*
* This extends a general `PriorityFilterPredicate` and includes an optional
* `value` field for UI metadata (e.g., selected log level or agent).
*
* @template T - The type of record being filtered (here, `LogRecord`).
*/
export type LogFilterPredicate = PriorityFilterPredicate<LogRecord> & {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
value: any };
/**
* React hook that manages the lifecycle of log records, including:
* - Receiving live log messages via Server-Sent Events (SSE),
* - Applying priority-based filtering rules,
* - Managing distinct logger names and reference-linked messages.
*
* Returns both the filtered logs (as reactive `Cell<LogRecord>` objects)
* and a set of distinct logger names for use in UI components (e.g., Filters).
*
* @param filterPredicates - A `Map` of log filter predicates, keyed by ID or type.
* @returns An object containing:
* - `filteredLogs`: The currently visible (filtered) log messages.
* - `distinctNames`: A set of all distinct logger names encountered.
*
* @example
* ```ts
* const { filteredLogs, distinctNames } = useLogs(activeFilters);
* ```
*/
export function useLogs(filterPredicates: Map<string, LogFilterPredicate>) {
/** Distinct logger names encountered across all logs. */
const [distinctNames, setDistinctNames] = useState<Set<string>>(new Set());
/** Filtered logs that pass all active predicates, stored as reactive cells. */
const [filtered, setFiltered] = useState<Cell<LogRecord>[]>([]);
/** Persistent reference to the active EventSource connection. */
const sseRef = useRef<EventSource | null>(null);
/** Keeps a stable reference to the current filter map (avoids re-renders). */
const filtersRef = useRef(filterPredicates);
/** Stores all received logs (the unfiltered full history). */
const logsRef = useRef<LogRecord[]>([]);
/** Map to store the first message for each reference, instance can be updated to change contents. */
const firstByRefRef = useRef<Map<string, Cell<LogRecord>>>(new Map());
/**
* Apply the filter predicates to a log record.
* Apply all active filter predicates to a log record.
* @param log The log record to apply the filters to.
* @returns `true` if the record passes.
* @returns `true` if the record passes all filters; otherwise `false`.
*/
const applyFilters = useCallback((log: LogRecord) =>
applyPriorityPredicates(log, [...filtersRef.current.values()]), []);
/** Recomputes the entire filtered list. Use when filter predicates change. */
/**
* Fully recomputes the filtered log list based on the current
* filter predicates and historical logs.
*
* Should be invoked whenever the filter map changes.
*/
const recomputeFiltered = useCallback(() => {
const newFiltered: Cell<LogRecord>[] = [];
firstByRefRef.current = new Map();
@@ -49,6 +101,7 @@ export function useLogs(filterPredicates: Map<string, LogFilterPredicate>) {
firstRelativeCreated: message.relativeCreated,
});
// Handle reference grouping: update the first message in the group.
if (message.reference) {
const first = firstByRefRef.current.get(message.reference);
if (first) {
@@ -59,14 +112,14 @@ export function useLogs(filterPredicates: Map<string, LogFilterPredicate>) {
firstRelativeCreated: prev.firstRelativeCreated ?? prev.relativeCreated,
}));
// Don't add it to the list again
continue;
continue; // Don't add it to the list again (it's a duplicate).
} else {
// Add the first message with this reference to the registry
firstByRefRef.current.set(message.reference, messageCell);
}
}
// Include only if it passes current filters.
if (applyFilters(message)) {
newFiltered.push(messageCell);
}
@@ -75,20 +128,23 @@ export function useLogs(filterPredicates: Map<string, LogFilterPredicate>) {
setFiltered(newFiltered);
}, [applyFilters, setFiltered]);
// Reapply filters to all logs, only when filters change
// Re-filter all logs whenever filter predicates change.
useEffect(() => {
filtersRef.current = filterPredicates;
recomputeFiltered();
}, [filterPredicates, recomputeFiltered]);
/**
* Handle a new log message. Updates the filtered list and to the full history.
* @param message The new log message.
* Handles a newly received log record.
* Updates the full log history, distinct names set, and filtered log list.
*
* @param message - The new log record to process.
*/
const handleNewMessage = useCallback((message: LogRecord) => {
// Add to the full history for re-filtering on filter changes
// Store in complete history for future refiltering.
logsRef.current.push(message);
// Track distinct logger names.
setDistinctNames((prev) => {
if (prev.has(message.name)) return prev;
const newSet = new Set(prev);
@@ -96,12 +152,14 @@ export function useLogs(filterPredicates: Map<string, LogFilterPredicate>) {
return newSet;
});
// Wrap in a reactive cell for UI binding.
const messageCell = cell<LogRecord>({
...message,
firstCreated: message.created,
firstRelativeCreated: message.relativeCreated,
});
// Handle reference-linked updates.
if (message.reference) {
const first = firstByRefRef.current.get(message.reference);
if (first) {
@@ -112,20 +170,28 @@ export function useLogs(filterPredicates: Map<string, LogFilterPredicate>) {
firstRelativeCreated: prev.firstRelativeCreated ?? prev.relativeCreated,
}));
// Don't add it to the list again
return;
return; // Do not duplicate reference group entries.
} else {
// Add the first message with this reference to the registry
firstByRefRef.current.set(message.reference, messageCell);
}
}
// Only append if message passes filters.
if (applyFilters(message)) {
setFiltered((curr) => [...curr, messageCell]);
}
}, [applyFilters, setFiltered]);
/**
* Initializes the SSE (Server-Sent Events) stream for real-time logs.
*
* Subscribes to messages from the backend logging endpoint and
* dispatches each message to `handleNewMessage`.
*
* Cleans up the EventSource connection when the component unmounts.
*/
useEffect(() => {
// Only create one SSE connection for the lifetime of the hook.
if (sseRef.current) return;
const es = new EventSource("http://localhost:8000/logs/stream");