Improve data model & fix tests
This commit is contained in:
41
app/package-lock.json
generated
41
app/package-lock.json
generated
@@ -46,6 +46,47 @@
|
|||||||
"warning": "^4.0.1"
|
"warning": "^4.0.1"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"@material-ui/icons": {
|
||||||
|
"version": "3.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/@material-ui/icons/-/icons-3.0.1.tgz",
|
||||||
|
"integrity": "sha512-1kNcxYiIT1x8iDPEAlgmKrfRTIV8UyK6fLVcZ9kMHIKGWft9I451V5mvSrbCjbf7MX1TbLWzZjph0aVCRf9MqQ==",
|
||||||
|
"dev": true,
|
||||||
|
"requires": {
|
||||||
|
"@babel/runtime": "7.0.0",
|
||||||
|
"recompose": "^0.29.0"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"@babel/runtime": {
|
||||||
|
"version": "7.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.0.0.tgz",
|
||||||
|
"integrity": "sha512-7hGhzlcmg01CvH1EHdSPVXYX1aJ8KCEyz6I9xYIi/asDtzBPMyMhVibhM/K6g/5qnKBwjZtp10bNZIEFTRW1MA==",
|
||||||
|
"dev": true,
|
||||||
|
"requires": {
|
||||||
|
"regenerator-runtime": "^0.12.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"hoist-non-react-statics": {
|
||||||
|
"version": "2.5.5",
|
||||||
|
"resolved": "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-2.5.5.tgz",
|
||||||
|
"integrity": "sha512-rqcy4pJo55FTTLWt+bU8ukscqHeE/e9KWvsOW2b/a3afxQZhwkQdT1rPPCJ0rYXdj4vNcasY8zHTH+jF/qStxw==",
|
||||||
|
"dev": true
|
||||||
|
},
|
||||||
|
"recompose": {
|
||||||
|
"version": "0.29.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/recompose/-/recompose-0.29.0.tgz",
|
||||||
|
"integrity": "sha512-J/qLXNU4W+AeHCDR70ajW8eMd1uroqZaECTj6qqDLPMILz3y0EzpYlvrnxKB9DnqcngWrtGwjXY9JeXaW9kS1A==",
|
||||||
|
"dev": true,
|
||||||
|
"requires": {
|
||||||
|
"@babel/runtime": "^7.0.0",
|
||||||
|
"change-emitter": "^0.1.2",
|
||||||
|
"fbjs": "^0.8.1",
|
||||||
|
"hoist-non-react-statics": "^2.3.1",
|
||||||
|
"react-lifecycles-compat": "^3.0.2",
|
||||||
|
"symbol-observable": "^1.0.4"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"@material-ui/utils": {
|
"@material-ui/utils": {
|
||||||
"version": "3.0.0-alpha.2",
|
"version": "3.0.0-alpha.2",
|
||||||
"resolved": "https://registry.npmjs.org/@material-ui/utils/-/utils-3.0.0-alpha.2.tgz",
|
"resolved": "https://registry.npmjs.org/@material-ui/utils/-/utils-3.0.0-alpha.2.tgz",
|
||||||
|
|||||||
@@ -9,7 +9,9 @@
|
|||||||
},
|
},
|
||||||
"author": "",
|
"author": "",
|
||||||
"license": "ISC",
|
"license": "ISC",
|
||||||
"devDependencies": {},
|
"devDependencies": {
|
||||||
|
"@material-ui/icons": "^3.0.1"
|
||||||
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@types/node": "^10.12.18",
|
"@types/node": "^10.12.18",
|
||||||
"awesome-typescript-loader": "^5.2.1",
|
"awesome-typescript-loader": "^5.2.1",
|
||||||
|
|||||||
@@ -1,37 +1,55 @@
|
|||||||
import * as React from "react";
|
import * as React from "react";
|
||||||
import * as q from '../../backend/src/Model'
|
import * as q from '../../backend/src/Model'
|
||||||
|
|
||||||
import { AppBar, Toolbar, Typography, InputBase } from '@material-ui/core';
|
import { Tree } from "./components/Tree"
|
||||||
|
import TitleBar from "./components/TitleBar";
|
||||||
import { Tree } from "./components/Tree";
|
|
||||||
import { Sidebar } from "./components/Sidebar";
|
import { Sidebar } from "./components/Sidebar";
|
||||||
import { withStyles } from '@material-ui/core/styles';
|
|
||||||
|
import { withTheme, Theme } from '@material-ui/core/styles';
|
||||||
|
|
||||||
class State {
|
class State {
|
||||||
public selectedNode?: q.TreeNode | undefined
|
public selectedNode?: q.TreeNode | undefined
|
||||||
}
|
}
|
||||||
|
|
||||||
export class App extends React.Component<{}, State> {
|
interface Props {
|
||||||
|
theme: Theme
|
||||||
|
}
|
||||||
|
|
||||||
|
class App extends React.Component<Props, State> {
|
||||||
constructor(props: any) {
|
constructor(props: any) {
|
||||||
super(props);
|
super(props);
|
||||||
this.state = {
|
this.state = {
|
||||||
selectedNode: undefined
|
selectedNode: undefined
|
||||||
}
|
}
|
||||||
|
|
||||||
|
console.log("asd", this.props)
|
||||||
|
this.theme = this.props.theme
|
||||||
|
this.styles = {
|
||||||
|
primaryText: {
|
||||||
|
backgroundColor: this.theme.palette.background.default,
|
||||||
|
padding: `${this.theme.spacing.unit}px ${this.theme.spacing.unit * 2}px`,
|
||||||
|
color: this.theme.palette.text.primary,
|
||||||
|
},
|
||||||
|
primaryColor: {
|
||||||
|
backgroundColor: this.theme.palette.background.default,
|
||||||
|
//padding: `${this.theme.spacing.unit}px ${this.theme.spacing.unit * 2}px`,
|
||||||
|
color: this.theme.palette.common.white,
|
||||||
|
},
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private theme: Theme
|
||||||
|
private styles: any
|
||||||
|
|
||||||
public render() {
|
public render() {
|
||||||
return <div>
|
return <div style={this.styles.primaryColor}>
|
||||||
<AppBar>
|
<TitleBar />
|
||||||
<Toolbar>
|
|
||||||
<Typography variant="h6" color="inherit">MQTT-Xplorer</Typography>
|
|
||||||
</Toolbar>
|
|
||||||
<InputBase />
|
|
||||||
</AppBar>
|
|
||||||
<Tree didSelectNode={(node: q.TreeNode) => {
|
<Tree didSelectNode={(node: q.TreeNode) => {
|
||||||
this.setState({selectedNode: node})
|
this.setState({selectedNode: node})
|
||||||
console.log('did select', node)
|
console.log('did select', node)
|
||||||
}}/>
|
}} />
|
||||||
// <Sidebar node={this.state.selectedNode} />
|
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export default withTheme()(App)
|
||||||
|
|||||||
@@ -27,12 +27,6 @@ export class Sidebar extends React.Component<Props, State> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private getStyle(): {[s: string]: any} {
|
|
||||||
return {
|
|
||||||
marginTop: '64px'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public componentWillReceiveProps(nextProps: Props) {
|
public componentWillReceiveProps(nextProps: Props) {
|
||||||
this.props.node && this.props.node.removeListener('update', this.updateNode)
|
this.props.node && this.props.node.removeListener('update', this.updateNode)
|
||||||
nextProps.node && nextProps.node.on('update', this.updateNode)
|
nextProps.node && nextProps.node.on('update', this.updateNode)
|
||||||
@@ -69,6 +63,4 @@ export class Sidebar extends React.Component<Props, State> {
|
|||||||
</Paper>
|
</Paper>
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
93
app/src/components/TitleBar.tsx
Normal file
93
app/src/components/TitleBar.tsx
Normal file
@@ -0,0 +1,93 @@
|
|||||||
|
import * as React from "react";
|
||||||
|
import * as q from '../../../backend/src/Model'
|
||||||
|
|
||||||
|
import SearchIcon from '@material-ui/icons/Search';
|
||||||
|
|
||||||
|
import { AppBar, Toolbar, Typography, InputBase } from '@material-ui/core';
|
||||||
|
import { withStyles, StyleRulesCallback } from '@material-ui/core/styles';
|
||||||
|
import { fade } from '@material-ui/core/styles/colorManipulator';
|
||||||
|
|
||||||
|
const styles: StyleRulesCallback = (theme) => ({
|
||||||
|
title: {
|
||||||
|
display: 'none',
|
||||||
|
[theme.breakpoints.up('sm')]: {
|
||||||
|
display: 'block',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
search: {
|
||||||
|
position: 'relative',
|
||||||
|
borderRadius: theme.shape.borderRadius,
|
||||||
|
backgroundColor: fade(theme.palette.common.white, 0.15),
|
||||||
|
'&:hover': {
|
||||||
|
backgroundColor: fade(theme.palette.common.white, 0.25),
|
||||||
|
},
|
||||||
|
marginRight: theme.spacing.unit * 2,
|
||||||
|
marginLeft: 0,
|
||||||
|
width: '100%',
|
||||||
|
[theme.breakpoints.up('sm')]: {
|
||||||
|
marginLeft: theme.spacing.unit * 3,
|
||||||
|
width: 'auto',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
searchIcon: {
|
||||||
|
width: theme.spacing.unit * 9,
|
||||||
|
height: '100%',
|
||||||
|
position: 'absolute',
|
||||||
|
pointerEvents: 'none',
|
||||||
|
display: 'flex',
|
||||||
|
alignItems: 'center',
|
||||||
|
justifyContent: 'center',
|
||||||
|
},
|
||||||
|
inputRoot: {
|
||||||
|
color: 'inherit',
|
||||||
|
width: '100%',
|
||||||
|
},
|
||||||
|
inputInput: {
|
||||||
|
paddingTop: theme.spacing.unit,
|
||||||
|
paddingRight: theme.spacing.unit,
|
||||||
|
paddingBottom: theme.spacing.unit,
|
||||||
|
paddingLeft: theme.spacing.unit * 10,
|
||||||
|
transition: theme.transitions.create('width'),
|
||||||
|
width: '100%',
|
||||||
|
[theme.breakpoints.up('md')]: {
|
||||||
|
width: 200,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
classes: any
|
||||||
|
}
|
||||||
|
|
||||||
|
class TitleBar extends React.Component<Props, {}> {
|
||||||
|
constructor(props: any) {
|
||||||
|
super(props);
|
||||||
|
this.state = {
|
||||||
|
selectedNode: undefined
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public render() {
|
||||||
|
const { classes } = this.props;
|
||||||
|
|
||||||
|
return <AppBar position="static">
|
||||||
|
<Toolbar>
|
||||||
|
<Typography className={classes.title} variant="h6" color="inherit">MQTT-Xplorer</Typography>
|
||||||
|
<div className={classes.search}>
|
||||||
|
<div className={classes.searchIcon}>
|
||||||
|
<SearchIcon />
|
||||||
|
</div>
|
||||||
|
<InputBase
|
||||||
|
placeholder="Search…"
|
||||||
|
classes={{
|
||||||
|
root: classes.inputRoot,
|
||||||
|
input: classes.inputInput,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</Toolbar>
|
||||||
|
</AppBar>
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default withStyles(styles)(TitleBar)
|
||||||
@@ -52,15 +52,9 @@ export class Tree extends React.Component<TreeNodeProps, TreeState> {
|
|||||||
this.socket.removeAllListeners()
|
this.socket.removeAllListeners()
|
||||||
}
|
}
|
||||||
|
|
||||||
private getStyle(): {[s: string]: any} {
|
|
||||||
return {
|
|
||||||
marginTop: '64px'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public render() {
|
public render() {
|
||||||
return <div {...this.props}>
|
return <div>
|
||||||
<List style={this.getStyle()}>
|
<List>
|
||||||
<TreeNode
|
<TreeNode
|
||||||
didSelectNode={this.props.didSelectNode}
|
didSelectNode={this.props.didSelectNode}
|
||||||
treeNode={this.state.tree}
|
treeNode={this.state.tree}
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
import * as React from "react";
|
import * as React from "react";
|
||||||
import * as io from 'socket.io-client';
|
|
||||||
import * as q from '../../../backend/src/Model'
|
import * as q from '../../../backend/src/Model'
|
||||||
import List from '@material-ui/core/List';
|
import List from '@material-ui/core/List';
|
||||||
import ListItem from '@material-ui/core/ListItem';
|
import ListItem from '@material-ui/core/ListItem';
|
||||||
@@ -82,7 +81,7 @@ export class TreeNode extends React.Component<TreeNodeProps, TreeNodeState> {
|
|||||||
|
|
||||||
if (edges.length > 0) {
|
if (edges.length > 0) {
|
||||||
const listItems = edges
|
const listItems = edges
|
||||||
.map(edge => edge.node)
|
.map(edge => edge.target)
|
||||||
.map(node => <ListItem style={listItemStyle} button key={node.hash()}>
|
.map(node => <ListItem style={listItemStyle} button key={node.hash()}>
|
||||||
<TreeNode didSelectNode={this.props.didSelectNode} treeNode={node} />
|
<TreeNode didSelectNode={this.props.didSelectNode} treeNode={node} />
|
||||||
</ListItem>)
|
</ListItem>)
|
||||||
@@ -125,11 +124,11 @@ export class TreeNode extends React.Component<TreeNodeProps, TreeNodeState> {
|
|||||||
paddingLeft: '5px',
|
paddingLeft: '5px',
|
||||||
display: 'inline-block',
|
display: 'inline-block',
|
||||||
}
|
}
|
||||||
return this.props.treeNode.value
|
return this.props.treeNode.message
|
||||||
? <span
|
? <span
|
||||||
style={style}
|
style={style}
|
||||||
onMouseOver={() => this.props.didSelectNode && this.props.didSelectNode(this.props.treeNode)}
|
onMouseOver={() => this.props.didSelectNode && this.props.didSelectNode(this.props.treeNode)}
|
||||||
> = {this.props.treeNode.value.toString()}</span>
|
> = {this.props.treeNode.message.toString()}</span>
|
||||||
: null
|
: null
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -146,7 +145,6 @@ export class TreeNode extends React.Component<TreeNodeProps, TreeNodeState> {
|
|||||||
|
|
||||||
public render() {
|
public render() {
|
||||||
const nodeStyle: React.CSSProperties = {
|
const nodeStyle: React.CSSProperties = {
|
||||||
//marginLeft: '10px',
|
|
||||||
display: 'block',
|
display: 'block',
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -32,21 +32,21 @@ export class ValueRenderer extends React.Component<Props, State> {
|
|||||||
|
|
||||||
public render() {
|
public render() {
|
||||||
let node = this.props.node
|
let node = this.props.node
|
||||||
if (!node) {
|
if (!node || !node.message) {
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
let json
|
let json
|
||||||
try {
|
try {
|
||||||
json = JSON.parse(node.value)
|
json = JSON.parse(node.message.value)
|
||||||
} catch(error) {
|
} catch(error) {
|
||||||
return this.renderRawValue(node.value)
|
return this.renderRawValue(node.message.value)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (typeof json === 'string') {
|
if (typeof json === 'string') {
|
||||||
return this.renderRawValue(node.value)
|
return this.renderRawValue(node.message.value)
|
||||||
} else if (typeof json === 'number') {
|
} else if (typeof json === 'number') {
|
||||||
return this.renderRawValue(node.value)
|
return this.renderRawValue(node.message.value)
|
||||||
} else {
|
} else {
|
||||||
return <ReactJson src={json} />
|
return <ReactJson src={json} />
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,10 +1,21 @@
|
|||||||
import * as React from "react";
|
import * as React from "react";
|
||||||
import * as ReactDOM from "react-dom";
|
import * as ReactDOM from "react-dom";
|
||||||
import { App } from './App'
|
|
||||||
|
import App from './App'
|
||||||
|
import { MuiThemeProvider, createMuiTheme } from '@material-ui/core/styles';
|
||||||
|
|
||||||
|
const theme = createMuiTheme({
|
||||||
|
palette: {
|
||||||
|
type: 'dark', // Switching the dark mode on is a single property value change.
|
||||||
|
},
|
||||||
|
typography: { useNextVariants: true },
|
||||||
|
});
|
||||||
|
|
||||||
declare var document: any
|
declare var document: any
|
||||||
|
|
||||||
ReactDOM.render(
|
ReactDOM.render(
|
||||||
<App />,
|
<MuiThemeProvider theme={theme}>
|
||||||
|
<App />
|
||||||
|
</MuiThemeProvider>,
|
||||||
document.getElementById("example")
|
document.getElementById("example")
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ const sha1 = require('sha1')
|
|||||||
export class Edge implements Hashable {
|
export class Edge implements Hashable {
|
||||||
public name: string
|
public name: string
|
||||||
|
|
||||||
public node!: TreeNode
|
public target!: TreeNode
|
||||||
public source?: TreeNode | undefined
|
public source?: TreeNode | undefined
|
||||||
private cachedHash?: string
|
private cachedHash?: string
|
||||||
|
|
||||||
@@ -13,7 +13,7 @@ export class Edge implements Hashable {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public edges() {
|
public edges() {
|
||||||
return this.node ? Object.values(this.node.edges) : []
|
return this.target ? Object.values(this.target.edges) : []
|
||||||
}
|
}
|
||||||
|
|
||||||
public hash(): string {
|
public hash(): string {
|
||||||
|
|||||||
3
backend/src/Model/Message.ts
Normal file
3
backend/src/Model/Message.ts
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
export interface Message {
|
||||||
|
value?: any | undefined
|
||||||
|
}
|
||||||
@@ -1,20 +1,27 @@
|
|||||||
import { Edge } from './'
|
import { Edge, Message } from './'
|
||||||
import { EventEmitter } from 'events'
|
import { EventEmitter } from 'events'
|
||||||
|
|
||||||
export class TreeNode extends EventEmitter {
|
export class TreeNode extends EventEmitter {
|
||||||
public sourceEdge?: Edge
|
public sourceEdge?: Edge
|
||||||
public value?: any | null
|
public message?: Message
|
||||||
public edges: {[s: string]: Edge} = {}
|
public edges: {[s: string]: Edge} = {}
|
||||||
public collapsed = false
|
public collapsed = false
|
||||||
|
public messages: number = 0
|
||||||
|
|
||||||
constructor(sourceEdge?: Edge, value?: any) {
|
constructor(sourceEdge?: Edge, message?: Message) {
|
||||||
super()
|
super()
|
||||||
|
|
||||||
if (sourceEdge) {
|
if (sourceEdge) {
|
||||||
this.sourceEdge = sourceEdge
|
this.sourceEdge = sourceEdge
|
||||||
sourceEdge.node = this
|
sourceEdge.target = this
|
||||||
}
|
}
|
||||||
this.value = value
|
|
||||||
|
this.setMessage(message)
|
||||||
|
}
|
||||||
|
|
||||||
|
public setMessage(value: any) {
|
||||||
|
this.message = value
|
||||||
|
this.messages += 1
|
||||||
}
|
}
|
||||||
|
|
||||||
public hash(): string {
|
public hash(): string {
|
||||||
@@ -22,7 +29,7 @@ export class TreeNode extends EventEmitter {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public firstNode(): TreeNode {
|
public firstNode(): TreeNode {
|
||||||
return this.sourceEdge ? this.sourceEdge.firstEdge().node : this
|
return this.sourceEdge && this.sourceEdge.source ? this.sourceEdge.source.firstNode() : this
|
||||||
}
|
}
|
||||||
|
|
||||||
public path(): string {
|
public path(): string {
|
||||||
@@ -52,9 +59,10 @@ export class TreeNode extends EventEmitter {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public updateWithNode(node: TreeNode) {
|
public updateWithNode(node: TreeNode) {
|
||||||
if (node.value !== undefined) {
|
if (node.message) {
|
||||||
this.value = node.value
|
this.setMessage(node.message)
|
||||||
}
|
}
|
||||||
|
|
||||||
this.mergeEdges(node)
|
this.mergeEdges(node)
|
||||||
this.emit('update')
|
this.emit('update')
|
||||||
}
|
}
|
||||||
@@ -65,7 +73,7 @@ export class TreeNode extends EventEmitter {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return Object.values(this.edges)
|
return Object.values(this.edges)
|
||||||
.map(e => e.node.leafes())
|
.map(e => e.target.leafes())
|
||||||
.reduce((a, b) => a.concat(b), [])
|
.reduce((a, b) => a.concat(b), [])
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -74,7 +82,7 @@ export class TreeNode extends EventEmitter {
|
|||||||
for (let edgeKey of edgeKeys) {
|
for (let edgeKey of edgeKeys) {
|
||||||
let matchingEdge = this.edges[edgeKey]
|
let matchingEdge = this.edges[edgeKey]
|
||||||
if (matchingEdge) {
|
if (matchingEdge) {
|
||||||
matchingEdge.node.updateWithNode(node.edges[edgeKey].node)
|
matchingEdge.target.updateWithNode(node.edges[edgeKey].target)
|
||||||
} else {
|
} else {
|
||||||
this.addEdge(node.edges[edgeKey])
|
this.addEdge(node.edges[edgeKey])
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,32 +1,17 @@
|
|||||||
import { Edge, Tree, TreeNode } from './'
|
import { Edge, Message, Tree, TreeNode } from './'
|
||||||
|
|
||||||
export abstract class TreeNodeFactory {
|
export abstract class TreeNodeFactory {
|
||||||
public static fromEdgesAndValue(edgeNames: Array<string>, value: any): TreeNode {
|
public static fromEdgesAndValue(edgeNames: Array<string>, value: any): TreeNode {
|
||||||
const lastEdgeIndex = edgeNames.length - 1
|
let currentNode: TreeNode = new Tree()
|
||||||
var edges = edgeNames
|
for (const edgeName of edgeNames) {
|
||||||
.map((name, idx) => {
|
const edge = new Edge(edgeName)
|
||||||
const edge = new Edge(name)
|
let newNode = new TreeNode(edge)
|
||||||
|
edge.target = newNode
|
||||||
const nodeValue = lastEdgeIndex == idx ? value : undefined
|
currentNode.addEdge(edge)
|
||||||
const node = new TreeNode(edge, nodeValue)
|
currentNode = newNode
|
||||||
edge.node = node
|
|
||||||
return edge
|
|
||||||
})
|
|
||||||
|
|
||||||
let reversed: Array<Edge> = edges.reverse()
|
|
||||||
let previous: Edge | undefined = undefined;
|
|
||||||
for (let edge of reversed) {
|
|
||||||
if (previous) {
|
|
||||||
edge.node.addEdge(previous)
|
|
||||||
}
|
|
||||||
previous = edge;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
let leaf = reversed[0].node
|
currentNode.setMessage({ value: value })
|
||||||
|
return currentNode
|
||||||
let sourceTree = new Tree()
|
|
||||||
sourceTree.updateWithNode(leaf.firstNode())
|
|
||||||
|
|
||||||
return leaf
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,10 +1,7 @@
|
|||||||
import { Edge } from './Edge'
|
export { Edge } from './Edge'
|
||||||
import { TreeNode } from './TreeNode'
|
export { TreeNode } from './TreeNode'
|
||||||
import { TreeNodeFactory } from './TreeNodeFactory'
|
export { Message } from './Message'
|
||||||
import { Tree } from './Tree'
|
export { TreeNodeFactory } from './TreeNodeFactory'
|
||||||
import { TopicProperties } from './TopicProperties'
|
export { Tree } from './Tree'
|
||||||
import { Hashable } from './Hashable'
|
export { TopicProperties } from './TopicProperties'
|
||||||
|
export { Hashable } from './Hashable'
|
||||||
export {
|
|
||||||
Edge, TreeNode, TreeNodeFactory, Tree, TopicProperties, Hashable
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -1,14 +1,27 @@
|
|||||||
import { Edge, TreeNode } from '../'
|
import { Edge, TreeNode, TreeNodeFactory } from '../'
|
||||||
import { expect } from 'chai';
|
import { expect } from 'chai'
|
||||||
import 'mocha';
|
import 'mocha'
|
||||||
|
|
||||||
describe('Edge', () => {
|
describe('Edge', () => {
|
||||||
|
|
||||||
it('should contain a name', () => {
|
it('should contain a name', () => {
|
||||||
let e = new Edge('foo')
|
let e = new Edge('foo')
|
||||||
expect(e.name).to.equal('foo')
|
expect(e.name).to.equal('foo')
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('firstEdge should retireve the first edge', () => {
|
||||||
|
const topics = 'foo/bar/baz'.split('/')
|
||||||
|
const leaf = TreeNodeFactory.fromEdgesAndValue(topics, 5)
|
||||||
|
let bazEdge = leaf.sourceEdge
|
||||||
|
|
||||||
|
if (!bazEdge) {
|
||||||
|
expect.fail('should not be undefined')
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
expect(bazEdge.name).to.eq('baz')
|
||||||
|
expect(bazEdge.firstEdge().name).to.eq('foo')
|
||||||
|
});
|
||||||
|
|
||||||
it('hash should not be empty', () => {
|
it('hash should not be empty', () => {
|
||||||
let e = new Edge('bar')
|
let e = new Edge('bar')
|
||||||
expect(e.hash().length).to.be.gt(0)
|
expect(e.hash().length).to.be.gt(0)
|
||||||
@@ -20,12 +33,17 @@ describe('Edge', () => {
|
|||||||
expect(e.hash()).to.eq(previousHash)
|
expect(e.hash()).to.eq(previousHash)
|
||||||
});
|
});
|
||||||
|
|
||||||
it('hash should change when parent is present', () => {
|
it('hash should include change if parents are different', () => {
|
||||||
let foo = new Edge('foo')
|
const topics1 = 'foo/bar/baz'.split('/')
|
||||||
let bar = new Edge('bar')
|
const bazEdge1 = TreeNodeFactory.fromEdgesAndValue(topics1, 5).sourceEdge
|
||||||
|
|
||||||
var previousHash = bar.hash()
|
const topics2 = 'foo/foo/baz'.split('/')
|
||||||
bar.source = new TreeNode(foo, undefined)
|
const bazEdge2 = TreeNodeFactory.fromEdgesAndValue(topics2, 5).sourceEdge
|
||||||
expect(bar.hash()).to.not.eq(previousHash)
|
|
||||||
|
if (!bazEdge1 || !bazEdge2) {
|
||||||
|
throw Error('should not happen')
|
||||||
|
}
|
||||||
|
|
||||||
|
expect(bazEdge1.hash()).to.not.eq(bazEdge2.hash())
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { Edge, Tree, TreeNode, TreeNodeFactory } from '../'
|
import { Tree, TreeNodeFactory } from '../'
|
||||||
import { expect } from 'chai';
|
import { expect } from 'chai'
|
||||||
import 'mocha';
|
import 'mocha'
|
||||||
|
|
||||||
import './TreeNode.findNode'
|
import './TreeNode.findNode'
|
||||||
|
|
||||||
@@ -10,7 +10,7 @@ describe('Tree', () => {
|
|||||||
|
|
||||||
const topics = 'foo/bar'.split('/')
|
const topics = 'foo/bar'.split('/')
|
||||||
const leaf = TreeNodeFactory.fromEdgesAndValue(topics, 3)
|
const leaf = TreeNodeFactory.fromEdgesAndValue(topics, 3)
|
||||||
debugger
|
|
||||||
tree.updateWithNode(leaf.firstNode())
|
tree.updateWithNode(leaf.firstNode())
|
||||||
let expectedNode = tree.findNode('foo/bar')
|
let expectedNode = tree.findNode('foo/bar')
|
||||||
expect(expectedNode).to.eq(leaf)
|
expect(expectedNode).to.eq(leaf)
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { TreeNode, TreeNodeFactory } from '../'
|
import { TreeNodeFactory } from '../'
|
||||||
import { expect } from 'chai';
|
import { expect } from 'chai'
|
||||||
import 'mocha';
|
import 'mocha'
|
||||||
|
|
||||||
import './TreeNode.findNode'
|
import './TreeNode.findNode'
|
||||||
|
|
||||||
@@ -10,20 +10,20 @@ describe('TreeNode.findNode', () => {
|
|||||||
const leaf = TreeNodeFactory.fromEdgesAndValue(topics, 5)
|
const leaf = TreeNodeFactory.fromEdgesAndValue(topics, 5)
|
||||||
|
|
||||||
let root = leaf.firstNode()
|
let root = leaf.firstNode()
|
||||||
expect(root.sourceEdge.name).to.eq('')
|
expect(root.sourceEdge).to.eq(undefined)
|
||||||
|
|
||||||
let barNode = root.findNode('foo/bar')
|
let barNode = root.findNode('foo/bar')
|
||||||
if (!barNode) {
|
if (!barNode) {
|
||||||
expect.fail('did not find node')
|
expect.fail('did not find node')
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
expect(barNode.sourceEdge.name).to.eq('bar')
|
expect(barNode.sourceEdge && barNode.sourceEdge.name).to.eq('bar')
|
||||||
|
|
||||||
let bazNode = root.findNode('foo/bar/baz')
|
let bazNode = root.findNode('foo/bar/baz')
|
||||||
if (!bazNode) {
|
if (!bazNode) {
|
||||||
expect.fail('did not find node')
|
expect.fail('did not find node')
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
expect(bazNode.sourceEdge.name).to.eq('baz')
|
expect(bazNode.sourceEdge && bazNode.sourceEdge.name).to.eq('baz')
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
import { TreeNode } from '../'
|
import { TreeNode } from '../'
|
||||||
|
declare module '../' {
|
||||||
declare module "../" {
|
|
||||||
interface TreeNode {
|
interface TreeNode {
|
||||||
findNode(path: String): TreeNode | undefined
|
findNode(path: String): TreeNode | undefined
|
||||||
}
|
}
|
||||||
@@ -11,9 +10,9 @@ TreeNode.prototype.findNode = function(path: String): TreeNode | undefined {
|
|||||||
let edge = this.edges[topics[0]]
|
let edge = this.edges[topics[0]]
|
||||||
let remainingTopics = topics.slice(1, topics.length)
|
let remainingTopics = topics.slice(1, topics.length)
|
||||||
if (edge && remainingTopics.length === 0) {
|
if (edge && remainingTopics.length === 0) {
|
||||||
return edge.node
|
return edge.target
|
||||||
} else if (edge) {
|
} else if (edge) {
|
||||||
return edge.node.findNode(remainingTopics.join('/'))
|
return edge.target.findNode(remainingTopics.join('/'))
|
||||||
}
|
}
|
||||||
|
|
||||||
return undefined
|
return undefined
|
||||||
|
|||||||
@@ -1,36 +1,44 @@
|
|||||||
import { Edge, Tree, TreeNode, TreeNodeFactory } from '../'
|
import { TreeNodeFactory } from '../'
|
||||||
import { expect } from 'chai';
|
|
||||||
import 'mocha';
|
|
||||||
|
|
||||||
import './TreeNode.findNode'
|
import './TreeNode.findNode'
|
||||||
|
import { expect } from 'chai'
|
||||||
|
import 'mocha'
|
||||||
|
|
||||||
describe('TreeNode', () => {
|
describe('TreeNode', () => {
|
||||||
|
it('firstNode should retrieve first node', () => {
|
||||||
|
const topics = 'foo/bar'.split('/')
|
||||||
|
const leaf = TreeNodeFactory.fromEdgesAndValue(topics, 3)
|
||||||
|
|
||||||
|
expect(leaf.firstNode().edges['foo']).to.not.eq(undefined)
|
||||||
|
})
|
||||||
|
|
||||||
it('updateWithNode should update value', () => {
|
it('updateWithNode should update value', () => {
|
||||||
const topics = 'foo/bar'.split('/')
|
const topics = 'foo/bar'.split('/')
|
||||||
const leaf = TreeNodeFactory.fromEdgesAndValue(topics, 3)
|
const leaf = TreeNodeFactory.fromEdgesAndValue(topics, 3)
|
||||||
expect(leaf.value).to.eq(3)
|
expect(leaf.message && leaf.message.value).to.eq(3)
|
||||||
const updateLeave = TreeNodeFactory.fromEdgesAndValue(topics, 5)
|
const updateLeave = TreeNodeFactory.fromEdgesAndValue(topics, 5)
|
||||||
leaf.firstNode().updateWithNode(updateLeave.firstNode())
|
|
||||||
|
|
||||||
expect(leaf.firstNode().sourceEdge.name).to.eq(updateLeave.firstNode().sourceEdge.name)
|
let root = leaf.firstNode()
|
||||||
expect(leaf.value).to.eq(5)
|
root.updateWithNode(updateLeave.firstNode())
|
||||||
|
|
||||||
|
expect(root.sourceEdge).to.eq(undefined)
|
||||||
|
expect(leaf.message && leaf.message.value).to.eq(5)
|
||||||
})
|
})
|
||||||
|
|
||||||
it('updateWithNode should update intermediate nodes', () => {
|
it('updateWithNode should update intermediate nodes', () => {
|
||||||
const topics1 = 'foo/bar/baz'.split('/')
|
const topics1 = 'foo/bar/baz'.split('/')
|
||||||
const leaf = TreeNodeFactory.fromEdgesAndValue(topics1, 3)
|
const leaf = TreeNodeFactory.fromEdgesAndValue(topics1, 3)
|
||||||
expect(leaf.value).to.eq(3)
|
expect(leaf.message && leaf.message.value).to.eq(3)
|
||||||
|
|
||||||
const topics2 = 'foo/bar'.split('/')
|
const topics2 = 'foo/bar'.split('/')
|
||||||
const updateLeave = TreeNodeFactory.fromEdgesAndValue(topics2, 5)
|
const updateLeave = TreeNodeFactory.fromEdgesAndValue(topics2, 5)
|
||||||
leaf.firstNode().updateWithNode(updateLeave.firstNode())
|
leaf.firstNode().updateWithNode(updateLeave.firstNode())
|
||||||
|
|
||||||
let barNode = leaf.firstNode().findNode('foo/bar')
|
let barNode = leaf.firstNode().findNode('foo/bar')
|
||||||
expect(barNode && barNode.sourceEdge.name).to.eq('bar')
|
expect(barNode && barNode.sourceEdge && barNode.sourceEdge.name).to.eq('bar')
|
||||||
expect(barNode && barNode.value).to.eq(5)
|
expect(barNode && barNode.message && barNode.message.value).to.eq(5)
|
||||||
|
|
||||||
expect(leaf.sourceEdge.name).to.eq('baz')
|
expect(leaf.sourceEdge && leaf.sourceEdge.name).to.eq('baz')
|
||||||
expect(leaf.value).to.eq(3)
|
expect(leaf.message && leaf.message.value).to.eq(3)
|
||||||
})
|
})
|
||||||
|
|
||||||
it('updateWithNode should add nodes to the tree', () => {
|
it('updateWithNode should add nodes to the tree', () => {
|
||||||
|
|||||||
@@ -1,24 +1,33 @@
|
|||||||
import { Edge, TreeNode, TreeNodeFactory } from '../'
|
import { TreeNodeFactory } from '../'
|
||||||
import { expect } from 'chai';
|
import { expect } from 'chai'
|
||||||
import 'mocha';
|
import 'mocha'
|
||||||
|
import './TreeNode.findNode'
|
||||||
|
|
||||||
describe('TreeNodeFactory', () => {
|
describe('TreeNodeFactory', () => {
|
||||||
|
it('root node must not have a sourceEdge', () => {
|
||||||
|
let topic = 'foo/bar'
|
||||||
|
let edges = topic.split('/')
|
||||||
|
let leaf = TreeNodeFactory.fromEdgesAndValue(edges, 5)
|
||||||
|
|
||||||
|
expect(leaf.firstNode().sourceEdge).to.eq(undefined)
|
||||||
|
});
|
||||||
|
|
||||||
it('should create node', () => {
|
it('should create node', () => {
|
||||||
let topic = 'foo/bar'
|
let topic = 'foo/bar'
|
||||||
let edges = topic.split('/')
|
let edges = topic.split('/')
|
||||||
let node = TreeNodeFactory.fromEdgesAndValue(edges, 5)
|
let node = TreeNodeFactory.fromEdgesAndValue(edges, 5)
|
||||||
|
|
||||||
expect(node).to.not.eq(undefined)
|
if (!node.sourceEdge || !node.sourceEdge.source || !node.message) {
|
||||||
expect(node.sourceEdge.name).to.eq('bar')
|
|
||||||
expect(node.value).to.eq(5)
|
|
||||||
|
|
||||||
if (!node.sourceEdge.source) {
|
|
||||||
expect.fail('should not happen')
|
expect.fail('should not happen')
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
let foo = node.sourceEdge.source.sourceEdge
|
expect(node).to.not.eq(undefined)
|
||||||
expect(foo.name).to.eq('foo')
|
expect(node.sourceEdge.name).to.eq('bar')
|
||||||
|
expect(node.message.value).to.eq(5)
|
||||||
|
|
||||||
|
let foo = node.firstNode().findNode('foo')
|
||||||
|
expect(foo && foo.sourceEdge && foo.sourceEdge.name).to.eq('foo')
|
||||||
});
|
});
|
||||||
|
|
||||||
it('node should contain edges in order', () => {
|
it('node should contain edges in order', () => {
|
||||||
@@ -26,18 +35,23 @@ describe('TreeNodeFactory', () => {
|
|||||||
let edges = topic.split('/')
|
let edges = topic.split('/')
|
||||||
let node = TreeNodeFactory.fromEdgesAndValue(edges, 5)
|
let node = TreeNodeFactory.fromEdgesAndValue(edges, 5)
|
||||||
|
|
||||||
expect(node.value).to.eq(5)
|
if (!node.sourceEdge || !node.sourceEdge.source || !node.message) {
|
||||||
|
expect.fail('should not happen')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
expect(node.message.value).to.eq(5)
|
||||||
expect(node.sourceEdge.name).to.eq('baz')
|
expect(node.sourceEdge.name).to.eq('baz')
|
||||||
|
|
||||||
const barNode = node.sourceEdge.source
|
const barNode = node.sourceEdge.source
|
||||||
if (!barNode) {
|
if (!barNode || !barNode.sourceEdge) {
|
||||||
expect.fail('should not fail')
|
expect.fail('should not fail')
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
expect(barNode.sourceEdge.name).to.eq('bar')
|
expect(barNode.sourceEdge.name).to.eq('bar')
|
||||||
|
|
||||||
const fooNode = barNode.sourceEdge.source
|
const fooNode = barNode.sourceEdge.source
|
||||||
if (!fooNode) {
|
if (!fooNode || !fooNode.sourceEdge) {
|
||||||
expect.fail('should not fail')
|
expect.fail('should not fail')
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,13 +16,13 @@ io.on('connection', client => {
|
|||||||
a.forEach(b => {
|
a.forEach(b => {
|
||||||
io.emit('message', b)
|
io.emit('message', b)
|
||||||
})
|
})
|
||||||
client.on('event', data => { /* … */ });
|
|
||||||
client.on('disconnect', () => { /* … */ });
|
client.on('disconnect', () => { /* … */ });
|
||||||
});
|
});
|
||||||
server.listen(3000);
|
server.listen(3000);
|
||||||
|
|
||||||
let state = dataSource.connect(options)
|
let state = dataSource.connect(options)
|
||||||
dataSource.onMessage((topic: string, payload: Buffer) => {
|
dataSource.onMessage((topic: string, payload: Buffer) => {
|
||||||
|
a.push({ topic, payload: payload.toString('base64') })
|
||||||
if (payload.length > 10000) {
|
if (payload.length > 10000) {
|
||||||
payload = payload.slice(0, 10000)
|
payload = payload.slice(0, 10000)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,6 +14,6 @@
|
|||||||
"exclude": [
|
"exclude": [
|
||||||
"app",
|
"app",
|
||||||
"node_modules",
|
"node_modules",
|
||||||
"**/*.spec.ts"
|
"src/**/*.spec.ts"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,7 +5,9 @@
|
|||||||
"main": "electron.js",
|
"main": "electron.js",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"start": "electron .",
|
"start": "electron .",
|
||||||
"release": "./release.sh"
|
"test": "npm run test-backend",
|
||||||
|
"test-backend": "cd backend && npm run test && cd ..",
|
||||||
|
"release": "npm run test && ./release.sh"
|
||||||
},
|
},
|
||||||
"build": {
|
"build": {
|
||||||
"appId": "mq-explorer",
|
"appId": "mq-explorer",
|
||||||
|
|||||||
Reference in New Issue
Block a user