Skip to content

Commit

Permalink
fix: Updates Discover component to support previous / next buttons (#496
Browse files Browse the repository at this point in the history
)

* Discover.tsx
 * Move loading-panel into its own component DiscoverLoadingPanel
 * Puts the pods into tabs based on the projects/namespaces
 * New DiscoverProjectContent for each project

* DiscoverProjectContent
 * Lists the limited selection of pods from management-service
 * When next button is clicked, the signal is sent to the management-service
   to fetch the 'next' selection of pods for the given namespace

* DiscoverToolbar
 * Sorts the tab order since they are the namespaces
 * Sorts the pods in each namespace tab
 * Filters applied in line with patternfly guidelines
  * Filters of same attribute applied as OR
  * Filters of different attributes applied as AND
 * Filters are first concatenated together before being applied

* HeaderMenuDropDown
 * Reimplemented with flyout sub-menus for the pods separated by namespace

* context
 * Replacement of groupds / pods with projects
 * Addition of refreshing flag to indicate when pods are being refreshed
   awaiting a management update from prev/next buttons

* discover-service split with new discover-project class that encapsulates
  the discover-projects and pods received from management
  • Loading branch information
phantomjinx committed Sep 10, 2024
1 parent a9710b5 commit 7cb599c
Show file tree
Hide file tree
Showing 13 changed files with 585 additions and 289 deletions.
8 changes: 8 additions & 0 deletions packages/online-shell/src/discover/Discover.css
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,14 @@
font-style: italic;
}

.discover-loading-compact {
margin-top: 0 !important;
}

.discover-loading-compact div {
padding: 0;
}

