Skip to content

Commit

Permalink
refactor(#125): Added attribute data to context for proactive fetching
Browse files Browse the repository at this point in the history
  • Loading branch information
joshiraez committed Apr 11, 2023
1 parent fcb70d3 commit c31c188
Show file tree
Hide file tree
Showing 6 changed files with 104 additions and 94 deletions.
49 changes: 49 additions & 0 deletions packages/hawtio/src/plugins/SelectionNodeData.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
import { AttributeValues } from './connect/jolokia-service'
import { MBeanNode } from './shared'
import { attributeService } from './shared/attributes/attribute-service'

export type MBeanAttributeData = {
nodeData: MBeanAttributes | void
children: {
[id: string]: MBeanAttributes | void
}
}

export type MBeanChartData = {
nodeData: MBeanChartDataEntries | void
children: {
[id: string]: MBeanChartDataEntries | void
}
}

export type MBeanAttributes = {
node: MBeanNode
data: AttributeValues
}

export type MBeanChartDataEntries = {
node: MBeanNode
entries: MBeanChartDataEntriesTime[]
}

export type MBeanChartDataEntriesTime = {
time: number
data: AttributeValues[]
}

async function getAttributesForSpecificNode(node: MBeanNode): Promise<MBeanAttributes | null> {
if (!node || !node?.objectName) return Promise.resolve(null)

return Promise.resolve({ node, data: await attributeService.read(node.objectName) })
}

export async function getAttributesForNode(node: MBeanNode | null): Promise<MBeanAttributeData> {
if (!node) return Promise.resolve({ nodeData: undefined, children: {} })

return {
nodeData: (await getAttributesForSpecificNode(node)) ?? undefined,
children: Object.fromEntries(
await Promise.all(node.getChildren().map(async child => [child.id, await getAttributesForSpecificNode(child)])),
),
}
}
24 changes: 19 additions & 5 deletions packages/hawtio/src/plugins/jmx/JmxContent.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
import {
Card,
CardBody,
EmptyState,
EmptyStateIcon,
EmptyStateVariant,
Expand All @@ -14,18 +16,27 @@ import {
} from '@patternfly/react-core'
import './JmxContent.css'
import { CubesIcon } from '@patternfly/react-icons'
import React, { useContext } from 'react'
import React, { FunctionComponent, useContext } from 'react'
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, AttributeTable } from '@hawtiosrc/plugins/shared/attributes'
import { JmxContentMBeans, MBeanNode } from '@hawtiosrc/plugins/shared'
import { PluginNodeSelectionContext } from '../selectionNodeContext'
import { isObject } from '@hawtiosrc/util/objects'

export const JmxContent: React.FunctionComponent = () => {
const { selectedNode } = useContext(MBeanTreeContext)
const { selectedNode, selectedNodeAttributes, isReadingAttributes } = useContext(PluginNodeSelectionContext)
const { pathname, search } = useLocation()

const isReadingAttributesCard: FunctionComponent = () => (
<Card>
<CardBody>
<Text component='p'>Reading attributes...</Text>
</CardBody>
</Card>
)

if (!selectedNode) {
return (
<PageSection variant={PageSectionVariants.light} isFilled>
Expand All @@ -39,12 +50,15 @@ export const JmxContent: React.FunctionComponent = () => {
)
}

const mBeanApplicable = (node: MBeanNode) => Boolean(node.objectName)
const mBeanCollectionApplicable = (node: MBeanNode) => Boolean(node.children?.every(child => child.objectName))
const loadingAttributes = (node: MBeanNode) => isReadingAttributes
const mBeanApplicable = (node: MBeanNode) => isObject(selectedNodeAttributes.nodeData)
const mBeanCollectionApplicable = (node: MBeanNode) =>
Object.values(selectedNodeAttributes.children).every(nodeAttributes => isObject(nodeAttributes))
const ALWAYS = (node: MBeanNode) => true

const tableSelector: (node: MBeanNode) => React.FunctionComponent = (node: MBeanNode) => {
const tablePriorityList: { condition: (node: MBeanNode) => boolean; element: React.FunctionComponent }[] = [
{ condition: loadingAttributes, element: isReadingAttributesCard },
{ condition: mBeanApplicable, element: Attributes },
{ condition: mBeanCollectionApplicable, element: AttributeTable },
]
Expand Down
23 changes: 21 additions & 2 deletions packages/hawtio/src/plugins/selectionNodeContext.ts
Original file line number Diff line number Diff line change
@@ -1,22 +1,41 @@
import { createContext, useState } from 'react'
import { createContext, useEffect, useState } from 'react'
import { MBeanNode } from '@hawtiosrc/plugins/shared'
import { getAttributesForNode, MBeanAttributeData } from './SelectionNodeData'

/**
* Custom React hook for using JMX MBean tree.
*/
export function usePluginNodeSelected() {
const [selectedNode, setSelectedNode] = useState<MBeanNode | null>(null)
return { selectedNode, setSelectedNode }
const [selectedNodeAttributes, setSelectedNodeAttributes] = useState<MBeanAttributeData>({
nodeData: undefined,
children: {},
})
const [isReadingAttributes, setIsReadingAttributes] = useState<boolean>(false)

useEffect(() => {
;(async () => {
setIsReadingAttributes(true)
setSelectedNodeAttributes(await getAttributesForNode(selectedNode))
setIsReadingAttributes(false)
})()
}, [selectedNode])

return { selectedNode, setSelectedNode, selectedNodeAttributes, isReadingAttributes }
}

type PluginNodeSelectionContext = {
selectedNode: MBeanNode | null
setSelectedNode: (selectedNode: MBeanNode | null) => void
selectedNodeAttributes: MBeanAttributeData
isReadingAttributes: boolean
}

export const PluginNodeSelectionContext = createContext<PluginNodeSelectionContext>({
selectedNode: null,
setSelectedNode: () => {
/* no-op */
},
selectedNodeAttributes: { nodeData: undefined, children: {} },
isReadingAttributes: false,
})
45 changes: 8 additions & 37 deletions packages/hawtio/src/plugins/shared/attributes/AttributeTable.tsx
Original file line number Diff line number Diff line change
@@ -1,54 +1,25 @@
import { useContext, useEffect, useState } from 'react'
import { Card, CardBody, Text } from '@patternfly/react-core'
import { useContext } from 'react'
import { Card } 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'
import { MBeanAttributes } from '@hawtiosrc/plugins/SelectionNodeData'
import { isObject } from '@hawtiosrc/util/objects'

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

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])
const attributesList: AttributeValues[] = Object.values(selectedNodeAttributes.children)
.filter((mbeanAttribute): mbeanAttribute is MBeanAttributes => isObject(mbeanAttribute))
.map(mbeanAttribute => mbeanAttribute.data)

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
Expand Down
51 changes: 3 additions & 48 deletions packages/hawtio/src/plugins/shared/attributes/Attributes.tsx
Original file line number Diff line number Diff line change
@@ -1,67 +1,22 @@
import { AttributeValues } from '@hawtiosrc/plugins/connect/jolokia-service'
import { isObject } from '@hawtiosrc/util/objects'
import { Card, CardBody, Text } from '@patternfly/react-core'
import { InfoCircleIcon } from '@patternfly/react-icons'
import { OnRowClick, Table, TableBody, TableHeader, TableProps } from '@patternfly/react-table'
import { IResponse } from 'jolokia.js'
import { useEffect, useState, useContext } from 'react'
import { useState, useContext } from 'react'
import { PluginNodeSelectionContext } from '@hawtiosrc/plugins/selectionNodeContext'
import { log } from '../globals'
import { attributeService } from './attribute-service'
import { AttributeModal } from './AttributeModal'

export const Attributes: React.FunctionComponent = () => {
const { selectedNode } = useContext(PluginNodeSelectionContext)
const [attributes, setAttributes] = useState<AttributeValues>({})
const [isReading, setIsReading] = useState(true)
const { selectedNode, selectedNodeAttributes } = useContext(PluginNodeSelectionContext)
const [isModalOpen, setIsModalOpen] = useState(false)
const [selected, setSelected] = useState({ name: '', value: '' })

useEffect(() => {
if (!selectedNode || !selectedNode.mbean || !selectedNode.objectName) {
return
}

setIsReading(true)
const objectName = selectedNode.objectName
const readAttributes = async () => {
const attrs = await attributeService.read(objectName)
setAttributes(attrs)
setIsReading(false)
}
readAttributes()
}, [selectedNode])

useEffect(() => {
if (!selectedNode || !selectedNode.mbean || !selectedNode.objectName) {
return
}

const mbean = selectedNode.objectName
attributeService.register({ type: 'read', mbean }, (response: IResponse) => {
log.debug('Scheduler - Attributes:', response.value)
setAttributes(response.value as AttributeValues)
})

return () => attributeService.unregisterAll()
}, [selectedNode])

if (!selectedNode || !selectedNode.mbean || !selectedNode.objectName) {
return null
}

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

const columns: TableProps['cells'] = ['Attribute', 'Value']
const rows: TableProps['rows'] = Object.entries(attributes).map(([name, value]) => [
const rows: TableProps['rows'] = Object.entries(selectedNodeAttributes?.nodeData?.data ?? {}).map(([name, value]) => [
name,
isObject(value) ? JSON.stringify(value) : String(value),
])
Expand Down
6 changes: 4 additions & 2 deletions packages/hawtio/src/ui/page/HawtioPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ export const HawtioPage: React.FunctionComponent = () => {
const { plugins, pluginsLoaded } = usePlugins()
const navigate = useNavigate()
const { search } = useLocation()
const { selectedNode, setSelectedNode } = usePluginNodeSelected()
const { selectedNode, setSelectedNode, selectedNodeAttributes, isReadingAttributes } = usePluginNodeSelected()

if (!userLoaded || !pluginsLoaded) {
return <HawtioLoading />
Expand Down Expand Up @@ -72,7 +72,9 @@ export const HawtioPage: React.FunctionComponent = () => {
defaultManagedSidebarIsOpen={showVerticalNavByDefault}
>
{/* Provider for handling selected node shared between the plugins */}
<PluginNodeSelectionContext.Provider value={{ selectedNode, setSelectedNode }}>
<PluginNodeSelectionContext.Provider
value={{ selectedNode, setSelectedNode, selectedNodeAttributes, isReadingAttributes }}
>
<Routes>
{/* plugins */}
{plugins.map(plugin => (
Expand Down

0 comments on commit c31c188

Please sign in to comment.