diff --git a/extensions/prototypes/labgraph_monitor/README.md b/extensions/prototypes/labgraph_monitor/README.md index a5b1566b..698aefca 100644 --- a/extensions/prototypes/labgraph_monitor/README.md +++ b/extensions/prototypes/labgraph_monitor/README.md @@ -7,7 +7,7 @@ This extension is an interactive visualization tool to monitor and make real-tim **Prerequisites**: - [Node.js](https://nodejs.org/en/) -- [Yarn](https://classic.yarnpkg.com/lang/en/docs/install) +- [Yarn](https://classic.yarnpkg.com/lang/en/docs/install) (**RECOMMENDED**) Check that node and yarn were properly installed by running the following commands @@ -21,28 +21,31 @@ yarn -v **Set up the application** +(!) The following tutorial utilizes **yarn** (recommended) however **npm** can be utilized as well. + 1. Be sure that you are inside **extensions/prototypes/labgraph_monitor** directory ``` -cd extensions/prototypes/labgraph_monitor +labgraph> cd extensions/prototypes/labgraph_monitor +labgraph\extensions\prototypes\labgraph_monitor> ``` 2. Install dependencies by running **yarn** command ``` -yarn +labgraph\extensions\prototypes\labgraph_monitor> yarn ``` 3. Test the application by running the following command ``` -yarn test --watchAll=false +labgraph\extensions\prototypes\labgraph_monitor> yarn test --watchAll=false ``` 4. Run the application ``` -yarn start +labgraph\extensions\prototypes\labgraph_monitor> yarn start ``` (!) The application will be running on **localhost:3000** by default @@ -71,7 +74,8 @@ REACT_APP_WS_API="ws://127.0.0.1:9000" (!) LabGraph Websocket server runs on localhost:9000 by default -2. Run Labgraph Websockets server. The following tutorial shows how to run LabGraph Websocket server properly : [tutorial](https://github.com/facebookresearch/labgraph/pull/58/files#diff-247005c77570899ce53f81a83b2a5fe6e7535616cc96564d67378fe7f73dac49) +2. Run Labgraph Websockets server. + `python3 extensions/yaml_support/labgraph_monitor/examples/labgraph_monitor_example.py` 3. Under LabGraph Monitor settings panel click on REALTIME option and click connect. @@ -82,7 +86,7 @@ To see the information related to a specific node or edge, just click on it, the - + @@ -93,3 +97,19 @@ To see the information related to a specific node or edge, just click on it, the **Nodes** : currently when a node is clicked its name will be displayed, However, this feature will be updated in the future to include more information. **Edges** : currently when an edge is clicked the "message_name", "message_fields" and "fields_datatypes" will be displayed, However, this feature will be updated in the future to include more information (E.g: the field value in realtime). + +## Architecture Overview + +This is a bird's-eye view of the architecture of Labgraph Monitor. + + + +This web application is composed of multiple components (redux, pages, mocks, contextx, and components) below is the overview diagram for each: + +### Redux diagram: + + + +### Redux & Edge Settings + + diff --git a/extensions/prototypes/labgraph_monitor/src/components/SettingPanel/EdgeSettings.tsx b/extensions/prototypes/labgraph_monitor/src/components/SettingPanel/EdgeSettings.tsx index be5c4f74..cb086f68 100644 --- a/extensions/prototypes/labgraph_monitor/src/components/SettingPanel/EdgeSettings.tsx +++ b/extensions/prototypes/labgraph_monitor/src/components/SettingPanel/EdgeSettings.tsx @@ -14,15 +14,22 @@ import { TableHead, TableRow, Typography, + Button, } from '@mui/material'; -import React from 'react'; +import React, { useEffect } from 'react'; import { RootState } from '../../redux/store'; -import { useSelector } from 'react-redux'; +import { useDispatch, useSelector } from 'react-redux'; import WS_STATE from '../../redux/reducers/graph/ws/enums/WS_STATE'; +import { setMockRealtimeData } from '../../redux/reducers/graph/mock/mockReducer'; interface IMessage { name: string; - fields: { [fieldName: string]: string }; + fields: { + [fieldName: string]: { + type: string; + content: any; + }; + }; } /** @@ -37,8 +44,11 @@ const Edge: React.FC = (): JSX.Element => { (state: RootState) => state.ws ); const { mockGraph } = useSelector((state: RootState) => state.mock); - + const mockData = useSelector( + (state: RootState) => state.mock.mockRealtimeData + ); const graph = connection === WS_STATE.CONNECTED ? realtimeGraph : mockGraph; + const [open, setOpen] = React.useState(false); const messages: IMessage[] = graph && selectedEdge.target @@ -46,6 +56,23 @@ const Edge: React.FC = (): JSX.Element => { selectedEdge.source ] : []; + const handleToggle = () => { + setOpen(!open); + }; + // creating mock data, check mockReducer.ts, IMock.ts and EdgeSettings.tsx for future updates + const dispatch = useDispatch(); + + useEffect(() => { + const id = setInterval(() => { + const date = Date.now(); + + dispatch( + setMockRealtimeData([date, date % 10, date * 3, date / 4]) + ); + }, 100); + + return () => clearInterval(id); + }, [dispatch]); return ( @@ -62,20 +89,55 @@ const Edge: React.FC = (): JSX.Element => { + {/* ZMQMessage Edge */} {Object.entries(message.fields).map( - ([name, type]) => { + (field, index) => { return ( - + - {name} + {field[0]} - {type} + {field[1].type} ); } )} + + + {Object.entries(message.fields).map( + (field, index) => { + return ( + + {open && ( + + {field[0]} + + )} + {open ? ( + + {connection === + WS_STATE.CONNECTED + ? `${field[1].content}, ` + : mockData.join( + ' ' + )} + + ) : null} + + ); + } + )}
NodeEdgeedge
diff --git a/extensions/prototypes/labgraph_monitor/src/components/SettingPanel/SettingPanel.tsx b/extensions/prototypes/labgraph_monitor/src/components/SettingPanel/SettingPanel.tsx index 2bc3b7a9..24b9883a 100644 --- a/extensions/prototypes/labgraph_monitor/src/components/SettingPanel/SettingPanel.tsx +++ b/extensions/prototypes/labgraph_monitor/src/components/SettingPanel/SettingPanel.tsx @@ -12,7 +12,9 @@ import { SettingsApplicationsRounded, AlignHorizontalLeftOutlined, AlignVerticalTopOutlined, + Mode, } from '@mui/icons-material'; +import React, { useCallback } from 'react'; import SettingTabs from './SettingTabs'; import { makeStyles } from '@mui/styles'; import { useUIContext } from '../../contexts'; @@ -20,8 +22,9 @@ import { RootState } from '../../redux/store'; import { useSelector, useDispatch } from 'react-redux'; import { setPanel } from '../../redux/reducers/config/configReducer'; -const PANEL_WIDTH = 280; - +export const DEFAULT_PANEL_WIDTH = 280; +export const MIN_PANEL_WIDTH = 280; +export const MAX_PANEL_WIDTH = 600; const useStyles = makeStyles({ root: { display: 'flex', @@ -41,10 +44,10 @@ const useStyles = makeStyles({ }, settingPanel: { - width: PANEL_WIDTH, + width: DEFAULT_PANEL_WIDTH, flexShrink: 0, '& .MuiDrawer-paper': { - width: PANEL_WIDTH, + width: DEFAULT_PANEL_WIDTH, }, }, @@ -55,6 +58,30 @@ const useStyles = makeStyles({ alignItem: 'center', padding: '2px 4px 2px 4px', }, + dragger_light: { + width: '5px', + cursor: 'ew-resize', + padding: '4px 0 0', + borderTop: '1px solid #ddd', + position: 'absolute', + top: 0, + left: 0, + bottom: 0, + zIndex: '100', + backgroundColor: '#f4f7f9', + }, + dragger_dark: { + width: '5px', + cursor: 'ew-resize', + padding: '4px 0 0', + borderTop: '1px solid #111827', + position: 'absolute', + top: 0, + left: 0, + bottom: 0, + zIndex: '100', + backgroundColor: '#1f2b3c', + }, }); /** @@ -68,7 +95,22 @@ const SettingPanel: React.FC = (): JSX.Element => { const { mode, layout, toggleMode, toggleLayout } = useUIContext(); const { panelOpen } = useSelector((state: RootState) => state.config); const dispatch = useDispatch(); - + const [panelWidth, setPanelWidth] = React.useState(DEFAULT_PANEL_WIDTH); + const handleMouseDown = () => { + document.addEventListener('mouseup', handleMouseUp, true); + document.addEventListener('mousemove', handleMouseMove, true); + }; + const handleMouseUp = () => { + document.removeEventListener('mouseup', handleMouseUp, true); + document.removeEventListener('mousemove', handleMouseMove, true); + }; + const handleMouseMove = useCallback((e) => { + const newWidth = + document.body.offsetLeft + document.body.offsetWidth - e.clientX; + if (newWidth > MIN_PANEL_WIDTH && newWidth < MAX_PANEL_WIDTH) { + setPanelWidth(newWidth); + } + }, []); return ( @@ -96,6 +138,7 @@ const SettingPanel: React.FC = (): JSX.Element => { className={classes.settingPanel} variant="persistent" anchor="right" + PaperProps={{ style: { width: panelWidth } }} open={panelOpen} > @@ -106,6 +149,17 @@ const SettingPanel: React.FC = (): JSX.Element => { + +
handleMouseDown()} + className={ + mode === 'light' + ? classes.dragger_light + : classes.dragger_dark + } + /> + { } data-testid="tab-list" > - - - + + + diff --git a/extensions/prototypes/labgraph_monitor/src/contexts/WSContext/WSContext.tsx b/extensions/prototypes/labgraph_monitor/src/contexts/WSContext/WSContext.tsx index c640a8ff..a49b6bea 100644 --- a/extensions/prototypes/labgraph_monitor/src/contexts/WSContext/WSContext.tsx +++ b/extensions/prototypes/labgraph_monitor/src/contexts/WSContext/WSContext.tsx @@ -34,24 +34,33 @@ const WSContextProvider: React.FC = ({ children }): JSX.Element => { const dispatch = useDispatch(); useEffect(() => { - if (!process.env.REACT_APP_WS_API) return; + if (!process.env.REACT_APP_WS_API) { + alert('Error: Undefined Environment Variable: REACT_APP_WS_API'); + dispatch(setConnection(WS_STATE.DISCONNECTED)); + // dispatch to be disocnnected disconnect + return; + } switch (connection) { case WS_STATE.IS_CONNECTING: - clientRef.current = new W3CWebSocket( - process.env.REACT_APP_WS_API as string - ); + try { + clientRef.current = new W3CWebSocket( + process.env.REACT_APP_WS_API as string + ); - clientRef.current.onopen = () => { - clientRef.current?.send(JSON.stringify(startStreamRequest)); - dispatch(setConnection(WS_STATE.CONNECTED)); - }; + clientRef.current.onopen = () => { + clientRef.current?.send( + JSON.stringify(startStreamRequest) + ); + dispatch(setConnection(WS_STATE.CONNECTED)); + }; - clientRef.current.onerror = (err: any) => { + clientRef.current.onerror = (err: any) => { + dispatch(setConnection(WS_STATE.DISCONNECTED)); + }; + } catch (error) { dispatch(setConnection(WS_STATE.DISCONNECTED)); - }; - + } break; - case WS_STATE.CONNECTED: if (!clientRef.current) return; clientRef.current.onmessage = (message: any) => { diff --git a/extensions/prototypes/labgraph_monitor/src/mocks/json/demo.json b/extensions/prototypes/labgraph_monitor/src/mocks/json/demo.json index 868386e2..c86583f8 100644 --- a/extensions/prototypes/labgraph_monitor/src/mocks/json/demo.json +++ b/extensions/prototypes/labgraph_monitor/src/mocks/json/demo.json @@ -16,8 +16,14 @@ { "name": "RandomMessage", "fields": { - "timestamp": "float", - "data": "ndarray" + "timestamp": { + "type":"float", + "content": 1644931422.141309 + }, + "data": { + "type": "ndarray", + "content": [0.77135353, 0.61299748, 0.34146919, 0.46154968, 0.19577749] + } } } ] @@ -29,8 +35,14 @@ { "name": "RandomMessage", "fields": { - "timestamp": "float", - "data": "ndarray" + "timestamp": { + "type":"float", + "content": 1644931422.141309 + }, + "data": { + "type": "ndarray", + "content": [0.77135353, 0.61299748, 0.34146919, 0.46154968, 0.19577749] + } } } ] @@ -42,8 +54,14 @@ { "name": "RandomMessage", "fields": { - "timestamp": "float", - "data": "ndarray" + "timestamp": { + "type":"float", + "content": 1644931422.141309 + }, + "data": { + "type": "ndarray", + "content": [0.77135353, 0.61299748, 0.34146919, 0.46154968, 0.19577749] + } } } ] @@ -55,8 +73,14 @@ { "name": "RandomMessage", "fields": { - "timestamp": "float", - "data": "ndarray" + "timestamp": { + "type":"float", + "content": 1644931422.141309 + }, + "data": { + "type": "ndarray", + "content": [0.77135353, 0.61299748, 0.34146919, 0.46154968, 0.19577749] + } } } ], @@ -64,8 +88,14 @@ { "name": "RandomMessage", "fields": { - "timestamp": "float", - "data": "ndarray" + "timestamp": { + "type":"float", + "content": 1644931422.141309 + }, + "data": { + "type": "ndarray", + "content": [0.77135353, 0.61299748, 0.34146919, 0.46154968, 0.19577749] + } } } ], @@ -73,8 +103,14 @@ { "name": "RandomMessage", "fields": { - "timestamp": "float", - "data": "ndarray" + "timestamp": { + "type":"float", + "content": 1644931422.141309 + }, + "data": { + "type": "ndarray", + "content": [0.77135353, 0.61299748, 0.34146919, 0.46154968, 0.19577749] + } } } ] diff --git a/extensions/prototypes/labgraph_monitor/src/mocks/json/simple_viz.json b/extensions/prototypes/labgraph_monitor/src/mocks/json/simple_viz.json index cb09e684..d8ca8be0 100644 --- a/extensions/prototypes/labgraph_monitor/src/mocks/json/simple_viz.json +++ b/extensions/prototypes/labgraph_monitor/src/mocks/json/simple_viz.json @@ -13,8 +13,14 @@ { "name": "RandomMessage", "fields": { - "timestamp": "float", - "data": "ndarray" + "timestamp": { + "type":"float", + "content": 1647047794.474803 + }, + "data": { + "type": "ndarray", + "content": [0.77135353, 0.61299748, 0.34146919, 0.46154968, 0.19577749] + } } } ] @@ -29,8 +35,14 @@ { "name": "RandomMessage", "fields": { - "timestamp": "float", - "data": "ndarray" + "timestamp": { + "type":"float", + "content": 1647047794.474803 + }, + "data": { + "type": "ndarray", + "content": [0.77135353, 0.61299748, 0.34146919, 0.46154968, 0.19577749] + } } } ] diff --git a/extensions/prototypes/labgraph_monitor/src/mocks/json/simple_viz_fixed_rate.json b/extensions/prototypes/labgraph_monitor/src/mocks/json/simple_viz_fixed_rate.json index efdd93bf..88d93e80 100644 --- a/extensions/prototypes/labgraph_monitor/src/mocks/json/simple_viz_fixed_rate.json +++ b/extensions/prototypes/labgraph_monitor/src/mocks/json/simple_viz_fixed_rate.json @@ -13,8 +13,14 @@ { "name": "RandomMessage", "fields": { - "timestamp": "float", - "data": "ndarray" + "timestamp": { + "type":"float", + "content": 1644930955.5364213 + }, + "data": { + "type": "ndarray", + "content": [0.77135353, 0.61299748, 0.34146919, 0.46154968, 0.19577749] + } } } ] @@ -29,8 +35,14 @@ { "name": "RandomMessage", "fields": { - "timestamp": "float", - "data": "ndarray" + "timestamp": { + "type":"float", + "content": 1644930955.5364213 + }, + "data": { + "type": "ndarray", + "content": [0.77135353, 0.61299748, 0.34146919, 0.46154968, 0.19577749] + } } } ] diff --git a/extensions/prototypes/labgraph_monitor/src/mocks/json/simple_viz_zmq.json b/extensions/prototypes/labgraph_monitor/src/mocks/json/simple_viz_zmq.json index fd54835a..9f3e4c8c 100644 --- a/extensions/prototypes/labgraph_monitor/src/mocks/json/simple_viz_zmq.json +++ b/extensions/prototypes/labgraph_monitor/src/mocks/json/simple_viz_zmq.json @@ -13,8 +13,14 @@ { "name": "RandomMessage", "fields": { - "timestamp": "float", - "data": "ndarray" + "timestamp": { + "type":"float", + "content": 1644931059.3961816 + }, + "data": { + "type": "ndarray", + "content": [0.77135353, 0.61299748, 0.34146919, 0.46154968, 0.19577749] + } } } ] @@ -29,7 +35,14 @@ { "name": "ZMQMessage", "fields": { - "data": "bytes" + "timestamp": { + "type":"float", + "content": 1644931059.3961816 + }, + "data": { + "type": "bytes", + "content": [0.77135353, 0.61299748, 0.34146919, 0.46154968, 0.19577749] + } } } ] @@ -41,8 +54,14 @@ { "name": "RandomMessage", "fields": { - "timestamp": "float", - "data": "ndarray" + "timestamp": { + "type":"float", + "content": 1644931059.3961816 + }, + "data": { + "type": "ndarray", + "content": [0.77135353, 0.61299748, 0.34146919, 0.46154968, 0.19577749] + } } } ] diff --git a/extensions/prototypes/labgraph_monitor/src/mocks/json/simulation.json b/extensions/prototypes/labgraph_monitor/src/mocks/json/simulation.json index 87856e25..22bd68e6 100644 --- a/extensions/prototypes/labgraph_monitor/src/mocks/json/simulation.json +++ b/extensions/prototypes/labgraph_monitor/src/mocks/json/simulation.json @@ -16,8 +16,14 @@ { "name": "SimulationMessage", "fields": { - "timestamp": "ndarray", - "daub_data": "ndarray" + "timestamp": { + "type":"ndarray", + "content": 1644931259.3396878 + }, + "data": { + "type": "ndarray", + "content": [0.77135353, 0.61299748, 0.34146919, 0.46154968, 0.19577749] + } } } ] diff --git a/extensions/prototypes/labgraph_monitor/src/redux/reducers/graph/common/interfaces/INode.ts b/extensions/prototypes/labgraph_monitor/src/redux/reducers/graph/common/interfaces/INode.ts index 1e4e5889..ececc668 100644 --- a/extensions/prototypes/labgraph_monitor/src/redux/reducers/graph/common/interfaces/INode.ts +++ b/extensions/prototypes/labgraph_monitor/src/redux/reducers/graph/common/interfaces/INode.ts @@ -10,7 +10,10 @@ interface INode { [upstream: string]: Array<{ name: string; fields: { - [fieldName: string]: string; + [fieldName: string]: { + type: string; + content: number | number[]; + }; }; }>; }; diff --git a/extensions/prototypes/labgraph_monitor/src/redux/reducers/graph/mock/interfaces/IMock.ts b/extensions/prototypes/labgraph_monitor/src/redux/reducers/graph/mock/interfaces/IMock.ts index 6d6df85e..72cac8da 100644 --- a/extensions/prototypes/labgraph_monitor/src/redux/reducers/graph/mock/interfaces/IMock.ts +++ b/extensions/prototypes/labgraph_monitor/src/redux/reducers/graph/mock/interfaces/IMock.ts @@ -8,6 +8,7 @@ import IGraph from '../../common/interfaces/IGraph'; interface IMock { mockGraph: IGraph; + mockRealtimeData: number[]; } export default IMock; diff --git a/extensions/prototypes/labgraph_monitor/src/redux/reducers/graph/mock/mockReducer.ts b/extensions/prototypes/labgraph_monitor/src/redux/reducers/graph/mock/mockReducer.ts index ffe996de..d7a335c7 100644 --- a/extensions/prototypes/labgraph_monitor/src/redux/reducers/graph/mock/mockReducer.ts +++ b/extensions/prototypes/labgraph_monitor/src/redux/reducers/graph/mock/mockReducer.ts @@ -11,6 +11,7 @@ import { selectMock } from '../../../../mocks'; const initialState: IMock = { mockGraph: {} as IGraph, + mockRealtimeData: [Date.now()], }; export const mockSlice = createSlice({ @@ -30,9 +31,14 @@ export const mockSlice = createSlice({ copyRealtimeGraph: (state, action: PayloadAction) => { state.mockGraph = action.payload; }, + + setMockRealtimeData: (state, action: PayloadAction) => { + state.mockRealtimeData = action.payload; + }, }, }); -export const { copyRealtimeGraph, setMockGraph } = mockSlice.actions; +export const { copyRealtimeGraph, setMockGraph, setMockRealtimeData } = + mockSlice.actions; export default mockSlice.reducer; diff --git a/extensions/prototypes/labgraph_monitor/src/setupTests.ts b/extensions/prototypes/labgraph_monitor/src/setupTests.ts index 018b9ca0..f2426270 100644 --- a/extensions/prototypes/labgraph_monitor/src/setupTests.ts +++ b/extensions/prototypes/labgraph_monitor/src/setupTests.ts @@ -9,3 +9,6 @@ import '@testing-library/jest-dom/extend-expect'; import ResizeObserver from 'resize-observer-polyfill'; global.ResizeObserver = ResizeObserver; +global.alert = (content: string) => { + console.log('Window alert mock - ', content); +};