+ ({ 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],
+ )
);
}