.. _application_architecture: ======================== Application Architecture ======================== This document provides a comprehensive guide to the architecture of the SDP QA Display application. It details the primary components, data flow patterns, and design principles, serving as an essential resource for developers working on the project. Architectural Overview ====================== The application's architecture is designed for clarity, scalability, and maintainability. It is built on a clean separation of concerns, revolving around three core pillars: 1. **Centralized Data Transport:** A unified WebSocket client, ``DataSocket``, manages all real-time data streams, providing a consistent interface for data consumption throughout the application. 2. **State Management with React Context:** Runtime configuration, such as the selected subarray, beam, or processing block, is managed globally through React Context. This provides a single source of truth and eliminates the need for prop-drilling. 3. **Composable Data Hooks:** Custom React Hooks serve as the primary mechanism for components to access and shape live data. These hooks encapsulate subscription logic and data transformations, promoting code reuse and simplifying component implementation. This structure results in thin, focused components that are easy to test, maintain, and extend. ---- Application Shell ================= **File:** ``src/App/App.tsx`` The main application shell establishes the global layout, theme, and providers. * **Layout:** Uses ``AppWrapper`` for a consistent layout structure across all pages. * **Providers:** Wraps the entire application with necessary providers for theme, internationalization (i18n), user authentication (MSAL), and the custom configuration contexts. * **Core UI:** Renders the main navigation, header (including the Subarray/Beam selectors), and the routing outlet for pages. ---- Pages and Routing ================= **Pages Folder:** ``src/pages/`` **Router File:** ``src/App/App.tsx`` The application includes a suite of visibility pages, each dedicated to a specific metric visualization. Visibility Pages ---------------- * ``SpectrumPage.tsx`` * ``SpectrogramPage.tsx`` * ``PhaseAmplitudePage.tsx`` * ``BandAvgCorrPage.tsx`` * ``WeightDistributionPage.tsx`` Each page consumes one or more data hooks to get its data and responds to changes from the configuration contexts (e.g., subarray or beam selection). Router Integration ------------------ Routes for all pages are defined in ``src/App/App.tsx``. The visibility pages are mapped as follows: * ``/vis/spectrum`` → ``SpectrumPage`` * ``/vis/spectrogram`` → ``SpectrogramPage`` * ``/vis/phaseamplitude`` → ``PhaseAmplitudePage`` * ``/vis/bandavgcorr`` → ``BandAvgCorrPage`` * ``/vis/weightdistribution`` → ``WeightDistributionPage`` ---- Configuration Contexts ====================== **Folder:** ``src/services/configWebSocket/`` The application uses React's Context API to manage globally relevant state, such as the currently selected subarray and beam. This state is managed by a central ``WebSocketManager`` and exposed to the component tree via React Context providers. * **State Management (``configWebSocket.ts``):** The ``WebSocketManager`` class is a singleton responsible for establishing a WebSocket connection to receive configuration data. It holds the definitive list of available subarrays, beams, and their current state. * **Context Providers (``subarrayContext.tsx``, ``beamContext.tsx``):** These files contain the React components that act as Context Providers. They instantiate or subscribe to the ``WebSocketManager`` and make the relevant state (e.g., the list of subarrays or the currently selected beam) available to the entire component tree. * **Updaters (``SubarrayDropDown.tsx``, ``BeamDropDown.tsx``):** UI components, such as dropdown menus, are responsible for calling methods on the ``WebSocketManager`` to update the selected state (e.g., ``setSelectedSubarray(newId)``). * **Consumers:** Hooks and pages throughout the application consume the context to get the current selection state. This allows them to derive the correct data topics and API queries, and they automatically re-render when the context value changes. Benefits -------- * **Single Source of Truth:** Provides a centralized and predictable location for application-wide state. * **Decoupling:** Avoids prop-drilling and allows components to consume state without being tightly coupled to their parent's data-fetching logic. * **Reactivity:** All subscribers automatically re-render when the context value changes. ---- DataSocket: The Unified WebSocket Client ======================================== **File:** ``src/services/webSocket/DataSocket.ts`` The ``DataSocket`` class is the cornerstone of the application's data transport layer. It abstracts away the complexities of WebSocket communication, providing a robust and generic client for all real-time data needs. Key Capabilities ---------------- * **Protocol Support:** Natively handles both **JSON** and **MsgPack** protocols, automatically configuring the ``binaryType`` based on the subscription requirements. * **Generic Payloads:** The client is typed with a generic (``DataWebSocket``), allowing for type-safe data consumption in hooks and components. * **Status Handling:** Emits ``SOCKET_STATUS`` updates for connection state and automatically recognizes ``{ status: ... }`` control frames from the backend. * **Automatic Reconnection:** Implements a bounded exponential backoff strategy for reconnection, ensuring resilience against temporary network or backend outages. Manual disconnections are respected and do not trigger reconnection. * **Robust Decoding:** Gracefully handles and reports decoding errors, surfacing them through the status and error properties of the data hook. * **Local Mocking:** Includes a built-in mock data provider that is activated when ``DATA_LOCAL`` is ``true``. This allows developers to work with deterministic, streamed mock payloads from ``src/mockData/WebSocket/*`` without needing a live backend connection. Topic and Metric Mapping ------------------------ * Topics follow the convention ``metrics--...``. The ``DataSocket`` class extracts the ```` portion to route to the correct mock data source in local development mode. * Metric types are enumerated in ``METRIC_TYPES``, which are used to manage subscriptions and organize data flow to the appropriate pages. Key Advantages -------------- * **Eliminates Boilerplate:** Centralizes socket setup, teardown, reconnection, and decoding logic, preventing duplication across the codebase. * **Testable and Swappable:** The abstraction makes the data transport layer easy to test in isolation and allows for future enhancements without impacting consumer components. * **Separation of Concerns:** Keeps pages and hooks focused on rendering and data transformation, rather than I/O and connection management. ---- Data Hooks: Typed Subscriptions and Shaping =========================================== **Folder:** ``src/hooks/`` Data hooks are the standard way for components to subscribe to and consume real-time data. They bridge the gap between the ``DataSocket`` service and the UI. * **``useDataWebSocket.ts``:** This is the generic, low-level subscription hook that wraps the ``DataSocket`` client. It manages the subscription lifecycle, returning a standard ``{ data, status, isLoading, topic, messageCount, lastUpdate }`` object, and ensures the socket connection is properly closed on component unmount. * **Specialized Hooks:** Hooks like ``useLagPlotData.ts`` and ``useSpectrogramData.ts`` compose ``useDataWebSocket``. They are responsible for specifying the correct topic and protocol, and performing any necessary data shaping or transformation required by their corresponding UI components. Advantages of using Hooks ------------------------- * **Co-location:** Subscription and data-shaping logic are co-located, making it easy to understand how data is fetched and prepared for a component. * **Simplified Components:** Pages and components are simplified, as they are freed from managing subscription state and lifecycle. * **Improved Testability:** Hooks can be unit-tested in isolation by mocking the underlying ``DataSocket`` or its responses. ---- REST Helper for Configuration: `getFlows` ========================================= **File:** ``src/services/getFlows/getFlows.ts`` The ``getFlows`` helper is responsible for fetching flow configuration data for a given processing block. Purpose and Functionality ------------------------- * **Data Retrieval:** In a production environment, it performs a ``GET`` request to the ``/config/flows`` API endpoint, with support for request cancellation via an ``AbortController``. * **Mocking:** In local development mode (``DATA_LOCAL = true``), it returns a filtered set of mock data from ``mockFlows`` without making a network request. * **Stable Dependencies:** It memoizes the list of metrics to include, ensuring a stable function identity to prevent unnecessary re-fetches in ``useEffect`` hooks. * **Standardized Output:** Returns a consistent ``{ flowData, isLoading, error, processingBlockID }`` object for predictable consumption in the UI. This helper centralizes configuration retrieval logic, ensuring consistency across all pages that require it. ---- Local vs. Remote Data Modes =========================== The application can run in two data modes, controlled by the ``DATA_LOCAL`` flag in ``@/utils/constants``. This is a key feature for developer productivity. * **Local Mode (``DATA_LOCAL = true``):** * **WebSockets:** The ``DataSocket`` client simulates a connection, streaming mock data from files in ``src/mockData/WebSocket``. No real socket is opened. * **REST:** Configuration requests are served from on-disk mocks in ``src/mockData``. * **Remote Mode (``DATA_LOCAL = false``):** * **WebSockets:** The client connects to the live WebSocket endpoint at ``${WS_API_URL}/ws/ws``. * **REST:** API requests are made to the live backend at ``${DATA_API_URL}``. This dual-mode system allows developers to work on features and UI without requiring a live, data-producing backend. ---- Data Hook Contract and Error Handling ===================================== Hook Contract ------------- Any data hook in the application should adhere to a minimal contract: * **Inputs:** Accepts any optional metric type or data transformation functions. * **Outputs:** Data hooks typically return one or more objects, such as ``{ data, socketStatus, isLoading }`` for WebSocket subscriptions. The ``data`` property contains the latest payload received from the backend (or mock source), shaped according to the hook's logic. The ``isLoading`` property is a boolean indicating whether the hook is actively waiting for initial data or a response. The ``socketStatus`` property is a value from the ``SOCKET_STATUS`` enum, and other properties may vary depending on the hook's purpose and the data source. Error Handling and Edge Cases ----------------------------- The architecture is designed to be resilient and handle common edge cases gracefully: * **Null Selections:** If a required context value (like ``subarrayId``) is null, hooks remain idle and do not attempt to open a socket. * **Protocol Mismatches:** An attempt to decode a binary message as text (or vice-versa) will result in a decode error, which is surfaced to the UI via ``status=ERROR`` and the ``error`` object. * **Backend Outages:** The ``DataSocket``'s automatic reconnection logic handles temporary service interruptions. The UI can use the ``status`` property to display the current connection state to the user. * **Rapid Context Switching:** Quickly changing the subarray or beam selection will trigger the old socket to close and a new one to open, without leaking events from the previous subscription. ---- Developer Guide: Adding a New Metric Page ========================================= Follow these steps to add a new page with a live data visualization: 1. **Define Metric:** If the metric is new, add its topic/name type to ``METRIC_TYPES``. 2. **Create Data Hook:** Create a new, specialized hook in ``src/hooks/`` that composes ``useDataWebSocket``. Configure it with the correct topic and any required data-shaping logic. 3. **Create Page Component:** Add a new page component under ``src/pages//YourPage.tsx``. This component should consume your new hook and any necessary configuration contexts. 4. **Register Route:** Add a new route for your page in ``src/App/App.tsx``. 5. **Provide Mock Data:** For local development, add a mock payload in ``src/mockData/WebSocket`` and update the routing logic in ``DataSocket.simulateLocalData()`` to serve it. 6. **Add REST Helper (if needed):** If the page requires additional configuration from a REST API, create a helper function similar to ``getFlows``. ---- Architectural Principles ======================== The architecture is guided by the following principles: * **Consistency:** A single, standardized way to subscribe to live data reduces cognitive load and makes the system more predictable. * **Resilience:** Built-in reconnection and clear error surfacing improve runtime stability and the user experience. * **Testability:** Separating concerns allows hooks, components, and services to be tested in isolation with mocks. * **Extensibility:** The pattern for adding new metrics and pages is straightforward and repeatable. * **Developer Experience:** A robust local mocking system ensures that development can proceed efficiently without a dependency on a live backend. ---- Best Practices and Guidelines ============================= * **Topic Naming:** Keep topic names consistent with the ``metrics--...`` convention for predictable mocking and routing. * **Connection Status UI:** Consider adding small UI affordances (e.g., a status badge) that are driven by the ``status`` property from data hooks to give users feedback on the data connection. * **Binary Payloads:** When introducing new binary payloads, ensure the corresponding hook selects the correct protocol and provides the appropriate decoding logic.