Expose message Ids to the user
This commit is contained in:
@@ -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>
|
||||||
|
|||||||
@@ -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)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
@@ -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 (
|
||||||
|
|||||||
20
app/src/components/Sidebar/MessageId.tsx
Normal file
20
app/src/components/Sidebar/MessageId.tsx
Normal 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>
|
||||||
|
)
|
||||||
|
})
|
||||||
@@ -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 (
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|
||||||
|
<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
|
||||||
|
|||||||
@@ -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 (
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
@@ -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}>
|
||||||
{' '}
|
{' '}
|
||||||
|
|||||||
@@ -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()
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -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> {
|
||||||
|
|||||||
@@ -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
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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())
|
||||||
|
|||||||
@@ -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())
|
||||||
|
|||||||
@@ -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
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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')
|
||||||
|
|||||||
@@ -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)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
@@ -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())
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
16
backend/src/Model/spec/makeTreeNode.ts
Normal file
16
backend/src/Model/spec/makeTreeNode.ts
Normal 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)
|
||||||
|
}
|
||||||
@@ -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,
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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> {
|
||||||
|
|||||||
Reference in New Issue
Block a user