Expose message Ids to the user

This commit is contained in:
Thomas Nordquist
2020-04-20 18:23:15 +02:00
parent 6fc0d3f28d
commit 07458cd712
25 changed files with 145 additions and 106 deletions

View File

@@ -160,7 +160,7 @@
</style> </style>
</head> </head>
<body> <body>
<div id="app" style="font:-webkit-control"></div> <div id="app" style="font: -webkit-control;"></div>
<script> <script>
function loadScript(path) { function loadScript(path) {
var script = document.createElement('script') var script = document.createElement('script')
@@ -174,7 +174,8 @@
// loadScript("<%= JSON.stringify(htmlWebpackPlugin) %>") // loadScript("<%= JSON.stringify(htmlWebpackPlugin) %>")
} }
</script> </script>
<% _.forEach(htmlWebpackPlugin.files.js, function(file) { %><script src="<%- file %>"></script <% _.forEach(htmlWebpackPlugin.files.js, function(file) { %>
><% }); %> <script src="<%- file %>"></script>
<% }); %>
</body> </body>
</html> </html>

View File

@@ -112,8 +112,8 @@ export const filterTopics = (filterStr: string) => (dispatch: Dispatch<any>, get
const messageMatches = const messageMatches =
node.message && node.message &&
node.message.value && node.message.payload &&
Base64Message.toUnicodeString(node.message.value).toLowerCase().indexOf(filterStr) !== -1 Base64Message.toUnicodeString(node.message.payload).toLowerCase().indexOf(filterStr) !== -1
return Boolean(messageMatches) return Boolean(messageMatches)
} }

View File

@@ -48,6 +48,7 @@ export const clearTopic = (topic: q.TreeNode<any>, recursive: boolean) => async
payload: null, payload: null,
retain: true, retain: true,
qos: 0 as 0, qos: 0 as 0,
messageId: undefined,
} }
// Rate limit deletion // Rate limit deletion
setTimeout(() => rendererEvents.emit(publishEvent, mqttMessage), 20 * idx) setTimeout(() => rendererEvents.emit(publishEvent, mqttMessage), 20 * idx)

View File

@@ -123,8 +123,8 @@ function renderStat(tree: q.Tree<TopicViewModel>, stat: Stats) {
return null return null
} }
const str = node.message.value ? Base64Message.toUnicodeString(node.message.value) : '' const str = node.message.payload ? Base64Message.toUnicodeString(node.message.payload) : ''
let value = node.message && node.message.value ? parseFloat(str) : NaN let value = node.message && node.message.payload ? parseFloat(str) : NaN
value = !isNaN(value) ? abbreviate(value) : str value = !isNaN(value) ? abbreviate(value) : str
return ( return (

View File

@@ -0,0 +1,20 @@
import React, { memo } from 'react'
import { Message } from '../../../../backend/src/Model'
import { Tooltip } from '@material-ui/core'
export const MessageId = memo(function MessageId(props: { message: Message; addComma?: boolean }) {
const { message, addComma } = props
if (!message.messageId) {
return null
}
return (
<Tooltip title="MessageIds are used to signal a successful transmission of a message.">
<span>
#msg: {message.messageId}
{addComma ? ', ' : ''}
</span>
</Tooltip>
)
})

View File

@@ -8,7 +8,7 @@ export const TopicDeleteButton = (props: {
deleteTopicAction: (node: q.TreeNode<any>) => void deleteTopicAction: (node: q.TreeNode<any>) => void
}) => { }) => {
const { node } = props const { node } = props
if (!node || !node.message || !node.message.value) { if (!node || !node.message || !node.message.payload) {
return null return null
} }
return ( return (

View File

@@ -12,6 +12,7 @@ import { bindActionCreators } from 'redux'
import { chartActions } from '../../../actions' import { chartActions } from '../../../actions'
import { connect } from 'react-redux' import { connect } from 'react-redux'
import CustomIconButton from '../../helper/CustomIconButton' import CustomIconButton from '../../helper/CustomIconButton'
import { MessageId } from '../MessageId'
const throttle = require('lodash.throttle') const throttle = require('lodash.throttle')
@@ -81,12 +82,13 @@ class MessageHistory extends React.PureComponent<Props, State> {
const history = node.messageHistory.toArray() const history = node.messageHistory.toArray()
let previousMessage: q.Message | undefined = node.message let previousMessage: q.Message | undefined = node.message
const historyElements = [...history].reverse().map((message, idx) => { const historyElements = [...history].reverse().map((message, idx) => {
const value = message.value ? Base64Message.toUnicodeString(message.value) : '' const value = message.payload ? Base64Message.toUnicodeString(message.payload) : ''
const element = { const element = {
value, value,
key: `${message.messageNumber}-${message.received}`, key: `${message.messageNumber}-${message.received}`,
title: ( title: (
<span> <span>
<div style={{ float: 'left' }}>
<DateFormatter date={message.received} /> <DateFormatter date={message.received} />
{previousMessage && previousMessage !== message ? ( {previousMessage && previousMessage !== message ? (
<i> <i>
@@ -94,6 +96,11 @@ class MessageHistory extends React.PureComponent<Props, State> {
<DateFormatter date={message.received} intervalSince={previousMessage.received} />) <DateFormatter date={message.received} intervalSince={previousMessage.received} />)
</i> </i>
) : null} ) : null}
</div>
<span>
&nbsp;
<MessageId message={message} />
</span>
<div style={{ float: 'right' }}> <div style={{ float: 'right' }}>
<Copy value={value} /> <Copy value={value} />
</div> </div>
@@ -106,7 +113,7 @@ class MessageHistory extends React.PureComponent<Props, State> {
}) })
const isMessagePlottable = const isMessagePlottable =
node.message && node.message.value && isPlottable(Base64Message.toUnicodeString(node.message.value)) node.message && node.message.payload && isPlottable(Base64Message.toUnicodeString(node.message.payload))
return ( return (
<div> <div>
<History <History

View File

@@ -13,6 +13,7 @@ import { Theme, Typography, withStyles } from '@material-ui/core'
import { connect } from 'react-redux' import { connect } from 'react-redux'
import { sidebarActions } from '../../../actions' import { sidebarActions } from '../../../actions'
import DeleteSelectedTopicButton from './DeleteSelectedTopicButton' import DeleteSelectedTopicButton from './DeleteSelectedTopicButton'
import { MessageId } from '../MessageId'
interface Props { interface Props {
node?: q.TreeNode<any> node?: q.TreeNode<any>
@@ -36,7 +37,7 @@ function ValuePanel(props: Props) {
const { node, compareMessage } = props const { node, compareMessage } = props
function renderViewOptions() { function renderViewOptions() {
if (!props.node || !props.node.message || !props.node.mqttMessage) { if (!props.node || !props.node.message) {
return null return null
} }
@@ -46,7 +47,7 @@ function ValuePanel(props: Props) {
<ActionButtons /> <ActionButtons />
</span> </span>
<span style={{ flex: 6, textAlign: 'right' }}> <span style={{ flex: 6, textAlign: 'right' }}>
{props.node.mqttMessage.retain ? <DeleteSelectedTopicButton /> : null} {props.node.message.retain ? <DeleteSelectedTopicButton /> : null}
</span> </span>
{messageMetaInfo()} {messageMetaInfo()}
</div> </div>
@@ -54,13 +55,16 @@ function ValuePanel(props: Props) {
} }
function messageMetaInfo() { function messageMetaInfo() {
if (!props.node || !props.node.message || !props.node.mqttMessage) { if (!props.node || !props.node.message) {
return null return null
} }
return ( return (
<span style={{ width: '100%', paddingLeft: '8px', flex: 6 }}> <span style={{ width: '100%', paddingLeft: '8px', flex: 6 }}>
<Typography style={{ textAlign: 'right' }}>QoS: {props.node.mqttMessage.qos}</Typography> <Typography style={{ textAlign: 'right' }}>
<MessageId message={props.node.message} addComma={true} />
{`QoS: ${props.node.message.qos}`}
</Typography>
<Typography style={{ textAlign: 'right' }}> <Typography style={{ textAlign: 'right' }}>
<i> <i>
<DateFormatter date={props.node.message.received} /> <DateFormatter date={props.node.message.received} />
@@ -82,8 +86,8 @@ function ValuePanel(props: Props) {
) )
const copyValue = const copyValue =
node && node.message && node.message.value ? ( node && node.message && node.message.payload ? (
<Copy value={Base64Message.toUnicodeString(node.message.value)} /> <Copy value={Base64Message.toUnicodeString(node.message.payload)} />
) : null ) : null
return ( return (

View File

@@ -63,12 +63,12 @@ class ValueRenderer extends React.Component<Props, State> {
} }
private renderRawMode(message: q.Message, compare?: q.Message) { private renderRawMode(message: q.Message, compare?: q.Message) {
if (!message.value) { if (!message.payload) {
return return
} }
const [value, valueLanguage] = this.convertMessage(message.value) const [value, valueLanguage] = this.convertMessage(message.payload)
const [compareStr, compareStrLanguage] = const [compareStr, compareStrLanguage] =
compare && compare.value ? this.convertMessage(compare.value) : [undefined, undefined] compare && compare.payload ? this.convertMessage(compare.payload) : [undefined, undefined]
return ( return (
<div> <div>
@@ -95,12 +95,12 @@ class ValueRenderer extends React.Component<Props, State> {
if (renderMode === 'raw') { if (renderMode === 'raw') {
return this.renderRawMode(message, compareWith) return this.renderRawMode(message, compareWith)
} }
if (!message.value) { if (!message.payload) {
return null return null
} }
const compareValue = compareMessage.value || message.value const compareValue = compareMessage.payload || message.payload
const [current, currentLanguage] = this.convertMessage(message.value) const [current, currentLanguage] = this.convertMessage(message.payload)
const [compare, compareLanguage] = this.convertMessage(compareValue) const [compare, compareLanguage] = this.convertMessage(compareValue)
const language = currentLanguage === compareLanguage && compareLanguage === 'json' ? 'json' : undefined const language = currentLanguage === compareLanguage && compareLanguage === 'json' ? 'json' : undefined

View File

@@ -28,7 +28,7 @@ function filterUsingTimeRange(startTime: number | undefined, data: Array<q.Messa
function nodeToHistory(startTime: number | undefined, history: q.MessageHistory) { function nodeToHistory(startTime: number | undefined, history: q.MessageHistory) {
return filterUsingTimeRange(startTime, history.toArray()) return filterUsingTimeRange(startTime, history.toArray())
.map((message: q.Message) => { .map((message: q.Message) => {
const value = message.value ? toPlottableValue(Base64Message.toUnicodeString(message.value)) : NaN const value = message.payload ? toPlottableValue(Base64Message.toUnicodeString(message.payload)) : NaN
return { x: message.received.getTime(), y: toPlottableValue(value) } return { x: message.received.getTime(), y: toPlottableValue(value) }
}) })
.filter(data => !isNaN(data.y as any)) as any .filter(data => !isNaN(data.y as any)) as any
@@ -39,7 +39,7 @@ function nodeDotPathToHistory(startTime: number | undefined, history: q.MessageH
.map((message: q.Message) => { .map((message: q.Message) => {
let json = {} let json = {}
try { try {
json = message.value ? JSON.parse(Base64Message.toUnicodeString(message.value)) : {} json = message.payload ? JSON.parse(Base64Message.toUnicodeString(message.payload)) : {}
} catch (ignore) {} } catch (ignore) {}
const value = dotProp.get(json, dotPath) const value = dotProp.get(json, dotPath)

View File

@@ -27,17 +27,17 @@ class TreeNodeTitle extends React.PureComponent<TreeNodeProps, {}> {
private truncatedMessage() { private truncatedMessage() {
const limit = 400 const limit = 400
if (!this.props.treeNode.message || !this.props.treeNode.message.value) { if (!this.props.treeNode.message || !this.props.treeNode.message.payload) {
return '' return ''
} }
const str = Base64Message.toUnicodeString(this.props.treeNode.message.value) const str = Base64Message.toUnicodeString(this.props.treeNode.message.payload)
return str.length > limit ? `${str.slice(0, limit)}` : str return str.length > limit ? `${str.slice(0, limit)}` : str
} }
private renderValue() { private renderValue() {
return this.props.treeNode.message && return this.props.treeNode.message &&
this.props.treeNode.message.value && this.props.treeNode.message.payload &&
this.props.treeNode.message.length > 0 ? ( this.props.treeNode.message.length > 0 ? (
<span key="value" className={this.props.classes.value}> <span key="value" className={this.props.classes.value}>
{' '} {' '}

View File

@@ -81,7 +81,7 @@ function TreeNodeComponent(props: Props) {
const mouseOver = useCallback( const mouseOver = useCallback(
(event: React.MouseEvent) => { (event: React.MouseEvent) => {
event.stopPropagation() event.stopPropagation()
if (settings.get('selectTopicWithMouseOver') && treeNode && treeNode.message && treeNode.message.value) { if (settings.get('selectTopicWithMouseOver') && treeNode && treeNode.message && treeNode.message.payload) {
didSelectTopic() didSelectTopic()
} }
}, },

View File

@@ -1,7 +1,7 @@
import { DataSourceStateMachine } from './' import { DataSourceStateMachine } from './'
import { MqttMessage } from '../../../events' import { MqttMessage } from '../../../events'
type MessageCallback = (topic: string, payload: Buffer) => void type MessageCallback = (topic: string, payload: Buffer, packet: any) => void
// A DataSource should automatically reconnect if connection was broken // A DataSource should automatically reconnect if connection was broken
interface DataSource<DataSourceOptions> { interface DataSource<DataSourceOptions> {

View File

@@ -1,8 +1,17 @@
import { Base64Message } from './Base64Message' import { Base64Message } from './Base64Message'
import { QoS } from '../DataSource/MqttSource'
export interface Message { export interface Message {
value?: Base64Message // mqtt based info
payload: Base64Message | null
messageId?: number
retain: boolean
qos: QoS
// meta info
length: number length: number
received: Date received: Date
// Global message counter, not mqtt related
messageNumber: number messageNumber: number
} }

View File

@@ -75,13 +75,7 @@ export class Tree<ViewModel extends Destroyable> extends TreeNode<ViewModel> {
public applyUnmergedChanges() { public applyUnmergedChanges() {
this.unmergedMessages.popAll().forEach(bufferedItem => { this.unmergedMessages.popAll().forEach(bufferedItem => {
const edges = bufferedItem.message.topic.split('/') const node = TreeNodeFactory.fromMessage<ViewModel>(bufferedItem.message, bufferedItem.received)
const node = TreeNodeFactory.fromEdgesAndValue<ViewModel>(
edges,
bufferedItem.message.payload,
bufferedItem.received
)
node.mqttMessage = bufferedItem.message
if (!this.nodeFilter || this.nodeFilter(node)) { if (!this.nodeFilter || this.nodeFilter(node)) {
this.updateWithNode(node.firstNode()) this.updateWithNode(node.firstNode())

View File

@@ -1,11 +1,10 @@
import { Destroyable } from './Destroyable' import { Destroyable } from './Destroyable'
import { Edge, Message, RingBuffer, MessageHistory } from './' import { Edge, Message, RingBuffer, MessageHistory } from './'
import { EventDispatcher, MqttMessage } from '../../../events' import { EventDispatcher } from '../../../events'
export class TreeNode<ViewModel extends Destroyable> { export class TreeNode<ViewModel extends Destroyable> {
public sourceEdge?: Edge<ViewModel> public sourceEdge?: Edge<ViewModel>
public message?: Message public message?: Message
public mqttMessage?: MqttMessage
public messageHistory: MessageHistory = new RingBuffer<Message>(20000, 100) public messageHistory: MessageHistory = new RingBuffer<Message>(20000, 100)
public viewModel?: ViewModel public viewModel?: ViewModel
public edges: { [s: string]: Edge<ViewModel> } = {} public edges: { [s: string]: Edge<ViewModel> } = {}
@@ -105,7 +104,7 @@ export class TreeNode<ViewModel extends Destroyable> {
} }
public hasMessage() { public hasMessage() {
return this.message && this.message.value && this.message.value.length !== 0 return this.message && this.message.payload && this.message.length !== 0
} }
public destroy() { public destroy() {
@@ -131,7 +130,6 @@ export class TreeNode<ViewModel extends Destroyable> {
public unconnectedClone() { public unconnectedClone() {
const node = new TreeNode<ViewModel>() const node = new TreeNode<ViewModel>()
node.message = this.message node.message = this.message
node.mqttMessage = this.mqttMessage
node.messageHistory = this.messageHistory.clone() node.messageHistory = this.messageHistory.clone()
node.messages = this.messages node.messages = this.messages
node.lastUpdate = this.lastUpdate node.lastUpdate = this.lastUpdate
@@ -203,7 +201,6 @@ export class TreeNode<ViewModel extends Destroyable> {
if (node.message) { if (node.message) {
this.setMessage(node.message) this.setMessage(node.message)
this.onMessage.dispatch(node.message) this.onMessage.dispatch(node.message)
this.mqttMessage = node.mqttMessage
} }
this.removeFromTreeIfEmpty() this.removeFromTreeIfEmpty()
@@ -236,7 +233,7 @@ export class TreeNode<ViewModel extends Destroyable> {
public childTopics(): Array<TreeNode<ViewModel>> { public childTopics(): Array<TreeNode<ViewModel>> {
if (this.cachedChildTopics === undefined) { if (this.cachedChildTopics === undefined) {
const initialValue = this.message && this.message.value ? [this] : [] const initialValue = this.message && this.message.payload ? [this] : []
this.cachedChildTopics = this.edgeArray this.cachedChildTopics = this.edgeArray
.map(e => e.target.childTopics()) .map(e => e.target.childTopics())

View File

@@ -1,6 +1,6 @@
import { Base64Message } from './Base64Message'
import { Destroyable } from './Destroyable' import { Destroyable } from './Destroyable'
import { Edge, Tree, TreeNode } from './' import { Edge, Tree, TreeNode } from './'
import { MqttMessage } from '../../../events'
export abstract class TreeNodeFactory { export abstract class TreeNodeFactory {
private static messageCounter = 0 private static messageCounter = 0
@@ -20,21 +20,23 @@ export abstract class TreeNodeFactory {
node.sourceEdge!.target = node node.sourceEdge!.target = node
} }
public static fromEdgesAndValue<ViewModel extends Destroyable>( public static fromMessage<ViewModel extends Destroyable>(
edgeNames: Array<string>, mqttMessage: MqttMessage,
value?: Base64Message | null,
receiveDate: Date = new Date() receiveDate: Date = new Date()
): TreeNode<ViewModel> { ): TreeNode<ViewModel> {
const node = new TreeNode<ViewModel>() const node = new TreeNode<ViewModel>()
const edges = mqttMessage.topic.split('/')
mqttMessage.retain
node.setMessage({ node.setMessage({
value: value || undefined, ...mqttMessage,
length: value ? value.length : 0, length: mqttMessage.payload?.length ?? 0,
received: receiveDate, received: receiveDate,
messageNumber: this.messageCounter, messageNumber: this.messageCounter,
}) })
this.messageCounter += 1 this.messageCounter += 1
this.insertNodeAtPosition<ViewModel>(edgeNames, node) this.insertNodeAtPosition<ViewModel>(edges, node)
return node return node
} }

View File

@@ -1,6 +1,7 @@
import { Edge, TreeNodeFactory } from '../'
import { expect } from 'chai'
import 'mocha' import 'mocha'
import { expect } from 'chai'
import { Edge } from '../'
import { makeTreeNode } from './makeTreeNode'
describe('Edge', () => { describe('Edge', () => {
it('should contain a name', () => { it('should contain a name', () => {
@@ -9,8 +10,7 @@ describe('Edge', () => {
}) })
it('firstEdge should retrieve the first edge', () => { it('firstEdge should retrieve the first edge', () => {
const topics = 'foo/bar/baz'.split('/') const leaf = makeTreeNode('foo/bar/baz')
const leaf = TreeNodeFactory.fromEdgesAndValue(topics, undefined)
const bazEdge = leaf.sourceEdge const bazEdge = leaf.sourceEdge
if (!bazEdge) { if (!bazEdge) {
@@ -34,11 +34,8 @@ describe('Edge', () => {
}) })
it('hash should include change if parents are different', () => { it('hash should include change if parents are different', () => {
const topics1 = 'foo/bar/baz'.split('/') const bazEdge1 = makeTreeNode('foo/bar/baz').sourceEdge
const bazEdge1 = TreeNodeFactory.fromEdgesAndValue(topics1, undefined).sourceEdge const bazEdge2 = makeTreeNode('foo/foo/baz').sourceEdge
const topics2 = 'foo/foo/baz'.split('/')
const bazEdge2 = TreeNodeFactory.fromEdgesAndValue(topics2, undefined).sourceEdge
if (!bazEdge1 || !bazEdge2) { if (!bazEdge1 || !bazEdge2) {
throw Error('should not happen') throw Error('should not happen')

View File

@@ -1,18 +1,16 @@
import 'mocha' import 'mocha'
import { Tree, TreeNodeFactory } from '../'
import { expect } from 'chai' import { expect } from 'chai'
import { Tree } from '../'
import { makeTreeNode } from './makeTreeNode'
describe('Tree', () => { describe('Tree', () => {
it('node can be merged into a tree', () => { it('node can be merged into a tree', () => {
const tree = new Tree() const tree = new Tree()
const leaf = makeTreeNode('foo/bar')
const topics = 'foo/bar'.split('/')
const leaf = TreeNodeFactory.fromEdgesAndValue(topics, undefined)
tree.updateWithNode(leaf.firstNode()) tree.updateWithNode(leaf.firstNode())
const expectedNode = tree.findNode('foo/bar') const expectedNode = tree.findNode('foo/bar')
expect(expectedNode).to.eq(leaf) expect(expectedNode).to.eq(leaf)
}) })
}) })

View File

@@ -2,11 +2,11 @@ import 'mocha'
import { TreeNodeFactory } from '../' import { TreeNodeFactory } from '../'
import { expect } from 'chai' import { expect } from 'chai'
import { makeTreeNode } from './makeTreeNode'
describe('TreeNode.findNode', () => { describe('TreeNode.findNode', () => {
it('findNode should retrieve node', () => { it('findNode should retrieve node', () => {
const topics = 'foo/bar/baz'.split('/') const leaf = makeTreeNode('foo/bar/baz')
const leaf = TreeNodeFactory.fromEdgesAndValue(topics, undefined)
const root = leaf.firstNode() const root = leaf.firstNode()
expect(root.sourceEdge).to.eq(undefined) expect(root.sourceEdge).to.eq(undefined)

View File

@@ -1,55 +1,51 @@
import 'mocha' import 'mocha'
import { TreeNodeFactory } from '../'
import { expect } from 'chai' import { expect } from 'chai'
import { Base64Message } from '../Base64Message' import { Base64Message } from '../Base64Message'
import { makeTreeNode } from './makeTreeNode'
describe('TreeNode', () => { describe('TreeNode', () => {
const number3 = Base64Message.fromString('3')
const number5 = Base64Message.fromString('5')
it('firstNode should retrieve first node', () => { it('firstNode should retrieve first node', () => {
const topics = 'foo/bar'.split('/') const leaf = makeTreeNode('foo/bar')
const leaf = TreeNodeFactory.fromEdgesAndValue(topics, undefined)
expect(leaf.firstNode().edges['foo']).to.not.eq(undefined) 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, Base64Message.fromString('3')) const leaf = makeTreeNode('foo/bar', '3')
expect(Base64Message.toUnicodeString(leaf.message!.value!)).to.eq('3') expect(Base64Message.toUnicodeString(leaf.message!.payload!)).to.eq('3')
const updateLeave = TreeNodeFactory.fromEdgesAndValue(topics, Base64Message.fromString('5'))
const updateLeave = makeTreeNode('foo/bar', '5')
const root = leaf.firstNode() const root = leaf.firstNode()
root.updateWithNode(updateLeave.firstNode()) root.updateWithNode(updateLeave.firstNode())
expect(root.sourceEdge).to.eq(undefined) expect(root.sourceEdge).to.eq(undefined)
expect(Base64Message.toUnicodeString(leaf.message!.value!)).to.eq('5') expect(Base64Message.toUnicodeString(leaf.message!.payload!)).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, Base64Message.fromString('3')) const leaf = makeTreeNode('foo/bar/baz', '3')
expect(Base64Message.toUnicodeString(leaf.message!.value!)).to.eq('3') expect(Base64Message.toUnicodeString(leaf.message!.payload!)).to.eq('3')
const topics2 = 'foo/bar'.split('/') const topics2 = 'foo/bar'.split('/')
const updateLeave = TreeNodeFactory.fromEdgesAndValue(topics2, Base64Message.fromString('5')) const updateLeave = makeTreeNode('foo/bar', '5')
leaf.firstNode().updateWithNode(updateLeave.firstNode()) leaf.firstNode().updateWithNode(updateLeave.firstNode())
const barNode = leaf.firstNode().findNode('foo/bar') const barNode = leaf.firstNode().findNode('foo/bar')
expect(barNode && barNode.sourceEdge && barNode.sourceEdge.name).to.eq('bar') expect(barNode && barNode.sourceEdge && barNode.sourceEdge.name).to.eq('bar')
expect(Base64Message.toUnicodeString(barNode!.message!.value!)).to.eq('5') expect(Base64Message.toUnicodeString(barNode!.message!.payload!)).to.eq('5')
expect(leaf.sourceEdge && leaf.sourceEdge.name).to.eq('baz') expect(leaf.sourceEdge && leaf.sourceEdge.name).to.eq('baz')
expect(Base64Message.toUnicodeString(leaf.message!.value!)).to.eq('3') expect(Base64Message.toUnicodeString(leaf.message!.payload!)).to.eq('3')
}) })
it('updateWithNode should add nodes to the tree', () => { it('updateWithNode should add nodes to the tree', () => {
const topics1 = 'foo/bar'.split('/') const leaf1 = makeTreeNode('foo/bar', 'foo')
const leaf1 = TreeNodeFactory.fromEdgesAndValue(topics1, Base64Message.fromString('foo')) const leaf2 = makeTreeNode('foo/bar/baz', 'bar')
const topics2 = 'foo/bar/baz'.split('/')
const leaf2 = TreeNodeFactory.fromEdgesAndValue(topics2, Base64Message.fromString('bar'))
leaf1.firstNode().updateWithNode(leaf2.firstNode()) leaf1.firstNode().updateWithNode(leaf2.firstNode())

View File

@@ -1,22 +1,17 @@
import 'mocha' import 'mocha'
import { TreeNodeFactory } from '../'
import { expect } from 'chai' import { expect } from 'chai'
import { Base64Message } from '../Base64Message' import { Base64Message } from '../Base64Message'
import { makeTreeNode } from './makeTreeNode'
describe('TreeNodeFactory', () => { describe('TreeNodeFactory', () => {
it('root node must not have a sourceEdge', () => { it('root node must not have a sourceEdge', () => {
const topic = 'foo/bar' const leaf = makeTreeNode('foo/bar')
const edges = topic.split('/')
const leaf = TreeNodeFactory.fromEdgesAndValue(edges, undefined)
expect(leaf.firstNode().sourceEdge).to.eq(undefined) expect(leaf.firstNode().sourceEdge).to.eq(undefined)
}) })
it('should create node', () => { it('should create node', () => {
const topic = 'foo/bar' const node = makeTreeNode('foo/bar', '5')
const edges = topic.split('/')
const node = TreeNodeFactory.fromEdgesAndValue(edges, Base64Message.fromString('5'))
if (!node.sourceEdge || !node.sourceEdge.source || !node.message) { if (!node.sourceEdge || !node.sourceEdge.source || !node.message) {
expect.fail('should not happen') expect.fail('should not happen')
@@ -25,23 +20,21 @@ describe('TreeNodeFactory', () => {
expect(node).to.not.eq(undefined) expect(node).to.not.eq(undefined)
expect(node.sourceEdge.name).to.eq('bar') expect(node.sourceEdge.name).to.eq('bar')
expect(Base64Message.toUnicodeString(node.message.value!)).to.eq('5') expect(Base64Message.toUnicodeString(node.message.payload!)).to.eq('5')
const foo = node.firstNode().findNode('foo') const foo = node.firstNode().findNode('foo')
expect(foo && foo.sourceEdge && foo.sourceEdge.name).to.eq('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', () => {
const topic = 'foo/bar/baz' const node = makeTreeNode('foo/bar/baz', '5')
const edges = topic.split('/')
const node = TreeNodeFactory.fromEdgesAndValue(edges, Base64Message.fromString('5'))
if (!node.sourceEdge || !node.sourceEdge.source || !node.message) { if (!node.sourceEdge || !node.sourceEdge.source || !node.message) {
expect.fail('should not happen') expect.fail('should not happen')
return return
} }
expect(Base64Message.toUnicodeString(node.message.value!)).to.eq('5') expect(Base64Message.toUnicodeString(node.message.payload!)).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

View File

@@ -0,0 +1,16 @@
import { TreeNodeFactory } from '../'
import { Base64Message } from '../Base64Message'
import { TreeNode } from '../TreeNode'
import { MqttMessage } from '../../../../events'
export function makeTreeNode(topic: string, message?: string): TreeNode<any> {
const mqttMessage: MqttMessage = {
topic,
payload: message ? Base64Message.fromString(message) : null,
qos: 0,
retain: false,
messageId: undefined,
}
return TreeNodeFactory.fromMessage(mqttMessage)
}

View File

@@ -45,11 +45,13 @@ export class ConnectionManager {
if (buffer.length > 20000) { if (buffer.length > 20000) {
buffer = buffer.slice(0, 20000) buffer = buffer.slice(0, 20000)
} }
backendEvents.emit(messageEvent, { backendEvents.emit(messageEvent, {
topic, topic,
payload: Base64Message.fromBuffer(buffer), payload: Base64Message.fromBuffer(buffer),
qos: packet.qos, qos: packet.qos,
retain: packet.retain, retain: packet.retain,
messageId: packet.messageId,
}) })
}) })
} }

View File

@@ -34,6 +34,8 @@ export interface MqttMessage {
payload: Base64Message | null payload: Base64Message | null
qos: 0 | 1 | 2 qos: 0 | 1 | 2
retain: boolean retain: boolean
// Set if QoS is > 0 on received messages
messageId: number | undefined
} }
export function makePublishEvent(connectionId: string): Event<MqttMessage> { export function makePublishEvent(connectionId: string): Event<MqttMessage> {