-
Notifications
You must be signed in to change notification settings - Fork 1
Use SignalR for website live updates #2
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
base: main
Are you sure you want to change the base?
Conversation
Reviewer's GuideThis PR replaces the polling-based data refresh in LiveDataComponent with a SignalR-driven real-time update flow by establishing a hub connection, joining the dashboard group, and subscribing to server push events for immediate data refresh. Sequence diagram for SignalR-based live data updates in LiveDataComponentsequenceDiagram
participant User as actor User
participant LiveDataComponent
participant SignalRHub as NatureOS SignalR Hub
User->>LiveDataComponent: Mounts component
LiveDataComponent->>SignalRHub: Establish SignalR connection
LiveDataComponent->>SignalRHub: Invoke JoinDashboardGroup
SignalRHub-->>LiveDataComponent: Connection established
LiveDataComponent->>SignalRHub: Fetch initial data
SignalRHub-->>LiveDataComponent: Initial data response
SignalRHub-->>LiveDataComponent: DeviceUpdate/SimulationUpdate/SystemUpdate events
LiveDataComponent->>SignalRHub: Fetch updated data (on event)
SignalRHub-->>LiveDataComponent: Updated data response
User-->>LiveDataComponent: Unmounts component
LiveDataComponent->>SignalRHub: Stop connection
File-Level Changes
Tips and commandsInteracting with Sourcery
Customizing Your ExperienceAccess your dashboard to:
Getting Help
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hey @nodefather - I've reviewed your changes and they look great!
Prompt for AI Agents
Please address the comments from this code review:
## Individual Comments
### Comment 1
<location> `website-integration/components/LiveDataComponent.jsx:10` </location>
<code_context>
import { motion, AnimatePresence } from 'framer-motion';
+import { HubConnectionBuilder, LogLevel } from '@microsoft/signalr';
export default function LiveDataComponent() {
const [liveData, setLiveData] = useState(null);
</code_context>
<issue_to_address>
Consider extracting the SignalR and polling logic into custom hooks to keep the component focused on rendering.
```suggestion
This component has grown a lot of nested logic around SignalR and polling. Extract both into small custom hooks to keep the component focused on rendering.
1) Create a `useInterval` hook for polling:
```ts
// hooks/useInterval.ts
import { useEffect } from 'react';
export function useInterval(cb: () => void, delay: number) {
useEffect(() => {
if (delay == null) return;
const id = setInterval(cb, delay);
return () => clearInterval(id);
}, [cb, delay]);
}
```
2) Create a `useSignalR` hook to handle connection, reconnection, events & errors:
```ts
// hooks/useSignalR.ts
import { useEffect, useState } from 'react';
import { HubConnectionBuilder, LogLevel } from '@microsoft/signalr';
const EVENTS = ['DeviceUpdate', 'SimulationUpdate', 'SystemUpdate'];
export function useSignalR(onUpdate: () => void) {
const [isConnected, setIsConnected] = useState(false);
const [error, setError] = useState<string | null>(null);
useEffect(() => {
const conn = new HubConnectionBuilder()
.withUrl('/natureos-hub')
.withAutomaticReconnect()
.configureLogging(LogLevel.Information)
.build();
conn.onclose(() => setIsConnected(false));
EVENTS.forEach(e => conn.on(e, onUpdate));
(async () => {
try {
await conn.start();
setIsConnected(true);
await conn.invoke('JoinDashboardGroup');
await onUpdate();
} catch (err: any) {
setError(err.message);
setIsConnected(false);
}
})();
return () => { conn.stop(); };
}, [onUpdate]);
return { isConnected, error };
}
```
3) Simplify your component to just:
```ts
// LiveDataComponent.tsx
import { useState, useCallback } from 'react';
import { motion } from 'framer-motion';
import { useInterval } from './hooks/useInterval';
import { useSignalR } from './hooks/useSignalR';
export default function LiveDataComponent() {
const [data, setData] = useState<any>(null);
const [lastUpdate, setLastUpdate] = useState<Date>();
const fetchLiveData = useCallback(async () => {
try {
const d = await fetch('/natureos').then(r => r.json());
setData(d);
setLastUpdate(new Date());
} catch {
/* handle error locally or via useSignalR */
}
}, []);
const { isConnected, error } = useSignalR(fetchLiveData);
useInterval(fetchLiveData, 5000);
if (!data) {
return (
<div className="flex items-center justify-center p-8">
<motion.div
animate={{ rotate: 360 }}
transition={{ duration: 2, repeat: Infinity, ease: "linear" }}
className="w-8 h-8 border-2 border-green-500 border-t-transparent rounded-full"
/>
</div>
);
}
// ...render data, connection state, lastUpdate, error...
}
```
This preserves all functionality but pulls complex logic into reusable hooks, improving readability and testability.
</issue_to_address>Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.
| import { motion, AnimatePresence } from 'framer-motion'; | ||
| import { HubConnectionBuilder, LogLevel } from '@microsoft/signalr'; | ||
|
|
||
| export default function LiveDataComponent() { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
issue (complexity): Consider extracting the SignalR and polling logic into custom hooks to keep the component focused on rendering.
| export default function LiveDataComponent() { | |
| This component has grown a lot of nested logic around SignalR and polling. Extract both into small custom hooks to keep the component focused on rendering. | |
| 1) Create a `useInterval` hook for polling: | |
| ```ts | |
| // hooks/useInterval.ts | |
| import { useEffect } from 'react'; | |
| export function useInterval(cb: () => void, delay: number) { | |
| useEffect(() => { | |
| if (delay == null) return; | |
| const id = setInterval(cb, delay); | |
| return () => clearInterval(id); | |
| }, [cb, delay]); | |
| } |
- Create a
useSignalRhook to handle connection, reconnection, events & errors:
// hooks/useSignalR.ts
import { useEffect, useState } from 'react';
import { HubConnectionBuilder, LogLevel } from '@microsoft/signalr';
const EVENTS = ['DeviceUpdate', 'SimulationUpdate', 'SystemUpdate'];
export function useSignalR(onUpdate: () => void) {
const [isConnected, setIsConnected] = useState(false);
const [error, setError] = useState<string | null>(null);
useEffect(() => {
const conn = new HubConnectionBuilder()
.withUrl('/natureos-hub')
.withAutomaticReconnect()
.configureLogging(LogLevel.Information)
.build();
conn.onclose(() => setIsConnected(false));
EVENTS.forEach(e => conn.on(e, onUpdate));
(async () => {
try {
await conn.start();
setIsConnected(true);
await conn.invoke('JoinDashboardGroup');
await onUpdate();
} catch (err: any) {
setError(err.message);
setIsConnected(false);
}
})();
return () => { conn.stop(); };
}, [onUpdate]);
return { isConnected, error };
}- Simplify your component to just:
// LiveDataComponent.tsx
import { useState, useCallback } from 'react';
import { motion } from 'framer-motion';
import { useInterval } from './hooks/useInterval';
import { useSignalR } from './hooks/useSignalR';
export default function LiveDataComponent() {
const [data, setData] = useState<any>(null);
const [lastUpdate, setLastUpdate] = useState<Date>();
const fetchLiveData = useCallback(async () => {
try {
const d = await fetch('/natureos').then(r => r.json());
setData(d);
setLastUpdate(new Date());
} catch {
/* handle error locally or via useSignalR */
}
}, []);
const { isConnected, error } = useSignalR(fetchLiveData);
useInterval(fetchLiveData, 5000);
if (!data) {
return (
<div className="flex items-center justify-center p-8">
<motion.div
animate={{ rotate: 360 }}
transition={{ duration: 2, repeat: Infinity, ease: "linear" }}
className="w-8 h-8 border-2 border-green-500 border-t-transparent rounded-full"
/>
</div>
);
}
// ...render data, connection state, lastUpdate, error...
}This preserves all functionality but pulls complex logic into reusable hooks, improving readability and testability.
Summary
LiveDataComponentto use SignalR rather than interval pollingTesting
dotnet build NatureOS.sln(fails: IProactiveMonitoringService not found)npm testinwebsite-integration(fails: missing package.json)https://chatgpt.com/codex/tasks/task_e_689654e63724832ab03aea8115d85d36
Summary by Sourcery
Replace polling-based live data updates with a SignalR-based connection that joins a dashboard group, fetches initial data, listens for server update events, and manages connection state.
Enhancements: