diff --git a/frontend/packages/console-demo-plugin/src/dashboards/inventory.tsx b/frontend/packages/console-demo-plugin/src/dashboards/inventory.tsx index cce3d9d867f..b397babb223 100644 --- a/frontend/packages/console-demo-plugin/src/dashboards/inventory.tsx +++ b/frontend/packages/console-demo-plugin/src/dashboards/inventory.tsx @@ -1,6 +1,9 @@ import * as React from 'react'; import { AddressBookIcon } from '@patternfly/react-icons'; -import { StatusGroupMapper } from '@console/internal/components/dashboard/inventory-card/inventory-item'; +import { + StatusGroupMapper, + ExpandedComponentProps, +} from '@console/internal/components/dashboard/inventory-card/inventory-item'; export const getRouteStatusGroups: StatusGroupMapper = (resources) => ({ 'demo-inventory-group': { @@ -13,3 +16,7 @@ export const getRouteStatusGroups: StatusGroupMapper = (resources) => ({ export const DemoGroupIcon: React.FC<{}> = () => ( ); + +export const ExpandedRoutes: React.FC = ({ resource }) => ( +
Additional content for {resource.length} routes
+); diff --git a/frontend/packages/console-demo-plugin/src/plugin.tsx b/frontend/packages/console-demo-plugin/src/plugin.tsx index cb1d1e1bced..abda42f0236 100644 --- a/frontend/packages/console-demo-plugin/src/plugin.tsx +++ b/frontend/packages/console-demo-plugin/src/plugin.tsx @@ -237,6 +237,10 @@ const plugin: Plugin = [ }, model: RouteModel, mapper: getRouteStatusGroups, + expandedComponent: () => + import('./dashboards/inventory' /* webpackChunkName: "demo-inventory-item" */).then( + (m) => m.ExpandedRoutes, + ), required: 'TEST_MODEL_FLAG', }, }, diff --git a/frontend/packages/console-plugin-sdk/src/typings/dashboards.ts b/frontend/packages/console-plugin-sdk/src/typings/dashboards.ts index 910e056e0b7..48ade261b9a 100644 --- a/frontend/packages/console-plugin-sdk/src/typings/dashboards.ts +++ b/frontend/packages/console-plugin-sdk/src/typings/dashboards.ts @@ -2,7 +2,10 @@ import { SubsystemHealth } from '@console/internal/components/dashboards-page/ov import { GridPosition } from '@console/internal/components/dashboard/grid'; import { FirehoseResource, Humanize, FirehoseResult } from '@console/internal/components/utils'; import { K8sKind, K8sResourceKind } from '@console/internal/module/k8s'; -import { StatusGroupMapper } from '@console/internal/components/dashboard/inventory-card/inventory-item'; +import { + StatusGroupMapper, + ExpandedComponentProps, +} from '@console/internal/components/dashboard/inventory-card/inventory-item'; import { OverviewQuery } from '@console/internal/components/dashboards-page/overview-dashboard/queries'; import { ConsumerMutator } from '@console/internal/components/dashboards-page/overview-dashboard/top-consumers-card'; import { MetricType } from '@console/internal/components/dashboard/top-consumers-card/metric-type'; @@ -118,6 +121,9 @@ namespace ExtensionProperties { /** Function which will map various statuses to groups. */ mapper: StatusGroupMapper; + + /** Loader for the component which will be used when item is expanded. */ + expandedComponent?: LazyLoader; } export interface DashboardsInventoryItemGroup extends DashboardExtension { diff --git a/frontend/packages/kubevirt-plugin/src/components/dashboards-page/overview-dashboard/inventory.tsx b/frontend/packages/kubevirt-plugin/src/components/dashboards-page/overview-dashboard/inventory.tsx index 203687fb8ba..a5d9278039b 100644 --- a/frontend/packages/kubevirt-plugin/src/components/dashboards-page/overview-dashboard/inventory.tsx +++ b/frontend/packages/kubevirt-plugin/src/components/dashboards-page/overview-dashboard/inventory.tsx @@ -23,7 +23,7 @@ import { import './inventory.scss'; const VM_STATUS_GROUP_MAPPER = { - [InventoryStatusGroup.OK]: [VM_STATUS_RUNNING], + [InventoryStatusGroup.NOT_MAPPED]: [VM_STATUS_RUNNING], 'vm-off': [VM_STATUS_OFF], [InventoryStatusGroup.PROGRESS]: [ VM_STATUS_V2V_CONVERSION_IN_PROGRESS, @@ -55,7 +55,7 @@ export const getVMStatusGroups: StatusGroupMapper = (vms, { pods, migrations }) statusIDs: [], count: 0, }, - [InventoryStatusGroup.OK]: { + [InventoryStatusGroup.UNKNOWN]: { statusIDs: [], count: 0, }, @@ -70,7 +70,7 @@ export const getVMStatusGroups: StatusGroupMapper = (vms, { pods, migrations }) const group = Object.keys(VM_STATUS_GROUP_MAPPER).find((key) => VM_STATUS_GROUP_MAPPER[key].includes(status), - ) || InventoryStatusGroup.NOT_MAPPED; + ) || InventoryStatusGroup.UNKNOWN; groups[group].count++; }); return groups; diff --git a/frontend/packages/metal3-plugin/src/components/baremetal-hosts/dashboard/InventoryCard.tsx b/frontend/packages/metal3-plugin/src/components/baremetal-hosts/dashboard/InventoryCard.tsx index d5dfb5d225a..7eb4c6c2171 100644 --- a/frontend/packages/metal3-plugin/src/components/baremetal-hosts/dashboard/InventoryCard.tsx +++ b/frontend/packages/metal3-plugin/src/components/baremetal-hosts/dashboard/InventoryCard.tsx @@ -82,8 +82,7 @@ const InventoryCard: React.FC = ({ diff --git a/frontend/packages/metal3-plugin/src/components/baremetal-hosts/dashboard/utils.ts b/frontend/packages/metal3-plugin/src/components/baremetal-hosts/dashboard/utils.ts index c1e4c52c0da..a3ee1456e44 100644 --- a/frontend/packages/metal3-plugin/src/components/baremetal-hosts/dashboard/utils.ts +++ b/frontend/packages/metal3-plugin/src/components/baremetal-hosts/dashboard/utils.ts @@ -8,14 +8,14 @@ import { findNodeMaintenance, getHostMachine } from '../../../selectors'; import { getHostFilterStatus } from '../table-filters'; const BMH_STATUS_GROUP_MAPPER = { - [InventoryStatusGroup.OK]: HOST_SUCCESS_STATES, + [InventoryStatusGroup.NOT_MAPPED]: HOST_SUCCESS_STATES, [InventoryStatusGroup.PROGRESS]: HOST_PROGRESS_STATES, [InventoryStatusGroup.ERROR]: HOST_ERROR_STATES, }; export const getBMHStatusGroups: StatusGroupMapper = (hosts, { machines, nodes, maintenances }) => { const groups = { - [InventoryStatusGroup.OK]: { + [InventoryStatusGroup.NOT_MAPPED]: { statusIDs: ['ready', 'provisioned'], count: 0, filterType: 'host-status', @@ -30,7 +30,7 @@ export const getBMHStatusGroups: StatusGroupMapper = (hosts, { machines, nodes, count: 0, filterType: 'host-status', }, - [InventoryStatusGroup.NOT_MAPPED]: { + [InventoryStatusGroup.UNKNOWN]: { statusIDs: ['other'], count: 0, filterType: 'host-status', diff --git a/frontend/public/components/dashboard/inventory-card/inventory-card.scss b/frontend/public/components/dashboard/inventory-card/inventory-card.scss index 62278886f9a..f28d8467542 100644 --- a/frontend/public/components/dashboard/inventory-card/inventory-card.scss +++ b/frontend/public/components/dashboard/inventory-card/inventory-card.scss @@ -1,31 +1,32 @@ .co-inventory-card__item { align-items: center; - border-bottom: 1px solid $pf-color-black-300; display: flex; font-size: 1rem; justify-content: space-between; - padding: 1em var(--pf-c-card--child--PaddingRight) 1em var(--pf-c-card--child--PaddingLeft); + line-height: 1.5rem; + padding: 1rem var(--pf-c-card--child--PaddingRight) 1rem var(--pf-c-card--child--PaddingLeft); +} + +.co-inventory-card__item--border { + border-bottom: 1px solid $pf-color-black-300; } .co-inventory-card__item-status { align-items: center; display: flex; flex-wrap: wrap; - - :last-child { - margin-right: 0; - } } .co-inventory-card__item-title { - margin-right: 0.5em; + display: flex; + margin-right: 0.5rem; } .co-inventory-card__status { align-items: center; display: flex; flex-shrink: 0; - margin-right: 0.5em; + margin-left: 1.8rem; } .co-inventory-card__status-icon--question { @@ -36,10 +37,51 @@ color: $pf-color-black-600; } -.co-inventory-card__status-icon--warn { - color: $pf-color-gold-400; +.co-inventory-card__status-text { + margin-left: 0.25rem; } -.co-inventory-card__status-text { - margin-left: 0.25em; +.co-inventory-card__accordion.pf-c-accordion { + border-bottom: 1px solid $pf-color-black-300; + box-shadow: none; + padding: 0; + + h5 { + font-size: var(--pf-c-card__body--FontSize); + font-weight: var(--pf-global--FontWeight--normal); + margin: 0; + } + + .co-inventory-card__accordion-toggle.pf-c-accordion__toggle { + border-left: none; + padding: 0 var(--pf-c-card--child--PaddingRight) 0 0; + + .pf-c-accordion__toggle-text { + width: 100%; + } + } + + .co-inventory-card__accordion-body.pf-c-accordion__expanded-content { + border-left: none; + + .pf-c-accordion__expanded-content-body { + padding: 1rem var(--pf-c-card--child--PaddingRight) 1rem var(--pf-c-card--child--PaddingLeft); + } + } +} + +.skeleton-inventory { + animation: $skeleton-animation; + background: $skeleton-color; + height: 20px; + width: 20px; + opacity: 0; + margin-right: 0.5rem; + &::after { + background-repeat: no-repeat; + content: ""; + display: block; + height: 100%; + width: 100%; + } } diff --git a/frontend/public/components/dashboard/inventory-card/inventory-item.tsx b/frontend/public/components/dashboard/inventory-card/inventory-item.tsx index cfd102dfbc6..764485310c0 100644 --- a/frontend/public/components/dashboard/inventory-card/inventory-item.tsx +++ b/frontend/public/components/dashboard/inventory-card/inventory-item.tsx @@ -1,31 +1,50 @@ import * as React from 'react'; import { Link } from 'react-router-dom'; import { InProgressIcon, QuestionCircleIcon } from '@patternfly/react-icons'; - import { - GreenCheckCircleIcon, - RedExclamationCircleIcon, - YellowExclamationTriangleIcon, -} from '@console/shared'; + Accordion, + AccordionItem, + AccordionToggle, + AccordionContent, +} from '@patternfly/react-core'; + +import { RedExclamationCircleIcon, YellowExclamationTriangleIcon } from '@console/shared'; import * as plugins from '../../../plugins'; -import { LoadingInline } from '../../utils'; +import { resourcePathFromModel } from '../../utils'; import { K8sResourceKind, K8sKind } from '../../../module/k8s'; import { InventoryStatusGroup } from './status-group'; import { connectToFlags, FlagsObject, WithFlagsProps } from '../../../reducers/features'; import { getFlagsForExtensions, isDashboardExtensionInUse } from '../../dashboards-page/utils'; const defaultStatusGroupIcons = { - [InventoryStatusGroup.OK]: , [InventoryStatusGroup.WARN]: , [InventoryStatusGroup.ERROR]: , [InventoryStatusGroup.PROGRESS]: ( ), - [InventoryStatusGroup.NOT_MAPPED]: ( + [InventoryStatusGroup.UNKNOWN]: ( ), }; +const getTop3Groups = (groupIDs: string[], flags: FlagsObject) => { + const groupStatuses: Array = [ + InventoryStatusGroup.ERROR, + InventoryStatusGroup.WARN, + InventoryStatusGroup.PROGRESS, + ]; + plugins.registry + .getDashboardsInventoryItemGroups() + .filter((e) => isDashboardExtensionInUse(e, flags)) + .forEach((group) => { + if (!groupStatuses.includes(group.properties.id)) { + groupStatuses.push(group.properties.id); + } + }); + groupStatuses.push(InventoryStatusGroup.UNKNOWN); + return groupIDs.sort((a, b) => groupStatuses.indexOf(b) - groupStatuses.indexOf(a)).slice(0, 3); +}; + const getStatusGroupIcons = (flags: FlagsObject) => { const groupStatusIcons = { ...defaultStatusGroupIcons }; plugins.registry @@ -40,30 +59,73 @@ const getStatusGroupIcons = (flags: FlagsObject) => { }; export const InventoryItem: React.FC = React.memo( - ({ isLoading, singularTitle, pluralTitle, count, children, error = false }) => { - const title = count !== 1 ? pluralTitle : singularTitle; - let status: React.ReactNode; - if (error) { - status =
Unavailable
; - } else if (isLoading) { - status = ; - } else { - status = children; - } - return ( -
+ ({ isLoading, title, count, children, error = false, TitleComponent, ExpandedComponent }) => { + const [expanded, setExpanded] = React.useState(false); + const onClick = React.useCallback(() => setExpanded(!expanded), [expanded]); + const pluralizedTitle = count !== 1 ? `${title}s` : title; + const titleMessage = isLoading || error ? pluralizedTitle : `${count} ${pluralizedTitle}`; + return ExpandedComponent ? ( + + + +
+
+ {isLoading && !error &&
} + {TitleComponent ? {titleMessage} : titleMessage} +
+ {!expanded && (error || !isLoading) && ( +
+ {error ? ( +
Not available
+ ) : ( + children + )} +
+ )} +
+ + + + + + + ) : ( +
- {isLoading || error ? title : `${count} ${title}`} + {isLoading && !error &&
} + {TitleComponent ? {titleMessage} : titleMessage}
-
{status}
+ {(error || !isLoading) && ( +
+ {error ? ( +
Not available
+ ) : ( + children + )} +
+ )}
); }, ); -export const Status: React.FC = React.memo(({ groupID, count, flags }) => { +export const Status = connectToFlags( + ...getFlagsForExtensions(plugins.registry.getDashboardsInventoryItemGroups()), +)(({ groupID, count, flags }) => { + if (groupID === InventoryStatusGroup.NOT_MAPPED || !count) { + return null; + } const statusGroupIcons = getStatusGroupIcons(flags); - const groupIcon = statusGroupIcons[groupID] || statusGroupIcons[InventoryStatusGroup.NOT_MAPPED]; + const groupIcon = statusGroupIcons[groupID] || statusGroupIcons[InventoryStatusGroup.UNKNOWN]; return (
{groupIcon} @@ -72,84 +134,90 @@ export const Status: React.FC = React.memo(({ groupID, count, flags ); }); -const StatusLink: React.FC = React.memo( - ({ groupID, count, statusIDs, kind, namespace, filterType, flags }) => { - const statusItems = encodeURIComponent(statusIDs.join(',')); - const namespacePath = namespace ? `ns/${namespace}` : 'all-namespaces'; - const to = - filterType && statusItems.length > 0 - ? `/k8s/${namespacePath}/${kind.plural}?rowFilter-${filterType}=${statusItems}` - : `/k8s/${namespacePath}/${kind.plural}`; - const statusGroupIcons = getStatusGroupIcons(flags); - const groupIcon = - statusGroupIcons[groupID] || statusGroupIcons[InventoryStatusGroup.NOT_MAPPED]; - return ( -
- - {groupIcon} - {count} - -
- ); - }, +const StatusLink = connectToFlags( + ...getFlagsForExtensions(plugins.registry.getDashboardsInventoryItemGroups()), +)(({ groupID, count, statusIDs, kind, namespace, filterType, flags }) => { + if (groupID === InventoryStatusGroup.NOT_MAPPED || !count) { + return null; + } + const statusItems = encodeURIComponent(statusIDs.join(',')); + const namespacePath = namespace ? `ns/${namespace}` : 'all-namespaces'; + const to = + filterType && statusItems.length > 0 + ? `/k8s/${namespacePath}/${kind.plural}?rowFilter-${filterType}=${statusItems}` + : `/k8s/${namespacePath}/${kind.plural}`; + const statusGroupIcons = getStatusGroupIcons(flags); + const groupIcon = statusGroupIcons[groupID] || statusGroupIcons[InventoryStatusGroup.NOT_MAPPED]; + return ( +
+ + {groupIcon} + {count} + +
+ ); +}); + +const ResourceTitleComponent = ({ kind, namespace, children }) => ( + {children} ); export const ResourceInventoryItem = connectToFlags( ...getFlagsForExtensions(plugins.registry.getDashboardsInventoryItemGroups()), )( - React.memo( - ({ - kind, - useAbbr, - resources, - additionalResources, - isLoading, - mapper, - namespace, - error, - showLink = true, - flags = {}, - }) => { - const groups = mapper(resources, additionalResources); - const [singularTitle, pluralTitle] = useAbbr - ? [kind.abbr, `${kind.abbr}s`] - : [kind.label, kind.labelPlural]; - return ( - - {Object.keys(groups) - .filter((key) => groups[key].count > 0) - .map((key) => - showLink ? ( - - ) : ( - - ), - )} - - ); - }, - ), + ({ + kind, + useAbbr, + resources = [], + additionalResources, + isLoading, + mapper, + namespace, + error, + showLink = true, + flags = {}, + ExpandedComponent, + }) => { + const TitleComponent = React.useCallback( + (props) => , + [kind, namespace], + ); + + const groups = mapper(resources, additionalResources); + const top3Groups = getTop3Groups( + Object.keys(groups).filter((key) => groups[key].count > 0), + flags, + ); + return ( + + {top3Groups.map((key) => + showLink ? ( + + ) : ( + + ), + )} + + ); + }, ); -export type StatusGroupMapper = ( - resources: K8sResourceKind[], - additionalResources?: { [key: string]: K8sResourceKind[] }, -) => { +type StatusGroup = { [key in InventoryStatusGroup | string]: { filterType?: string; statusIDs: string[]; @@ -157,13 +225,19 @@ export type StatusGroupMapper = ( } }; +export type StatusGroupMapper = ( + resources: K8sResourceKind[], + additionalResources?: { [key: string]: K8sResourceKind[] }, +) => StatusGroup; + type InventoryItemProps = { isLoading: boolean; - singularTitle: string; - pluralTitle: string; + title: string; count: number; children?: React.ReactNode; error: boolean; + TitleComponent?: React.ComponentType<{}>; + ExpandedComponent?: React.ComponentType<{}>; }; type StatusProps = WithFlagsProps & { @@ -178,6 +252,11 @@ type StatusLinkProps = StatusProps & { filterType?: string; }; +export type ExpandedComponentProps = { + resource: K8sResourceKind[]; + additionalResources?: { [key: string]: K8sResourceKind[] }; +}; + type ResourceInventoryItemProps = WithFlagsProps & { resources: K8sResourceKind[]; additionalResources?: { [key: string]: K8sResourceKind[] }; @@ -188,4 +267,5 @@ type ResourceInventoryItemProps = WithFlagsProps & { namespace?: string; error: boolean; showLink?: boolean; + ExpandedComponent?: React.ComponentType<{}>; }; diff --git a/frontend/public/components/dashboard/inventory-card/status-group.ts b/frontend/public/components/dashboard/inventory-card/status-group.ts index 2df76afe628..8ad1309fcbb 100644 --- a/frontend/public/components/dashboard/inventory-card/status-group.ts +++ b/frontend/public/components/dashboard/inventory-card/status-group.ts @@ -1,7 +1,7 @@ export enum InventoryStatusGroup { - OK = 'OK', WARN = 'WARN', ERROR = 'ERROR', PROGRESS = 'PROGRESS', NOT_MAPPED = 'NOT_MAPPED', + UNKNOWN = 'UNKNOWN', } diff --git a/frontend/public/components/dashboard/inventory-card/utils.ts b/frontend/public/components/dashboard/inventory-card/utils.ts index aa9374e860b..9c999b3f1de 100644 --- a/frontend/public/components/dashboard/inventory-card/utils.ts +++ b/frontend/public/components/dashboard/inventory-card/utils.ts @@ -4,32 +4,32 @@ import { StatusGroupMapper } from './inventory-item'; import { InventoryStatusGroup } from './status-group'; const POD_PHASE_GROUP_MAPPING = { - [InventoryStatusGroup.OK]: ['Running', 'Succeeded'], + [InventoryStatusGroup.NOT_MAPPED]: ['Running', 'Succeeded'], [InventoryStatusGroup.ERROR]: ['CrashLoopBackOff', 'Failed'], [InventoryStatusGroup.PROGRESS]: ['Terminating', 'Pending'], [InventoryStatusGroup.WARN]: ['Unknown'], }; const PVC_STATUS_GROUP_MAPPING = { - [InventoryStatusGroup.OK]: ['Bound'], + [InventoryStatusGroup.NOT_MAPPED]: ['Bound'], [InventoryStatusGroup.ERROR]: ['Lost'], [InventoryStatusGroup.PROGRESS]: ['Pending'], }; const PV_STATUS_GROUP_MAPPING = { - [InventoryStatusGroup.OK]: ['Available', 'Bound'], + [InventoryStatusGroup.NOT_MAPPED]: ['Available', 'Bound'], [InventoryStatusGroup.PROGRESS]: ['Released'], [InventoryStatusGroup.ERROR]: ['Failed'], }; const NODE_STATUS_GROUP_MAPPING = { - [InventoryStatusGroup.OK]: ['Ready'], + [InventoryStatusGroup.NOT_MAPPED]: ['Ready'], [InventoryStatusGroup.PROGRESS]: ['Not Ready'], }; const getStatusGroups = (resources, mapping, mapper, filterType) => { const groups = { - [InventoryStatusGroup.NOT_MAPPED]: { + [InventoryStatusGroup.UNKNOWN]: { statusIDs: [], count: 0, }, @@ -46,7 +46,7 @@ const getStatusGroups = (resources, mapping, mapper, filterType) => { const status = mapper(resource); const group = Object.keys(mapping).find((key) => mapping[key].includes(status)) || - InventoryStatusGroup.NOT_MAPPED; + InventoryStatusGroup.UNKNOWN; groups[group].count++; }); diff --git a/frontend/public/components/dashboards-page/overview-dashboard/inventory-card.tsx b/frontend/public/components/dashboards-page/overview-dashboard/inventory-card.tsx index 0fac819b216..ba66e1387d4 100644 --- a/frontend/public/components/dashboards-page/overview-dashboard/inventory-card.tsx +++ b/frontend/public/components/dashboards-page/overview-dashboard/inventory-card.tsx @@ -8,88 +8,111 @@ import { DashboardCardHeader, DashboardCardTitle, } from '../../dashboard/dashboard-card'; -import { ResourceInventoryItem } from '../../dashboard/inventory-card/inventory-item'; +import { + ResourceInventoryItem, + StatusGroupMapper, +} from '../../dashboard/inventory-card/inventory-item'; import { DashboardItemProps, withDashboardResources } from '../with-dashboard-resources'; import { PodModel, NodeModel, PersistentVolumeClaimModel } from '../../../models'; -import { K8sResourceKind, PodKind } from '../../../module/k8s'; +import { K8sResourceKind, K8sKind, referenceForModel } from '../../../module/k8s'; import { getPodStatusGroups, getNodeStatusGroups, getPVCStatusGroups, } from '../../dashboard/inventory-card/utils'; -import { FirehoseResource } from '../../utils'; -import { connectToFlags, FlagsObject, WithFlagsProps } from '../../../reducers/features'; +import { FirehoseResource, AsyncComponent } from '../../utils'; +import { connectToFlags, FlagsObject } from '../../../reducers/features'; import { getFlagsForExtensions, isDashboardExtensionInUse } from '../utils'; -import { uniqueResource } from './utils'; import { InventoryBody } from '../../dashboard/inventory-card/inventory-body'; - -const k8sResources: FirehoseResource[] = [ - { - isList: true, - kind: PodModel.kind, - prop: 'pods', - }, - { - isList: true, - kind: NodeModel.kind, - prop: 'nodes', - }, - { - isList: true, - kind: PersistentVolumeClaimModel.kind, - prop: 'pvcs', - }, -]; +import { LazyLoader } from '@console/plugin-sdk/src/typings/types'; const getItems = (flags: FlagsObject) => plugins.registry .getDashboardsOverviewInventoryItems() .filter((e) => isDashboardExtensionInUse(e, flags)); -const getResourcesToWatch = (flags: FlagsObject): FirehoseResource[] => { - const allResources = [...k8sResources]; - getItems(flags).forEach((item, index) => { - allResources.push(uniqueResource(item.properties.resource, index)); - if (item.properties.additionalResources) { - item.properties.additionalResources.forEach((ar) => - allResources.push(uniqueResource(ar, index)), - ); - } - }); - return allResources; -}; - -const InventoryCard_: React.FC = ({ - watchK8sResource, - stopWatchK8sResource, - resources, - flags = {}, -}) => { - React.useEffect(() => { - const resourcesToWatch = getResourcesToWatch(flags); - resourcesToWatch.forEach((r) => watchK8sResource(r)); +const getFirehoseResource = (model: K8sKind) => ({ + isList: true, + kind: model.crd ? referenceForModel(model) : model.kind, + prop: 'resource', +}); - return () => { - resourcesToWatch.forEach((r) => stopWatchK8sResource(r)); - }; - // TODO: to be removed: use JSON.stringify(flags) to avoid deep comparison of flags object - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [watchK8sResource, stopWatchK8sResource, JSON.stringify(flags)]); +const ClusterInventoryItem = withDashboardResources( + React.memo( + ({ + watchK8sResource, + stopWatchK8sResource, + resources, + model, + mapper, + useAbbr, + additionalResources, + expandedComponent, + }: ClusterInventoryItemProps) => { + React.useEffect(() => { + const resource = getFirehoseResource(model); + watchK8sResource(resource); + if (additionalResources) { + additionalResources.forEach(watchK8sResource); + } + return () => { + stopWatchK8sResource(resource); + if (additionalResources) { + additionalResources.forEach(stopWatchK8sResource); + } + }; + }, [watchK8sResource, stopWatchK8sResource, model, additionalResources]); - const nodesLoaded = _.get(resources.nodes, 'loaded'); - const nodesLoadError = _.get(resources.nodes, 'loadError'); - const nodesData = _.get(resources.nodes, 'data', []) as K8sResourceKind[]; + const resourceData = _.get(resources.resource, 'data') as K8sResourceKind[]; + const resourceLoaded = _.get(resources.resource, 'loaded'); + const resourceLoadError = _.get(resources.resource, 'loadError'); - const podsLoaded = _.get(resources.pods, 'loaded'); - const podsLoadError = _.get(resources.pods, 'loadError'); - const podsData = _.get(resources.pods, 'data', []) as PodKind[]; + const additionalResourcesData = {}; + let additionalResourcesLoaded = true; + let additionalResourcesLoadError = false; + if (additionalResources) { + additionalResourcesLoaded = additionalResources.every((r) => + _.get(resources[r.prop], 'loaded'), + ); + additionalResources.forEach((r) => { + additionalResourcesData[r.prop] = _.get(resources[r.prop], 'data'); + }); + additionalResourcesLoadError = additionalResources.some( + (r) => !!_.get(resources[r.prop], 'loadError'), + ); + } - const pvcsLoaded = _.get(resources.pvcs, 'loaded'); - const pvcsLoadError = _.get(resources.pvcs, 'loadError'); - const pvcsData = _.get(resources.pvcs, 'data', []) as K8sResourceKind[]; + const ExpandedComponent = React.useCallback( + () => ( + + ), + [resourceData, additionalResourcesData, expandedComponent], + ); - const pluginItems = getItems(flags); + return ( + + ); + }, + ), +); +export const InventoryCard = connectToFlags( + ...getFlagsForExtensions(plugins.registry.getDashboardsOverviewInventoryItems()), +)(({ flags }) => { + const items = getItems(flags); return ( @@ -97,71 +120,33 @@ const InventoryCard_: React.FC = ({ - - - + + - {pluginItems.map((item, index) => { - const resource = _.get(resources, uniqueResource(item.properties.resource, index).prop); - const resourceLoaded = _.get(resource, 'loaded'); - const resourceLoadError = _.get(resource, 'loadError'); - const resourceData = _.get(resource, 'data', []) as K8sResourceKind[]; - - const additionalResources = {}; - if (item.properties.additionalResources) { - item.properties.additionalResources.forEach((ar) => { - additionalResources[ar.prop] = _.get(resources, uniqueResource(ar, index).prop); - }); - } - const additionalResourcesLoaded = Object.keys(additionalResources).every( - (key) => - !additionalResources[key] || - additionalResources[key].loaded || - additionalResources[key].loadError, - ); - const additionalResourcesData = {}; - - Object.keys(additionalResources).forEach( - (key) => (additionalResourcesData[key] = _.get(additionalResources[key], 'data', [])), - ); - - return ( - - ); - })} + {items.map((item) => ( + + ))} ); -}; +}); -export const InventoryCard = connectToFlags( - ...getFlagsForExtensions(plugins.registry.getDashboardsOverviewInventoryItems()), -)(withDashboardResources(InventoryCard_)); +type ClusterInventoryItemProps = DashboardItemProps & { + model: K8sKind; + mapper: StatusGroupMapper; + useAbbr?: boolean; + additionalResources?: FirehoseResource[]; + expandedComponent?: LazyLoader; +}; diff --git a/frontend/public/components/dashboards-page/with-dashboard-resources.tsx b/frontend/public/components/dashboards-page/with-dashboard-resources.tsx index c75b60ced73..7109a8e7f9c 100644 --- a/frontend/public/components/dashboards-page/with-dashboard-resources.tsx +++ b/frontend/public/components/dashboards-page/with-dashboard-resources.tsx @@ -92,12 +92,18 @@ export const withDashboardResources =

( nextProps[RESULTS_TYPE.PROMETHEUS].getIn([ALERTS_KEY, 'loadError']); const k8sResourcesChanged = this.state.k8sResources !== nextState.k8sResources; + const nextExternalProps = this.getExternalProps(nextProps); + const externalProps = this.getExternalProps(this.props); + return ( urlResultChanged || queryResultChanged || k8sResourcesChanged || (this.watchingAlerts && alertsResultChanged) || - !_.isEqual(this.getExternalProps(nextProps), this.getExternalProps(this.props)) + Object.keys(nextExternalProps).length !== Object.keys(externalProps).length || + Object.keys(nextExternalProps).some( + (key) => nextExternalProps[key] !== externalProps[key], + ) ); }