Skip to content

Commit

Permalink
feat(jmx): Tabular attribute table (#215)
Browse files Browse the repository at this point in the history
* wip(#125): Progress on multiattribute table. Have to implement a better way to know which are JMX nodes

* feat(#125): Tabular attribute table

* feat(#125): Added NodeName table and better filtering

* feat(#125): Refactored filtering logic

* feat(#125): Added test for helper function. Created helper function class. Simplified JMXContent component taking into account new logic

* feat(#125): Removed NodeNameTable and using JMXContentMBeans.

* feat(#125): Moved helper function to proper class. Redefined paths to components

* feat(#125): Changed humanize labels to use function implementation

* feat(#125): Removed array creation: map handles it

* feat(#125): Added extra test cases. Some refactoring

* feat(#125): Extra refinement

* feat(#125): Added extra tests

* feat(#125): Used strings isBlank function

* feat(#125): Fixed logic after changing to using isBlank function
  • Loading branch information
joshiraez authored Apr 11, 2023
1 parent 289d6c3 commit fcb70d3
Show file tree
Hide file tree
Showing 6 changed files with 154 additions and 14 deletions.
34 changes: 21 additions & 13 deletions packages/hawtio/src/plugins/jmx/JmxContent.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ import { NavLink, Navigate, Route, Routes, useLocation } from 'react-router-dom'
import { MBeanTreeContext } from './context'
import { Chart } from '@hawtiosrc/plugins/shared/chart'
import { Operations } from '@hawtiosrc/plugins/shared/operations'
import { Attributes } from '@hawtiosrc/plugins/shared/attributes'
import { Attributes, AttributeTable } from '@hawtiosrc/plugins/shared/attributes'
import { JmxContentMBeans, MBeanNode } from '@hawtiosrc/plugins/shared'

export const JmxContent: React.FunctionComponent = () => {
Expand All @@ -39,10 +39,21 @@ export const JmxContent: React.FunctionComponent = () => {
)
}

const mBeanApplicable = (node: MBeanNode) => node.objectName
const mBeanApplicable = (node: MBeanNode) => Boolean(node.objectName)
const mBeanCollectionApplicable = (node: MBeanNode) => Boolean(node.children?.every(child => child.objectName))
const ALWAYS = (node: MBeanNode) => true

const tableSelector: (node: MBeanNode) => React.FunctionComponent = (node: MBeanNode) => {
const tablePriorityList: { condition: (node: MBeanNode) => boolean; element: React.FunctionComponent }[] = [
{ condition: mBeanApplicable, element: Attributes },
{ condition: mBeanCollectionApplicable, element: AttributeTable },
]

return tablePriorityList.find(entry => entry.condition(node))?.element ?? JmxContentMBeans
}

const allNavItems = [
{ id: 'attributes', title: 'Attributes', component: Attributes, isApplicable: mBeanApplicable },
{ id: 'attributes', title: 'Attributes', component: tableSelector(selectedNode), isApplicable: ALWAYS },
{ id: 'operations', title: 'Operations', component: Operations, isApplicable: mBeanApplicable },
{ id: 'chart', title: 'Chart', component: Chart, isApplicable: mBeanApplicable },
]
Expand Down Expand Up @@ -73,18 +84,15 @@ export const JmxContent: React.FunctionComponent = () => {
<Title headingLevel='h1'>{selectedNode.name}</Title>
<Text component='small'>{selectedNode.objectName}</Text>
</PageSection>
{navItems.length > 0 && <PageNavigation>{mbeanNav}</PageNavigation>}
<PageNavigation>{mbeanNav}</PageNavigation>
</PageGroup>
<PageSection className={'jmx-main'}>
{navItems.length > 0 && (
<React.Fragment>
<Routes>
{mbeanRoutes}
<Route key='root' path='/' element={<Navigate to={navItems[0].id} />} />
</Routes>
</React.Fragment>
)}
{navItems.length === 0 && !selectedNode.objectName && <JmxContentMBeans />}
<React.Fragment>
<Routes>
{mbeanRoutes}
<Route key='root' path='/' element={<Navigate to={navItems[0].id} />} />
</Routes>
</React.Fragment>
</PageSection>
</React.Fragment>
)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
.attribute-table > tr > th {
max-width: none;
}
90 changes: 90 additions & 0 deletions packages/hawtio/src/plugins/shared/attributes/AttributeTable.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
import { useContext, useEffect, useState } from 'react'
import { Card, CardBody, Text } from '@patternfly/react-core'
import { Table, TableBody, TableHeader, TableProps } from '@patternfly/react-table'
import { PluginNodeSelectionContext } from '@hawtiosrc/plugins/selectionNodeContext'
import { AttributeValues } from '@hawtiosrc/plugins/connect/jolokia-service'
import { attributeService } from './attribute-service'
import './AttributeTable.css'
import { JmxContentMBeans } from '@hawtiosrc/plugins/shared/JmxContentMBeans'
import { humanizeLabels } from '@hawtiosrc/util/strings'

export const AttributeTable: React.FunctionComponent = () => {
const { selectedNode } = useContext(PluginNodeSelectionContext)
const [attributesList, setAttributesList] = useState<AttributeValues[]>([{}])
const [isReading, setIsReading] = useState<boolean>(false)

useEffect(() => {
if (!selectedNode) {
return
}

const readAttributes = async () => {
const childrenMbeansAttributes: AttributeValues[] = []
setIsReading(true)
if (selectedNode.children)
for (const mbean of selectedNode.children) {
const objectName = mbean.objectName
if (objectName) {
const attrs = await attributeService.read(objectName)
childrenMbeansAttributes.push(attrs)
}
}
setAttributesList([...childrenMbeansAttributes])
setIsReading(false)
}
readAttributes()
}, [selectedNode])

if (!selectedNode) {
return null
}

if (isReading) {
return (
<Card>
<CardBody>
<Text component='p'>Reading attributes...</Text>
</CardBody>
</Card>
)
}

const checkIfAllMBeansHaveSameAttributes = (attributesList: AttributeValues[]) => {
if (attributesList.length <= 1) {
return true
}

const firstMBeanAttributesElements = attributesList[0].length

if (!attributesList.every(mbeanAttributes => mbeanAttributes.length === firstMBeanAttributesElements)) {
return false
}

const labelSet: Set<string> = new Set()
Object.keys(attributesList[0]).forEach(label => labelSet.add(label))

return attributesList.every(attributes => Object.keys(attributes).every(label => labelSet.has(label)))
}

if (
attributesList.some(attribute => Object.entries(attribute).length === 0) ||
!checkIfAllMBeansHaveSameAttributes(attributesList)
) {
return <JmxContentMBeans />
}

const labels = Object.keys(attributesList[0])
const columns: TableProps['cells'] = labels.map(label => humanizeLabels(label))
const rows: TableProps['rows'] = attributesList.map(attribute =>
labels.map(label => JSON.stringify(attribute[label])),
)

return (
<Card isFullHeight>
<Table aria-label='MBeans' variant='compact' cells={columns} rows={rows}>
<TableHeader className={'attribute-table'} />
<TableBody />
</Table>
</Card>
)
}
1 change: 1 addition & 0 deletions packages/hawtio/src/plugins/shared/attributes/index.ts
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
export { Attributes } from './Attributes'
export { AttributeModal } from './AttributeModal'
export { AttributeTable } from './AttributeTable'
19 changes: 18 additions & 1 deletion packages/hawtio/src/util/strings.test.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { isString, parseBoolean, toString, trimQuotes } from './strings'
import { humanizeLabels, isString, parseBoolean, toString, trimQuotes } from './strings'

describe('strings', () => {
test('isString', () => {
Expand Down Expand Up @@ -41,4 +41,21 @@ describe('strings', () => {
expect(parseBoolean('FaLsE')).toBeFalsy()
expect(parseBoolean('0')).toBeFalsy()
})

test('humanizeLabels()', () => {
expect(humanizeLabels('ObjectName')).toEqual('Object Name')
expect(humanizeLabels('XHTTPRequest')).toEqual('XHTTP Request')
expect(humanizeLabels('MBeanName')).toEqual('MBean Name')
expect(humanizeLabels('MBeanHTML')).toEqual('MBean HTML')

expect(humanizeLabels('object-name')).toEqual('Object Name')
expect(humanizeLabels('double--dashes')).toEqual('Double Dashes')
expect(humanizeLabels('mbean-name')).toEqual('MBean Name')

expect(humanizeLabels('-Object-Name-')).toEqual('Object Name')

expect(humanizeLabels('')).toEqual('')
expect(humanizeLabels(' ')).toEqual('')
expect(humanizeLabels('Object Name')).toEqual('Object Name')
})
})
21 changes: 21 additions & 0 deletions packages/hawtio/src/util/strings.ts
Original file line number Diff line number Diff line change
Expand Up @@ -99,3 +99,24 @@ export function parseBoolean(value: string): boolean {

return /^true$/i.test(value) || parseInt(value) === 1
}

/**
* Will format a property to a standard human readable string with its spaces.
* It will respect MBean and leave it together
* @param str The property to transform
* @returns The property with its proper spaces
*/
export function humanizeLabels(str: string): string {
return str
.split('-')
.filter(str => !isBlank(str))
.map(str => str.replace(/^./, str => str.toUpperCase()))
.join(' ')
.replace(/([a-z])([A-Z])/g, '$1 $2')
.replace(/\b([A-Z]+)([A-Z])([a-z])/, '$1 $2$3')
.replace('M Bean', 'MBean')
.replace('Mbean', 'MBean')
.replace(/^./, str => str.toUpperCase())
.replace(/ +/, ' ')
.trim()
}

0 comments on commit fcb70d3

Please sign in to comment.