Skip to content

Commit

Permalink
chore: decode data in frontend
Browse files Browse the repository at this point in the history
  • Loading branch information
Thomas Nordquist committed May 21, 2024
1 parent 10aae59 commit 980072f
Show file tree
Hide file tree
Showing 22 changed files with 303 additions and 283 deletions.
17 changes: 9 additions & 8 deletions app/src/actions/Settings.ts
Original file line number Diff line number Diff line change
Expand Up @@ -68,13 +68,14 @@ export const selectTopicWithMouseOver = (doSelect: boolean) => (dispatch: Dispat
dispatch(storeSettings())
}

export const setValueDisplayMode = (valueRendererDisplayMode: ValueRendererDisplayMode) => (dispatch: Dispatch<any>) => {
dispatch({
valueRendererDisplayMode,
type: ActionTypes.SETTINGS_SET_VALUE_RENDERER_DISPLAY_MODE,
})
dispatch(storeSettings())
}
export const setValueDisplayMode =
(valueRendererDisplayMode: ValueRendererDisplayMode) => (dispatch: Dispatch<any>) => {
dispatch({
valueRendererDisplayMode,
type: ActionTypes.SETTINGS_SET_VALUE_RENDERER_DISPLAY_MODE,
})
dispatch(storeSettings())
}

