Skip to content
This repository was archived by the owner on Nov 1, 2024. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 27 additions & 7 deletions extensions/prototypes/labgraph_monitor/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand All @@ -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
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

An empty UI has launched after running this line.
Reference: https://pasteboard.co/K6noFCNDD7A3.png

Copy link
Contributor Author

@fzchriha fzchriha Apr 22, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do you see the icon on the right?
Screen Shot 2022-04-22 at 2 33 13 PM

```

(!) The application will be running on **localhost:3000** by default
Expand Down Expand Up @@ -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.

Expand All @@ -82,7 +86,7 @@ To see the information related to a specific node or edge, just click on it, the
<table>
<tr>
<td>Node</td>
<td>Edge</td>
<td>edge</td>
</tr>
<tr>
<td><img src="https://i.ibb.co/MnB045Z/node-frame.png"></td>
Expand All @@ -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.

<image src="https://i.ibb.co/chtfM5R/lg-Architecture.png" width="500px" alt="labgraph monitor's architecture overview"/>

This web application is composed of multiple components (redux, pages, mocks, contextx, and components) below is the overview diagram for each:

### Redux diagram:

<image src="https://i.ibb.co/LznCmt7/redux-Diagram.png" width="500px" alt=""/>

### Redux & Edge Settings

<image src="https://i.ibb.co/0qGRLr0/edge-redux.png" width="500px" alt=""/>
Original file line number Diff line number Diff line change
Expand Up @@ -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;
};
};
}

/**
Expand All @@ -37,15 +44,35 @@ 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
? graph['nodes'][selectedEdge.target]['upstreams'][
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 (
<React.Fragment>
<Box data-testid="edge-settings">
Expand All @@ -62,20 +89,55 @@ const Edge: React.FC = (): JSX.Element => {
</TableRow>
</TableHead>
<TableBody>
{/* ZMQMessage Edge */}
{Object.entries(message.fields).map(
([name, type]) => {
(field, index) => {
return (
<TableRow key={name}>
<TableRow key={index}>
<TableCell>
{name}
{field[0]}
</TableCell>
<TableCell>
{type}
{field[1].type}
</TableCell>
</TableRow>
);
}
)}
<Button onClick={handleToggle}>
{open ? 'Show less' : 'Show more'}
</Button>

{Object.entries(message.fields).map(
(field, index) => {
return (
<TableRow key={index}>
{open && (
<TableCell>
{field[0]}
</TableCell>
)}
{open ? (
<TableCell
style={{
whiteSpace:
'normal',
wordBreak:
'break-word',
}}
>
{connection ===
WS_STATE.CONNECTED
? `${field[1].content}, `
: mockData.join(
' '
)}
</TableCell>
) : null}
</TableRow>
);
}
)}
</TableBody>
</Table>
</TableContainer>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,16 +12,19 @@ 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';
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',
Expand All @@ -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,
},
},

Expand All @@ -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',
},
});

/**
Expand All @@ -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);
Comment on lines +103 to +104
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm a bit confused on what's going on here. The function is handleMouseUp, and its passing itself in the event listener?

The onMouseDown and related event callbacks were made in order to not use the native document object. In React's event system, the listener is onMouseDown itself, iirc it uses the native mousedown event internally, so you don't needa use both.

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 (
<Box className={classes.root} data-testid="setting-panel">
<CssBaseline />
Expand Down Expand Up @@ -96,6 +138,7 @@ const SettingPanel: React.FC = (): JSX.Element => {
className={classes.settingPanel}
variant="persistent"
anchor="right"
PaperProps={{ style: { width: panelWidth } }}
open={panelOpen}
>
<Box>
Expand All @@ -106,6 +149,17 @@ const SettingPanel: React.FC = (): JSX.Element => {
<ChevronRight />
</IconButton>
</Box>

<div
id="dragger"
onMouseDown={() => handleMouseDown()}
className={
mode === 'light'
? classes.dragger_light
: classes.dragger_dark
}
/>

<Divider />
<Box className={classes.themeBar}>
<IconButton
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,9 @@ const useStyles = makeStyles({
root: {
width: '100%',
},
tab: {
width: '33%',
},
});

/**
Expand All @@ -44,9 +47,9 @@ const SettingTabs: React.FC = (): JSX.Element => {
}
data-testid="tab-list"
>
<Tab label="graph" value="1" />
<Tab label="node" value="2" />
<Tab label="edge" value="3" />
<Tab label="graph" value="1" className={classes.tab} />
<Tab label="node" value="2" className={classes.tab} />
<Tab label="edge" value="3" className={classes.tab} />
</TabList>
</Box>
<TabPanel style={{ padding: 0 }} value="1">
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,24 +34,33 @@ const WSContextProvider: React.FC<ReactNode> = ({ 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) => {
Expand Down
Loading