.discover-group-label {
text-align: left;
color: rgb(117, 117, 117);
Expand Down
77 changes: 46 additions & 31 deletions packages/online-shell/src/discover/Discover.tsx
Original file line number Diff line number Diff line change
@@ -1,44 +1,55 @@
import React from 'react'
import React, { useEffect } from 'react'
import {
Alert,
Card,
CardBody,
EmptyState,
EmptyStateBody,
EmptyStateIcon,
List,
PageSection,
PageSectionVariants,
Panel,
PanelHeader,
PanelMain,
PanelMainBody,
Title,
EmptyStateHeader,
Tabs,
TabTitleText,
Tab,
} from '@patternfly/react-core'
import { CubesIcon } from '@patternfly/react-icons'
import { HawtioLoadingCard } from '@hawtio/react'
import { discoverService } from './discover-service'
import { DiscoverToolbar } from './DiscoverToolbar'
import { DiscoverContext, useDisplayItems } from './context'
import { DiscoverGroupList } from './DiscoverGroupList'
import { DiscoverPodItem } from './DiscoverPodItem'
import { DiscoverProjectContent } from './DiscoverProjectContent'
import { DiscoverLoadingPanel } from './DiscoverLoadingPanel'

export const Discover: React.FunctionComponent = () => {
const { error, isLoading, discoverGroups, setDiscoverGroups, discoverPods, setDiscoverPods, filters, setFilters } =
const { error, isLoading, refreshing, setRefreshing, discoverProjects, setDiscoverProjects, filters, setFilters } =
useDisplayItems()
const [activeTabKey, setActiveTabKey] = React.useState<string>('')

const handleTabClick = (
event: React.MouseEvent<unknown> | React.KeyboardEvent | MouseEvent,
tabIndex: string | number,
) => {
setActiveTabKey(`${tabIndex}`)
}

useEffect(() => {
if (isLoading || error || discoverProjects.length === 0) return

setActiveTabKey(activeKey => {
if (activeKey.length === 0) return discoverProjects[0].name

const projects = discoverProjects.filter(p => p.name === activeKey)
if (projects.length === 0) return discoverProjects[0].name // active key no longer in projects

return activeKey
})
}, [isLoading, error, discoverProjects, filters])

if (isLoading) {
return (
<PageSection variant={PageSectionVariants.light}>
<Panel className='discover-loading'>
<PanelHeader>Waiting for Hawtio Containers ...</PanelHeader>
<PanelMain>
<PanelMainBody>
<HawtioLoadingCard />
</PanelMainBody>
</PanelMain>
</Panel>
<DiscoverLoadingPanel />
</PageSection>
)
}
Expand All @@ -63,17 +74,17 @@ export const Discover: React.FunctionComponent = () => {

<DiscoverContext.Provider
value={{
discoverGroups,
setDiscoverGroups,
discoverPods,
setDiscoverPods,
refreshing,
setRefreshing,
discoverProjects,
setDiscoverProjects,
filters,
setFilters,
}}
>
<DiscoverToolbar />

{discoverGroups.length + discoverPods.length === 0 && (
{discoverProjects.length === 0 && (
<EmptyState>
<EmptyStateHeader
titleText='No Hawtio Containers'
Expand All @@ -86,14 +97,18 @@ export const Discover: React.FunctionComponent = () => {
</EmptyState>
)}

{discoverGroups.length > 0 && <DiscoverGroupList />}

{discoverPods.length > 0 && (
<List isBordered={true} iconSize='large'>
{discoverPods.map(pod => {
return <DiscoverPodItem pod={pod} key={pod.uid} />
})}
</List>
{discoverProjects.length > 0 && (
<Tabs activeKey={activeTabKey} onSelect={handleTabClick} isBox>
{discoverProjects.map(discoverProject => (
<Tab
eventKey={discoverProject.name}
title={<TabTitleText>{discoverProject.name}</TabTitleText>}
key={`discover-project-${discoverProject.name}`}
>
<DiscoverProjectContent project={discoverProject} />
</Tab>
))}
</Tabs>
)}
</DiscoverContext.Provider>
</PageSection>
Expand Down
38 changes: 25 additions & 13 deletions packages/online-shell/src/discover/DiscoverGroupList.tsx
Original file line number Diff line number Diff line change
@@ -1,41 +1,53 @@
import React, { useContext } from 'react'
import React, { useState } from 'react'
import { Accordion, AccordionContent, AccordionItem, AccordionToggle, List } from '@patternfly/react-core'
import { DiscoverGroup } from './globals'
import { DiscoverGroupLabel } from './DiscoverGroupLabel'
import { DiscoverPodItem } from './DiscoverPodItem'
import './Discover.css'
import { DiscoverContext } from './context'

export const DiscoverGroupList: React.FunctionComponent = () => {
const { discoverGroups, setDiscoverGroups } = useContext(DiscoverContext)
interface DiscoverGroupListProps {
groups: DiscoverGroup[]
}

export const DiscoverGroupList: React.FunctionComponent<DiscoverGroupListProps> = (props: DiscoverGroupListProps) => {
const [hidden, setHidden] = useState<string[]>([])

const onToggle = (group: DiscoverGroup) => {
const groups = [...discoverGroups]
const newHidden = [...hidden]

groups.forEach(g => {
if (g.uid === group.uid) g.expanded = !g.expanded
})
const index = newHidden.indexOf(group.uid)
if (index === -1) {
// Since no uid in hidden then expanded by default
// then add to hidden to hide
newHidden.push(group.uid)
} else {
newHidden.splice(index, 1)
}

setHidden(newHidden)
}

setDiscoverGroups(groups)
const isHidden = (group: DiscoverGroup): boolean => {
return hidden.indexOf(group.uid) > -1
}

if (discoverGroups.length === 0) return <></>
if (props.groups.length === 0) return <></>

return (
<Accordion asDefinitionList>
{discoverGroups.map(group => {
{props.groups.map(group => {
return (
<AccordionItem key={'item-' + group.name}>
<AccordionToggle
onClick={() => {
onToggle(group)
}}
isExpanded={group.expanded}
isExpanded={!isHidden(group)}
id={'item-' + group.name}
>
<DiscoverGroupLabel group={group} />
</AccordionToggle>
<AccordionContent id={'item-' + group.name + 'expand'} isHidden={!group.expanded}>
<AccordionContent id={'item-' + group.name + 'expand'} isHidden={isHidden(group)}>
<List isBordered={true} iconSize='large'>
{group.replicas.map(replica => (
<DiscoverPodItem key={replica.uid} pod={replica} />
Expand Down
25 changes: 25 additions & 0 deletions packages/online-shell/src/discover/DiscoverLoadingPanel.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import React from 'react'
import { Panel, PanelHeader, PanelMain, PanelMainBody } from '@patternfly/react-core'
import { HawtioLoadingCard } from '@hawtio/react'

interface DiscoverLoadingPanelProps {
compact?: boolean
}

export const DiscoverLoadingPanel: React.FunctionComponent<DiscoverLoadingPanelProps> = (
props: DiscoverLoadingPanelProps,
) => {
let classNames = 'discover-loading'
if (props.compact) classNames = classNames + ' discover-loading-compact'

return (
<Panel className={classNames}>
<PanelHeader>Waiting for Hawtio Containers ...</PanelHeader>
<PanelMain>
<PanelMainBody>
<HawtioLoadingCard />
</PanelMainBody>
</PanelMain>
</Panel>
)
}
4 changes: 2 additions & 2 deletions packages/online-shell/src/discover/DiscoverPodConnect.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ export const DiscoverPodConnect: React.FunctionComponent<DiscoverPodConnectProps
props: DiscoverPodConnectProps,
) => {
const connectionNames: string[] = mgmtService.refreshConnections(props.pod.mPod)
const mgmtError = props.pod.mPod?.getManagementError()
const mgmtError = props.pod.mPod?.mgmtError

const [isOpen, setIsOpen] = React.useState(false)

Expand All @@ -39,7 +39,7 @@ export const DiscoverPodConnect: React.FunctionComponent<DiscoverPodConnectProps
const disableContainerButton = (): boolean => {
return (
mgmtService.podStatus(props.pod.mPod) !== 'Running' ||
!props.pod.mPod.getManagement().status.managed ||
!props.pod.mPod.management.status.managed ||
connectionNames.length === 0 ||
mgmtError !== null
)
Expand Down
16 changes: 8 additions & 8 deletions packages/online-shell/src/discover/DiscoverPodItem.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,32 +17,32 @@ interface DiscoverPodItemProps {

export const DiscoverPodItem: React.FunctionComponent<DiscoverPodItemProps> = (props: DiscoverPodItemProps) => {
const nodeLabel = (): ReactNode => {
if (props.pod.mPod.getSpec()?.nodeName) {
if (props.pod.mPod.spec?.nodeName) {
return (
<ConsoleLink type={ConsoleType.node} selector={props.pod.mPod.getSpec()?.nodeName}>
{props.pod.mPod.getSpec()?.nodeName}
<ConsoleLink type={ConsoleType.node} selector={props.pod.mPod.spec?.nodeName}>
{props.pod.mPod.spec?.nodeName}
</ConsoleLink>
)
}

return props.pod.mPod.getStatus()?.hostIP
return props.pod.mPod.status?.hostIP
}

const containersLabel = (): ReactNode => {
const total = props.pod.mPod.getSpec()?.containers.length || 0
const total = props.pod.mPod.spec?.containers.length || 0
return `${total} container${total !== 1 ? 's' : ''}`
}

const routesLabel = (): ReactNode => {
if (!props.pod.mPod.getManagement().status.managed) {
if (!props.pod.mPod.management.status.managed) {
return (
<Label color='grey' icon={<FontAwesomeIcon icon={faSpinner} spin />} className='pod-item-routes'>
{`querying routes ...`}
</Label>
)
}

const error = props.pod.mPod.getManagement().status.error
const error = props.pod.mPod.management.status.error
if (error) {
return (
<Label color='red' icon={<CamelRouteIcon />} className='pod-item-routes'>
Expand All @@ -51,7 +51,7 @@ export const DiscoverPodItem: React.FunctionComponent<DiscoverPodItemProps> = (p
)
}

const total = props.pod.mPod.getManagement().camel.routes_count
const total = props.pod.mPod.management.camel.routes_count
return (
<Label color='gold' icon={<CamelRouteIcon />} className='pod-item-routes'>
{`${total} route${total !== 1 ? 's' : ''}`}
Expand Down
94 changes: 94 additions & 0 deletions packages/online-shell/src/discover/DiscoverProjectContent.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
import React, { useContext } from 'react'
import {
Button,
List,
Panel,
PanelHeader,
PanelMain,
PanelMainBody,
Toolbar,
ToolbarContent,
ToolbarGroup,
ToolbarItem,
} from '@patternfly/react-core'
import { DiscoverProject } from './discover-project'
import { DiscoverGroupList } from './DiscoverGroupList'
import { DiscoverPodItem } from './DiscoverPodItem'
import { mgmtService } from '@hawtio/online-management-api'
import { DiscoverLoadingPanel } from './DiscoverLoadingPanel'
import { DiscoverContext } from './context'

interface DiscoverProjectCntProps {
project: DiscoverProject
}

export const DiscoverProjectContent: React.FunctionComponent<DiscoverProjectCntProps> = (
props: DiscoverProjectCntProps,
) => {
const { refreshing, setRefreshing } = useContext(DiscoverContext)

const hasPrevPods = (): boolean => {
return mgmtService.hasPrevious(props.project.name)
}

const prevPods = () => {
setRefreshing(true)
mgmtService.previous(props.project.name)
}

const hasNextPods = (): boolean => {
return mgmtService.hasNext(props.project.name)
}

const nextPods = () => {
setRefreshing(true)
mgmtService.next(props.project.name)
}

return (
<Panel>
<PanelHeader>
<Toolbar id='pagination-toolbar-items' className='paginated-pods-toolbar-content' isSticky>
<ToolbarContent>
<ToolbarGroup variant='button-group' align={{ default: 'alignLeft' }}>
<ToolbarItem>
<Button variant='control' onClick={() => prevPods()} isDisabled={!hasPrevPods()}>
&lt;&lt; Previous
</Button>
</ToolbarItem>
</ToolbarGroup>

{refreshing && (
<ToolbarGroup isOverflowContainer>
<ToolbarItem widths={{ default: '100%' }}>
<DiscoverLoadingPanel compact={true} />
</ToolbarItem>
</ToolbarGroup>
)}

<ToolbarGroup variant='button-group' align={{ default: 'alignRight' }}>
<ToolbarItem>
<Button variant='control' onClick={() => nextPods()} isDisabled={!hasNextPods()}>
Next &gt;&gt;
</Button>
</ToolbarItem>
</ToolbarGroup>
</ToolbarContent>
</Toolbar>
</PanelHeader>
<PanelMain>
<PanelMainBody>
{props.project.groups.length > 0 && <DiscoverGroupList groups={props.project.groups} />}

{props.project.pods.length > 0 && (
<List isBordered={true} iconSize='large'>
{props.project.pods.map(pod => {
return <DiscoverPodItem pod={pod} key={pod.uid} />
})}
</List>
)}
</PanelMainBody>
</PanelMain>
</Panel>
)
}
Loading

0 comments on commit 7cb599c

Please sign in to comment.