export const toggleHighlightTopicUpdates = () => (dispatch: Dispatch<any>) => {
dispatch({
Expand Down Expand Up @@ -117,7 +118,7 @@ export const filterTopics = (filterStr: string) => (dispatch: Dispatch<any>, get
const messageMatches =
node.message &&
node.message.payload &&
Base64Message.toUnicodeString(node.message.payload).toLowerCase().indexOf(filterStr) !== -1
node.message.payload.toUnicodeString().toLowerCase().indexOf(filterStr) !== -1

return Boolean(messageMatches)
}
Expand Down
2 changes: 1 addition & 1 deletion app/src/components/SettingsDrawer/BrokerStatistics.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -123,7 +123,7 @@ function renderStat(tree: q.Tree<TopicViewModel>, stat: Stats) {
return null
}

const str = node.message.payload ? Base64Message.toUnicodeString(node.message.payload) : ''
const str = node.message.payload ? node.message.payload.toUnicodeString() : ''
let value = node.message && node.message.payload ? parseFloat(str) : NaN
value = !isNaN(value) ? abbreviate(value) : str

Expand Down
9 changes: 1 addition & 8 deletions app/src/components/Sidebar/TopicPanel/TopicPanel.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -23,21 +23,14 @@ const TopicPanel = (props: { node?: q.TreeNode<any>; actions: typeof sidebarActi
props.actions.clearTopic(topic, recursive)
}, [])

const setTopicType = useCallback((node?: q.TreeNode<any>, type: q.TopicDataType = 'string') => {
if (!node) {
return
}
node.type = type
}, [])

return useMemo(
() => (
<Panel disabled={!Boolean(node)}>
<span>
Topic {copyTopic}
<TopicDeleteButton node={node} deleteTopicAction={deleteTopic} />
<RecursiveTopicDeleteButton node={node} deleteTopicAction={deleteTopic} />
<TopicTypeButton node={node} setTopicType={setTopicType} />
<TopicTypeButton node={node} />
</span>
<Topic node={node} />
</Panel>
Expand Down
87 changes: 58 additions & 29 deletions app/src/components/Sidebar/TopicPanel/TopicTypeButton.tsx
Original file line number Diff line number Diff line change
@@ -1,46 +1,59 @@
import React, { useCallback } from 'react'
import React, { useCallback, useMemo } from 'react'
import * as q from '../../../../../backend/src/Model'
import CustomIconButton from '../../helper/CustomIconButton'
import Code from '@material-ui/icons/Code'
import ClickAwayListener from '@material-ui/core/ClickAwayListener'
import Grow from '@material-ui/core/Grow'
import Button from '@material-ui/core/Button'
import Paper from '@material-ui/core/Paper'
import Popper from '@material-ui/core/Popper'
import MenuItem from '@material-ui/core/MenuItem'
import MenuList from '@material-ui/core/MenuList'
import WarningRounded from '@material-ui/icons/WarningRounded'
import { IDecoder, decoders } from '../../../../../backend/src/Model/sparkplugb'
import { Tooltip } from '@material-ui/core'

// const options: q.TopicDataType[] = ['json', 'string', 'hex', 'integer', 'unsigned int', 'floating point']
const options: q.TopicDataType[] = ['json', 'string', 'hex', 'uint8', 'uint16', 'uint32', 'uint64', 'int8', 'int16', 'int32', 'int64', 'float', 'double']
const options: q.TopicDataType[] = [
'json',
'string',
'hex',
'uint8',
'uint16',
'uint32',
'uint64',
'int8',
'int16',
'int32',
'int64',
'float',
'double',
]

export const TopicTypeButton = (props: {
node?: q.TreeNode<any>
setTopicType: (node: q.TreeNode<any>, type: q.TopicDataType) => void
}) => {
export const TopicTypeButton = (props: { node?: q.TreeNode<any> }) => {
const { node } = props
if (!node || !node.message || !node.message.payload) {
return null
}

const [anchorEl, setAnchorEl] = React.useState<null | HTMLElement>(null);
const options = decoders.flatMap(decoder => decoder.formats.map(format => [decoder, format] as const))

const [anchorEl, setAnchorEl] = React.useState<null | HTMLElement>(null)
const [open, setOpen] = React.useState(false)

const handleMenuItemClick = useCallback(
(mouseEvent: React.MouseEvent, element: q.TreeNode<any>, type: q.TopicDataType) => {
if (!element || !type) {
return
}
props.setTopicType(element, type as q.TopicDataType)
setOpen(false)
},
[props.setTopicType]
)
const selectOption = useCallback((decoder: IDecoder, format: string) => {
if (!node) {
return
}
node.decoder = decoder
node.decoderFormat = format
setOpen(false)
}, [])

const handleToggle = (event: React.MouseEvent<HTMLElement>) => {
if (open === true) {
return
}
setAnchorEl(event.currentTarget)
setOpen((prevOpen) => !prevOpen)
setOpen(prevOpen => !prevOpen)
}

const handleClose = (event: React.MouseEvent<Document, MouseEvent>) => {
Expand All @@ -51,8 +64,8 @@ export const TopicTypeButton = (props: {
}

return (
<CustomIconButton tooltip="" onClick={handleToggle}>
<Code />
<Button onClick={handleToggle}>
{props.node?.decoderFormat ?? props.node?.type}
<Popper open={open} anchorEl={anchorEl} role={undefined} transition>
{({ TransitionProps, placement }) => (
<Grow
Expand All @@ -64,13 +77,13 @@ export const TopicTypeButton = (props: {
<Paper>
<ClickAwayListener onClickAway={handleClose}>
<MenuList id="topicTypeMode">
{options.map((option, index) => (
{options.map(([decoder, format], index) => (
<MenuItem
key={option}
selected={node && option === node.type}
onClick={(event) => handleMenuItemClick(event, node, option)}
key={format}
selected={node && format === node.type}
onClick={() => selectOption(decoder, format)}
>
{option}
<DecoderStatus decoder={decoder} format={format} node={node} />
</MenuItem>
))}
</MenuList>
Expand All @@ -79,6 +92,22 @@ export const TopicTypeButton = (props: {
</Grow>
)}
</Popper>
</CustomIconButton>
</Button>
)
}

function DecoderStatus({ node, decoder, format }: { node: q.TreeNode<any>; decoder: IDecoder; format: string }) {
const decoded = useMemo(() => {
return node.message?.payload && decoder.decode(node.message?.payload, format)
}, [node.message, decoder, format])

return decoded?.error ? (
<Tooltip title={decoded.error}>
<div>
{format} <WarningRounded />
</div>
</Tooltip>
) : (
<>{format}</>
)
}
}
10 changes: 6 additions & 4 deletions app/src/components/Sidebar/ValueRenderer/MessageHistory.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -82,9 +82,10 @@ class MessageHistory extends React.PureComponent<Props, State> {
const history = node.messageHistory.toArray()
let previousMessage: q.Message | undefined = node.message
const historyElements = [...history].reverse().map((message, idx) => {
const [value, ignore] = Base64Message.format(message.payload, node.type)
const value = node.message ? node.decodeMessage(node.message)?.format()[0] ?? null : null

const element = {
value,
value: value ?? '',
key: `${message.messageNumber}-${message.received}`,
title: (
<span>
Expand All @@ -102,7 +103,7 @@ class MessageHistory extends React.PureComponent<Props, State> {
<MessageId message={message} />
</span>
<div style={{ float: 'right' }}>
<Copy value={value ? value : ''} />
<Copy value={value ?? ''} />
</div>
</span>
),
Expand All @@ -112,7 +113,8 @@ class MessageHistory extends React.PureComponent<Props, State> {
return element
})

const [value, ignore] = node.message && node.message.payload ? Base64Message.format(node.message.payload, node.type) : [null, undefined]
const value = node.message ? node.decodeMessage(node.message)?.format()[0] ?? null : null

const isMessagePlottable = isPlottable(value)
return (
<div>
Expand Down
7 changes: 3 additions & 4 deletions app/src/components/Sidebar/ValueRenderer/ValuePanel.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -85,10 +85,9 @@ function ValuePanel(props: Props) {
[compareMessage]
)

const [value, ignore] = node && node.message && node.message.payload ? Base64Message.format(node.message.payload, node.type) : [null, undefined]
const copyValue = value ? (
<Copy value={value} />
) : null
const [value] =
node && node.message && node.message.payload ? node.message.payload?.format(node.type) : [null, undefined]
const copyValue = value ? <Copy value={value} /> : null

return (
<Panel>
Expand Down
10 changes: 4 additions & 6 deletions app/src/components/Sidebar/ValueRenderer/ValueRenderer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ import * as q from '../../../../../backend/src/Model'
import * as React from 'react'
import CodeDiff from '../CodeDiff'
import { AppState } from '../../../reducers'
import { Base64Message } from '../../../../../backend/src/Model/Base64Message'
import { connect } from 'react-redux'
import { ValueRendererDisplayMode } from '../../../reducers/Settings'
import { Fade } from '@material-ui/core'
Expand Down Expand Up @@ -47,9 +46,8 @@ class ValueRenderer extends React.Component<Props, State> {
const previousMessage = previousMessages[previousMessages.length - 2]
const compareMessage = compare || previousMessage || message

const compareValue = compareMessage.payload || message.payload
const [currentStr, currentType] = Base64Message.format(message.payload, treeNode.type)
const [compareStr, compareType] = Base64Message.format(compareValue, treeNode.type)
const [currentStr, currentType] = treeNode.decodeMessage(message)?.format(treeNode.type) ?? []
const [compareStr, compareType] = treeNode.decodeMessage(compareMessage)?.format(treeNode.type) ?? []

const language = currentType === compareType && compareType === 'json' ? 'json' : undefined

Expand All @@ -61,9 +59,9 @@ class ValueRenderer extends React.Component<Props, State> {
return
}

const [currentStr, currentType] = Base64Message.format(message.payload, treeNode.type)
const [currentStr, currentType] = treeNode.decodeMessage(message)?.format(treeNode.type) ?? []
const [compareStr, compareType] =
compare && compare.payload ? Base64Message.format(compare.payload, treeNode.type) : [undefined, undefined]
compare && compare.payload ? treeNode.decodeMessage(compare)?.format(treeNode.type) ?? [] : []

return (
<div>
Expand Down
35 changes: 21 additions & 14 deletions app/src/components/TopicPlot.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -26,23 +26,28 @@ function filterUsingTimeRange(startTime: number | undefined, data: Array<q.Messa
return data
}

function nodeToHistory(startTime: number | undefined, history: q.MessageHistory, type: q.TopicDataType) {
function nodeToHistory(startTime: number | undefined, history: q.MessageHistory, node: q.TreeNode<any>) {
return filterUsingTimeRange(startTime, history.toArray())
.map((message: q.Message) => {
const [value, ignore] = message.payload ? Base64Message.format(message.payload, type) : [NaN, undefined]
// const value = message.payload ? toPlottableValue(Base64Message.toUnicodeString(message.payload)) : NaN
return { x: message.received.getTime(), y: toPlottableValue(value) }
const decoded = node.decodeMessage(message)?.toUnicodeString()
return { x: message.received.getTime(), y: toPlottableValue(decoded) }
})
.filter(data => !isNaN(data.y as any)) as any
}

function nodeDotPathToHistory(startTime: number | undefined, history: q.MessageHistory, dotPath: string, type: q.TopicDataType) {
function nodeDotPathToHistory(
startTime: number | undefined,
history: q.MessageHistory,
dotPath: string,
node: q.TreeNode<any>
) {
return filterUsingTimeRange(startTime, history.toArray())
.map((message: q.Message) => {
let json: any = {}
try {
json = message.payload ? JSON.parse(Base64Message.toUnicodeString(message.payload)) : {}
} catch (ignore) { }
const decoded = node.decodeMessage(message)
json = decoded ? JSON.parse(decoded.toUnicodeString()) : {}
} catch (ignore) {}

const value = dotProp.get(json, dotPath)

Expand All @@ -53,13 +58,15 @@ function nodeDotPathToHistory(startTime: number | undefined, history: q.MessageH

function TopicPlot(props: Props) {
const startOffset = props.timeInterval ? parseDuration(props.timeInterval) : undefined
const data = React.useMemo(
() =>
props.dotPath
? nodeDotPathToHistory(startOffset, props.history, props.dotPath, props.node ? props.node.type : 'string')
: nodeToHistory(startOffset, props.history, props.node ? props.node.type : 'string'),
[props.history.last(), startOffset, props.dotPath]
)
const data = React.useMemo(() => {
if (!props.node) {
return []
}

return props.dotPath
? nodeDotPathToHistory(startOffset, props.history, props.dotPath, props.node)
: nodeToHistory(startOffset, props.history, props.node)
}, [props.history.last(), startOffset, props.dotPath])

return (
<PlotHistory
Expand Down
17 changes: 9 additions & 8 deletions app/src/components/Tree/TreeNode/TreeNodeTitle.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import * as q from '../../../../../backend/src/Model'
import React, { memo } from 'react'
import { Base64Message } from '../../../../../backend/src/Model/Base64Message'
import { Theme, withStyles } from '@material-ui/core'
import { TopicViewModel } from '../../../model/TopicViewModel'

Expand Down Expand Up @@ -30,20 +29,21 @@ class TreeNodeTitle extends React.PureComponent<TreeNodeProps, {}> {
if (!this.props.treeNode.message || !this.props.treeNode.message.payload) {
return ''
}
const [value = ''] =
this.props.treeNode.decodeMessage(this.props.treeNode.message)?.format(this.props.treeNode.type) ?? []

const [value, ignore] = Base64Message.format(this.props.treeNode.message.payload, this.props.treeNode.type)
return value.length > limit ? `${value.slice(0, limit)}…` : value
}

private renderValue() {
return this.props.treeNode.message &&
this.props.treeNode.message.payload &&
this.props.treeNode.message.length > 0 ? (
<span key="value" className={this.props.classes.value}>
{' '}
<span key="value" className={this.props.classes.value}>
{' '}
= {this.truncatedMessage()}
</span>
) : null
</span>
) : null
}

private renderExpander() {
Expand All @@ -66,8 +66,9 @@ class TreeNodeTitle extends React.PureComponent<TreeNodeProps, {}> {
const messages = this.props.treeNode.leafMessageCount()
const topicCount = this.props.treeNode.childTopicCount()
return (
<span key="metadata" className={this.props.classes.collapsedSubnodes}>{` (${topicCount} ${topicCount === 1 ? 'topic' : 'topics'
}, ${messages} ${messages === 1 ? 'message' : 'messages'})`}</span>
<span key="metadata" className={this.props.classes.collapsedSubnodes}>{` (${topicCount} ${
topicCount === 1 ? 'topic' : 'topics'
}, ${messages} ${messages === 1 ? 'message' : 'messages'})`}</span>
)
}

Expand Down
8 changes: 4 additions & 4 deletions app/src/components/UpdateNotifier.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import * as compareVersions from 'compare-versions'
import * as electron from 'electron'
import * as os from 'os'
import * as React from 'react'
import compareVersions from 'compare-versions'
import electron from 'electron'
import os from 'os'
import React from 'react'
import axios from 'axios'
import Close from '@material-ui/icons/Close'
import CloudDownload from '@material-ui/icons/CloudDownload'
Expand Down
Loading

0 comments on commit 980072f

Please sign in to comment.